@vectojs/core 1.17.0 → 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
 
@@ -1107,10 +1107,10 @@ function cacheKey(source) {
1107
1107
  if (source instanceof URL) return source.href;
1108
1108
  return null;
1109
1109
  }
1110
- async function compile(source) {
1111
- if (source instanceof ArrayBuffer || ArrayBuffer.isView(source)) {
1112
- return new WebAssembly.Module(source);
1113
- }
1110
+ function compileBytes(bytes) {
1111
+ return new WebAssembly.Module(bytes);
1112
+ }
1113
+ async function compileRemote(source) {
1114
1114
  const response = typeof source === "string" || source instanceof URL ? await fetch(String(source)) : await source;
1115
1115
  if (typeof WebAssembly.compileStreaming === "function") {
1116
1116
  const buffered = response.clone();
@@ -1135,7 +1135,7 @@ async function loadCoreWasmModule(source) {
1135
1135
  }
1136
1136
  }
1137
1137
  }
1138
- const pending = compile(source);
1138
+ const pending = source instanceof ArrayBuffer || ArrayBuffer.isView(source) ? Promise.resolve().then(() => compileBytes(source)) : compileRemote(source);
1139
1139
  if (key !== null) moduleCache.set(key, pending);
1140
1140
  try {
1141
1141
  return await pending;
@@ -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
  }
@@ -3477,11 +3539,17 @@ var Scene = (_class7 = class _Scene {
3477
3539
  this.syncOptionalAttribute(el, "aria-modal", attrs.ariaModal);
3478
3540
  this.syncOptionalAttribute(el, "aria-labelledby", attrs.labelledby);
3479
3541
  this.syncOptionalAttribute(el, "aria-describedby", attrs.describedby);
3480
- this.syncOptionalAttribute(
3481
- el,
3482
- "aria-required",
3483
- attrs.required === void 0 ? void 0 : String(attrs.required)
3484
- );
3542
+ if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
3543
+ const wantRequired = attrs.required === true;
3544
+ if (el.required !== wantRequired) el.required = wantRequired;
3545
+ this.syncOptionalAttribute(el, "aria-required", void 0);
3546
+ } else {
3547
+ this.syncOptionalAttribute(
3548
+ el,
3549
+ "aria-required",
3550
+ attrs.required === void 0 ? void 0 : String(attrs.required)
3551
+ );
3552
+ }
3485
3553
  this.syncOptionalAttribute(
3486
3554
  el,
3487
3555
  "aria-invalid",
@@ -3501,8 +3569,10 @@ var Scene = (_class7 = class _Scene {
3501
3569
  el._lastSyncedValue = attrs.value;
3502
3570
  }
3503
3571
  }
3504
- } else {
3572
+ } else if (RANGE_VALUE_ROLES.has(_nullishCoalesce(attrs.role, () => ( "")))) {
3505
3573
  this.syncOptionalAttribute(el, "aria-valuenow", attrs.value);
3574
+ } else {
3575
+ this.syncOptionalAttribute(el, "aria-valuenow", void 0);
3506
3576
  }
3507
3577
  } else if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
3508
3578
  this.syncOptionalAttribute(el, "aria-valuenow", void 0);
@@ -3638,7 +3708,7 @@ var Scene = (_class7 = class _Scene {
3638
3708
  el.addEventListener(
3639
3709
  "wheel",
3640
3710
  (e2) => {
3641
- node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("wheel", node, e2));
3711
+ node.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)("wheel", node, e2));
3642
3712
  },
3643
3713
  { passive: false }
3644
3714
  );
@@ -4441,7 +4511,7 @@ var Scene = (_class7 = class _Scene {
4441
4511
  let walkHadAnimation = false;
4442
4512
  let walkHadInteractive = false;
4443
4513
  const runUpdate = (node) => {
4444
- const overridesUpdate = node.update !== _chunkDUYB4GX4js.Entity.prototype.update;
4514
+ const overridesUpdate = node.update !== _chunkIA3KW4CGjs.Entity.prototype.update;
4445
4515
  let pending = node.hasPendingAnimations();
4446
4516
  if (pending || overridesUpdate) {
4447
4517
  node.update(dt, time);
@@ -4450,7 +4520,7 @@ var Scene = (_class7 = class _Scene {
4450
4520
  if (pending) walkHadAnimation = true;
4451
4521
  if (!walkHadInteractive && node.interactive) walkHadInteractive = true;
4452
4522
  if (this._devActive && this._devFrameCount % 120 === 0) {
4453
- if (overridesUpdate && node.hasPendingAnimations === _chunkDUYB4GX4js.Entity.prototype.hasPendingAnimations) {
4523
+ if (overridesUpdate && node.hasPendingAnimations === _chunkIA3KW4CGjs.Entity.prototype.hasPendingAnimations) {
4454
4524
  this._devWarn(
4455
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.`
4456
4526
  );
@@ -4882,7 +4952,7 @@ var Scene = (_class7 = class _Scene {
4882
4952
  }
4883
4953
  return true;
4884
4954
  }
4885
- }, _class7.__initStatic(), _class7.__initStatic2(), _class7.__initStatic3(), _class7.__initStatic4(), _class7);
4955
+ }, _class7.__initStatic(), _class7.__initStatic2(), _class7.__initStatic3(), _class7.__initStatic4(), _class7.__initStatic5(), _class7);
4886
4956
  function intersectBounds(a, b) {
4887
4957
  const x = Math.max(a.x, b.x);
4888
4958
  const y = Math.max(a.y, b.y);
@@ -4909,20 +4979,20 @@ function defaultMeasurer() {
4909
4979
  if (sharedMeasurer === void 0) sharedMeasurer = _layout.createCanvasMeasurer.call(void 0, "sans-serif");
4910
4980
  return sharedMeasurer;
4911
4981
  }
4912
- var TextEntity = (_class8 = class extends _chunkDUYB4GX4js.Entity {
4982
+ var TextEntity = (_class8 = class extends _chunkIA3KW4CGjs.Entity {
4913
4983
 
4914
4984
 
4915
4985
 
4916
4986
 
4917
- __init134() {this.nodes = []}
4987
+ __init136() {this.nodes = []}
4918
4988
 
4919
- __init135() {this.fillStyle = "#94a3b8"}
4920
- __init136() {this.strokeStyle = null}
4921
- __init137() {this.hoveredFillStyle = "#ffffff"}
4922
- __init138() {this.lineWidth = 1}
4923
- __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}
4924
4994
  constructor(text, atlas, maxWidth, fontSize = 24) {
4925
- 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);;
4926
4996
  this.text = text;
4927
4997
  this.atlas = atlas;
4928
4998
  this.fontSize = fontSize;
@@ -5035,17 +5105,17 @@ var TextEntity = (_class8 = class extends _chunkDUYB4GX4js.Entity {
5035
5105
  }, _class8);
5036
5106
 
5037
5107
  // src/components/GridTextEntity.ts
5038
- var GridTextEntity = (_class9 = class extends _chunkDUYB4GX4js.Entity {
5108
+ var GridTextEntity = (_class9 = class extends _chunkIA3KW4CGjs.Entity {
5039
5109
 
5040
- __init140() {this.fillStyle = "#ffffff"}
5041
- __init141() {this.grid = []}
5110
+ __init142() {this.fillStyle = "#ffffff"}
5111
+ __init143() {this.grid = []}
5042
5112
  // Array of rows
5043
- __init142() {this.cols = 0}
5044
- __init143() {this.rows = 0}
5113
+ __init144() {this.cols = 0}
5114
+ __init145() {this.rows = 0}
5045
5115
 
5046
5116
 
5047
5117
  constructor(_atlas, fontSize = 10) {
5048
- 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);;
5049
5119
  this.fontSize = fontSize;
5050
5120
  this.charWidth = fontSize * 1;
5051
5121
  this.charHeight = fontSize * 1.1;
@@ -5129,7 +5199,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
5129
5199
  const ey = py - cy;
5130
5200
  return ex * ex + ey * ey;
5131
5201
  }
5132
- var SplineEntity = (_class10 = class extends _chunkDUYB4GX4js.Entity {
5202
+ var SplineEntity = (_class10 = class extends _chunkIA3KW4CGjs.Entity {
5133
5203
 
5134
5204
 
5135
5205
 
@@ -5137,23 +5207,23 @@ var SplineEntity = (_class10 = class extends _chunkDUYB4GX4js.Entity {
5137
5207
 
5138
5208
 
5139
5209
 
5140
- __init144() {this.offscreen = null}
5141
- __init145() {this.baked = false}
5210
+ __init146() {this.offscreen = null}
5211
+ __init147() {this.baked = false}
5142
5212
  /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
5143
- __init146() {this.bakedWidth = 0}
5144
- __init147() {this.bakedHeight = 0}
5213
+ __init148() {this.bakedWidth = 0}
5214
+ __init149() {this.bakedHeight = 0}
5145
5215
  /** Gradient strokes can't be baked to a solid-color bitmap; they render per-frame. */
5146
5216
 
5147
5217
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
5148
- __init148() {this.polylines = null}
5218
+ __init150() {this.polylines = null}
5149
5219
  /**
5150
5220
  * When `true`, the renderer draws a rounded-rect outline of the entity's
5151
5221
  * local bounds after painting the curves. Useful for drag feedback and
5152
5222
  * debugging hit areas. Defaults to `false`.
5153
5223
  */
5154
- __init149() {this.showBounds = false}
5224
+ __init151() {this.showBounds = false}
5155
5225
  constructor(doc, opts = {}) {
5156
- 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);;
5157
5227
  this.doc = doc;
5158
5228
  this.lineWidth = _nullishCoalesce(opts.lineWidth, () => ( 2));
5159
5229
  this.cache = _nullishCoalesce(opts.cache, () => ( true));
@@ -5404,7 +5474,7 @@ async function loadSpline(url) {
5404
5474
  }
5405
5475
 
5406
5476
  // src/components/Rect.ts
5407
- var Rect = class extends _chunkDUYB4GX4js.Entity {
5477
+ var Rect = class extends _chunkIA3KW4CGjs.Entity {
5408
5478
 
5409
5479
 
5410
5480
 
@@ -5453,7 +5523,7 @@ var Rect = class extends _chunkDUYB4GX4js.Entity {
5453
5523
  };
5454
5524
 
5455
5525
  // src/components/Circle.ts
5456
- var Circle = class extends _chunkDUYB4GX4js.Entity {
5526
+ var Circle = class extends _chunkIA3KW4CGjs.Entity {
5457
5527
 
5458
5528
 
5459
5529
 
@@ -5513,7 +5583,7 @@ var Circle = class extends _chunkDUYB4GX4js.Entity {
5513
5583
  };
5514
5584
 
5515
5585
  // src/components/Group.ts
5516
- var Group = class extends _chunkDUYB4GX4js.Entity {
5586
+ var Group = class extends _chunkIA3KW4CGjs.Entity {
5517
5587
  constructor(...children) {
5518
5588
  super();
5519
5589
  if (children.length > 0) this.add(...children);
@@ -5532,21 +5602,21 @@ var _math = require('@vectojs/math'); _createStarExport(_math);
5532
5602
 
5533
5603
 
5534
5604
  // src/tree/DOMPortalEntity.ts
5535
- var DOMPortalEntity = (_class11 = class extends _chunkDUYB4GX4js.Entity {
5605
+ var DOMPortalEntity = (_class11 = class extends _chunkIA3KW4CGjs.Entity {
5536
5606
 
5537
- __init150() {this.isDOMPortal = true}
5538
- __init151() {this.domListeners = []}
5539
- __init152() {this.resizeObserver = null}
5540
- __init153() {this.domBound = false}
5541
- __init154() {this.cachedWidth = 100}
5542
- __init155() {this.cachedHeight = 100}
5543
- __init156() {this.lastWidth = ""}
5544
- __init157() {this.lastHeight = ""}
5545
- __init158() {this.lastTransform = ""}
5546
- __init159() {this.lastZIndex = ""}
5547
- __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 = ""}
5548
5618
  constructor(domElement, width, height, id) {
5549
- 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);;
5550
5620
  this.domElement = domElement;
5551
5621
  this.width = _nullishCoalesce(width, () => ( 0));
5552
5622
  this.height = _nullishCoalesce(height, () => ( 0));
@@ -5588,7 +5658,7 @@ var DOMPortalEntity = (_class11 = class extends _chunkDUYB4GX4js.Entity {
5588
5658
  ];
5589
5659
  for (const type of events) {
5590
5660
  const handler = (e) => {
5591
- this.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)(type, this, e));
5661
+ this.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)(type, this, e));
5592
5662
  };
5593
5663
  this.domElement.addEventListener(type, handler);
5594
5664
  this.domListeners.push({ type, handler, capture: false });
@@ -5599,7 +5669,7 @@ var DOMPortalEntity = (_class11 = class extends _chunkDUYB4GX4js.Entity {
5599
5669
  ];
5600
5670
  for (const { native, vecto } of hoverEvents) {
5601
5671
  const handler = (e) => {
5602
- this.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)(vecto, this, e, false));
5672
+ this.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)(vecto, this, e, false));
5603
5673
  };
5604
5674
  this.domElement.addEventListener(native, handler);
5605
5675
  this.domListeners.push({ type: native, handler, capture: false });
@@ -5607,7 +5677,7 @@ var DOMPortalEntity = (_class11 = class extends _chunkDUYB4GX4js.Entity {
5607
5677
  const focusEvents = ["focus", "blur"];
5608
5678
  for (const type of focusEvents) {
5609
5679
  const handler = (e) => {
5610
- this.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)(type, this, e, true));
5680
+ this.dispatchEvent(new (0, _chunkIA3KW4CGjs.VectoJSEvent)(type, this, e, true));
5611
5681
  };
5612
5682
  this.domElement.addEventListener(type, handler, true);
5613
5683
  this.domListeners.push({ type, handler, capture: true });
@@ -5694,4 +5764,4 @@ Scene.registerWebGPUParticleSystemManager(_chunkL4SWVP2Hjs.WebGPUParticleSystemM
5694
5764
 
5695
5765
 
5696
5766
 
5697
- 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";
@@ -1106,10 +1106,10 @@ function cacheKey(source) {
1106
1106
  if (source instanceof URL) return source.href;
1107
1107
  return null;
1108
1108
  }
1109
- async function compile(source) {
1110
- if (source instanceof ArrayBuffer || ArrayBuffer.isView(source)) {
1111
- return new WebAssembly.Module(source);
1112
- }
1109
+ function compileBytes(bytes) {
1110
+ return new WebAssembly.Module(bytes);
1111
+ }
1112
+ async function compileRemote(source) {
1113
1113
  const response = typeof source === "string" || source instanceof URL ? await fetch(String(source)) : await source;
1114
1114
  if (typeof WebAssembly.compileStreaming === "function") {
1115
1115
  const buffered = response.clone();
@@ -1134,7 +1134,7 @@ async function loadCoreWasmModule(source) {
1134
1134
  }
1135
1135
  }
1136
1136
  }
1137
- const pending = compile(source);
1137
+ const pending = source instanceof ArrayBuffer || ArrayBuffer.isView(source) ? Promise.resolve().then(() => compileBytes(source)) : compileRemote(source);
1138
1138
  if (key !== null) moduleCache.set(key, pending);
1139
1139
  try {
1140
1140
  return await pending;
@@ -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
@@ -3476,11 +3538,17 @@ var Scene = class _Scene {
3476
3538
  this.syncOptionalAttribute(el, "aria-modal", attrs.ariaModal);
3477
3539
  this.syncOptionalAttribute(el, "aria-labelledby", attrs.labelledby);
3478
3540
  this.syncOptionalAttribute(el, "aria-describedby", attrs.describedby);
3479
- this.syncOptionalAttribute(
3480
- el,
3481
- "aria-required",
3482
- attrs.required === void 0 ? void 0 : String(attrs.required)
3483
- );
3541
+ if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
3542
+ const wantRequired = attrs.required === true;
3543
+ if (el.required !== wantRequired) el.required = wantRequired;
3544
+ this.syncOptionalAttribute(el, "aria-required", void 0);
3545
+ } else {
3546
+ this.syncOptionalAttribute(
3547
+ el,
3548
+ "aria-required",
3549
+ attrs.required === void 0 ? void 0 : String(attrs.required)
3550
+ );
3551
+ }
3484
3552
  this.syncOptionalAttribute(
3485
3553
  el,
3486
3554
  "aria-invalid",
@@ -3500,8 +3568,10 @@ var Scene = class _Scene {
3500
3568
  el._lastSyncedValue = attrs.value;
3501
3569
  }
3502
3570
  }
3503
- } else {
3571
+ } else if (RANGE_VALUE_ROLES.has(attrs.role ?? "")) {
3504
3572
  this.syncOptionalAttribute(el, "aria-valuenow", attrs.value);
3573
+ } else {
3574
+ this.syncOptionalAttribute(el, "aria-valuenow", void 0);
3505
3575
  }
3506
3576
  } else if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
3507
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.0",
3
+ "version": "1.18.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },