• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

graphty-org / graphty-element / 20219217725

15 Dec 2025 03:10AM UTC coverage: 83.666% (-2.7%) from 86.405%
20219217725

push

github

apowers313
chore: delint

4072 of 4771 branches covered (85.35%)

Branch coverage included in aggregate %.

15 of 26 new or added lines in 1 file covered. (57.69%)

890 existing lines in 12 files now uncovered.

18814 of 22583 relevant lines covered (83.31%)

8168.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.41
/src/graphty-element.ts
1
// WORKAROUND: Import InstancedMesh side-effect first
2✔
2
// See: https://github.com/graphty-org/graphty-element/issues/54
3
import "@babylonjs/core/Meshes/instancedMesh";
2✔
4

5
import {LitElement} from "lit";
2✔
6
import {customElement, property} from "lit/decorators.js";
2✔
7
import {set as setDeep} from "lodash";
2✔
8

9
import type {StyleSchema} from "./config";
10
import type {PartialXRConfig} from "./config/xr-config-schema";
11
import {Graph} from "./Graph";
2✔
12
import type {ScreenshotOptions, ScreenshotResult} from "./screenshot/types.js";
13

14
/**
15
 * Graphty creates a graph
16
 */
17
@customElement("graphty-element")
2✔
18
export class Graphty extends LitElement {
2✔
19
    #graph: Graph;
2✔
20
    #element: Element;
2✔
21
    #resizeObserver: ResizeObserver | null = null;
2✔
22

23
    constructor() {
2✔
24
        super();
171✔
25

26
        this.#element = document.createElement("div");
171✔
27
        // Ensure the container div fills the graphty-element
28
        // position: relative is needed for absolute positioning of XR UI overlay
29
        this.#element.setAttribute("style", "width: 100%; height: 100%; display: block; position: relative;");
171✔
30
        this.#graph = new Graph(this.#element);
171✔
31
    }
171✔
32

33
    connectedCallback(): void {
2✔
34
        super.connectedCallback();
171✔
35
        this.renderRoot.appendChild(this.#element);
171✔
36

37
        // Watch for container size changes and resize the canvas accordingly
38
        this.#resizeObserver = new ResizeObserver(() => {
171✔
39
            // Resize the Babylon.js engine when the container size changes
40
            this.#graph.engine.resize();
173✔
41
        });
171✔
42
        this.#resizeObserver.observe(this);
171✔
43

44
        // Parse URL parameters
45
        this.parseURLParams();
171✔
46
    }
171✔
47

48
    private parseURLParams(): void {
2✔
49
        const urlParams = new URLSearchParams(window.location.search);
171✔
50

51
        // Check for profiling parameter
52
        if (urlParams.get("profiling") === "true") {
171!
UNCOV
53
            this.enableDetailedProfiling = true;
×
UNCOV
54
        }
×
55
    }
171✔
56

57
    // update(changedProperties: Map<string, unknown>) {
58
    //     super.update(changedProperties);
59
    //     // console.log(`update: ${[... changedProperties.keys()].join(", ")}`);
60
    // }
61

62
    firstUpdated(changedProperties: Map<string, unknown>): void {
2✔
63
        super.firstUpdated(changedProperties);
171✔
64
        // console.log(`firstUpdated: ${[... changedProperties.keys()].join(", ")}`);
65

66
        this.asyncFirstUpdated()
171✔
67
            .catch((e: unknown) => {
171✔
UNCOV
68
                throw e;
×
69
            });
171✔
70
    }
171✔
71

72
    async asyncFirstUpdated(): Promise<void> {
2✔
73
        // Forward ALL internal graph events as DOM CustomEvents
74
        // This allows external code (e.g., React) to listen for any graph event
75
        // using standard DOM addEventListener (e.g., "style-changed", "graph-settled", etc.)
76
        this.#graph.eventManager.onGraphEvent.add((event) => {
171✔
77
            this.dispatchEvent(new CustomEvent(event.type, {
10,066✔
78
                detail: event,
10,066✔
79
                bubbles: true,
10,066✔
80
                composed: true,
10,066✔
81
            }));
10,066✔
82
        });
171✔
83

84
        // Note: Property setters now forward to Graph methods automatically,
85
        // so we don't need to check changedProperties here. The setters have
86
        // already been called by the time we reach this lifecycle method.
87

88
        // Initialize the graph (only needs to happen once)
89
        await this.#graph.init();
171✔
90

91
        // Wait for first render frame to ensure graph is visible
92
        await new Promise((resolve) => requestAnimationFrame(() => {
171✔
93
            requestAnimationFrame(resolve);
171✔
94
        }));
171✔
95

96
        this.#graph.engine.resize();
171✔
97
    }
171✔
98

99
    render(): Element {
2✔
100
        return this.#element;
171✔
101
    }
171✔
102

103
    disconnectedCallback(): void {
2✔
104
        // Disconnect the resize observer
105
        if (this.#resizeObserver) {
144✔
106
            this.#resizeObserver.disconnect();
144✔
107
            this.#resizeObserver = null;
144✔
108
        }
144✔
109

110
        this.#graph.shutdown();
144✔
111
        super.disconnectedCallback();
144✔
112
    }
144✔
113

114
    // Private backing fields for reactive properties
115
    #nodeData?: Record<string, unknown>[];
2✔
116
    #edgeData?: Record<string, unknown>[];
2✔
117
    #dataSource?: string;
2✔
118
    #dataSourceConfig?: Record<string, unknown>;
2✔
119
    #nodeIdPath?: string;
2✔
120
    #edgeSrcIdPath?: string;
2✔
121
    #edgeDstIdPath?: string;
2✔
122
    #layout?: string;
2✔
123
    #layoutConfig?: Record<string, unknown>;
2✔
124
    #layout2d?: boolean;
2✔
125
    #styleTemplate?: StyleSchema;
2✔
126
    #runAlgorithmsOnLoad?: boolean;
2✔
127
    #xr?: PartialXRConfig;
2✔
128

129
    /**
130
     * An array of objects describing the node data.
131
     * The path to the unique ID for the node is `.id` unless
132
     * otherwise specified in `known-properties`.
133
     *
134
     * @remarks
135
     * **Important**: Setting this property REPLACES all existing nodes with the new data.
136
     * To add nodes incrementally without replacing existing ones, use the
137
     * `graph.addNodes()` method instead.
138
     *
139
     * @example
140
     * ```typescript
141
     * // Replace all nodes (this is what the property setter does)
142
     * element.nodeData = [{id: "1"}, {id: "2"}];
143
     *
144
     * // Add nodes incrementally (use the API method)
145
     * await element.graph.addNodes([{id: "3"}, {id: "4"}]);
146
     * ```
147
     */
148
    @property({attribute: "node-data"})
2✔
149
    get nodeData(): Record<string, unknown>[] | undefined {
2✔
150
        return this.#nodeData;
324✔
151
    }
324✔
152
    set nodeData(value: Record<string, unknown>[] | undefined) {
2✔
153
        const oldValue = this.#nodeData;
51✔
154
        this.#nodeData = value;
51✔
155

156
        // Forward to Graph method (which queues operation)
157
        if (value && Array.isArray(value)) {
51✔
158
            void this.#graph.addNodes(value);
51✔
159
        }
51✔
160

161
        this.requestUpdate("nodeData", oldValue);
51✔
162
    }
51✔
163

164
    /**
165
     * An array of objects describing the edge data.
166
     * The path to the source node ID and destination node ID are `src` and
167
     * `dst` (respectively) unless otherwise specified in `known-properties`.
168
     *
169
     * @remarks
170
     * **Important**: Setting this property REPLACES all existing edges with the new data.
171
     * To add edges incrementally without replacing existing ones, use the
172
     * `graph.addEdges()` method instead.
173
     *
174
     * @example
175
     * ```typescript
176
     * // Replace all edges (this is what the property setter does)
177
     * element.edgeData = [{src: "1", dst: "2"}];
178
     *
179
     * // Add edges incrementally (use the API method)
180
     * await element.graph.addEdges([{source: "2", target: "3"}], "source", "target");
181
     * ```
182
     */
183
    @property({attribute: "edge-data"})
2✔
184
    get edgeData(): Record<string, unknown>[] | undefined {
2✔
185
        return this.#edgeData;
324✔
186
    }
324✔
187
    set edgeData(value: Record<string, unknown>[] | undefined) {
2✔
188
        const oldValue = this.#edgeData;
51✔
189
        this.#edgeData = value;
51✔
190

191
        // Forward to Graph method (which queues operation)
192
        if (value && Array.isArray(value)) {
51✔
193
            void this.#graph.addEdges(value);
51✔
194
        }
51✔
195

196
        this.requestUpdate("edgeData", oldValue);
51✔
197
    }
51✔
198

199
    /**
200
     * The type of data source (e.g. "json"). See documentation for
201
     * data sources for more information.
202
     */
203
    @property({attribute: "data-source"})
2✔
204
    get dataSource(): string | undefined {
2✔
205
        return this.#dataSource;
480✔
206
    }
480✔
207
    set dataSource(value: string | undefined) {
2✔
208
        const oldValue = this.#dataSource;
103✔
209
        this.#dataSource = value;
103✔
210

211
        // Try to initialize data source if both dataSource and dataSourceConfig are set
212
        this.#tryInitializeDataSource();
103✔
213

214
        this.requestUpdate("dataSource", oldValue);
103✔
215
    }
103✔
216

217
    /**
218
     * The configuration for the data source. See documentation for
219
     * data sources for more information.
220
     */
221
    @property({attribute: "data-source-config"})
2✔
222
    get dataSourceConfig(): Record<string, unknown> | undefined {
2✔
223
        return this.#dataSourceConfig;
480✔
224
    }
480✔
225
    set dataSourceConfig(value: Record<string, unknown> | undefined) {
2✔
226
        const oldValue = this.#dataSourceConfig;
103✔
227
        this.#dataSourceConfig = value;
103✔
228

229
        // Try to initialize data source if both dataSource and dataSourceConfig are set
230
        this.#tryInitializeDataSource();
103✔
231

232
        this.requestUpdate("dataSourceConfig", oldValue);
103✔
233
    }
103✔
234

235
    /**
236
     * Helper method to initialize data source only when both properties are set
237
     */
238
    #dataSourceInitialized = false;
2✔
239
    #tryInitializeDataSource(): void {
2✔
240
        // Only initialize once, and only if both dataSource and dataSourceConfig are set
241
        if (!this.#dataSourceInitialized && this.#dataSource && this.#dataSourceConfig) {
206✔
242
            this.#dataSourceInitialized = true;
101✔
243
            void this.#graph.addDataFromSource(this.#dataSource, this.#dataSourceConfig);
101✔
244
        }
101✔
245
    }
206✔
246

247
    /**
248
     * A jmespath string that can be used to select the unique node identifier
249
     * for each node. Defaults to "id", as in `{id: 42}` is the identifier of
250
     * the node.
251
     */
252
    @property({attribute: "node-id-path"})
2✔
253
    get nodeIdPath(): string | undefined {
2✔
254
        return this.#nodeIdPath;
171✔
255
    }
171✔
256
    set nodeIdPath(value: string | undefined) {
2✔
257
        const oldValue = this.#nodeIdPath;
×
258
        this.#nodeIdPath = value;
×
259

UNCOV
260
        if (value) {
×
261
            setDeep(this.#graph.styles.config, "data.knownFields.nodeIdPath", value);
×
262
        }
×
263

UNCOV
264
        this.requestUpdate("nodeIdPath", oldValue);
×
UNCOV
265
    }
×
266

267
    /**
268
     * Similar to the nodeIdPath property / node-id-path attribute, this is a
269
     * jmespath that describes where to find the source node identifier for this edge.
270
     * Defaults to "src", as in `{src: 42, dst: 31337}`
271
     */
272
    @property({attribute: "edge-src-id-path"})
2✔
273
    get edgeSrcIdPath(): string | undefined {
2✔
274
        return this.#edgeSrcIdPath;
171✔
275
    }
171✔
276
    set edgeSrcIdPath(value: string | undefined) {
2✔
277
        const oldValue = this.#edgeSrcIdPath;
×
278
        this.#edgeSrcIdPath = value;
×
279

UNCOV
280
        if (value) {
×
281
            setDeep(this.#graph.styles.config, "data.knownFields.edgeSrcIdPath", value);
×
282
        }
×
283

UNCOV
284
        this.requestUpdate("edgeSrcIdPath", oldValue);
×
UNCOV
285
    }
×
286

287
    /**
288
     * Similar to the nodeIdPath property / node-id-path attribute, this is a
289
     * jmespath that describes where to find the desination node identifier for this edge.
290
     * Defaults to "dst", as in `{src: 42, dst: 31337}`
291
     */
292
    @property({attribute: "edge-dst-id-path"})
2✔
293
    get edgeDstIdPath(): string | undefined {
2✔
294
        return this.#edgeDstIdPath;
171✔
295
    }
171✔
296
    set edgeDstIdPath(value: string | undefined) {
2✔
297
        const oldValue = this.#edgeDstIdPath;
×
298
        this.#edgeDstIdPath = value;
×
299

UNCOV
300
        if (value) {
×
301
            setDeep(this.#graph.styles.config, "data.knownFields.edgeDstIdPath", value);
×
302
        }
×
303

UNCOV
304
        this.requestUpdate("edgeDstIdPath", oldValue);
×
UNCOV
305
    }
×
306

307
    /**
308
     * Specifies which type of layout to use. See the layout documentation for
309
     * more information.
310
     */
311
    @property()
2✔
312
    get layout(): string | undefined {
2✔
313
        return this.#layout;
480✔
314
    }
480✔
315
    set layout(value: string | undefined) {
2✔
316
        const oldValue = this.#layout;
103✔
317
        this.#layout = value;
103✔
318

319
        // Forward to Graph method (which queues operation)
320
        if (value) {
103✔
321
            const templateLayoutOptions = this.#graph.styles.config.graph.layoutOptions ?? {};
103✔
322
            const mergedConfig = {... templateLayoutOptions, ... (this.#layoutConfig ?? {})};
103✔
323
            void this.#graph.setLayout(value, mergedConfig);
103✔
324
        }
103✔
325

326
        this.requestUpdate("layout", oldValue);
103✔
327
    }
103✔
328

329
    /**
330
     * Specifies which type of layout to use. See the layout documentation for
331
     * more information.
332
     */
333
    @property({attribute: "layout-config"})
2✔
334
    get layoutConfig(): Record<string, unknown> | undefined {
2✔
335
        return this.#layoutConfig;
519✔
336
    }
519✔
337
    set layoutConfig(value: Record<string, unknown> | undefined) {
2✔
338
        const oldValue = this.#layoutConfig;
116✔
339
        this.#layoutConfig = value;
116✔
340

341
        // If layout is already set, update it with new config
342
        if (this.#layout) {
116✔
343
            const templateLayoutOptions = this.#graph.styles.config.graph.layoutOptions ?? {};
30✔
344
            const mergedConfig = {... templateLayoutOptions, ... (value ?? {})};
30!
345
            void this.#graph.setLayout(this.#layout, mergedConfig);
30✔
346
        }
30✔
347

348
        this.requestUpdate("layoutConfig", oldValue);
116✔
349
    }
116✔
350

351
    /**
352
     * Specifies that the layout should be rendered in two dimensions (as
353
     * opposed to 3D)
354
     */
355
    @property({attribute: "layout-2d"})
2✔
356
    get layout2d(): boolean | undefined {
2✔
357
        return this.#layout2d;
171✔
358
    }
171✔
359
    set layout2d(value: boolean | undefined) {
2✔
360
        const oldValue = this.#layout2d;
×
361
        this.#layout2d = value;
×
362

UNCOV
363
        if (value !== undefined) {
×
364
            setDeep(this.#graph.styles.config, "graph.twoD", value);
×
365
        }
×
366

UNCOV
367
        this.requestUpdate("layout2d", oldValue);
×
UNCOV
368
    }
×
369

370
    /**
371
     * Specifies that the layout should be rendered in two dimensions (as
372
     * opposed to 3D)
373
     */
374
    @property({attribute: "style-template"})
2✔
375
    get styleTemplate(): StyleSchema | undefined {
2✔
376
        return this.#styleTemplate;
769✔
377
    }
769✔
378
    set styleTemplate(value: StyleSchema | undefined) {
2✔
379
        const oldValue = this.#styleTemplate;
153✔
380
        this.#styleTemplate = value;
153✔
381

382
        // Forward to Graph method (which queues operation)
383
        if (value) {
153✔
384
            void this.#graph.setStyleTemplate(value);
152✔
385
        }
152✔
386

387
        this.requestUpdate("styleTemplate", oldValue);
153✔
388
    }
153✔
389

390
    /**
391
     * Whether or not to run all algorithims in a style template when the
392
     * template is loaded
393
     */
394
    @property({attribute: "run-algorithms-on-load"})
2✔
395
    get runAlgorithmsOnLoad(): boolean | undefined {
2✔
396
        return this.#runAlgorithmsOnLoad;
252✔
397
    }
252✔
398
    set runAlgorithmsOnLoad(value: boolean | undefined) {
2✔
399
        const oldValue = this.#runAlgorithmsOnLoad;
27✔
400
        this.#runAlgorithmsOnLoad = value;
27✔
401

402
        if (value !== undefined) {
27✔
403
            this.#graph.runAlgorithmsOnLoad = value;
27✔
404
        }
27✔
405

406
        this.requestUpdate("runAlgorithmsOnLoad", oldValue);
27✔
407
    }
27✔
408

409
    #enableDetailedProfiling?: boolean;
2✔
410

411
    /**
412
     * Enable detailed performance profiling.
413
     * When enabled, hierarchical timing and advanced statistics will be collected.
414
     * Access profiling data via graph.getStatsManager().getSnapshot() or
415
     * graph.getStatsManager().reportDetailed().
416
     */
417
    @property({attribute: "enable-detailed-profiling", type: Boolean})
2✔
418
    get enableDetailedProfiling(): boolean | undefined {
2✔
419
        return this.#enableDetailedProfiling;
171✔
420
    }
171✔
421
    set enableDetailedProfiling(value: boolean | undefined) {
2✔
422
        const oldValue = this.#enableDetailedProfiling;
×
423
        this.#enableDetailedProfiling = value;
×
424

UNCOV
425
        if (value !== undefined) {
×
426
            this.#graph.enableDetailedProfiling = value;
×
427
        }
×
428

UNCOV
429
        this.requestUpdate("enableDetailedProfiling", oldValue);
×
UNCOV
430
    }
×
431

432
    /**
433
     * XR (VR/AR) configuration.
434
     * Controls XR UI buttons, VR/AR mode settings, and input handling.
435
     *
436
     * @example
437
     * ```typescript
438
     * element.xr = {
439
     *   enabled: true,
440
     *   ui: {
441
     *     enabled: true,
442
     *     position: 'bottom-right',
443
     *     showAvailabilityWarning: true  // Show warning if XR unavailable
444
     *   },
445
     *   input: {
446
     *     handTracking: true,
447
     *     controllers: true
448
     *   }
449
     * };
450
     * ```
451
     */
452
    @property({attribute: false})
2✔
453
    get xr(): PartialXRConfig | undefined {
2✔
454
        return this.#xr;
174✔
455
    }
174✔
456
    set xr(value: PartialXRConfig | undefined) {
2✔
457
        const oldValue = this.#xr;
1✔
458
        this.#xr = value;
1✔
459

460
        // Forward to Graph method
461
        if (value !== undefined) {
1✔
462
            this.#graph.setXRConfig(value);
1✔
463
        }
1✔
464

465
        this.requestUpdate("xr", oldValue);
1✔
466
    }
1✔
467

468
    /**
469
     * Capture a screenshot of the current graph visualization.
470
     *
471
     * @param options - Screenshot options (format, resolution, destinations, etc.)
472
     * @returns Promise resolving to ScreenshotResult with blob and metadata
473
     *
474
     * @example
475
     * ```typescript
476
     * const el = document.querySelector('graphty-element');
477
     *
478
     * // Basic PNG screenshot
479
     * const result = await el.captureScreenshot();
480
     *
481
     * // High-res JPEG with download
482
     * const result = await el.captureScreenshot({
483
     *   format: 'jpeg',
484
     *   multiplier: 2,
485
     *   destination: { download: true }
486
     * });
487
     *
488
     * // Copy to clipboard
489
     * const result = await el.captureScreenshot({
490
     *   destination: { clipboard: true }
491
     * });
492
     * ```
493
     */
494
    async captureScreenshot(options?: ScreenshotOptions): Promise<ScreenshotResult> {
2✔
UNCOV
495
        return this.#graph.captureScreenshot(options);
×
UNCOV
496
    }
×
497

498
    /**
499
     * Phase 6: Capability Check API
500
     * Check if screenshot can be captured with given options.
501
     * Available from Phase 6 onwards.
502
     *
503
     * @param options - Screenshot options to validate
504
     * @returns Promise<CapabilityCheck> - Result indicating whether screenshot is supported
505
     *
506
     * @example
507
     * ```typescript
508
     * const el = document.querySelector('graphty-element');
509
     *
510
     * // Check if 4x multiplier is supported
511
     * const check = await el.canCaptureScreenshot({ multiplier: 4 });
512
     * if (!check.supported) {
513
     *   alert(`Cannot capture: ${check.reason}`);
514
     * } else if (check.warnings) {
515
     *   console.warn('Warnings:', check.warnings);
516
     * }
517
     * ```
518
     */
519
    async canCaptureScreenshot(options?: ScreenshotOptions): Promise<import("./screenshot/capability-check.js").CapabilityCheck> {
2✔
UNCOV
520
        return this.#graph.canCaptureScreenshot(options);
×
UNCOV
521
    }
×
522

523
    /**
524
     * Phase 7: Video Capture API
525
     * Capture an animation as a video (stationary or animated camera)
526
     * Available from Phase 7 onwards.
527
     *
528
     * @param options - Animation capture options
529
     * @returns Promise<AnimationResult> - Result with video blob and metadata
530
     *
531
     * @example
532
     * ```typescript
533
     * const el = document.querySelector('graphty-element');
534
     *
535
     * // Basic 5-second video
536
     * const result = await el.captureAnimation({
537
     *   duration: 5000,
538
     *   fps: 30,
539
     *   cameraMode: 'stationary'
540
     * });
541
     *
542
     * // With download
543
     * const result = await el.captureAnimation({
544
     *   duration: 10000,
545
     *   fps: 60,
546
     *   cameraMode: 'stationary',
547
     *   download: true,
548
     *   downloadFilename: 'my-video.webm'
549
     * });
550
     * ```
551
     */
552
    async captureAnimation(options: import("./video/VideoCapture.js").AnimationOptions): Promise<import("./video/VideoCapture.js").AnimationResult> {
2✔
553
        return this.#graph.captureAnimation(options);
×
UNCOV
554
    }
×
555

556
    /**
557
     * Phase 7: Cancel Animation Capture
558
     * Cancel an ongoing animation capture
559
     * Available from Phase 7 onwards.
560
     *
561
     * @returns true if a capture was cancelled, false if no capture was in progress
562
     *
563
     * @example
564
     * ```typescript
565
     * const el = document.querySelector("graphty-element");
566
     *
567
     * // Start a 10-second capture
568
     * const capturePromise = el.captureAnimation({
569
     *   duration: 10000,
570
     *   fps: 30,
571
     *   cameraMode: 'stationary'
572
     * });
573
     *
574
     * // Cancel after 2 seconds
575
     * setTimeout(() => {
576
     *   const wasCancelled = el.cancelAnimationCapture();
577
     *   console.log('Cancelled:', wasCancelled);
578
     * }, 2000);
579
     *
580
     * // Handle the cancellation
581
     * try {
582
     *   await capturePromise;
583
     * } catch (error) {
584
     *   if (error.name === 'AnimationCancelledError') {
585
     *     console.log('Capture was cancelled by user');
586
     *   }
587
     * }
588
     * ```
589
     */
590
    cancelAnimationCapture(): boolean {
2✔
591
        return this.#graph.cancelAnimationCapture();
×
UNCOV
592
    }
×
593

594
    /**
595
     * Phase 7: Check if animation capture is in progress
596
     * Available from Phase 7 onwards.
597
     *
598
     * @returns true if a capture is currently running
599
     */
600
    isAnimationCapturing(): boolean {
2✔
UNCOV
601
        return this.#graph.isAnimationCapturing();
×
602
    }
×
603

604
    /**
605
     * Phase 7: Animation Capture Estimation
606
     * Estimate performance and potential issues for animation capture
607
     * Available from Phase 7 onwards.
608
     *
609
     * @param options - Animation options to estimate
610
     * @returns Promise<CaptureEstimate> - Estimation result
611
     *
612
     * @example
613
     * ```typescript
614
     * const el = document.querySelector("graphty-element");
615
     *
616
     * const estimate = await el.estimateAnimationCapture({
617
     *   duration: 5000,
618
     *   fps: 60,
619
     *   width: 3840,
620
     *   height: 2160
621
     * });
622
     *
623
     * if (estimate.likelyToDropFrames) {
624
     *   console.warn(`May drop frames. Try ${estimate.recommendedFps}fps instead.`);
625
     * }
626
     * ```
627
     */
628
    async estimateAnimationCapture(options: Pick<import("./video/VideoCapture.js").AnimationOptions, "duration" | "fps" | "width" | "height">): Promise<import("./video/estimation.js").CaptureEstimate> {
2✔
629
        return this.#graph.estimateAnimationCapture(options);
×
UNCOV
630
    }
×
631

632
    /**
633
     * Phase 4: Camera State API
634
     */
635

636
    /**
637
     * Get current camera state (supports both 2D and 3D)
638
     * @returns Current camera state including position, target, zoom, etc.
639
     */
640
    getCameraState(): import("./screenshot/types.js").CameraState {
2✔
641
        return this.#graph.getCameraState();
×
642
    }
×
643

644
    /**
645
     * Set camera state (supports both 2D and 3D)
646
     * @param state - Camera state to apply or preset name
647
     * @param options - Animation options
648
     * @returns Promise that resolves when the camera state is applied (or animation completes)
649
     */
650
    async setCameraState(
2✔
651
        state: import("./screenshot/types.js").CameraState | {preset: string},
×
652
        options?: import("./screenshot/types.js").CameraAnimationOptions,
×
653
    ): Promise<void> {
×
654
        return this.#graph.setCameraState(state, options);
×
655
    }
×
656

657
    /**
658
     * Set camera position (3D)
659
     * @param position - Target position {x, y, z}
660
     * @param options - Animation options
661
     * @returns Promise that resolves when the position is applied (or animation completes)
662
     */
663
    async setCameraPosition(
2✔
664
        position: {x: number, y: number, z: number},
×
665
        options?: import("./screenshot/types.js").CameraAnimationOptions,
×
666
    ): Promise<void> {
×
667
        return this.#graph.setCameraPosition(position, options);
×
668
    }
×
669

670
    /**
671
     * Set camera target (3D)
672
     * @param target - Target point to look at {x, y, z}
673
     * @param options - Animation options
674
     * @returns Promise that resolves when the target is applied (or animation completes)
675
     */
676
    async setCameraTarget(
2✔
677
        target: {x: number, y: number, z: number},
×
UNCOV
678
        options?: import("./screenshot/types.js").CameraAnimationOptions,
×
UNCOV
679
    ): Promise<void> {
×
UNCOV
680
        return this.#graph.setCameraTarget(target, options);
×
UNCOV
681
    }
×
682

683
    /**
684
     * Set camera zoom (2D)
685
     * @param zoom - Zoom level
686
     * @param options - Animation options
687
     * @returns Promise that resolves when the zoom is applied (or animation completes)
688
     */
689
    async setCameraZoom(
2✔
UNCOV
690
        zoom: number,
×
UNCOV
691
        options?: import("./screenshot/types.js").CameraAnimationOptions,
×
UNCOV
692
    ): Promise<void> {
×
UNCOV
693
        return this.#graph.setCameraZoom(zoom, options);
×
UNCOV
694
    }
×
695

696
    /**
697
     * Set camera pan (2D)
698
     * @param pan - Pan position {x, y}
699
     * @param options - Animation options
700
     * @returns Promise that resolves when the pan is applied (or animation completes)
701
     */
702
    async setCameraPan(
2✔
UNCOV
703
        pan: {x: number, y: number},
×
704
        options?: import("./screenshot/types.js").CameraAnimationOptions,
×
705
    ): Promise<void> {
×
UNCOV
706
        return this.#graph.setCameraPan(pan, options);
×
UNCOV
707
    }
×
708

709
    /**
710
     * Reset camera to default position
711
     * @param options - Animation options
712
     * @returns Promise that resolves when the reset is applied (or animation completes)
713
     */
714
    async resetCamera(options?: import("./screenshot/types.js").CameraAnimationOptions): Promise<void> {
2✔
UNCOV
715
        return this.#graph.resetCamera(options);
×
UNCOV
716
    }
×
717

718
    /**
719
     * Save current camera state as a named preset
720
     * Available from Phase 5 onwards
721
     * @param name - Name for the preset
722
     */
723
    saveCameraPreset(name: string): void {
2✔
UNCOV
724
        this.#graph.saveCameraPreset(name);
×
UNCOV
725
    }
×
726

727
    /**
728
     * Load a camera preset (built-in or user-defined)
729
     * Available from Phase 5 onwards
730
     * @param name - Name of the preset to load
731
     * @param options - Animation options
732
     */
733
    async loadCameraPreset(name: string, options?: import("./screenshot/types.js").CameraAnimationOptions): Promise<void> {
2✔
UNCOV
734
        return this.#graph.loadCameraPreset(name, options);
×
UNCOV
735
    }
×
736

737
    /**
738
     * Get all camera presets (built-in + user-defined)
739
     * Available from Phase 5 onwards
740
     * @returns Record of preset names to their state (built-in presets are marked)
741
     */
742
    getCameraPresets(): Record<string, import("./screenshot/types.js").CameraState | {builtin: true}> {
2✔
UNCOV
743
        return this.#graph.getCameraPresets();
×
UNCOV
744
    }
×
745

746
    /**
747
     * Export user-defined presets as JSON
748
     * Available from Phase 5 onwards
749
     * @returns Record of user-defined preset names to their state
750
     */
751
    exportCameraPresets(): Record<string, import("./screenshot/types.js").CameraState> {
2✔
UNCOV
752
        return this.#graph.exportCameraPresets();
×
UNCOV
753
    }
×
754

755
    /**
756
     * Import user-defined presets from JSON
757
     * Available from Phase 5 onwards
758
     * @param presets - Record of preset names to their state
759
     */
760
    importCameraPresets(presets: Record<string, import("./screenshot/types.js").CameraState>): void {
2✔
UNCOV
761
        this.#graph.importCameraPresets(presets);
×
UNCOV
762
    }
×
763

764
    /**
765
     * Get the underlying Graph instance for debugging purposes
766
     */
767
    get graph(): Graph {
2✔
768
        return this.#graph;
59✔
769
    }
59✔
770
}
2✔
771

772
// Type alias for easier importing
773
export type GraphtyElement = Graphty;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc