@vectojs/core 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -729,15 +729,23 @@ var Entity = class {
729
729
  return null;
730
730
  }
731
731
  /**
732
- * Whether this entity still has a queued/running tween animation.
732
+ * Whether this entity still has a queued/running tween animation, or an
733
+ * active {@link setTransition}/{@link animateTo}/{@link springTo} property
734
+ * driver.
733
735
  *
734
- * Used by {@link Scene}'s `onDemand` render mode to keep redrawing while an
735
- * animation is in flight.
736
+ * Used by {@link Scene} to keep rendering continuously while an animation
737
+ * is in flight — both in `onDemand` render mode, and to hold off the
738
+ * `always`-mode idle auto-throttle. Without checking `_drivers` here, a
739
+ * property driver becomes invisible to that throttle: `markDirty()` called
740
+ * from inside `update()`/`tickDrivers()` is wiped by the loop's own
741
+ * `dirty = false` at the end of that same tick, so once the throttle
742
+ * engages an in-flight spring/tween only advances one animation-frame per
743
+ * external `markDirty()` trigger instead of every render frame.
736
744
  *
737
- * @returns `true` if at least one animation remains.
745
+ * @returns `true` if at least one animation or property driver remains.
738
746
  */
739
747
  hasPendingAnimations() {
740
- return this.animations.length > 0;
748
+ return this.animations.length > 0 || this._drivers.size > 0;
741
749
  }
742
750
  };
743
751
 
@@ -729,15 +729,23 @@ var Entity = (_class4 = class {
729
729
  return null;
730
730
  }
731
731
  /**
732
- * Whether this entity still has a queued/running tween animation.
732
+ * Whether this entity still has a queued/running tween animation, or an
733
+ * active {@link setTransition}/{@link animateTo}/{@link springTo} property
734
+ * driver.
733
735
  *
734
- * Used by {@link Scene}'s `onDemand` render mode to keep redrawing while an
735
- * animation is in flight.
736
+ * Used by {@link Scene} to keep rendering continuously while an animation
737
+ * is in flight — both in `onDemand` render mode, and to hold off the
738
+ * `always`-mode idle auto-throttle. Without checking `_drivers` here, a
739
+ * property driver becomes invisible to that throttle: `markDirty()` called
740
+ * from inside `update()`/`tickDrivers()` is wiped by the loop's own
741
+ * `dirty = false` at the end of that same tick, so once the throttle
742
+ * engages an in-flight spring/tween only advances one animation-frame per
743
+ * external `markDirty()` trigger instead of every render frame.
736
744
  *
737
- * @returns `true` if at least one animation remains.
745
+ * @returns `true` if at least one animation or property driver remains.
738
746
  */
739
747
  hasPendingAnimations() {
740
- return this.animations.length > 0;
748
+ return this.animations.length > 0 || this._drivers.size > 0;
741
749
  }
742
750
  }, _class4);
743
751
 
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ var _chunkLIX7DJTIjs = require('./chunk-LIX7DJTI.js');
22
22
 
23
23
 
24
24
 
25
- var _chunkW3SFIVXOjs = require('./chunk-W3SFIVXO.js');
25
+ var _chunkLA3FJLP2js = require('./chunk-LA3FJLP2.js');
26
26
 
27
27
 
28
28
 
@@ -39,7 +39,7 @@ var PARTICLE_OFFSET_ORIGIN_X = 4;
39
39
  var PARTICLE_OFFSET_ORIGIN_Y = 5;
40
40
  var PARTICLE_OFFSET_SIZE = 6;
41
41
  var PARTICLE_OFFSET_LIFE = 7;
42
- var ComputeParticleEntity = (_class = class extends _chunkW3SFIVXOjs.Entity {
42
+ var ComputeParticleEntity = (_class = class extends _chunkLA3FJLP2js.Entity {
43
43
 
44
44
 
45
45
 
@@ -318,16 +318,18 @@ var Scene = (_class2 = class _Scene {
318
318
  */
319
319
  __init9() {this.renderMode = "always"}
320
320
  __init10() {this.dirty = true}
321
+ /** Whether to throttle rendering to 2 FPS when the scene is static to save power. */
322
+ __init11() {this.autoThrottle = true}
321
323
  /**
322
324
  * Frame-rate cap (power saving). `0` = uncapped (native refresh). When set,
323
325
  * the loop renders at most `maxFPS` times per second; animations still run,
324
326
  * just less often. See {@link SceneOptions.maxFPS}.
325
327
  */
326
- __init11() {this.maxFPS = 60}
328
+ __init12() {this.maxFPS = 60}
327
329
  /** Whether the OS prefers-reduced-motion setting auto-caps the loop. */
328
- __init12() {this.respectReducedMotion = true}
330
+ __init13() {this.respectReducedMotion = true}
329
331
  /** Cached media-query list; `.matches` is read live each frame. */
330
- __init13() {this.reducedMotionQuery = null}
332
+ __init14() {this.reducedMotionQuery = null}
331
333
  /** True when the OS asks for reduced motion and we respect it. Read by the animation drivers. */
332
334
  get prefersReducedMotion() {
333
335
  return this.respectReducedMotion && !!_optionalChain([this, 'access', _13 => _13.reducedMotionQuery, 'optionalAccess', _14 => _14.matches]);
@@ -336,58 +338,58 @@ var Scene = (_class2 = class _Scene {
336
338
  * Throttle interval (ms) for the a11y/automation shadow sync. `0` = every
337
339
  * frame. See {@link SceneOptions.a11ySyncInterval}.
338
340
  */
339
- __init14() {this.a11ySyncInterval = 0}
341
+ __init15() {this.a11ySyncInterval = 0}
340
342
  /** Timestamp of the last a11y sync, for throttling. */
341
- __init15() {this.lastA11ySync = -Infinity}
343
+ __init16() {this.lastA11ySync = -Infinity}
342
344
  /** True if we skipped an a11y sync during animation and need to sync when at rest. */
343
- __init16() {this.a11yPendingSyncAfterAnimation = false}
345
+ __init17() {this.a11yPendingSyncAfterAnimation = false}
344
346
  // A11y / Automation Layer. `null` in non-DOM (SSR/Node) environments — the
345
347
  // whole projection degrades to a no-op so the engine's logic stays usable
346
348
  // server-side (e.g. headless layout / vector export) without jsdom.
347
349
 
348
- __init17() {this.a11yElements = /* @__PURE__ */ new Map()}
350
+ __init18() {this.a11yElements = /* @__PURE__ */ new Map()}
349
351
 
350
- __init18() {this.focusedA11yElement = null}
351
- __init19() {this.caretBlinkTimer = null}
352
- __init20() {this.a11yNeedsReorder = true}
353
- __init21() {this.portalRoot = null}
354
- __init22() {this.fullViewportElements = []}
355
- __init23() {this.normalElements = []}
356
- __init24() {this.activeIds = /* @__PURE__ */ new Set()}
357
- __init25() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
358
- __init26() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
359
- __init27() {this.portalEntities = /* @__PURE__ */ new Map()}
360
- __init28() {this.renderOrderCounter = 0}
352
+ __init19() {this.focusedA11yElement = null}
353
+ __init20() {this.caretBlinkTimer = null}
354
+ __init21() {this.a11yNeedsReorder = true}
355
+ __init22() {this.portalRoot = null}
356
+ __init23() {this.fullViewportElements = []}
357
+ __init24() {this.normalElements = []}
358
+ __init25() {this.activeIds = /* @__PURE__ */ new Set()}
359
+ __init26() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
360
+ __init27() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
361
+ __init28() {this.portalEntities = /* @__PURE__ */ new Map()}
362
+ __init29() {this.renderOrderCounter = 0}
361
363
  // Optional WebGL point-cloud layer (see SceneOptions.pointBackend).
362
- __init29() {this.pointRenderer = null}
363
- __init30() {this.glCanvas = null}
364
+ __init30() {this.pointRenderer = null}
365
+ __init31() {this.glCanvas = null}
364
366
 
365
367
 
366
368
 
367
- __init31() {this.disableWindowResize = false}
369
+ __init32() {this.disableWindowResize = false}
368
370
  // WebGPU properties
369
- __init32() {this.destroyed = false}
370
- __init33() {this.device = null}
371
- __init34() {this.deviceLost = false}
372
- __init35() {this.particleBackend = "auto"}
373
- __init36() {this._webgpuDisabled = false}
371
+ __init33() {this.destroyed = false}
372
+ __init34() {this.device = null}
373
+ __init35() {this.deviceLost = false}
374
+ __init36() {this.particleBackend = "auto"}
375
+ __init37() {this._webgpuDisabled = false}
374
376
  get webgpuDisabled() {
375
377
  return this._webgpuDisabled || this.particleBackend === "cpu";
376
378
  }
377
379
  set webgpuDisabled(value) {
378
380
  this._webgpuDisabled = value;
379
381
  }
380
- __init37() {this.recoveryTimerId = null}
381
- __init38() {this.manager = null}
382
- __init39() {this.initializingWebGPU = false}
383
- __init40() {this.gpuCanvas = null}
384
- __init41() {this.gpuContext = null}
385
- __init42() {this.mouseX = -9999}
386
- __init43() {this.mouseY = -9999}
387
- __init44() {this.pointerMoveListener = null}
388
- __init45() {this.pointerLeaveListener = null}
389
- __init46() {this.hasWarnedZeroSize = false}
390
- constructor(canvas, options = {}) {;_class2.prototype.__init7.call(this);_class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);_class2.prototype.__init10.call(this);_class2.prototype.__init11.call(this);_class2.prototype.__init12.call(this);_class2.prototype.__init13.call(this);_class2.prototype.__init14.call(this);_class2.prototype.__init15.call(this);_class2.prototype.__init16.call(this);_class2.prototype.__init17.call(this);_class2.prototype.__init18.call(this);_class2.prototype.__init19.call(this);_class2.prototype.__init20.call(this);_class2.prototype.__init21.call(this);_class2.prototype.__init22.call(this);_class2.prototype.__init23.call(this);_class2.prototype.__init24.call(this);_class2.prototype.__init25.call(this);_class2.prototype.__init26.call(this);_class2.prototype.__init27.call(this);_class2.prototype.__init28.call(this);_class2.prototype.__init29.call(this);_class2.prototype.__init30.call(this);_class2.prototype.__init31.call(this);_class2.prototype.__init32.call(this);_class2.prototype.__init33.call(this);_class2.prototype.__init34.call(this);_class2.prototype.__init35.call(this);_class2.prototype.__init36.call(this);_class2.prototype.__init37.call(this);_class2.prototype.__init38.call(this);_class2.prototype.__init39.call(this);_class2.prototype.__init40.call(this);_class2.prototype.__init41.call(this);_class2.prototype.__init42.call(this);_class2.prototype.__init43.call(this);_class2.prototype.__init44.call(this);_class2.prototype.__init45.call(this);_class2.prototype.__init46.call(this);
382
+ __init38() {this.recoveryTimerId = null}
383
+ __init39() {this.manager = null}
384
+ __init40() {this.initializingWebGPU = false}
385
+ __init41() {this.gpuCanvas = null}
386
+ __init42() {this.gpuContext = null}
387
+ __init43() {this.mouseX = -9999}
388
+ __init44() {this.mouseY = -9999}
389
+ __init45() {this.pointerMoveListener = null}
390
+ __init46() {this.pointerLeaveListener = null}
391
+ __init47() {this.hasWarnedZeroSize = false}
392
+ constructor(canvas, options = {}) {;_class2.prototype.__init7.call(this);_class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);_class2.prototype.__init10.call(this);_class2.prototype.__init11.call(this);_class2.prototype.__init12.call(this);_class2.prototype.__init13.call(this);_class2.prototype.__init14.call(this);_class2.prototype.__init15.call(this);_class2.prototype.__init16.call(this);_class2.prototype.__init17.call(this);_class2.prototype.__init18.call(this);_class2.prototype.__init19.call(this);_class2.prototype.__init20.call(this);_class2.prototype.__init21.call(this);_class2.prototype.__init22.call(this);_class2.prototype.__init23.call(this);_class2.prototype.__init24.call(this);_class2.prototype.__init25.call(this);_class2.prototype.__init26.call(this);_class2.prototype.__init27.call(this);_class2.prototype.__init28.call(this);_class2.prototype.__init29.call(this);_class2.prototype.__init30.call(this);_class2.prototype.__init31.call(this);_class2.prototype.__init32.call(this);_class2.prototype.__init33.call(this);_class2.prototype.__init34.call(this);_class2.prototype.__init35.call(this);_class2.prototype.__init36.call(this);_class2.prototype.__init37.call(this);_class2.prototype.__init38.call(this);_class2.prototype.__init39.call(this);_class2.prototype.__init40.call(this);_class2.prototype.__init41.call(this);_class2.prototype.__init42.call(this);_class2.prototype.__init43.call(this);_class2.prototype.__init44.call(this);_class2.prototype.__init45.call(this);_class2.prototype.__init46.call(this);_class2.prototype.__init47.call(this);
391
393
  this.canvas = canvas;
392
394
  this.debugA11y = _nullishCoalesce(options.debugA11y, () => ( false));
393
395
  this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
@@ -402,10 +404,11 @@ var Scene = (_class2 = class _Scene {
402
404
  const isTest = globalProcess && (_optionalChain([globalProcess, 'access', _15 => _15.env, 'optionalAccess', _16 => _16.NODE_ENV]) === "test" || _optionalChain([globalProcess, 'access', _17 => _17.env, 'optionalAccess', _18 => _18.VITEST]) === "true");
403
405
  this.maxFPS = _nullishCoalesce(options.maxFPS, () => ( (isTest ? 0 : 60)));
404
406
  this.respectReducedMotion = _nullishCoalesce(options.respectReducedMotion, () => ( true));
407
+ this.autoThrottle = _nullishCoalesce(options.autoThrottle, () => ( true));
405
408
  this.particleBackend = _nullishCoalesce(options.particleBackend, () => ( "auto"));
406
409
  this.a11ySyncInterval = _nullishCoalesce(options.a11ySyncInterval, () => ( 0));
407
410
  this.reducedMotionQuery = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
408
- this.root = new class RootEntity extends _chunkW3SFIVXOjs.Entity {
411
+ this.root = new class RootEntity extends _chunkLA3FJLP2js.Entity {
409
412
  isPointInside() {
410
413
  return false;
411
414
  }
@@ -414,7 +417,7 @@ var Scene = (_class2 = class _Scene {
414
417
  }
415
418
  }("root");
416
419
  this.root._scene = this;
417
- this.overlayRoot = new class OverlayRoot extends _chunkW3SFIVXOjs.Entity {
420
+ this.overlayRoot = new class OverlayRoot extends _chunkLA3FJLP2js.Entity {
418
421
  isPointInside() {
419
422
  return false;
420
423
  }
@@ -653,6 +656,17 @@ var Scene = (_class2 = class _Scene {
653
656
  this.caretBlinkTimer = null;
654
657
  }
655
658
  }
659
+ /**
660
+ * Manually advance the scene clock by `dt` milliseconds and render synchronously.
661
+ * Essential for deterministic rendering (e.g. video export).
662
+ * Note: You should call `scene.stop()` before using this to avoid conflict with the rAF loop.
663
+ */
664
+ step(dt) {
665
+ const time = this.lastTime + dt;
666
+ this.lastTime = time;
667
+ this.render(this.renderer, dt, time);
668
+ this.dirty = false;
669
+ }
656
670
  /**
657
671
  * Mark the scene as needing a redraw on the next frame.
658
672
  *
@@ -722,42 +736,42 @@ var Scene = (_class2 = class _Scene {
722
736
  el.style.background = "transparent";
723
737
  }
724
738
  el.addEventListener("click", (e) => {
725
- node.dispatchEvent(new (0, _chunkW3SFIVXOjs.VectoJSEvent)("click", node, e));
739
+ node.dispatchEvent(new (0, _chunkLA3FJLP2js.VectoJSEvent)("click", node, e));
726
740
  });
727
741
  el.addEventListener("mouseenter", (e) => {
728
742
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.2)";
729
- node.dispatchEvent(new (0, _chunkW3SFIVXOjs.VectoJSEvent)("hover", node, e, false));
743
+ node.dispatchEvent(new (0, _chunkLA3FJLP2js.VectoJSEvent)("hover", node, e, false));
730
744
  });
731
745
  el.addEventListener("mouseleave", (e) => {
732
746
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.05)";
733
- node.dispatchEvent(new (0, _chunkW3SFIVXOjs.VectoJSEvent)("pointerleave", node, e, false));
747
+ node.dispatchEvent(new (0, _chunkLA3FJLP2js.VectoJSEvent)("pointerleave", node, e, false));
734
748
  });
735
749
  const capEl = el;
736
750
  el.addEventListener("pointerdown", (e) => {
737
751
  if (typeof capEl.setPointerCapture === "function") capEl.setPointerCapture(e.pointerId);
738
- node.dispatchEvent(new (0, _chunkW3SFIVXOjs.VectoJSEvent)("pointerdown", node, e));
752
+ node.dispatchEvent(new (0, _chunkLA3FJLP2js.VectoJSEvent)("pointerdown", node, e));
739
753
  });
740
754
  el.addEventListener("pointerup", (e) => {
741
755
  if (typeof capEl.releasePointerCapture === "function")
742
756
  capEl.releasePointerCapture(e.pointerId);
743
- node.dispatchEvent(new (0, _chunkW3SFIVXOjs.VectoJSEvent)("pointerup", node, e));
757
+ node.dispatchEvent(new (0, _chunkLA3FJLP2js.VectoJSEvent)("pointerup", node, e));
744
758
  });
745
759
  el.addEventListener(
746
760
  "pointermove",
747
- (e) => node.dispatchEvent(new (0, _chunkW3SFIVXOjs.VectoJSEvent)("pointermove", node, e))
761
+ (e) => node.dispatchEvent(new (0, _chunkLA3FJLP2js.VectoJSEvent)("pointermove", node, e))
748
762
  );
749
763
  el.addEventListener(
750
764
  "wheel",
751
765
  (e) => {
752
- node.dispatchEvent(new (0, _chunkW3SFIVXOjs.VectoJSEvent)("wheel", node, e));
766
+ node.dispatchEvent(new (0, _chunkLA3FJLP2js.VectoJSEvent)("wheel", node, e));
753
767
  },
754
768
  { passive: false }
755
769
  );
756
770
  el.addEventListener("keydown", (e) => {
757
- node.dispatchEvent(new (0, _chunkW3SFIVXOjs.VectoJSEvent)("keydown", node, e));
771
+ node.dispatchEvent(new (0, _chunkLA3FJLP2js.VectoJSEvent)("keydown", node, e));
758
772
  });
759
773
  el.addEventListener("keyup", (e) => {
760
- node.dispatchEvent(new (0, _chunkW3SFIVXOjs.VectoJSEvent)("keyup", node, e));
774
+ node.dispatchEvent(new (0, _chunkLA3FJLP2js.VectoJSEvent)("keyup", node, e));
761
775
  });
762
776
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
763
777
  const input = el;
@@ -829,7 +843,7 @@ var Scene = (_class2 = class _Scene {
829
843
  el.addEventListener("keydown", (e) => {
830
844
  if (e.key === "Enter" || e.key === " ") {
831
845
  e.preventDefault();
832
- node.dispatchEvent(new (0, _chunkW3SFIVXOjs.VectoJSEvent)("click", node, e));
846
+ node.dispatchEvent(new (0, _chunkLA3FJLP2js.VectoJSEvent)("click", node, e));
833
847
  }
834
848
  });
835
849
  }
@@ -1090,7 +1104,7 @@ var Scene = (_class2 = class _Scene {
1090
1104
  loop(time) {
1091
1105
  if (!this.isRunning) return;
1092
1106
  let cap = this.effectiveMaxFPS();
1093
- const isStatic = !this.dirty && !this.hasAnyPendingAnimation(this.root) && !this.hasAnyPendingAnimation(this.overlayRoot);
1107
+ const isStatic = this.autoThrottle && !this.dirty && !this.hasAnyPendingAnimation(this.root) && !this.hasAnyPendingAnimation(this.overlayRoot);
1094
1108
  if (isStatic && this.renderMode === "always" && this.maxFPS > 0) {
1095
1109
  cap = Math.min(cap, 2);
1096
1110
  }
@@ -1106,19 +1120,17 @@ var Scene = (_class2 = class _Scene {
1106
1120
  }
1107
1121
  this.render(this.renderer, dt, time);
1108
1122
  const hasActiveAnimation = this.hasAnyPendingAnimation(this.root) || this.hasAnyPendingAnimation(this.overlayRoot);
1109
- if (hasActiveAnimation) {
1123
+ const hasInteractive = this.hasAnyInteractive(this.root) || this.hasAnyInteractive(this.overlayRoot);
1124
+ const shouldSyncInterval = this.a11ySyncInterval <= 0 || time - this.lastA11ySync >= this.a11ySyncInterval;
1125
+ if ((hasInteractive || this.a11yElements.size > 0) && (shouldSyncInterval || this.a11yPendingSyncAfterAnimation)) {
1126
+ this.lastA11ySync = time;
1127
+ if (hasInteractive) {
1128
+ this.syncA11y(this.root);
1129
+ }
1130
+ this.enforceA11yDomOrder();
1131
+ this.a11yPendingSyncAfterAnimation = hasActiveAnimation;
1132
+ } else if (hasActiveAnimation) {
1110
1133
  this.a11yPendingSyncAfterAnimation = true;
1111
- } else {
1112
- const hasInteractive = this.hasAnyInteractive(this.root) || this.hasAnyInteractive(this.overlayRoot);
1113
- const shouldSyncInterval = this.a11ySyncInterval <= 0 || time - this.lastA11ySync >= this.a11ySyncInterval;
1114
- if ((hasInteractive || this.a11yElements.size > 0) && (shouldSyncInterval || this.a11yPendingSyncAfterAnimation)) {
1115
- this.lastA11ySync = time;
1116
- if (hasInteractive) {
1117
- this.syncA11y(this.root);
1118
- }
1119
- this.enforceA11yDomOrder();
1120
- this.a11yPendingSyncAfterAnimation = false;
1121
- }
1122
1134
  }
1123
1135
  this.dirty = false;
1124
1136
  this.scheduleFrame();
@@ -1523,20 +1535,20 @@ function defaultMeasurer() {
1523
1535
  if (sharedMeasurer === void 0) sharedMeasurer = _chunk72WVPMSJjs.createCanvasMeasurer.call(void 0, "sans-serif");
1524
1536
  return sharedMeasurer;
1525
1537
  }
1526
- var TextEntity = (_class3 = class extends _chunkW3SFIVXOjs.Entity {
1538
+ var TextEntity = (_class3 = class extends _chunkLA3FJLP2js.Entity {
1527
1539
 
1528
1540
 
1529
1541
 
1530
1542
 
1531
- __init47() {this.nodes = []}
1543
+ __init48() {this.nodes = []}
1532
1544
 
1533
- __init48() {this.fillStyle = "#94a3b8"}
1534
- __init49() {this.strokeStyle = null}
1535
- __init50() {this.hoveredFillStyle = "#ffffff"}
1536
- __init51() {this.lineWidth = 1}
1537
- __init52() {this.isHovered = false}
1545
+ __init49() {this.fillStyle = "#94a3b8"}
1546
+ __init50() {this.strokeStyle = null}
1547
+ __init51() {this.hoveredFillStyle = "#ffffff"}
1548
+ __init52() {this.lineWidth = 1}
1549
+ __init53() {this.isHovered = false}
1538
1550
  constructor(text, atlas, maxWidth, fontSize = 24) {
1539
- super();_class3.prototype.__init47.call(this);_class3.prototype.__init48.call(this);_class3.prototype.__init49.call(this);_class3.prototype.__init50.call(this);_class3.prototype.__init51.call(this);_class3.prototype.__init52.call(this);;
1551
+ super();_class3.prototype.__init48.call(this);_class3.prototype.__init49.call(this);_class3.prototype.__init50.call(this);_class3.prototype.__init51.call(this);_class3.prototype.__init52.call(this);_class3.prototype.__init53.call(this);;
1540
1552
  this.text = text;
1541
1553
  this.atlas = atlas;
1542
1554
  this.fontSize = fontSize;
@@ -1622,17 +1634,17 @@ var TextEntity = (_class3 = class extends _chunkW3SFIVXOjs.Entity {
1622
1634
  }, _class3);
1623
1635
 
1624
1636
  // src/components/GridTextEntity.ts
1625
- var GridTextEntity = (_class4 = class extends _chunkW3SFIVXOjs.Entity {
1637
+ var GridTextEntity = (_class4 = class extends _chunkLA3FJLP2js.Entity {
1626
1638
 
1627
- __init53() {this.fillStyle = "#ffffff"}
1628
- __init54() {this.grid = []}
1639
+ __init54() {this.fillStyle = "#ffffff"}
1640
+ __init55() {this.grid = []}
1629
1641
  // Array of rows
1630
- __init55() {this.cols = 0}
1631
- __init56() {this.rows = 0}
1642
+ __init56() {this.cols = 0}
1643
+ __init57() {this.rows = 0}
1632
1644
 
1633
1645
 
1634
1646
  constructor(_atlas, fontSize = 10) {
1635
- super();_class4.prototype.__init53.call(this);_class4.prototype.__init54.call(this);_class4.prototype.__init55.call(this);_class4.prototype.__init56.call(this);;
1647
+ super();_class4.prototype.__init54.call(this);_class4.prototype.__init55.call(this);_class4.prototype.__init56.call(this);_class4.prototype.__init57.call(this);;
1636
1648
  this.fontSize = fontSize;
1637
1649
  this.charWidth = fontSize * 1;
1638
1650
  this.charHeight = fontSize * 1.1;
@@ -1716,7 +1728,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
1716
1728
  const ey = py - cy;
1717
1729
  return ex * ex + ey * ey;
1718
1730
  }
1719
- var SplineEntity = (_class5 = class extends _chunkW3SFIVXOjs.Entity {
1731
+ var SplineEntity = (_class5 = class extends _chunkLA3FJLP2js.Entity {
1720
1732
 
1721
1733
 
1722
1734
 
@@ -1724,18 +1736,18 @@ var SplineEntity = (_class5 = class extends _chunkW3SFIVXOjs.Entity {
1724
1736
 
1725
1737
 
1726
1738
 
1727
- __init57() {this.offscreen = null}
1728
- __init58() {this.baked = false}
1739
+ __init58() {this.offscreen = null}
1740
+ __init59() {this.baked = false}
1729
1741
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
1730
- __init59() {this.polylines = null}
1742
+ __init60() {this.polylines = null}
1731
1743
  /**
1732
1744
  * When `true`, the renderer draws a rounded-rect outline of the entity's
1733
1745
  * local bounds after painting the curves. Useful for drag feedback and
1734
1746
  * debugging hit areas. Defaults to `false`.
1735
1747
  */
1736
- __init60() {this.showBounds = false}
1748
+ __init61() {this.showBounds = false}
1737
1749
  constructor(doc, opts = {}) {
1738
- super();_class5.prototype.__init57.call(this);_class5.prototype.__init58.call(this);_class5.prototype.__init59.call(this);_class5.prototype.__init60.call(this);;
1750
+ super();_class5.prototype.__init58.call(this);_class5.prototype.__init59.call(this);_class5.prototype.__init60.call(this);_class5.prototype.__init61.call(this);;
1739
1751
  this.doc = doc;
1740
1752
  this.lineWidth = _nullishCoalesce(opts.lineWidth, () => ( 2));
1741
1753
  this.cache = _nullishCoalesce(opts.cache, () => ( true));
@@ -1982,9 +1994,9 @@ async function loadSpline(url) {
1982
1994
  // src/math/SpatialHashGrid.ts
1983
1995
  var SpatialHashGrid = (_class6 = class {
1984
1996
 
1985
- __init61() {this.grid = /* @__PURE__ */ new Map()}
1986
- __init62() {this.entityCells = /* @__PURE__ */ new Map()}
1987
- constructor(cellSize = 64) {;_class6.prototype.__init61.call(this);_class6.prototype.__init62.call(this);
1997
+ __init62() {this.grid = /* @__PURE__ */ new Map()}
1998
+ __init63() {this.entityCells = /* @__PURE__ */ new Map()}
1999
+ constructor(cellSize = 64) {;_class6.prototype.__init62.call(this);_class6.prototype.__init63.call(this);
1988
2000
  this.cellSize = cellSize;
1989
2001
  }
1990
2002
  hash(cx, cy) {
@@ -2074,19 +2086,19 @@ var SpatialHashGrid = (_class6 = class {
2074
2086
  }, _class6);
2075
2087
 
2076
2088
  // src/tree/DOMPortalEntity.ts
2077
- var DOMPortalEntity = (_class7 = class extends _chunkW3SFIVXOjs.Entity {
2089
+ var DOMPortalEntity = (_class7 = class extends _chunkLA3FJLP2js.Entity {
2078
2090
 
2079
- __init63() {this.isDOMPortal = true}
2080
- __init64() {this.domListeners = []}
2081
- __init65() {this.resizeObserver = null}
2082
- __init66() {this.cachedWidth = 100}
2083
- __init67() {this.cachedHeight = 100}
2084
- __init68() {this.lastWidth = ""}
2085
- __init69() {this.lastHeight = ""}
2086
- __init70() {this.lastTransform = ""}
2087
- __init71() {this.lastZIndex = ""}
2091
+ __init64() {this.isDOMPortal = true}
2092
+ __init65() {this.domListeners = []}
2093
+ __init66() {this.resizeObserver = null}
2094
+ __init67() {this.cachedWidth = 100}
2095
+ __init68() {this.cachedHeight = 100}
2096
+ __init69() {this.lastWidth = ""}
2097
+ __init70() {this.lastHeight = ""}
2098
+ __init71() {this.lastTransform = ""}
2099
+ __init72() {this.lastZIndex = ""}
2088
2100
  constructor(domElement, width, height, id) {
2089
- super(id);_class7.prototype.__init63.call(this);_class7.prototype.__init64.call(this);_class7.prototype.__init65.call(this);_class7.prototype.__init66.call(this);_class7.prototype.__init67.call(this);_class7.prototype.__init68.call(this);_class7.prototype.__init69.call(this);_class7.prototype.__init70.call(this);_class7.prototype.__init71.call(this);;
2101
+ super(id);_class7.prototype.__init64.call(this);_class7.prototype.__init65.call(this);_class7.prototype.__init66.call(this);_class7.prototype.__init67.call(this);_class7.prototype.__init68.call(this);_class7.prototype.__init69.call(this);_class7.prototype.__init70.call(this);_class7.prototype.__init71.call(this);_class7.prototype.__init72.call(this);;
2090
2102
  this.domElement = domElement;
2091
2103
  this.width = _nullishCoalesce(width, () => ( 0));
2092
2104
  this.height = _nullishCoalesce(height, () => ( 0));
@@ -2108,7 +2120,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkW3SFIVXOjs.Entity {
2108
2120
  const events = ["click", "pointerdown", "pointerup", "pointermove", "wheel"];
2109
2121
  for (const type of events) {
2110
2122
  const handler = (e) => {
2111
- this.dispatchEvent(new (0, _chunkW3SFIVXOjs.VectoJSEvent)(type, this, e));
2123
+ this.dispatchEvent(new (0, _chunkLA3FJLP2js.VectoJSEvent)(type, this, e));
2112
2124
  };
2113
2125
  this.domElement.addEventListener(type, handler);
2114
2126
  this.domListeners.push({ type, handler, capture: false });
@@ -2119,7 +2131,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkW3SFIVXOjs.Entity {
2119
2131
  ];
2120
2132
  for (const { native, vecto } of hoverEvents) {
2121
2133
  const handler = (e) => {
2122
- this.dispatchEvent(new (0, _chunkW3SFIVXOjs.VectoJSEvent)(vecto, this, e, false));
2134
+ this.dispatchEvent(new (0, _chunkLA3FJLP2js.VectoJSEvent)(vecto, this, e, false));
2123
2135
  };
2124
2136
  this.domElement.addEventListener(native, handler);
2125
2137
  this.domListeners.push({ type: native, handler, capture: false });
@@ -2127,7 +2139,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkW3SFIVXOjs.Entity {
2127
2139
  const focusEvents = ["focus", "blur"];
2128
2140
  for (const type of focusEvents) {
2129
2141
  const handler = (e) => {
2130
- this.dispatchEvent(new (0, _chunkW3SFIVXOjs.VectoJSEvent)(type, this, e, true));
2142
+ this.dispatchEvent(new (0, _chunkLA3FJLP2js.VectoJSEvent)(type, this, e, true));
2131
2143
  };
2132
2144
  this.domElement.addEventListener(type, handler, true);
2133
2145
  this.domListeners.push({ type, handler, capture: true });
@@ -2217,4 +2229,4 @@ Scene.registerWebGPUParticleSystemManager(_chunkLIX7DJTIjs.WebGPUParticleSystemM
2217
2229
 
2218
2230
 
2219
2231
 
2220
- exports.ArabicShaper = _chunkRW6NC4RBjs.ArabicShaper; exports.BidiResolver = _chunkRW6NC4RBjs.BidiResolver; exports.CanvasRenderer = _chunkLIX7DJTIjs.CanvasRenderer; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Easing = _chunkW3SFIVXOjs.Easing; exports.Entity = _chunkW3SFIVXOjs.Entity; exports.GridTextEntity = GridTextEntity; exports.LayoutEngine = _chunk72WVPMSJjs.LayoutEngine; exports.LayoutResultBuffer = _chunk72WVPMSJjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunkRW6NC4RBjs.LayoutWorkerManager; exports.MSDFFont = _chunkW3SFIVXOjs.MSDFFont; exports.MSDFTextEntity = _chunkW3SFIVXOjs.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.SVGEntity = _chunkW3SFIVXOjs.SVGEntity; exports.SVGRenderer = _chunkLIX7DJTIjs.SVGRenderer; exports.Scene = Scene; exports.SpatialHashGrid = SpatialHashGrid; exports.SplineEntity = SplineEntity; exports.SpringDriver = _chunkW3SFIVXOjs.SpringDriver; exports.SpringPhysics = _chunkW3SFIVXOjs.SpringPhysics; exports.TextEntity = TextEntity; exports.TweenDriver = _chunkW3SFIVXOjs.TweenDriver; exports.VectoJSEvent = _chunkW3SFIVXOjs.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkLIX7DJTIjs.WebGPUParticleSystemManager; exports.computeLineSegments = _chunk72WVPMSJjs.computeLineSegments; exports.createCanvasMeasurer = _chunk72WVPMSJjs.createCanvasMeasurer; exports.createWebGLPointRenderer = _chunkLIX7DJTIjs.createWebGLPointRenderer; exports.isTweenConfig = _chunkW3SFIVXOjs.isTweenConfig; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkLIX7DJTIjs.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier;
2232
+ exports.ArabicShaper = _chunkRW6NC4RBjs.ArabicShaper; exports.BidiResolver = _chunkRW6NC4RBjs.BidiResolver; exports.CanvasRenderer = _chunkLIX7DJTIjs.CanvasRenderer; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Easing = _chunkLA3FJLP2js.Easing; exports.Entity = _chunkLA3FJLP2js.Entity; exports.GridTextEntity = GridTextEntity; exports.LayoutEngine = _chunk72WVPMSJjs.LayoutEngine; exports.LayoutResultBuffer = _chunk72WVPMSJjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunkRW6NC4RBjs.LayoutWorkerManager; exports.MSDFFont = _chunkLA3FJLP2js.MSDFFont; exports.MSDFTextEntity = _chunkLA3FJLP2js.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.SVGEntity = _chunkLA3FJLP2js.SVGEntity; exports.SVGRenderer = _chunkLIX7DJTIjs.SVGRenderer; exports.Scene = Scene; exports.SpatialHashGrid = SpatialHashGrid; exports.SplineEntity = SplineEntity; exports.SpringDriver = _chunkLA3FJLP2js.SpringDriver; exports.SpringPhysics = _chunkLA3FJLP2js.SpringPhysics; exports.TextEntity = TextEntity; exports.TweenDriver = _chunkLA3FJLP2js.TweenDriver; exports.VectoJSEvent = _chunkLA3FJLP2js.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkLIX7DJTIjs.WebGPUParticleSystemManager; exports.computeLineSegments = _chunk72WVPMSJjs.computeLineSegments; exports.createCanvasMeasurer = _chunk72WVPMSJjs.createCanvasMeasurer; exports.createWebGLPointRenderer = _chunkLIX7DJTIjs.createWebGLPointRenderer; exports.isTweenConfig = _chunkLA3FJLP2js.isTweenConfig; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkLIX7DJTIjs.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier;
package/dist/index.mjs CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  TweenDriver,
23
23
  VectoJSEvent,
24
24
  isTweenConfig
25
- } from "./chunk-Y2N7TGEH.mjs";
25
+ } from "./chunk-H3QIE77O.mjs";
26
26
  import {
27
27
  ArabicShaper,
28
28
  BidiResolver,
@@ -318,6 +318,8 @@ var Scene = class _Scene {
318
318
  */
319
319
  renderMode = "always";
320
320
  dirty = true;
321
+ /** Whether to throttle rendering to 2 FPS when the scene is static to save power. */
322
+ autoThrottle = true;
321
323
  /**
322
324
  * Frame-rate cap (power saving). `0` = uncapped (native refresh). When set,
323
325
  * the loop renders at most `maxFPS` times per second; animations still run,
@@ -402,6 +404,7 @@ var Scene = class _Scene {
402
404
  const isTest = globalProcess && (globalProcess.env?.NODE_ENV === "test" || globalProcess.env?.VITEST === "true");
403
405
  this.maxFPS = options.maxFPS ?? (isTest ? 0 : 60);
404
406
  this.respectReducedMotion = options.respectReducedMotion ?? true;
407
+ this.autoThrottle = options.autoThrottle ?? true;
405
408
  this.particleBackend = options.particleBackend ?? "auto";
406
409
  this.a11ySyncInterval = options.a11ySyncInterval ?? 0;
407
410
  this.reducedMotionQuery = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
@@ -653,6 +656,17 @@ var Scene = class _Scene {
653
656
  this.caretBlinkTimer = null;
654
657
  }
655
658
  }
659
+ /**
660
+ * Manually advance the scene clock by `dt` milliseconds and render synchronously.
661
+ * Essential for deterministic rendering (e.g. video export).
662
+ * Note: You should call `scene.stop()` before using this to avoid conflict with the rAF loop.
663
+ */
664
+ step(dt) {
665
+ const time = this.lastTime + dt;
666
+ this.lastTime = time;
667
+ this.render(this.renderer, dt, time);
668
+ this.dirty = false;
669
+ }
656
670
  /**
657
671
  * Mark the scene as needing a redraw on the next frame.
658
672
  *
@@ -1090,7 +1104,7 @@ var Scene = class _Scene {
1090
1104
  loop(time) {
1091
1105
  if (!this.isRunning) return;
1092
1106
  let cap = this.effectiveMaxFPS();
1093
- const isStatic = !this.dirty && !this.hasAnyPendingAnimation(this.root) && !this.hasAnyPendingAnimation(this.overlayRoot);
1107
+ const isStatic = this.autoThrottle && !this.dirty && !this.hasAnyPendingAnimation(this.root) && !this.hasAnyPendingAnimation(this.overlayRoot);
1094
1108
  if (isStatic && this.renderMode === "always" && this.maxFPS > 0) {
1095
1109
  cap = Math.min(cap, 2);
1096
1110
  }
@@ -1106,19 +1120,17 @@ var Scene = class _Scene {
1106
1120
  }
1107
1121
  this.render(this.renderer, dt, time);
1108
1122
  const hasActiveAnimation = this.hasAnyPendingAnimation(this.root) || this.hasAnyPendingAnimation(this.overlayRoot);
1109
- if (hasActiveAnimation) {
1123
+ const hasInteractive = this.hasAnyInteractive(this.root) || this.hasAnyInteractive(this.overlayRoot);
1124
+ const shouldSyncInterval = this.a11ySyncInterval <= 0 || time - this.lastA11ySync >= this.a11ySyncInterval;
1125
+ if ((hasInteractive || this.a11yElements.size > 0) && (shouldSyncInterval || this.a11yPendingSyncAfterAnimation)) {
1126
+ this.lastA11ySync = time;
1127
+ if (hasInteractive) {
1128
+ this.syncA11y(this.root);
1129
+ }
1130
+ this.enforceA11yDomOrder();
1131
+ this.a11yPendingSyncAfterAnimation = hasActiveAnimation;
1132
+ } else if (hasActiveAnimation) {
1110
1133
  this.a11yPendingSyncAfterAnimation = true;
1111
- } else {
1112
- const hasInteractive = this.hasAnyInteractive(this.root) || this.hasAnyInteractive(this.overlayRoot);
1113
- const shouldSyncInterval = this.a11ySyncInterval <= 0 || time - this.lastA11ySync >= this.a11ySyncInterval;
1114
- if ((hasInteractive || this.a11yElements.size > 0) && (shouldSyncInterval || this.a11yPendingSyncAfterAnimation)) {
1115
- this.lastA11ySync = time;
1116
- if (hasInteractive) {
1117
- this.syncA11y(this.root);
1118
- }
1119
- this.enforceA11yDomOrder();
1120
- this.a11yPendingSyncAfterAnimation = false;
1121
- }
1122
1134
  }
1123
1135
  this.dirty = false;
1124
1136
  this.scheduleFrame();
package/dist/text.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkW3SFIVXOjs = require('./chunk-W3SFIVXO.js');
5
+ var _chunkLA3FJLP2js = require('./chunk-LA3FJLP2.js');
6
6
 
7
7
 
8
8
 
@@ -13,4 +13,4 @@ var _chunkRW6NC4RBjs = require('./chunk-RW6NC4RB.js');
13
13
 
14
14
 
15
15
 
16
- exports.ArabicShaper = _chunkRW6NC4RBjs.ArabicShaper; exports.BidiResolver = _chunkRW6NC4RBjs.BidiResolver; exports.MSDFFont = _chunkW3SFIVXOjs.MSDFFont; exports.MSDFTextEntity = _chunkW3SFIVXOjs.MSDFTextEntity; exports.SVGEntity = _chunkW3SFIVXOjs.SVGEntity;
16
+ exports.ArabicShaper = _chunkRW6NC4RBjs.ArabicShaper; exports.BidiResolver = _chunkRW6NC4RBjs.BidiResolver; exports.MSDFFont = _chunkLA3FJLP2js.MSDFFont; exports.MSDFTextEntity = _chunkLA3FJLP2js.MSDFTextEntity; exports.SVGEntity = _chunkLA3FJLP2js.SVGEntity;
package/dist/text.mjs CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  MSDFFont,
3
3
  MSDFTextEntity,
4
4
  SVGEntity
5
- } from "./chunk-Y2N7TGEH.mjs";
5
+ } from "./chunk-H3QIE77O.mjs";
6
6
  import {
7
7
  ArabicShaper,
8
8
  BidiResolver
@@ -413,12 +413,20 @@ export declare abstract class Entity {
413
413
  */
414
414
  getBatchRect(): BatchRect | null;
415
415
  /**
416
- * Whether this entity still has a queued/running tween animation.
416
+ * Whether this entity still has a queued/running tween animation, or an
417
+ * active {@link setTransition}/{@link animateTo}/{@link springTo} property
418
+ * driver.
417
419
  *
418
- * Used by {@link Scene}'s `onDemand` render mode to keep redrawing while an
419
- * animation is in flight.
420
+ * Used by {@link Scene} to keep rendering continuously while an animation
421
+ * is in flight — both in `onDemand` render mode, and to hold off the
422
+ * `always`-mode idle auto-throttle. Without checking `_drivers` here, a
423
+ * property driver becomes invisible to that throttle: `markDirty()` called
424
+ * from inside `update()`/`tickDrivers()` is wiped by the loop's own
425
+ * `dirty = false` at the end of that same tick, so once the throttle
426
+ * engages an in-flight spring/tween only advances one animation-frame per
427
+ * external `markDirty()` trigger instead of every render frame.
420
428
  *
421
- * @returns `true` if at least one animation remains.
429
+ * @returns `true` if at least one animation or property driver remains.
422
430
  */
423
431
  hasPendingAnimations(): boolean;
424
432
  abstract isPointInside(globalX: number, globalY: number): boolean;
@@ -70,6 +70,11 @@ export interface SceneOptions {
70
70
  * Useful when Vecto is running inside a custom layout container or offscreen canvas.
71
71
  */
72
72
  disableWindowResize?: boolean;
73
+ /**
74
+ * Enable automatic throttling to 2 FPS when the scene is static (no active transitions
75
+ * and not marked dirty) to save power/CPU. Default is `true`.
76
+ */
77
+ autoThrottle?: boolean;
73
78
  }
74
79
  /** Frame-rate the loop is capped to when the OS requests reduced motion. */
75
80
  export declare const REDUCED_MOTION_FPS = 30;
@@ -117,6 +122,8 @@ export declare class Scene {
117
122
  */
118
123
  renderMode: 'always' | 'onDemand';
119
124
  private dirty;
125
+ /** Whether to throttle rendering to 2 FPS when the scene is static to save power. */
126
+ autoThrottle: boolean;
120
127
  /**
121
128
  * Frame-rate cap (power saving). `0` = uncapped (native refresh). When set,
122
129
  * the loop renders at most `maxFPS` times per second; animations still run,
@@ -236,6 +243,12 @@ export declare class Scene {
236
243
  * Call {@link start} again to resume rendering.
237
244
  */
238
245
  stop(): void;
246
+ /**
247
+ * Manually advance the scene clock by `dt` milliseconds and render synchronously.
248
+ * Essential for deterministic rendering (e.g. video export).
249
+ * Note: You should call `scene.stop()` before using this to avoid conflict with the rAF loop.
250
+ */
251
+ step(dt: number): void;
239
252
  /**
240
253
  * Mark the scene as needing a redraw on the next frame.
241
254
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },