@vectojs/core 1.17.1 → 1.18.0

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.
@@ -237,7 +237,7 @@ var Entity = (_class2 = class {
237
237
  const s = this.scene;
238
238
  if (s) {
239
239
  s.a11yNeedsReorder = true;
240
- s.markDirty();
240
+ s.markDirty({ entity: this.id, reason: "a11y-reorder" });
241
241
  }
242
242
  }
243
243
  }
@@ -301,7 +301,7 @@ var Entity = (_class2 = class {
301
301
  if (s) {
302
302
  s.a11yNeedsReorder = true;
303
303
  _optionalChain([s, 'access', _40 => _40.markStructureChanged, 'optionalCall', _41 => _41()]);
304
- s.markDirty();
304
+ s.markDirty({ entity: this.id, reason: "child-added" });
305
305
  child._notifyMounted();
306
306
  }
307
307
  }
@@ -331,7 +331,7 @@ var Entity = (_class2 = class {
331
331
  s.detachA11y(child);
332
332
  s.a11yNeedsReorder = true;
333
333
  _optionalChain([s, 'access', _42 => _42.markStructureChanged, 'optionalCall', _43 => _43()]);
334
- s.markDirty();
334
+ s.markDirty({ entity: this.id, reason: "child-removed" });
335
335
  }
336
336
  }
337
337
  return this;
@@ -385,7 +385,7 @@ var Entity = (_class2 = class {
385
385
  startTime: -1,
386
386
  startProps: {}
387
387
  });
388
- _optionalChain([this, 'access', _44 => _44.scene, 'optionalAccess', _45 => _45.markDirty, 'call', _46 => _46()]);
388
+ _optionalChain([this, 'access', _44 => _44.scene, 'optionalAccess', _45 => _45.markDirty, 'call', _46 => _46({ entity: this.id, reason: "animation-start" })]);
389
389
  return this;
390
390
  }
391
391
  /** Write a driver-computed value to a backing field without re-triggering the setter. */
@@ -460,7 +460,7 @@ var Entity = (_class2 = class {
460
460
  const from = this._currentOf(prop);
461
461
  const driver = _animation.isTweenConfig.call(void 0, cfg) ? new (0, _animation.TweenDriver)(from, to, cfg) : new (0, _animation.SpringDriver)(from, to, cfg === "spring" ? {} : cfg);
462
462
  (this._drivers ??= /* @__PURE__ */ new Map()).set(prop, driver);
463
- _optionalChain([this, 'access', _65 => _65.scene, 'optionalAccess', _66 => _66.markDirty, 'call', _67 => _67()]);
463
+ _optionalChain([this, 'access', _65 => _65.scene, 'optionalAccess', _66 => _66.markDirty, 'call', _67 => _67({ entity: this.id, reason: "driver-added" })]);
464
464
  _optionalChain([this, 'access', _68 => _68.scene, 'optionalAccess', _69 => _69._registerActiveDriverEntity, 'call', _70 => _70(this)]);
465
465
  }
466
466
  /** Assignment path when a declarative transition is configured for `prop`. */
@@ -516,7 +516,7 @@ var Entity = (_class2 = class {
516
516
  this._applyAnimated(prop, driver.value);
517
517
  }
518
518
  }
519
- _optionalChain([this, 'access', _77 => _77.scene, 'optionalAccess', _78 => _78.markDirty, 'call', _79 => _79()]);
519
+ _optionalChain([this, 'access', _77 => _77.scene, 'optionalAccess', _78 => _78.markDirty, 'call', _79 => _79({ entity: this.id, reason: "driver-tick" })]);
520
520
  }
521
521
  /**
522
522
  * Internal: this entity's active-driver map (read-only view), or `null` if
@@ -235,7 +235,7 @@ var Entity = class {
235
235
  const s = this.scene;
236
236
  if (s) {
237
237
  s.a11yNeedsReorder = true;
238
- s.markDirty();
238
+ s.markDirty({ entity: this.id, reason: "a11y-reorder" });
239
239
  }
240
240
  }
241
241
  }
@@ -299,7 +299,7 @@ var Entity = class {
299
299
  if (s) {
300
300
  s.a11yNeedsReorder = true;
301
301
  s.markStructureChanged?.();
302
- s.markDirty();
302
+ s.markDirty({ entity: this.id, reason: "child-added" });
303
303
  child._notifyMounted();
304
304
  }
305
305
  }
@@ -329,7 +329,7 @@ var Entity = class {
329
329
  s.detachA11y(child);
330
330
  s.a11yNeedsReorder = true;
331
331
  s.markStructureChanged?.();
332
- s.markDirty();
332
+ s.markDirty({ entity: this.id, reason: "child-removed" });
333
333
  }
334
334
  }
335
335
  return this;
@@ -383,7 +383,7 @@ var Entity = class {
383
383
  startTime: -1,
384
384
  startProps: {}
385
385
  });
386
- this.scene?.markDirty();
386
+ this.scene?.markDirty({ entity: this.id, reason: "animation-start" });
387
387
  return this;
388
388
  }
389
389
  /** Write a driver-computed value to a backing field without re-triggering the setter. */
@@ -458,7 +458,7 @@ var Entity = class {
458
458
  const from = this._currentOf(prop);
459
459
  const driver = isTweenConfig(cfg) ? new TweenDriver(from, to, cfg) : new SpringDriver(from, to, cfg === "spring" ? {} : cfg);
460
460
  (this._drivers ??= /* @__PURE__ */ new Map()).set(prop, driver);
461
- this.scene?.markDirty();
461
+ this.scene?.markDirty({ entity: this.id, reason: "driver-added" });
462
462
  this.scene?._registerActiveDriverEntity(this);
463
463
  }
464
464
  /** Assignment path when a declarative transition is configured for `prop`. */
@@ -514,7 +514,7 @@ var Entity = class {
514
514
  this._applyAnimated(prop, driver.value);
515
515
  }
516
516
  }
517
- this.scene?.markDirty();
517
+ this.scene?.markDirty({ entity: this.id, reason: "driver-tick" });
518
518
  }
519
519
  /**
520
520
  * Internal: this entity's active-driver map (read-only view), or `null` if
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var _chunkL4SWVP2Hjs = require('./chunk-L4SWVP2H.js');
14
14
 
15
15
 
16
16
 
17
- var _chunkDUYB4GX4js = require('./chunk-DUYB4GX4.js');
17
+ var _chunkIA3KW4CGjs = require('./chunk-IA3KW4CG.js');
18
18
 
19
19
  // src/tree/Scene.ts
20
20
  var _animation = require('@vectojs/animation'); _createStarExport(_animation);
@@ -29,7 +29,7 @@ var PARTICLE_OFFSET_ORIGIN_X = 4;
29
29
  var PARTICLE_OFFSET_ORIGIN_Y = 5;
30
30
  var PARTICLE_OFFSET_SIZE = 6;
31
31
  var PARTICLE_OFFSET_LIFE = 7;
32
- var ComputeParticleEntity = (_class = class extends _chunkDUYB4GX4js.Entity {
32
+ var ComputeParticleEntity = (_class = class extends _chunkIA3KW4CGjs.Entity {
33
33
 
34
34
 
35
35
 
@@ -1191,6 +1191,7 @@ async function loadCoreWasmRuntime(source) {
1191
1191
 
1192
1192
  // src/tree/Scene.ts
1193
1193
  var _text = require('@vectojs/text'); _createStarExport(_text);
1194
+ var RANGE_VALUE_ROLES = /* @__PURE__ */ new Set(["slider", "spinbutton", "progressbar", "scrollbar", "meter"]);
1194
1195
  var INTERACTIVE_A11Y_ROLES = /* @__PURE__ */ new Set([
1195
1196
  "button",
1196
1197
  "switch",
@@ -1536,30 +1537,34 @@ var Scene = (_class7 = class _Scene {
1536
1537
  * event-driven UIs where idle frames should cost ~0.
1537
1538
  */
1538
1539
  __init29() {this.renderMode = "always"}
1539
- __init30() {this.dirty = true}
1540
+ /** Cap on distinct recorded dirty reasons (see `recordDirtyReason`). */
1541
+ static __initStatic4() {this.MAX_DIRTY_REASONS = 200}
1542
+ __init30() {this._dirtyTracking = false}
1543
+ __init31() {this._dirtyReasons = /* @__PURE__ */ new Map()}
1544
+ __init32() {this.dirty = true}
1540
1545
  /** Whether to throttle rendering to 2 FPS when the scene is static to save power. */
1541
- __init31() {this.autoThrottle = true}
1546
+ __init33() {this.autoThrottle = true}
1542
1547
  // --- Frame telemetry (read via `frameStats`) ---------------------------
1543
1548
  /** Wall-clock ms spent inside the last `render()` call. */
1544
- __init32() {this._lastFrameMs = 0}
1549
+ __init34() {this._lastFrameMs = 0}
1545
1550
  /** Rolling exponential average of rendered-frame intervals, in ms. */
1546
- __init33() {this._avgFrameIntervalMs = 0}
1551
+ __init35() {this._avgFrameIntervalMs = 0}
1547
1552
  /** dt (ms) handed to the last rendered frame. */
1548
- __init34() {this._lastDt = 0}
1553
+ __init36() {this._lastDt = 0}
1549
1554
  /** Count of frames actually rendered since the loop started. */
1550
- __init35() {this._renderedFrames = 0}
1555
+ __init37() {this._renderedFrames = 0}
1551
1556
  /** Count of rAF ticks skipped (idle / capped) since the loop started. */
1552
- __init36() {this._skippedFrames = 0}
1557
+ __init38() {this._skippedFrames = 0}
1553
1558
  /** `time` of the previous *rendered* frame, for interval measurement. */
1554
- __init37() {this._lastRenderTick = 0}
1559
+ __init39() {this._lastRenderTick = 0}
1555
1560
  /**
1556
1561
  * Frame-rate cap (power saving). `0` = uncapped (native refresh). When set,
1557
1562
  * the loop renders at most `maxFPS` times per second; animations still run,
1558
1563
  * just less often. See {@link SceneOptions.maxFPS}.
1559
1564
  */
1560
- __init38() {this.maxFPS = 60}
1565
+ __init40() {this.maxFPS = 60}
1561
1566
  /** Whether the OS prefers-reduced-motion setting auto-caps the loop. */
1562
- __init39() {this.respectReducedMotion = true}
1567
+ __init41() {this.respectReducedMotion = true}
1563
1568
  /**
1564
1569
  * Reading direction for accessibility tab/traversal order (`'ltr'` default,
1565
1570
  * `'rtl'`). Controls the inline sort within a visual row in
@@ -1575,15 +1580,15 @@ var Scene = (_class7 = class _Scene {
1575
1580
  this.a11yNeedsReorder = true;
1576
1581
  }
1577
1582
  }
1578
- __init40() {this._readingDirection = "ltr"}
1583
+ __init42() {this._readingDirection = "ltr"}
1579
1584
  /** Cached media-query list; `.matches` is read live each frame. */
1580
- __init41() {this.reducedMotionQuery = null}
1585
+ __init43() {this.reducedMotionQuery = null}
1581
1586
  /** Cached `(forced-colors: active)` query (Windows High Contrast etc.). A
1582
1587
  * canvas gets NO automatic forced-colors treatment from the browser (it's
1583
1588
  * opaque pixels), so components must read {@link forcedColors} and repaint
1584
1589
  * with system colors themselves; a change listener repaints idle scenes. */
1585
- __init42() {this.forcedColorsQuery = null}
1586
- __init43() {this.forcedColorsChangeHandler = null}
1590
+ __init44() {this.forcedColorsQuery = null}
1591
+ __init45() {this.forcedColorsChangeHandler = null}
1587
1592
  /** True when the OS asks for reduced motion and we respect it. Read by the animation drivers. */
1588
1593
  get prefersReducedMotion() {
1589
1594
  return this.respectReducedMotion && !!_optionalChain([this, 'access', _21 => _21.reducedMotionQuery, 'optionalAccess', _22 => _22.matches]);
@@ -1603,82 +1608,82 @@ var Scene = (_class7 = class _Scene {
1603
1608
  * Throttle interval (ms) for the a11y/automation shadow sync. `0` = every
1604
1609
  * frame. See {@link SceneOptions.a11ySyncInterval}.
1605
1610
  */
1606
- __init44() {this.a11ySyncInterval = 0}
1611
+ __init46() {this.a11ySyncInterval = 0}
1607
1612
  /** Timestamp of the last a11y sync, for throttling. */
1608
- __init45() {this.lastA11ySync = -Infinity}
1613
+ __init47() {this.lastA11ySync = -Infinity}
1609
1614
  /** True if we skipped an a11y sync during animation and need to sync when at rest. */
1610
- __init46() {this.a11yPendingSyncAfterAnimation = false}
1615
+ __init48() {this.a11yPendingSyncAfterAnimation = false}
1611
1616
  // A11y / Automation Layer. `null` in non-DOM (SSR/Node) environments — the
1612
1617
  // whole projection degrades to a no-op so the engine's logic stays usable
1613
1618
  // server-side (e.g. headless layout / vector export) without jsdom.
1614
1619
 
1615
- __init47() {this.a11yElements = /* @__PURE__ */ new Map()}
1620
+ __init49() {this.a11yElements = /* @__PURE__ */ new Map()}
1616
1621
  /** DOM nodes mirroring static text content, keyed by entity id. */
1617
- __init48() {this.contentElements = /* @__PURE__ */ new Map()}
1622
+ __init50() {this.contentElements = /* @__PURE__ */ new Map()}
1618
1623
  /** Pending cold font-calibration frame per projected grid entity. */
1619
- __init49() {this.contentGridCalibrationFrames = /* @__PURE__ */ new Map()}
1624
+ __init51() {this.contentGridCalibrationFrames = /* @__PURE__ */ new Map()}
1620
1625
  /** Detached, untransformed font probes used by the cold calibration pass. */
1621
- __init50() {this.contentGridCalibrationProbes = /* @__PURE__ */ new Map()}
1626
+ __init52() {this.contentGridCalibrationProbes = /* @__PURE__ */ new Map()}
1622
1627
  /** Invalidates grid font calibration after browser font availability changes. */
1623
- __init51() {this.contentFontEpoch = 0}
1628
+ __init53() {this.contentFontEpoch = 0}
1624
1629
  /** Cached Canvas-to-client scale for the current font/viewport epoch. */
1625
- __init52() {this.contentMetricScaleEpoch = -1}
1626
- __init53() {this.contentMetricScaleX = 1}
1627
- __init54() {this.contentProjectionEnabled = true}
1630
+ __init54() {this.contentMetricScaleEpoch = -1}
1631
+ __init55() {this.contentMetricScaleX = 1}
1632
+ __init56() {this.contentProjectionEnabled = true}
1628
1633
  // Virtualization margin (px) for content projection; `undefined` → one
1629
1634
  // viewport height, resolved at sync time. `Infinity` = materialize everything.
1630
- __init55() {this.contentProjectionMargin = void 0}
1635
+ __init57() {this.contentProjectionMargin = void 0}
1631
1636
  /**
1632
1637
  * True while a text-selection drag that started on a projection's blank
1633
1638
  * region (no text node under the press) is being driven manually — the
1634
1639
  * browser has no native anchor for it, so mousemove extends the Selection
1635
1640
  * from the position we resolved ourselves.
1636
1641
  */
1637
- __init56() {this.blankRegionSelectionDrag = false}
1638
- __init57() {this.contentSelectionAnchor = null}
1639
- __init58() {this.contentSelectionEndListener = null}
1642
+ __init58() {this.blankRegionSelectionDrag = false}
1643
+ __init59() {this.contentSelectionAnchor = null}
1644
+ __init60() {this.contentSelectionEndListener = null}
1640
1645
  // Animation/interactive flags collected during the render walk (tree-walk
1641
1646
  // fusion): the loop reads last frame's answers instead of re-walking the
1642
1647
  // tree up to 4× per tick. Start true so the first tick stays conservative.
1643
- __init59() {this.frameHadAnimation = true}
1644
- __init60() {this.frameHadInteractive = true}
1648
+ __init61() {this.frameHadAnimation = true}
1649
+ __init62() {this.frameHadInteractive = true}
1645
1650
 
1646
1651
  /** Active `(resolution: Ndppx)` media query watching for a runtime DPR change
1647
1652
  * (window moved between monitors, browser zoom) so the canvas backing store
1648
1653
  * can be re-scaled — otherwise it stays rasterized at the old DPR and blurs.
1649
1654
  * A resolution media query only fires when leaving its exact value, so the
1650
1655
  * handler re-arms a fresh query for the new DPR each time. */
1651
- __init61() {this.dprMediaQuery = null}
1656
+ __init63() {this.dprMediaQuery = null}
1652
1657
  /** For embedded (`disableWindowResize`) scenes: observes the canvas element so
1653
1658
  * a CSS/layout-driven size change re-runs `resize()`. A window `resize`
1654
1659
  * listener never fires for these (the window isn't what changed), so without
1655
1660
  * this an embedded canvas stayed at its initial size forever. */
1656
- __init62() {this.canvasResizeObserver = null}
1657
- __init63() {this.dprChangeHandler = null}
1658
- __init64() {this.focusedA11yElement = null}
1661
+ __init64() {this.canvasResizeObserver = null}
1662
+ __init65() {this.dprChangeHandler = null}
1663
+ __init66() {this.focusedA11yElement = null}
1659
1664
  /** Last geometry `syncOverlayGeometry` wrote, so an unchanged frame can skip the
1660
1665
  * style writes entirely. Reset to `null` to force the next sync (a new overlay
1661
1666
  * layer was created and has never been positioned). */
1662
- __init65() {this._overlayGeometry = null}
1667
+ __init67() {this._overlayGeometry = null}
1663
1668
  /** Shadow elements the pointer is currently inside. Lets a removal that happens
1664
1669
  * mid-hover synthesize the `pointerleave` the browser never sends for a
1665
1670
  * detached element, so the entity doesn't keep its hover state. */
1666
- __init66() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
1671
+ __init68() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
1667
1672
  /** Persistent tabindex=-1 element in a11yRoot. When the focused a11y mirror is
1668
1673
  * pruned (virtualization/streaming/removal) while it holds focus, we move
1669
1674
  * focus here instead of letting the browser drop it to <body> — keeping the
1670
1675
  * screen-reader virtual cursor inside the scene's a11y region. */
1671
- __init67() {this.focusSentinel = null}
1672
- __init68() {this.caretBlinkTimer = null}
1673
- __init69() {this.a11yNeedsReorder = true}
1674
- __init70() {this.portalRoot = null}
1675
- __init71() {this.fullViewportElements = []}
1676
- __init72() {this.normalElements = []}
1677
- __init73() {this.activeIds = /* @__PURE__ */ new Set()}
1678
- __init74() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
1679
- __init75() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
1680
- __init76() {this.portalEntities = /* @__PURE__ */ new Map()}
1681
- __init77() {this.renderOrderCounter = 0}
1676
+ __init69() {this.focusSentinel = null}
1677
+ __init70() {this.caretBlinkTimer = null}
1678
+ __init71() {this.a11yNeedsReorder = true}
1679
+ __init72() {this.portalRoot = null}
1680
+ __init73() {this.fullViewportElements = []}
1681
+ __init74() {this.normalElements = []}
1682
+ __init75() {this.activeIds = /* @__PURE__ */ new Set()}
1683
+ __init76() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
1684
+ __init77() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
1685
+ __init78() {this.portalEntities = /* @__PURE__ */ new Map()}
1686
+ __init79() {this.renderOrderCounter = 0}
1682
1687
  /**
1683
1688
  * Monotonic render-frame counter, bumped once per authoritative `render()`
1684
1689
  * pass. Entities stamp their per-frame world-matrix cache with this value and
@@ -1687,7 +1692,7 @@ var Scene = (_class7 = class _Scene {
1687
1692
  * back to the ancestor walk. Public for the same reason `Entity._getTrig`/
1688
1693
  * `_setWorldCache` are: it is a cross-class render-internal contract.
1689
1694
  */
1690
- __init78() {this.currentFrame = 0}
1695
+ __init80() {this.currentFrame = 0}
1691
1696
  // ── WASM transform backend (invisible accelerator) ──────────────────────────
1692
1697
  // When `_transformBackend === 'wasm'`, the main render walk sources each
1693
1698
  // entity's world matrix from an SoA store composed by `_wasm` (see
@@ -1695,24 +1700,24 @@ var Scene = (_class7 = class _Scene {
1695
1700
  // fallback and the default: a null backend, a non-main renderer, or any entity
1696
1701
  // absent from the store all fall back to the JS composition, so WASM can only
1697
1702
  // ever change *how fast* a world matrix is produced, never *what* it is.
1698
- __init79() {this._wasm = null}
1699
- __init80() {this._transformBackend = "js"}
1703
+ __init81() {this._wasm = null}
1704
+ __init82() {this._transformBackend = "js"}
1700
1705
  // Resident store state (Stage 3). The store layout — slot assignment + sibling
1701
1706
  // runs — depends only on tree TOPOLOGY, so it is rebuilt only when the
1702
1707
  // structure changes (add/remove/reparent bump `_structureVersion`). Between
1703
1708
  // rebuilds the per-frame cost is: gather each entity's transform into the
1704
1709
  // resident wasm input view + run the kernel — no reallocation, no readback.
1705
- __init81() {this._treeStore = null}
1706
- __init82() {this._slotEntity = []}
1710
+ __init83() {this._treeStore = null}
1711
+ __init84() {this._slotEntity = []}
1707
1712
  // store slot -> entity (also validates slots)
1708
- __init83() {this._wasmInputs = null}
1709
- __init84() {this._wasmWorld = null}
1710
- __init85() {this._structureVersion = 0}
1711
- __init86() {this._storeStructureVersion = -1}
1713
+ __init85() {this._wasmInputs = null}
1714
+ __init86() {this._wasmWorld = null}
1715
+ __init87() {this._structureVersion = 0}
1716
+ __init88() {this._storeStructureVersion = -1}
1712
1717
  // Cached list of ComputeParticleEntity instances in the tree, keyed by the
1713
1718
  // structure version it was gathered at. Rebuilt only on a topology change.
1714
- __init87() {this._computeEntities = []}
1715
- __init88() {this._computeEntitiesVersion = -1}
1719
+ __init89() {this._computeEntities = []}
1720
+ __init90() {this._computeEntitiesVersion = -1}
1716
1721
  /** Invalidate the resident WASM store layout; the next wasm-mode frame rebuilds
1717
1722
  * it. Called by `Entity.add`/`remove` (topology changes only). */
1718
1723
  markStructureChanged() {
@@ -1794,7 +1799,7 @@ var Scene = (_class7 = class _Scene {
1794
1799
  * cached globally; the instance is per-Scene, which is the isolation that
1795
1800
  * actually matters.
1796
1801
  */
1797
- __init89() {this._wasmRuntime = null}
1802
+ __init91() {this._wasmRuntime = null}
1798
1803
  /**
1799
1804
  * Load (or reuse) this Scene's shared WASM runtime.
1800
1805
  *
@@ -1822,30 +1827,30 @@ var Scene = (_class7 = class _Scene {
1822
1827
  get wasmRuntime() {
1823
1828
  return this._wasmRuntime;
1824
1829
  }
1825
- __init90() {this._hitWasm = null}
1830
+ __init92() {this._hitWasm = null}
1826
1831
  // Cache key: which frame + structure version the grid was last (successfully,
1827
1832
  // non-overflowing) built for. findEntityAt is called ad-hoc (pointer
1828
1833
  // hover/click), not every frame, so the grid is refreshed lazily on demand
1829
1834
  // rather than proactively every render() — unlike the transform store, which
1830
1835
  // every frame's draw depends on.
1831
- __init91() {this._hitGridFrame = -1}
1832
- __init92() {this._hitGridOk = false}
1833
- __init93() {this._hitSlotEntity = []}
1834
- __init94() {this._hitBoundless = []}
1836
+ __init93() {this._hitGridFrame = -1}
1837
+ __init94() {this._hitGridOk = false}
1838
+ __init95() {this._hitSlotEntity = []}
1839
+ __init96() {this._hitBoundless = []}
1835
1840
  /** Reused buffer for the fused gather, so a pointer query allocates nothing. */
1836
- __init95() {this._hitGatherBuffer = null}
1841
+ __init97() {this._hitGatherBuffer = null}
1837
1842
  /**
1838
1843
  * Whether the last grid build sourced its AABBs from the WASM transform store
1839
1844
  * rather than recomputing them in JS. Diagnostic only — both paths must
1840
1845
  * produce the same entity for a given point.
1841
1846
  */
1842
- __init96() {this._hitFusedGather = false}
1847
+ __init98() {this._hitFusedGather = false}
1843
1848
  /**
1844
1849
  * Whether `compute_aabbs` has run against the current frame's world matrices.
1845
1850
  * The AABB pass is only meaningful after a `compose_*`, so the fused gather
1846
1851
  * must not read the views before then.
1847
1852
  */
1848
- __init97() {this._wasmAabbsFresh = false}
1853
+ __init99() {this._wasmAabbsFresh = false}
1849
1854
  /** Did the last hit-grid build use the fused (WASM-store) gather? */
1850
1855
  get hitGatherPath() {
1851
1856
  return this._hitFusedGather ? "fused" : "js";
@@ -1959,25 +1964,25 @@ var Scene = (_class7 = class _Scene {
1959
1964
  // EasingFn (which cannot cross into WASM) all fall through to it — WASM can
1960
1965
  // only ever change *how* a driver is advanced, never *what* value it lands
1961
1966
  // on.
1962
- __init98() {this._animWasm = null}
1967
+ __init100() {this._animWasm = null}
1963
1968
  // Entities with at least one active driver, added by Entity._spawnDriver.
1964
1969
  // Self-pruning: _tickBatchedDrivers drops an entry the first time it visits
1965
1970
  // an entity whose drivers have since all completed or been removed. This is
1966
1971
  // what lets the batch pass find its candidates in O(active drivers), not
1967
1972
  // O(tree size) — the exact mistake G3's first integrated benchmark made.
1968
- __init99() {this._activeDriverEntities = /* @__PURE__ */ new Set()}
1973
+ __init101() {this._activeDriverEntities = /* @__PURE__ */ new Set()}
1969
1974
  // Reused across frames instead of allocating a fresh array + N {entity,prop,
1970
1975
  // driver} objects every call — the integrated benchmark
1971
1976
  // (benchmarks/anim-wasm-scene) found that allocation churn was the
1972
1977
  // dominant integrated cost, not the wasm kernel itself. Parallel arrays,
1973
1978
  // truncated to the live count after each use so a stale tail slot never
1974
1979
  // pins a no-longer-active entity/driver in memory.
1975
- __init100() {this._springEntities = []}
1976
- __init101() {this._springProps = []}
1977
- __init102() {this._springDrivers = []}
1978
- __init103() {this._tweenEntities = []}
1979
- __init104() {this._tweenProps = []}
1980
- __init105() {this._tweenDrivers = []}
1980
+ __init102() {this._springEntities = []}
1981
+ __init103() {this._springProps = []}
1982
+ __init104() {this._springDrivers = []}
1983
+ __init105() {this._tweenEntities = []}
1984
+ __init106() {this._tweenProps = []}
1985
+ __init107() {this._tweenDrivers = []}
1981
1986
  /**
1982
1987
  * Minimum number of batchable (spring, or named-easing tween) active drivers
1983
1988
  * before a frame engages the WASM batch path at all; below it, every driver
@@ -2049,7 +2054,7 @@ var Scene = (_class7 = class _Scene {
2049
2054
  * Setting {@link animDriverGateCount} overwrites all three, so existing code
2050
2055
  * that tuned the single knob keeps working unchanged.
2051
2056
  */
2052
- __init106() {this._animBatchedLastFrame = false}
2057
+ __init108() {this._animBatchedLastFrame = false}
2053
2058
  /**
2054
2059
  * Whether the WASM batch path actually ran on the most recent frame.
2055
2060
  *
@@ -2061,7 +2066,7 @@ var Scene = (_class7 = class _Scene {
2061
2066
  get animBatchedLastFrame() {
2062
2067
  return this._animBatchedLastFrame;
2063
2068
  }
2064
- __init107() {this.animGate = {
2069
+ __init109() {this.animGate = {
2065
2070
  spring: 128,
2066
2071
  tween: 256,
2067
2072
  mixed: 128
@@ -2099,7 +2104,7 @@ var Scene = (_class7 = class _Scene {
2099
2104
  // (benchmarks/particle-wasm). f32 (matches the WGSL shader), bit-identical to
2100
2105
  // a JS f32 reference oracle; updateCPU (f64) stays the permanent fallback when
2101
2106
  // no backend is installed or a scene runs on WebGPU.
2102
- __init108() {this._particleWasm = null}
2107
+ __init110() {this._particleWasm = null}
2103
2108
  /** Which backend runs the CPU particle simulation. Reflects only whether a
2104
2109
  * backend is installed (the WebGPU compute path, when active, is used first
2105
2110
  * regardless). */
@@ -2349,46 +2354,46 @@ var Scene = (_class7 = class _Scene {
2349
2354
  * sync, so retaining the order prevents a newly opened overlay from spending
2350
2355
  * its first frame below previously projected controls.
2351
2356
  */
2352
- __init109() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
2357
+ __init111() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
2353
2358
  // Optional WebGL point-cloud layer (see SceneOptions.pointBackend).
2354
- __init110() {this.pointRenderer = null}
2355
- __init111() {this.glCanvas = null}
2356
- __init112() {this.glContextLostHandler = null}
2357
- __init113() {this.glContextRestoredHandler = null}
2359
+ __init112() {this.pointRenderer = null}
2360
+ __init113() {this.glCanvas = null}
2361
+ __init114() {this.glContextLostHandler = null}
2362
+ __init115() {this.glContextRestoredHandler = null}
2358
2363
 
2359
2364
 
2360
2365
 
2361
- __init114() {this.disableWindowResize = false}
2366
+ __init116() {this.disableWindowResize = false}
2362
2367
  /** See {@link SceneOptions.maxDPR}. `undefined` = uncapped (real DPR). */
2363
2368
 
2364
2369
  // WebGPU properties
2365
- __init115() {this.destroyed = false}
2366
- __init116() {this.device = null}
2367
- __init117() {this.deviceLost = false}
2368
- __init118() {this.particleBackend = "auto"}
2369
- __init119() {this._webgpuDisabled = false}
2370
+ __init117() {this.destroyed = false}
2371
+ __init118() {this.device = null}
2372
+ __init119() {this.deviceLost = false}
2373
+ __init120() {this.particleBackend = "auto"}
2374
+ __init121() {this._webgpuDisabled = false}
2370
2375
  get webgpuDisabled() {
2371
2376
  return this._webgpuDisabled || this.particleBackend === "cpu";
2372
2377
  }
2373
2378
  set webgpuDisabled(value) {
2374
2379
  this._webgpuDisabled = value;
2375
2380
  }
2376
- __init120() {this.recoveryTimerId = null}
2377
- __init121() {this.manager = null}
2378
- __init122() {this.initializingWebGPU = false}
2379
- __init123() {this.gpuCanvas = null}
2380
- __init124() {this.gpuContext = null}
2381
+ __init122() {this.recoveryTimerId = null}
2382
+ __init123() {this.manager = null}
2383
+ __init124() {this.initializingWebGPU = false}
2384
+ __init125() {this.gpuCanvas = null}
2385
+ __init126() {this.gpuContext = null}
2381
2386
  /** True while the GPU canvas holds a presented particle frame (needs clearing when they leave). */
2382
- __init125() {this.gpuHasContent = false}
2383
- __init126() {this.mouseX = -9999}
2384
- __init127() {this.mouseY = -9999}
2385
- __init128() {this.pointerMoveListener = null}
2386
- __init129() {this.pointerLeaveListener = null}
2387
+ __init127() {this.gpuHasContent = false}
2388
+ __init128() {this.mouseX = -9999}
2389
+ __init129() {this.mouseY = -9999}
2390
+ __init130() {this.pointerMoveListener = null}
2391
+ __init131() {this.pointerLeaveListener = null}
2387
2392
  /** Element the pointer listeners are bound to (parent container if present,
2388
2393
  * else the canvas). Stored so `destroy()` detaches from the same element. */
2389
- __init130() {this.pointerEventTarget = null}
2390
- __init131() {this.hasWarnedZeroSize = false}
2391
- __init132() {this.fontLoadHandler = null}
2394
+ __init132() {this.pointerEventTarget = null}
2395
+ __init133() {this.hasWarnedZeroSize = false}
2396
+ __init134() {this.fontLoadHandler = null}
2392
2397
  // ── Dev-mode warning infrastructure ──────────────────────────────
2393
2398
  //
2394
2399
  // Enable with `Scene.devMode = true` or by setting `globalThis.__DEV__`.
@@ -2397,7 +2402,7 @@ var Scene = (_class7 = class _Scene {
2397
2402
  // Checks run once every ~120 frames (~2s at 60fps) to keep overhead
2398
2403
  // negligible even when dev mode is on.
2399
2404
  /** Toggle development-mode runtime warnings globally. */
2400
- static __initStatic4() {this.devMode = false}
2405
+ static __initStatic5() {this.devMode = false}
2401
2406
  static _devModeDetected() {
2402
2407
  if (_Scene.devMode) return true;
2403
2408
  const gp = typeof globalThis !== "undefined" ? globalThis : void 0;
@@ -2406,7 +2411,7 @@ var Scene = (_class7 = class _Scene {
2406
2411
  return false;
2407
2412
  }
2408
2413
 
2409
- __init133() {this._devFrameCount = 0}
2414
+ __init135() {this._devFrameCount = 0}
2410
2415
  _devWarn(message) {
2411
2416
  if (!this._devActive) return;
2412
2417
  console.warn(`[vectojs/dev] ${message}`);
@@ -2450,7 +2455,7 @@ var Scene = (_class7 = class _Scene {
2450
2455
  };
2451
2456
  walkProjections(this.root);
2452
2457
  }
2453
- constructor(canvas, options = {}) {;_class7.prototype.__init25.call(this);_class7.prototype.__init26.call(this);_class7.prototype.__init27.call(this);_class7.prototype.__init28.call(this);_class7.prototype.__init29.call(this);_class7.prototype.__init30.call(this);_class7.prototype.__init31.call(this);_class7.prototype.__init32.call(this);_class7.prototype.__init33.call(this);_class7.prototype.__init34.call(this);_class7.prototype.__init35.call(this);_class7.prototype.__init36.call(this);_class7.prototype.__init37.call(this);_class7.prototype.__init38.call(this);_class7.prototype.__init39.call(this);_class7.prototype.__init40.call(this);_class7.prototype.__init41.call(this);_class7.prototype.__init42.call(this);_class7.prototype.__init43.call(this);_class7.prototype.__init44.call(this);_class7.prototype.__init45.call(this);_class7.prototype.__init46.call(this);_class7.prototype.__init47.call(this);_class7.prototype.__init48.call(this);_class7.prototype.__init49.call(this);_class7.prototype.__init50.call(this);_class7.prototype.__init51.call(this);_class7.prototype.__init52.call(this);_class7.prototype.__init53.call(this);_class7.prototype.__init54.call(this);_class7.prototype.__init55.call(this);_class7.prototype.__init56.call(this);_class7.prototype.__init57.call(this);_class7.prototype.__init58.call(this);_class7.prototype.__init59.call(this);_class7.prototype.__init60.call(this);_class7.prototype.__init61.call(this);_class7.prototype.__init62.call(this);_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);_class7.prototype.__init72.call(this);_class7.prototype.__init73.call(this);_class7.prototype.__init74.call(this);_class7.prototype.__init75.call(this);_class7.prototype.__init76.call(this);_class7.prototype.__init77.call(this);_class7.prototype.__init78.call(this);_class7.prototype.__init79.call(this);_class7.prototype.__init80.call(this);_class7.prototype.__init81.call(this);_class7.prototype.__init82.call(this);_class7.prototype.__init83.call(this);_class7.prototype.__init84.call(this);_class7.prototype.__init85.call(this);_class7.prototype.__init86.call(this);_class7.prototype.__init87.call(this);_class7.prototype.__init88.call(this);_class7.prototype.__init89.call(this);_class7.prototype.__init90.call(this);_class7.prototype.__init91.call(this);_class7.prototype.__init92.call(this);_class7.prototype.__init93.call(this);_class7.prototype.__init94.call(this);_class7.prototype.__init95.call(this);_class7.prototype.__init96.call(this);_class7.prototype.__init97.call(this);_class7.prototype.__init98.call(this);_class7.prototype.__init99.call(this);_class7.prototype.__init100.call(this);_class7.prototype.__init101.call(this);_class7.prototype.__init102.call(this);_class7.prototype.__init103.call(this);_class7.prototype.__init104.call(this);_class7.prototype.__init105.call(this);_class7.prototype.__init106.call(this);_class7.prototype.__init107.call(this);_class7.prototype.__init108.call(this);_class7.prototype.__init109.call(this);_class7.prototype.__init110.call(this);_class7.prototype.__init111.call(this);_class7.prototype.__init112.call(this);_class7.prototype.__init113.call(this);_class7.prototype.__init114.call(this);_class7.prototype.__init115.call(this);_class7.prototype.__init116.call(this);_class7.prototype.__init117.call(this);_class7.prototype.__init118.call(this);_class7.prototype.__init119.call(this);_class7.prototype.__init120.call(this);_class7.prototype.__init121.call(this);_class7.prototype.__init122.call(this);_class7.prototype.__init123.call(this);_class7.prototype.__init124.call(this);_class7.prototype.__init125.call(this);_class7.prototype.__init126.call(this);_class7.prototype.__init127.call(this);_class7.prototype.__init128.call(this);_class7.prototype.__init129.call(this);_class7.prototype.__init130.call(this);_class7.prototype.__init131.call(this);_class7.prototype.__init132.call(this);_class7.prototype.__init133.call(this);
2458
+ constructor(canvas, options = {}) {;_class7.prototype.__init25.call(this);_class7.prototype.__init26.call(this);_class7.prototype.__init27.call(this);_class7.prototype.__init28.call(this);_class7.prototype.__init29.call(this);_class7.prototype.__init30.call(this);_class7.prototype.__init31.call(this);_class7.prototype.__init32.call(this);_class7.prototype.__init33.call(this);_class7.prototype.__init34.call(this);_class7.prototype.__init35.call(this);_class7.prototype.__init36.call(this);_class7.prototype.__init37.call(this);_class7.prototype.__init38.call(this);_class7.prototype.__init39.call(this);_class7.prototype.__init40.call(this);_class7.prototype.__init41.call(this);_class7.prototype.__init42.call(this);_class7.prototype.__init43.call(this);_class7.prototype.__init44.call(this);_class7.prototype.__init45.call(this);_class7.prototype.__init46.call(this);_class7.prototype.__init47.call(this);_class7.prototype.__init48.call(this);_class7.prototype.__init49.call(this);_class7.prototype.__init50.call(this);_class7.prototype.__init51.call(this);_class7.prototype.__init52.call(this);_class7.prototype.__init53.call(this);_class7.prototype.__init54.call(this);_class7.prototype.__init55.call(this);_class7.prototype.__init56.call(this);_class7.prototype.__init57.call(this);_class7.prototype.__init58.call(this);_class7.prototype.__init59.call(this);_class7.prototype.__init60.call(this);_class7.prototype.__init61.call(this);_class7.prototype.__init62.call(this);_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);_class7.prototype.__init72.call(this);_class7.prototype.__init73.call(this);_class7.prototype.__init74.call(this);_class7.prototype.__init75.call(this);_class7.prototype.__init76.call(this);_class7.prototype.__init77.call(this);_class7.prototype.__init78.call(this);_class7.prototype.__init79.call(this);_class7.prototype.__init80.call(this);_class7.prototype.__init81.call(this);_class7.prototype.__init82.call(this);_class7.prototype.__init83.call(this);_class7.prototype.__init84.call(this);_class7.prototype.__init85.call(this);_class7.prototype.__init86.call(this);_class7.prototype.__init87.call(this);_class7.prototype.__init88.call(this);_class7.prototype.__init89.call(this);_class7.prototype.__init90.call(this);_class7.prototype.__init91.call(this);_class7.prototype.__init92.call(this);_class7.prototype.__init93.call(this);_class7.prototype.__init94.call(this);_class7.prototype.__init95.call(this);_class7.prototype.__init96.call(this);_class7.prototype.__init97.call(this);_class7.prototype.__init98.call(this);_class7.prototype.__init99.call(this);_class7.prototype.__init100.call(this);_class7.prototype.__init101.call(this);_class7.prototype.__init102.call(this);_class7.prototype.__init103.call(this);_class7.prototype.__init104.call(this);_class7.prototype.__init105.call(this);_class7.prototype.__init106.call(this);_class7.prototype.__init107.call(this);_class7.prototype.__init108.call(this);_class7.prototype.__init109.call(this);_class7.prototype.__init110.call(this);_class7.prototype.__init111.call(this);_class7.prototype.__init112.call(this);_class7.prototype.__init113.call(this);_class7.prototype.__init114.call(this);_class7.prototype.__init115.call(this);_class7.prototype.__init116.call(this);_class7.prototype.__init117.call(this);_class7.prototype.__init118.call(this);_class7.prototype.__init119.call(this);_class7.prototype.__init120.call(this);_class7.prototype.__init121.call(this);_class7.prototype.__init122.call(this);_class7.prototype.__init123.call(this);_class7.prototype.__init124.call(this);_class7.prototype.__init125.call(this);_class7.prototype.__init126.call(this);_class7.prototype.__init127.call(this);_class7.prototype.__init128.call(this);_class7.prototype.__init129.call(this);_class7.prototype.__init130.call(this);_class7.prototype.__init131.call(this);_class7.prototype.__init132.call(this);_class7.prototype.__init133.call(this);_class7.prototype.__init134.call(this);_class7.prototype.__init135.call(this);
2454
2459
  this.canvas = canvas;
2455
2460
  this.debugA11y = _nullishCoalesce(options.debugA11y, () => ( false));
2456
2461
  this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
@@ -2481,7 +2486,7 @@ var Scene = (_class7 = class _Scene {
2481
2486
  this.forcedColorsChangeHandler = () => this.markDirty();
2482
2487
  _optionalChain([this, 'access', _45 => _45.forcedColorsQuery, 'access', _46 => _46.addEventListener, 'optionalCall', _47 => _47("change", this.forcedColorsChangeHandler)]);
2483
2488
  }
2484
- this.root = new class RootEntity extends _chunkDUYB4GX4js.Entity {
2489
+ this.root = new class RootEntity extends _chunkIA3KW4CGjs.Entity {
2485
2490
  isPointInside() {
2486
2491
  return false;
2487
2492
  }
@@ -2490,7 +2495,7 @@ var Scene = (_class7 = class _Scene {
2490
2495
  }
2491
2496
  }("root");
2492
2497
  this.root._scene = this;
2493
- this.overlayRoot = new class OverlayRoot extends _chunkDUYB4GX4js.Entity {
2498
+ this.overlayRoot = new class OverlayRoot extends _chunkIA3KW4CGjs.Entity {
2494
2499
  isPointInside() {
2495
2500
  return false;
2496
2501
  }
@@ -2856,7 +2861,7 @@ var Scene = (_class7 = class _Scene {
2856
2861
  }
2857
2862
  if (this.hoveredA11yElements.has(el)) {
2858
2863
  this.hoveredA11yElements.delete(el);
2859
- node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("pointerleave", node, void 0, false));
2864
+ node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("pointerleave", node, void 0, false));
2860
2865
  }
2861
2866
  this.preserveFocusOnRemoval(el);
2862
2867
  el.remove();
@@ -3156,8 +3161,65 @@ var Scene = (_class7 = class _Scene {
3156
3161
  * Only meaningful in `onDemand` {@link renderMode}: call it after mutating
3157
3162
  * entity state outside of {@link Entity.animate} so the change is rendered.
3158
3163
  */
3159
- markDirty() {
3164
+ markDirty(source) {
3160
3165
  this.dirty = true;
3166
+ if (this._dirtyTracking && source) this.recordDirtyReason(source);
3167
+ }
3168
+ /**
3169
+ * Record who marked the scene dirty and why.
3170
+ *
3171
+ * Kept separate from {@link markDirty} so the hot path is not a function call
3172
+ * with a branch — V8 inlines the one-field version reliably.
3173
+ */
3174
+ recordDirtyReason(source) {
3175
+ const key = `${_nullishCoalesce(source.entity, () => ( "scene"))}:${source.reason}${source.property ? `.${source.property}` : ""}`;
3176
+ const existing = this._dirtyReasons.get(key);
3177
+ if (existing) {
3178
+ existing.count++;
3179
+ existing.lastFrame = this.currentFrame;
3180
+ return;
3181
+ }
3182
+ if (this._dirtyReasons.size >= _Scene.MAX_DIRTY_REASONS) {
3183
+ const oldest = this._dirtyReasons.keys().next().value;
3184
+ if (oldest !== void 0) this._dirtyReasons.delete(oldest);
3185
+ }
3186
+ this._dirtyReasons.set(key, {
3187
+ entity: source.entity,
3188
+ reason: source.reason,
3189
+ property: source.property,
3190
+ count: 1,
3191
+ firstFrame: this.currentFrame,
3192
+ lastFrame: this.currentFrame
3193
+ });
3194
+ }
3195
+ /**
3196
+ * Start or stop recording dirty attributions.
3197
+ *
3198
+ * Off by default. `renderMode: 'onDemand'` silently degrades to always-on when
3199
+ * something marks the scene dirty every frame, and until now there was no way
3200
+ * to find out what — `dirty === true` said nothing about the cause. Enable
3201
+ * this, run the scene, then read {@link dirtyReasons}.
3202
+ */
3203
+ setDirtyTracking(enabled) {
3204
+ this._dirtyTracking = enabled;
3205
+ if (!enabled) this._dirtyReasons.clear();
3206
+ }
3207
+ /** Whether dirty attribution is currently being recorded. */
3208
+ get dirtyTracking() {
3209
+ return this._dirtyTracking;
3210
+ }
3211
+ /**
3212
+ * Recorded dirty attributions, most frequent first.
3213
+ *
3214
+ * `count` is what matters for the `onDemand` diagnosis: a reason appearing once
3215
+ * per frame over hundreds of frames is the thing keeping the scene awake.
3216
+ */
3217
+ get dirtyReasons() {
3218
+ return [...this._dirtyReasons.values()].sort((a, b) => b.count - a.count);
3219
+ }
3220
+ /** Drop recorded attributions, keeping tracking enabled. */
3221
+ clearDirtyReasons() {
3222
+ this._dirtyReasons.clear();
3161
3223
  }
3162
3224
  /**
3163
3225
  * Live frame telemetry for profilers and devtools overlays. All timings are
@@ -3266,20 +3328,20 @@ var Scene = (_class7 = class _Scene {
3266
3328
  el.style.background = "transparent";
3267
3329
  }
3268
3330
  el.addEventListener("click", (e) => {
3269
- node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("click", node, e));
3331
+ node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("click", node, e));
3270
3332
  });
3271
3333
  el.addEventListener("dblclick", (e) => {
3272
- node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("dblclick", node, e));
3334
+ node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("dblclick", node, e));
3273
3335
  });
3274
3336
  el.addEventListener("mouseenter", (e) => {
3275
3337
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.2)";
3276
3338
  this.hoveredA11yElements.add(el);
3277
- node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("hover", node, e, false));
3339
+ node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("hover", node, e, false));
3278
3340
  });
3279
3341
  el.addEventListener("mouseleave", (e) => {
3280
3342
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.05)";
3281
3343
  this.hoveredA11yElements.delete(el);
3282
- node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("pointerleave", node, e, false));
3344
+ node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("pointerleave", node, e, false));
3283
3345
  });
3284
3346
  const capEl = el;
3285
3347
  const releasePointer = (event) => {
@@ -3295,32 +3357,32 @@ var Scene = (_class7 = class _Scene {
3295
3357
  };
3296
3358
  el.addEventListener("pointerdown", (e) => {
3297
3359
  if (typeof capEl.setPointerCapture === "function") capEl.setPointerCapture(e.pointerId);
3298
- node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("pointerdown", node, e));
3360
+ node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("pointerdown", node, e));
3299
3361
  });
3300
3362
  el.addEventListener("pointerup", (e) => {
3301
3363
  releasePointer(e);
3302
- node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("pointerup", node, e));
3364
+ node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("pointerup", node, e));
3303
3365
  });
3304
3366
  el.addEventListener("pointercancel", (e) => {
3305
3367
  releasePointer(e);
3306
- node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("pointercancel", node, e));
3368
+ node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("pointercancel", node, e));
3307
3369
  });
3308
3370
  el.addEventListener(
3309
3371
  "pointermove",
3310
- (e) => node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("pointermove", node, e))
3372
+ (e) => node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("pointermove", node, e))
3311
3373
  );
3312
3374
  el.addEventListener(
3313
3375
  "wheel",
3314
3376
  (e) => {
3315
- node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("wheel", node, e));
3377
+ node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("wheel", node, e));
3316
3378
  },
3317
3379
  { passive: false }
3318
3380
  );
3319
3381
  el.addEventListener("keydown", (e) => {
3320
- node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("keydown", node, e));
3382
+ node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("keydown", node, e));
3321
3383
  });
3322
3384
  el.addEventListener("keyup", (e) => {
3323
- node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("keyup", node, e));
3385
+ node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("keyup", node, e));
3324
3386
  });
3325
3387
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
3326
3388
  const input = el;
@@ -3386,7 +3448,7 @@ var Scene = (_class7 = class _Scene {
3386
3448
  el.addEventListener("keydown", (e) => {
3387
3449
  if (e.key === "Enter" || e.key === " ") {
3388
3450
  e.preventDefault();
3389
- node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("click", node, e));
3451
+ node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("click", node, e));
3390
3452
  }
3391
3453
  });
3392
3454
  }
@@ -3507,8 +3569,10 @@ var Scene = (_class7 = class _Scene {
3507
3569
  el._lastSyncedValue = attrs.value;
3508
3570
  }
3509
3571
  }
3510
- } else {
3572
+ } else if (RANGE_VALUE_ROLES.has(_nullishCoalesce(attrs.role, () => ( "")))) {
3511
3573
  this.syncOptionalAttribute(el, "aria-valuenow", attrs.value);
3574
+ } else {
3575
+ this.syncOptionalAttribute(el, "aria-valuenow", void 0);
3512
3576
  }
3513
3577
  } else if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
3514
3578
  this.syncOptionalAttribute(el, "aria-valuenow", void 0);
@@ -3644,7 +3708,7 @@ var Scene = (_class7 = class _Scene {
3644
3708
  el.addEventListener(
3645
3709
  "wheel",
3646
3710
  (e2) => {
3647
- node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("wheel", node, e2));
3711
+ node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("wheel", node, e2));
3648
3712
  },
3649
3713
  { passive: false }
3650
3714
  );
@@ -4447,7 +4511,7 @@ var Scene = (_class7 = class _Scene {
4447
4511
  let walkHadAnimation = false;
4448
4512
  let walkHadInteractive = false;
4449
4513
  const runUpdate = (node) => {
4450
- const overridesUpdate = node.update !== _chunkDUYB4GX4js.Entity.prototype.update;
4514
+ const overridesUpdate = node.update !== _chunkIA3KW4CGjs.Entity.prototype.update;
4451
4515
  let pending = node.hasPendingAnimations();
4452
4516
  if (pending || overridesUpdate) {
4453
4517
  node.update(dt, time);
@@ -4456,7 +4520,7 @@ var Scene = (_class7 = class _Scene {
4456
4520
  if (pending) walkHadAnimation = true;
4457
4521
  if (!walkHadInteractive && node.interactive) walkHadInteractive = true;
4458
4522
  if (this._devActive && this._devFrameCount % 120 === 0) {
4459
- if (overridesUpdate && node.hasPendingAnimations === _chunkDUYB4GX4js.Entity.prototype.hasPendingAnimations) {
4523
+ if (overridesUpdate && node.hasPendingAnimations === _chunkIA3KW4CGjs.Entity.prototype.hasPendingAnimations) {
4460
4524
  this._devWarn(
4461
4525
  `Entity "${node.id}" overrides update() but not hasPendingAnimations(). Custom motion in update() without overriding hasPendingAnimations() causes the idle throttle to drop the animation to ~2fps. Override hasPendingAnimations() to return true while motion is in flight.`
4462
4526
  );
@@ -4888,7 +4952,7 @@ var Scene = (_class7 = class _Scene {
4888
4952
  }
4889
4953
  return true;
4890
4954
  }
4891
- }, _class7.__initStatic(), _class7.__initStatic2(), _class7.__initStatic3(), _class7.__initStatic4(), _class7);
4955
+ }, _class7.__initStatic(), _class7.__initStatic2(), _class7.__initStatic3(), _class7.__initStatic4(), _class7.__initStatic5(), _class7);
4892
4956
  function intersectBounds(a, b) {
4893
4957
  const x = Math.max(a.x, b.x);
4894
4958
  const y = Math.max(a.y, b.y);
@@ -4915,20 +4979,20 @@ function defaultMeasurer() {
4915
4979
  if (sharedMeasurer === void 0) sharedMeasurer = _layout.createCanvasMeasurer.call(void 0, "sans-serif");
4916
4980
  return sharedMeasurer;
4917
4981
  }
4918
- var TextEntity = (_class8 = class extends _chunkDUYB4GX4js.Entity {
4982
+ var TextEntity = (_class8 = class extends _chunkIA3KW4CGjs.Entity {
4919
4983
 
4920
4984
 
4921
4985
 
4922
4986
 
4923
- __init134() {this.nodes = []}
4987
+ __init136() {this.nodes = []}
4924
4988
 
4925
- __init135() {this.fillStyle = "#94a3b8"}
4926
- __init136() {this.strokeStyle = null}
4927
- __init137() {this.hoveredFillStyle = "#ffffff"}
4928
- __init138() {this.lineWidth = 1}
4929
- __init139() {this.isHovered = false}
4989
+ __init137() {this.fillStyle = "#94a3b8"}
4990
+ __init138() {this.strokeStyle = null}
4991
+ __init139() {this.hoveredFillStyle = "#ffffff"}
4992
+ __init140() {this.lineWidth = 1}
4993
+ __init141() {this.isHovered = false}
4930
4994
  constructor(text, atlas, maxWidth, fontSize = 24) {
4931
- super();_class8.prototype.__init134.call(this);_class8.prototype.__init135.call(this);_class8.prototype.__init136.call(this);_class8.prototype.__init137.call(this);_class8.prototype.__init138.call(this);_class8.prototype.__init139.call(this);;
4995
+ super();_class8.prototype.__init136.call(this);_class8.prototype.__init137.call(this);_class8.prototype.__init138.call(this);_class8.prototype.__init139.call(this);_class8.prototype.__init140.call(this);_class8.prototype.__init141.call(this);;
4932
4996
  this.text = text;
4933
4997
  this.atlas = atlas;
4934
4998
  this.fontSize = fontSize;
@@ -5041,17 +5105,17 @@ var TextEntity = (_class8 = class extends _chunkDUYB4GX4js.Entity {
5041
5105
  }, _class8);
5042
5106
 
5043
5107
  // src/components/GridTextEntity.ts
5044
- var GridTextEntity = (_class9 = class extends _chunkDUYB4GX4js.Entity {
5108
+ var GridTextEntity = (_class9 = class extends _chunkIA3KW4CGjs.Entity {
5045
5109
 
5046
- __init140() {this.fillStyle = "#ffffff"}
5047
- __init141() {this.grid = []}
5110
+ __init142() {this.fillStyle = "#ffffff"}
5111
+ __init143() {this.grid = []}
5048
5112
  // Array of rows
5049
- __init142() {this.cols = 0}
5050
- __init143() {this.rows = 0}
5113
+ __init144() {this.cols = 0}
5114
+ __init145() {this.rows = 0}
5051
5115
 
5052
5116
 
5053
5117
  constructor(_atlas, fontSize = 10) {
5054
- super();_class9.prototype.__init140.call(this);_class9.prototype.__init141.call(this);_class9.prototype.__init142.call(this);_class9.prototype.__init143.call(this);;
5118
+ super();_class9.prototype.__init142.call(this);_class9.prototype.__init143.call(this);_class9.prototype.__init144.call(this);_class9.prototype.__init145.call(this);;
5055
5119
  this.fontSize = fontSize;
5056
5120
  this.charWidth = fontSize * 1;
5057
5121
  this.charHeight = fontSize * 1.1;
@@ -5135,7 +5199,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
5135
5199
  const ey = py - cy;
5136
5200
  return ex * ex + ey * ey;
5137
5201
  }
5138
- var SplineEntity = (_class10 = class extends _chunkDUYB4GX4js.Entity {
5202
+ var SplineEntity = (_class10 = class extends _chunkIA3KW4CGjs.Entity {
5139
5203
 
5140
5204
 
5141
5205
 
@@ -5143,23 +5207,23 @@ var SplineEntity = (_class10 = class extends _chunkDUYB4GX4js.Entity {
5143
5207
 
5144
5208
 
5145
5209
 
5146
- __init144() {this.offscreen = null}
5147
- __init145() {this.baked = false}
5210
+ __init146() {this.offscreen = null}
5211
+ __init147() {this.baked = false}
5148
5212
  /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
5149
- __init146() {this.bakedWidth = 0}
5150
- __init147() {this.bakedHeight = 0}
5213
+ __init148() {this.bakedWidth = 0}
5214
+ __init149() {this.bakedHeight = 0}
5151
5215
  /** Gradient strokes can't be baked to a solid-color bitmap; they render per-frame. */
5152
5216
 
5153
5217
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
5154
- __init148() {this.polylines = null}
5218
+ __init150() {this.polylines = null}
5155
5219
  /**
5156
5220
  * When `true`, the renderer draws a rounded-rect outline of the entity's
5157
5221
  * local bounds after painting the curves. Useful for drag feedback and
5158
5222
  * debugging hit areas. Defaults to `false`.
5159
5223
  */
5160
- __init149() {this.showBounds = false}
5224
+ __init151() {this.showBounds = false}
5161
5225
  constructor(doc, opts = {}) {
5162
- super();_class10.prototype.__init144.call(this);_class10.prototype.__init145.call(this);_class10.prototype.__init146.call(this);_class10.prototype.__init147.call(this);_class10.prototype.__init148.call(this);_class10.prototype.__init149.call(this);;
5226
+ super();_class10.prototype.__init146.call(this);_class10.prototype.__init147.call(this);_class10.prototype.__init148.call(this);_class10.prototype.__init149.call(this);_class10.prototype.__init150.call(this);_class10.prototype.__init151.call(this);;
5163
5227
  this.doc = doc;
5164
5228
  this.lineWidth = _nullishCoalesce(opts.lineWidth, () => ( 2));
5165
5229
  this.cache = _nullishCoalesce(opts.cache, () => ( true));
@@ -5410,7 +5474,7 @@ async function loadSpline(url) {
5410
5474
  }
5411
5475
 
5412
5476
  // src/components/Rect.ts
5413
- var Rect = class extends _chunkDUYB4GX4js.Entity {
5477
+ var Rect = class extends _chunkIA3KW4CGjs.Entity {
5414
5478
 
5415
5479
 
5416
5480
 
@@ -5459,7 +5523,7 @@ var Rect = class extends _chunkDUYB4GX4js.Entity {
5459
5523
  };
5460
5524
 
5461
5525
  // src/components/Circle.ts
5462
- var Circle = class extends _chunkDUYB4GX4js.Entity {
5526
+ var Circle = class extends _chunkIA3KW4CGjs.Entity {
5463
5527
 
5464
5528
 
5465
5529
 
@@ -5519,7 +5583,7 @@ var Circle = class extends _chunkDUYB4GX4js.Entity {
5519
5583
  };
5520
5584
 
5521
5585
  // src/components/Group.ts
5522
- var Group = class extends _chunkDUYB4GX4js.Entity {
5586
+ var Group = class extends _chunkIA3KW4CGjs.Entity {
5523
5587
  constructor(...children) {
5524
5588
  super();
5525
5589
  if (children.length > 0) this.add(...children);
@@ -5538,21 +5602,21 @@ var _math = require('@vectojs/math'); _createStarExport(_math);
5538
5602
 
5539
5603
 
5540
5604
  // src/tree/DOMPortalEntity.ts
5541
- var DOMPortalEntity = (_class11 = class extends _chunkDUYB4GX4js.Entity {
5605
+ var DOMPortalEntity = (_class11 = class extends _chunkIA3KW4CGjs.Entity {
5542
5606
 
5543
- __init150() {this.isDOMPortal = true}
5544
- __init151() {this.domListeners = []}
5545
- __init152() {this.resizeObserver = null}
5546
- __init153() {this.domBound = false}
5547
- __init154() {this.cachedWidth = 100}
5548
- __init155() {this.cachedHeight = 100}
5549
- __init156() {this.lastWidth = ""}
5550
- __init157() {this.lastHeight = ""}
5551
- __init158() {this.lastTransform = ""}
5552
- __init159() {this.lastZIndex = ""}
5553
- __init160() {this.lastOpacity = ""}
5607
+ __init152() {this.isDOMPortal = true}
5608
+ __init153() {this.domListeners = []}
5609
+ __init154() {this.resizeObserver = null}
5610
+ __init155() {this.domBound = false}
5611
+ __init156() {this.cachedWidth = 100}
5612
+ __init157() {this.cachedHeight = 100}
5613
+ __init158() {this.lastWidth = ""}
5614
+ __init159() {this.lastHeight = ""}
5615
+ __init160() {this.lastTransform = ""}
5616
+ __init161() {this.lastZIndex = ""}
5617
+ __init162() {this.lastOpacity = ""}
5554
5618
  constructor(domElement, width, height, id) {
5555
- super(id);_class11.prototype.__init150.call(this);_class11.prototype.__init151.call(this);_class11.prototype.__init152.call(this);_class11.prototype.__init153.call(this);_class11.prototype.__init154.call(this);_class11.prototype.__init155.call(this);_class11.prototype.__init156.call(this);_class11.prototype.__init157.call(this);_class11.prototype.__init158.call(this);_class11.prototype.__init159.call(this);_class11.prototype.__init160.call(this);;
5619
+ super(id);_class11.prototype.__init152.call(this);_class11.prototype.__init153.call(this);_class11.prototype.__init154.call(this);_class11.prototype.__init155.call(this);_class11.prototype.__init156.call(this);_class11.prototype.__init157.call(this);_class11.prototype.__init158.call(this);_class11.prototype.__init159.call(this);_class11.prototype.__init160.call(this);_class11.prototype.__init161.call(this);_class11.prototype.__init162.call(this);;
5556
5620
  this.domElement = domElement;
5557
5621
  this.width = _nullishCoalesce(width, () => ( 0));
5558
5622
  this.height = _nullishCoalesce(height, () => ( 0));
@@ -5594,7 +5658,7 @@ var DOMPortalEntity = (_class11 = class extends _chunkDUYB4GX4js.Entity {
5594
5658
  ];
5595
5659
  for (const type of events) {
5596
5660
  const handler = (e) => {
5597
- this.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)(type, this, e));
5661
+ this.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)(type, this, e));
5598
5662
  };
5599
5663
  this.domElement.addEventListener(type, handler);
5600
5664
  this.domListeners.push({ type, handler, capture: false });
@@ -5605,7 +5669,7 @@ var DOMPortalEntity = (_class11 = class extends _chunkDUYB4GX4js.Entity {
5605
5669
  ];
5606
5670
  for (const { native, vecto } of hoverEvents) {
5607
5671
  const handler = (e) => {
5608
- this.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)(vecto, this, e, false));
5672
+ this.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)(vecto, this, e, false));
5609
5673
  };
5610
5674
  this.domElement.addEventListener(native, handler);
5611
5675
  this.domListeners.push({ type: native, handler, capture: false });
@@ -5613,7 +5677,7 @@ var DOMPortalEntity = (_class11 = class extends _chunkDUYB4GX4js.Entity {
5613
5677
  const focusEvents = ["focus", "blur"];
5614
5678
  for (const type of focusEvents) {
5615
5679
  const handler = (e) => {
5616
- this.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)(type, this, e, true));
5680
+ this.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)(type, this, e, true));
5617
5681
  };
5618
5682
  this.domElement.addEventListener(type, handler, true);
5619
5683
  this.domListeners.push({ type, handler, capture: true });
@@ -5700,4 +5764,4 @@ Scene.registerWebGPUParticleSystemManager(_chunkL4SWVP2Hjs.WebGPUParticleSystemM
5700
5764
 
5701
5765
 
5702
5766
 
5703
- exports.CanvasRenderer = _chunkL4SWVP2Hjs.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Entity = _chunkDUYB4GX4js.Entity; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.MSDFTextEntity = _chunkDUYB4GX4js.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.Rect = Rect; exports.SVGEntity = _chunkDUYB4GX4js.SVGEntity; exports.SVGRenderer = _chunkL4SWVP2Hjs.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkL4SWVP2Hjs.TextRasterCache; exports.VectoJSEvent = _chunkDUYB4GX4js.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkL4SWVP2Hjs.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkL4SWVP2Hjs.createWebGLPointRenderer; exports.isSafeUrl = _chunkL4SWVP2Hjs.isSafeUrl; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkL4SWVP2Hjs.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkL4SWVP2Hjs.sanitizeUrl;
5767
+ exports.CanvasRenderer = _chunkL4SWVP2Hjs.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Entity = _chunkIA3KW4CGjs.Entity; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.MSDFTextEntity = _chunkIA3KW4CGjs.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.Rect = Rect; exports.SVGEntity = _chunkIA3KW4CGjs.SVGEntity; exports.SVGRenderer = _chunkL4SWVP2Hjs.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkL4SWVP2Hjs.TextRasterCache; exports.VectoJSEvent = _chunkIA3KW4CGjs.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkL4SWVP2Hjs.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkL4SWVP2Hjs.createWebGLPointRenderer; exports.isSafeUrl = _chunkL4SWVP2Hjs.isSafeUrl; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkL4SWVP2Hjs.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkL4SWVP2Hjs.sanitizeUrl;
package/dist/index.mjs CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  MSDFTextEntity,
14
14
  SVGEntity,
15
15
  VectoJSEvent
16
- } from "./chunk-AQTO7OSU.mjs";
16
+ } from "./chunk-XWBVBXFZ.mjs";
17
17
 
18
18
  // src/tree/Scene.ts
19
19
  import { SpringDriver, TweenDriver } from "@vectojs/animation";
@@ -1190,6 +1190,7 @@ async function loadCoreWasmRuntime(source) {
1190
1190
 
1191
1191
  // src/tree/Scene.ts
1192
1192
  import { clearCssLineBoxMetrics, cssLineBoxBaseline } from "@vectojs/text";
1193
+ var RANGE_VALUE_ROLES = /* @__PURE__ */ new Set(["slider", "spinbutton", "progressbar", "scrollbar", "meter"]);
1193
1194
  var INTERACTIVE_A11Y_ROLES = /* @__PURE__ */ new Set([
1194
1195
  "button",
1195
1196
  "switch",
@@ -1535,6 +1536,10 @@ var Scene = class _Scene {
1535
1536
  * event-driven UIs where idle frames should cost ~0.
1536
1537
  */
1537
1538
  renderMode = "always";
1539
+ /** Cap on distinct recorded dirty reasons (see `recordDirtyReason`). */
1540
+ static MAX_DIRTY_REASONS = 200;
1541
+ _dirtyTracking = false;
1542
+ _dirtyReasons = /* @__PURE__ */ new Map();
1538
1543
  dirty = true;
1539
1544
  /** Whether to throttle rendering to 2 FPS when the scene is static to save power. */
1540
1545
  autoThrottle = true;
@@ -3155,8 +3160,65 @@ var Scene = class _Scene {
3155
3160
  * Only meaningful in `onDemand` {@link renderMode}: call it after mutating
3156
3161
  * entity state outside of {@link Entity.animate} so the change is rendered.
3157
3162
  */
3158
- markDirty() {
3163
+ markDirty(source) {
3159
3164
  this.dirty = true;
3165
+ if (this._dirtyTracking && source) this.recordDirtyReason(source);
3166
+ }
3167
+ /**
3168
+ * Record who marked the scene dirty and why.
3169
+ *
3170
+ * Kept separate from {@link markDirty} so the hot path is not a function call
3171
+ * with a branch — V8 inlines the one-field version reliably.
3172
+ */
3173
+ recordDirtyReason(source) {
3174
+ const key = `${source.entity ?? "scene"}:${source.reason}${source.property ? `.${source.property}` : ""}`;
3175
+ const existing = this._dirtyReasons.get(key);
3176
+ if (existing) {
3177
+ existing.count++;
3178
+ existing.lastFrame = this.currentFrame;
3179
+ return;
3180
+ }
3181
+ if (this._dirtyReasons.size >= _Scene.MAX_DIRTY_REASONS) {
3182
+ const oldest = this._dirtyReasons.keys().next().value;
3183
+ if (oldest !== void 0) this._dirtyReasons.delete(oldest);
3184
+ }
3185
+ this._dirtyReasons.set(key, {
3186
+ entity: source.entity,
3187
+ reason: source.reason,
3188
+ property: source.property,
3189
+ count: 1,
3190
+ firstFrame: this.currentFrame,
3191
+ lastFrame: this.currentFrame
3192
+ });
3193
+ }
3194
+ /**
3195
+ * Start or stop recording dirty attributions.
3196
+ *
3197
+ * Off by default. `renderMode: 'onDemand'` silently degrades to always-on when
3198
+ * something marks the scene dirty every frame, and until now there was no way
3199
+ * to find out what — `dirty === true` said nothing about the cause. Enable
3200
+ * this, run the scene, then read {@link dirtyReasons}.
3201
+ */
3202
+ setDirtyTracking(enabled) {
3203
+ this._dirtyTracking = enabled;
3204
+ if (!enabled) this._dirtyReasons.clear();
3205
+ }
3206
+ /** Whether dirty attribution is currently being recorded. */
3207
+ get dirtyTracking() {
3208
+ return this._dirtyTracking;
3209
+ }
3210
+ /**
3211
+ * Recorded dirty attributions, most frequent first.
3212
+ *
3213
+ * `count` is what matters for the `onDemand` diagnosis: a reason appearing once
3214
+ * per frame over hundreds of frames is the thing keeping the scene awake.
3215
+ */
3216
+ get dirtyReasons() {
3217
+ return [...this._dirtyReasons.values()].sort((a, b) => b.count - a.count);
3218
+ }
3219
+ /** Drop recorded attributions, keeping tracking enabled. */
3220
+ clearDirtyReasons() {
3221
+ this._dirtyReasons.clear();
3160
3222
  }
3161
3223
  /**
3162
3224
  * Live frame telemetry for profilers and devtools overlays. All timings are
@@ -3506,8 +3568,10 @@ var Scene = class _Scene {
3506
3568
  el._lastSyncedValue = attrs.value;
3507
3569
  }
3508
3570
  }
3509
- } else {
3571
+ } else if (RANGE_VALUE_ROLES.has(attrs.role ?? "")) {
3510
3572
  this.syncOptionalAttribute(el, "aria-valuenow", attrs.value);
3573
+ } else {
3574
+ this.syncOptionalAttribute(el, "aria-valuenow", void 0);
3511
3575
  }
3512
3576
  } else if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
3513
3577
  this.syncOptionalAttribute(el, "aria-valuenow", void 0);
package/dist/text.js CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkDUYB4GX4js = require('./chunk-DUYB4GX4.js');
5
+ var _chunkIA3KW4CGjs = require('./chunk-IA3KW4CG.js');
6
6
 
7
7
  // src/text/index.ts
8
8
  var _text = require('@vectojs/text'); _createStarExport(_text);
9
9
 
10
10
 
11
11
 
12
- exports.MSDFTextEntity = _chunkDUYB4GX4js.MSDFTextEntity; exports.SVGEntity = _chunkDUYB4GX4js.SVGEntity;
12
+ exports.MSDFTextEntity = _chunkIA3KW4CGjs.MSDFTextEntity; exports.SVGEntity = _chunkIA3KW4CGjs.SVGEntity;
package/dist/text.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  MSDFTextEntity,
3
3
  SVGEntity
4
- } from "./chunk-AQTO7OSU.mjs";
4
+ } from "./chunk-XWBVBXFZ.mjs";
5
5
 
6
6
  // src/text/index.ts
7
7
  export * from "@vectojs/text";
@@ -17,6 +17,31 @@ import { type CoreWasmRuntime } from '../wasm/runtime';
17
17
  import { type HitModuleSource, type HitTestBackend } from '../wasm/hit-backend';
18
18
  import { type AnimModuleSource, type AnimBackend } from '../wasm/anim-backend';
19
19
  import { type ParticleModuleSource, type ParticleBackend } from '../wasm/particle-backend';
20
+ /**
21
+ * Who marked the scene dirty, and why.
22
+ *
23
+ * Every field is optional except `reason` so a call site can be as specific as it
24
+ * cheaply can — an entity id costs nothing to pass, a property name is often
25
+ * already in scope.
26
+ */
27
+ export interface DirtySource {
28
+ /** Entity id responsible, when one is. Omitted for scene-level invalidation. */
29
+ entity?: string;
30
+ /** Short, stable category — e.g. `'text-changed'`, `'animation'`, `'resize'`. */
31
+ reason: string;
32
+ /** Property that changed, when the reason alone is ambiguous. */
33
+ property?: string;
34
+ }
35
+ /** An aggregated dirty attribution. */
36
+ export interface DirtyReasonEntry {
37
+ entity?: string;
38
+ reason: string;
39
+ property?: string;
40
+ /** How many times this exact attribution was recorded. */
41
+ count: number;
42
+ firstFrame: number;
43
+ lastFrame: number;
44
+ }
20
45
  /**
21
46
  * Options for {@link Scene}.
22
47
  */
@@ -203,6 +228,10 @@ export declare class Scene {
203
228
  * event-driven UIs where idle frames should cost ~0.
204
229
  */
205
230
  renderMode: 'always' | 'onDemand';
231
+ /** Cap on distinct recorded dirty reasons (see `recordDirtyReason`). */
232
+ private static readonly MAX_DIRTY_REASONS;
233
+ private _dirtyTracking;
234
+ private _dirtyReasons;
206
235
  private dirty;
207
236
  /** Whether to throttle rendering to 2 FPS when the scene is static to save power. */
208
237
  autoThrottle: boolean;
@@ -845,7 +874,34 @@ export declare class Scene {
845
874
  * Only meaningful in `onDemand` {@link renderMode}: call it after mutating
846
875
  * entity state outside of {@link Entity.animate} so the change is rendered.
847
876
  */
848
- markDirty(): void;
877
+ markDirty(source?: DirtySource): void;
878
+ /**
879
+ * Record who marked the scene dirty and why.
880
+ *
881
+ * Kept separate from {@link markDirty} so the hot path is not a function call
882
+ * with a branch — V8 inlines the one-field version reliably.
883
+ */
884
+ private recordDirtyReason;
885
+ /**
886
+ * Start or stop recording dirty attributions.
887
+ *
888
+ * Off by default. `renderMode: 'onDemand'` silently degrades to always-on when
889
+ * something marks the scene dirty every frame, and until now there was no way
890
+ * to find out what — `dirty === true` said nothing about the cause. Enable
891
+ * this, run the scene, then read {@link dirtyReasons}.
892
+ */
893
+ setDirtyTracking(enabled: boolean): void;
894
+ /** Whether dirty attribution is currently being recorded. */
895
+ get dirtyTracking(): boolean;
896
+ /**
897
+ * Recorded dirty attributions, most frequent first.
898
+ *
899
+ * `count` is what matters for the `onDemand` diagnosis: a reason appearing once
900
+ * per frame over hundreds of frames is the thing keeping the scene awake.
901
+ */
902
+ get dirtyReasons(): DirtyReasonEntry[];
903
+ /** Drop recorded attributions, keeping tracking enabled. */
904
+ clearDirtyReasons(): void;
849
905
  /**
850
906
  * Live frame telemetry for profilers and devtools overlays. All timings are
851
907
  * measured on the `requestAnimationFrame` loop; a scene driven only by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "1.17.1",
3
+ "version": "1.18.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },