@vectojs/core 1.39.1 → 1.40.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.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _createStarExport(obj) { Object.keys(obj) .filter((key) => key !== "default" && key !== "__esModule") .forEach((key) => { if (exports.hasOwnProperty(key)) { return; } Object.defineProperty(exports, key, {enumerable: true, configurable: true, get: () => obj[key]}); }); } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7; var _class8; var _class9; var _class10; var _class11; var _class12; var _class13; var _class14; var _class15; var _class16; var _class17; var _class18; var _class19;const __vecto_cjs_url=require("url").pathToFileURL(__filename).href;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _createStarExport(obj) { Object.keys(obj) .filter((key) => key !== "default" && key !== "__esModule") .forEach((key) => { if (exports.hasOwnProperty(key)) { return; } Object.defineProperty(exports, key, {enumerable: true, configurable: true, get: () => obj[key]}); }); } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7; var _class8; var _class9; var _class10; var _class11; var _class12; var _class13; var _class14; var _class15; var _class16; var _class17; var _class18; var _class19; var _class20; var _class21;const __vecto_cjs_url=require("url").pathToFileURL(__filename).href;
2
2
 
3
3
 
4
4
 
@@ -19,7 +19,7 @@ var _chunkBKXERWADjs = require('./chunk-BKXERWAD.js');
19
19
 
20
20
 
21
21
 
22
- var _chunkPOPXUPKRjs = require('./chunk-POPXUPKR.js');
22
+ var _chunkHC5EZOZJjs = require('./chunk-HC5EZOZJ.js');
23
23
 
24
24
  // src/tree/ComputeParticleEntity.ts
25
25
  var PARTICLE_STRIDE_FLOATS = 8;
@@ -31,7 +31,7 @@ var PARTICLE_OFFSET_ORIGIN_X = 4;
31
31
  var PARTICLE_OFFSET_ORIGIN_Y = 5;
32
32
  var PARTICLE_OFFSET_SIZE = 6;
33
33
  var PARTICLE_OFFSET_LIFE = 7;
34
- var ComputeParticleEntity = (_class = class extends _chunkPOPXUPKRjs.Entity {
34
+ var ComputeParticleEntity = (_class = class extends _chunkHC5EZOZJjs.Entity {
35
35
 
36
36
 
37
37
 
@@ -1599,6 +1599,62 @@ var HitTester = (_class6 = class {
1599
1599
  findEntityAt(x, y, frame, width, height) {
1600
1600
  const overlayHit = this.findHitRecursively(this.overlayRoot, x, y);
1601
1601
  if (overlayHit) return overlayHit;
1602
+ return this.findMainTreeHit(x, y, frame, width, height);
1603
+ }
1604
+ /**
1605
+ * The merged hit list (RFC5 §2, CTX-0600): the canvas spatial test plus
1606
+ * caller-observed DOM-native candidates (mirror / portal / `dom-visual`
1607
+ * extension point for CTX-0598) as ONE ordered candidate list.
1608
+ *
1609
+ * Query API only — no dispatch change: `findEntityAt` keeps its single
1610
+ * topmost answer byte-for-byte. Coordinates enter in scene space (callers
1611
+ * map client coordinates first, e.g. `Scene.findHitsAtClient`); backend
1612
+ * attribution follows. DOM candidates pass through the same
1613
+ * `disabled` / `pointerEvents: 'none'` predicate as the canvas paths
1614
+ * (RFC5 §2 rule 4); the browser's hit test is their geometric authority,
1615
+ * so scene-space `isPointInside` is not re-checked.
1616
+ *
1617
+ * Overlay order is authoritative (RFC5 §2 rule 2): overlay-subtree
1618
+ * candidates sort above main-tree candidates, DOM-native backends above
1619
+ * canvas within one subtree. Sorted descending by `priority`; ties keep
1620
+ * insertion order (canvas overlay, canvas main, then DOM in caller order).
1621
+ */
1622
+ findHitsAt(x, y, frame, width, height, domCandidates = []) {
1623
+ const results = [];
1624
+ const overlayHit = this.findHitRecursively(this.overlayRoot, x, y);
1625
+ if (overlayHit) results.push(this.toHitResult(overlayHit, "canvas", x, y));
1626
+ const mainHit = this.findMainTreeHit(x, y, frame, width, height);
1627
+ if (mainHit) results.push(this.toHitResult(mainHit, "canvas", x, y));
1628
+ for (const candidate of domCandidates) {
1629
+ if (!this.isHitEligible(candidate.node, x, y)) continue;
1630
+ results.push(this.toHitResult(candidate.node, candidate.backend, x, y));
1631
+ }
1632
+ results.sort((a, b) => b.priority - a.priority);
1633
+ return results;
1634
+ }
1635
+ /**
1636
+ * Attribute one candidate: scene-space point plus node-local point, with
1637
+ * the compositor-layer priority (overlay subtree above main tree,
1638
+ * DOM-native backends above canvas within one subtree).
1639
+ */
1640
+ toHitResult(node, backend, x, y) {
1641
+ return {
1642
+ node,
1643
+ backend,
1644
+ localPoint: node.worldToLocal(x, y),
1645
+ worldPoint: { x, y },
1646
+ priority: (this.isInOverlaySubtree(node) ? 2 : 0) + (backend === "canvas" ? 0 : 1)
1647
+ };
1648
+ }
1649
+ /** Whether `node` lives under the overlay root (drawn above the main tree). */
1650
+ isInOverlaySubtree(node) {
1651
+ for (let current = node; current; current = current.parent) {
1652
+ if (current === this.overlayRoot) return true;
1653
+ }
1654
+ return false;
1655
+ }
1656
+ /** Main-tree arm of {@link findEntityAt}, shared with {@link findHitsAt}. */
1657
+ findMainTreeHit(x, y, frame, width, height) {
1602
1658
  if (this.backends.hit && this.ensureHitGrid(frame, width, height)) {
1603
1659
  return this.findEntityAtWasm(x, y);
1604
1660
  }
@@ -3502,6 +3558,207 @@ function normalizeChord(input) {
3502
3558
  return ordered.join("+");
3503
3559
  }
3504
3560
 
3561
+ // src/tree/scene/SemanticProjectionPolicy.ts
3562
+ var DEFAULT_SEMANTIC_PROJECTION_POLICY = {
3563
+ choose: () => "project"
3564
+ };
3565
+ function getSemanticProjectionCapabilities() {
3566
+ return {
3567
+ htmlInCanvas: supportsHTMLInCanvas(),
3568
+ canvasTextRecovery: false
3569
+ };
3570
+ }
3571
+ var CONTROL_SEMANTIC_ROLES = /* @__PURE__ */ new Set([
3572
+ "button",
3573
+ "link",
3574
+ "checkbox",
3575
+ "radio",
3576
+ "switch",
3577
+ "slider",
3578
+ "spinbutton",
3579
+ "combobox",
3580
+ "listbox",
3581
+ "option",
3582
+ "menuitem",
3583
+ "menuitemcheckbox",
3584
+ "menuitemradio",
3585
+ "tab",
3586
+ "textbox",
3587
+ "searchbox",
3588
+ "progressbar",
3589
+ "scrollbar",
3590
+ "meter"
3591
+ ]);
3592
+ function isDeferrableSemanticNode(node) {
3593
+ const attrs = node.getA11yAttributes();
3594
+ if (attrs.tag !== void 0 && attrs.tag !== "div") return false;
3595
+ if (attrs.role !== void 0 && CONTROL_SEMANTIC_ROLES.has(attrs.role)) return false;
3596
+ if (typeof attrs.tabIndex === "number" && attrs.tabIndex >= 0) return false;
3597
+ const projection = _optionalChain([node, 'access', _86 => _86.getContentProjection, 'optionalCall', _87 => _87()]);
3598
+ if (_optionalChain([projection, 'optionalAccess', _88 => _88.selectable])) return false;
3599
+ return true;
3600
+ }
3601
+ function describeHTMLInCanvasSupport() {
3602
+ if (typeof document === "undefined") return { supported: false, reason: "no-dom" };
3603
+ try {
3604
+ const canvas = document.createElement("canvas");
3605
+ const ctx = canvas.getContext("2d");
3606
+ const native2D = typeof CanvasRenderingContext2D !== "undefined" && ctx instanceof CanvasRenderingContext2D;
3607
+ if (native2D && typeof ctx["drawElementImage"] === "function") {
3608
+ return { supported: true };
3609
+ }
3610
+ } catch (e15) {
3611
+ return { supported: false, reason: "no-backend" };
3612
+ }
3613
+ return { supported: false, reason: "no-backend" };
3614
+ }
3615
+ function supportsHTMLInCanvas() {
3616
+ return describeHTMLInCanvasSupport().supported;
3617
+ }
3618
+
3619
+ // src/tree/scene/ProjectionPolicy.ts
3620
+ var DEFAULT_PROJECTION_CAPABILITIES = {
3621
+ /** Static text (short): findability already covered by content projection. */
3622
+ text: {
3623
+ domSupported: true,
3624
+ nativeValue: "medium",
3625
+ domCost: "low",
3626
+ canvasCost: "low",
3627
+ autoDefault: "canvas"
3628
+ },
3629
+ /** Long/selectable text, Markdown blocks: selection, Ctrl+F, translation. */
3630
+ prose: {
3631
+ domSupported: true,
3632
+ nativeValue: "high",
3633
+ domCost: "medium",
3634
+ canvasCost: "high",
3635
+ autoDefault: "dom"
3636
+ },
3637
+ /** Code blocks: selectable source, but canvas carriers already serve selection — rests canvas until measured (RFC4 §6 hybrid mapping). */
3638
+ code: {
3639
+ domSupported: true,
3640
+ nativeValue: "medium",
3641
+ domCost: "medium",
3642
+ canvasCost: "high",
3643
+ autoDefault: "canvas"
3644
+ },
3645
+ /** Text input / editable: IME, caret, clipboard, BiDi come free in DOM. */
3646
+ input: {
3647
+ domSupported: true,
3648
+ nativeValue: "high",
3649
+ domCost: "low",
3650
+ canvasCost: "prohibitive",
3651
+ autoDefault: "dom"
3652
+ },
3653
+ /** Button / Link: cheap either way, native wins on focus + AT clicks. */
3654
+ button: {
3655
+ domSupported: true,
3656
+ nativeValue: "medium",
3657
+ domCost: "low",
3658
+ canvasCost: "low",
3659
+ autoDefault: "dom"
3660
+ },
3661
+ link: {
3662
+ domSupported: true,
3663
+ nativeValue: "medium",
3664
+ domCost: "low",
3665
+ canvasCost: "low",
3666
+ autoDefault: "dom"
3667
+ },
3668
+ /** Select / Dropdown / Checkbox / Radio: popup, keyboard, form semantics. */
3669
+ select: {
3670
+ domSupported: true,
3671
+ nativeValue: "high",
3672
+ domCost: "low",
3673
+ canvasCost: "high",
3674
+ autoDefault: "dom"
3675
+ },
3676
+ /** Container / layout group: no direct representation, children negotiate. */
3677
+ container: {
3678
+ domSupported: true,
3679
+ nativeValue: "none",
3680
+ domCost: "n/a",
3681
+ canvasCost: "low",
3682
+ autoDefault: "canvas"
3683
+ },
3684
+ /** Transform / camera rig: owns space, not pixels — never materialized. */
3685
+ transform: {
3686
+ domSupported: true,
3687
+ nativeValue: "none",
3688
+ domCost: "n/a",
3689
+ canvasCost: "n/a",
3690
+ autoDefault: "canvas"
3691
+ },
3692
+ /** Image / video frame: blit is cheap, one element per item is not. */
3693
+ image: {
3694
+ domSupported: true,
3695
+ nativeValue: "low",
3696
+ domCost: "medium",
3697
+ canvasCost: "low",
3698
+ autoDefault: "canvas"
3699
+ },
3700
+ /** Particles / danmaku / chart glyphs: bulk stays canvas, unconditionally. */
3701
+ particles: {
3702
+ domSupported: true,
3703
+ nativeValue: "none",
3704
+ domCost: "prohibitive",
3705
+ canvasCost: "low",
3706
+ autoDefault: "canvas"
3707
+ },
3708
+ /** Graph nodes/edges (interactive, moderate count): canvas until measured. */
3709
+ graph: {
3710
+ domSupported: true,
3711
+ nativeValue: "low",
3712
+ domCost: "medium",
3713
+ canvasCost: "medium",
3714
+ autoDefault: "canvas"
3715
+ },
3716
+ /** Custom shader / GPU particles: no DOM representation exists. */
3717
+ shader: {
3718
+ domSupported: false,
3719
+ nativeValue: "none",
3720
+ domCost: "unsupported",
3721
+ canvasCost: "low",
3722
+ autoDefault: "canvas"
3723
+ }
3724
+ };
3725
+ var UNKNOWN_PROJECTION_CAPABILITY = {
3726
+ domSupported: false,
3727
+ nativeValue: "none",
3728
+ domCost: "unsupported",
3729
+ canvasCost: "low",
3730
+ autoDefault: "canvas"
3731
+ };
3732
+ var PROJECTION_AUTO_HYSTERESIS_FRAMES = 3;
3733
+ var PROJECTION_AUTO_DOM_BUDGET = 500;
3734
+ function resolveProjection(want, cap, ctx) {
3735
+ if (want === "canvas") return { resolved: "canvas", reason: null };
3736
+ if (!ctx.hasDOM || !ctx.domBackendMounted) {
3737
+ return { resolved: "canvas", reason: "no-dom-backend" };
3738
+ }
3739
+ if (!cap.domSupported) return { resolved: "canvas", reason: "unsupported-kind" };
3740
+ if (want === "dom") {
3741
+ if (cap.domCost === "prohibitive") return { resolved: "canvas", reason: "prohibitive-cost" };
3742
+ return { resolved: "dom", reason: null };
3743
+ }
3744
+ if (cap.nativeValue === "high" && cap.domCost !== "prohibitive") {
3745
+ return { resolved: "dom", reason: null };
3746
+ }
3747
+ if (cap.canvasCost === "prohibitive" && cap.domSupported) {
3748
+ return { resolved: "dom", reason: null };
3749
+ }
3750
+ if (cap.autoDefault === "dom") return { resolved: "dom", reason: null };
3751
+ return { resolved: "canvas", reason: null };
3752
+ }
3753
+ function hysteresisVote(current, desired, consecutive, hysteresisFrames) {
3754
+ if (current === void 0 || current === desired) {
3755
+ return { resolved: desired, consecutive: 0, flipped: false };
3756
+ }
3757
+ const next = consecutive + 1;
3758
+ if (next >= hysteresisFrames) return { resolved: desired, consecutive: 0, flipped: true };
3759
+ return { resolved: current, consecutive: next, flipped: false };
3760
+ }
3761
+
3505
3762
  // src/tree/Scene.ts
3506
3763
  var RANGE_VALUE_ROLES = /* @__PURE__ */ new Set(["slider", "spinbutton", "progressbar", "scrollbar", "meter"]);
3507
3764
  var INTERACTIVE_A11Y_ROLES = /* @__PURE__ */ new Set([
@@ -3525,7 +3782,8 @@ var KEYBOARD_OWNING_ROLES = /* @__PURE__ */ new Set([
3525
3782
  ]);
3526
3783
  function ownsKeyboard(el) {
3527
3784
  if (!el) return false;
3528
- if (el === document.body || el === document.documentElement) return false;
3785
+ if (typeof document !== "undefined" && (el === document.body || el === document.documentElement))
3786
+ return false;
3529
3787
  if (el.hasAttribute("data-vecto-a11y-root")) return false;
3530
3788
  const tag = el.tagName;
3531
3789
  if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
@@ -3565,6 +3823,7 @@ var SCENE_OPTION_KEYS = [
3565
3823
  "renderer",
3566
3824
  "renderMode",
3567
3825
  "respectReducedMotion",
3826
+ "semanticProjectionPolicy",
3568
3827
  "userTiming"
3569
3828
  ];
3570
3829
  var SCENE_FIELD_NOT_OPTION = {
@@ -3681,9 +3940,9 @@ function parseCssMatrix(transform) {
3681
3940
  }
3682
3941
  function clientToGridLocal(contentEl, canvas, clientX, clientY) {
3683
3942
  const line = contentEl.querySelector("[data-vecto-grid-line]");
3684
- const originMarker = _optionalChain([line, 'optionalAccess', _86 => _86.querySelector, 'call', _87 => _87('[data-vecto-grid-basis="origin"]')]);
3685
- const xMarker = _optionalChain([line, 'optionalAccess', _88 => _88.querySelector, 'call', _89 => _89('[data-vecto-grid-basis="x"]')]);
3686
- const yMarker = _optionalChain([line, 'optionalAccess', _90 => _90.querySelector, 'call', _91 => _91('[data-vecto-grid-basis="y"]')]);
3943
+ const originMarker = _optionalChain([line, 'optionalAccess', _89 => _89.querySelector, 'call', _90 => _90('[data-vecto-grid-basis="origin"]')]);
3944
+ const xMarker = _optionalChain([line, 'optionalAccess', _91 => _91.querySelector, 'call', _92 => _92('[data-vecto-grid-basis="x"]')]);
3945
+ const yMarker = _optionalChain([line, 'optionalAccess', _93 => _93.querySelector, 'call', _94 => _94('[data-vecto-grid-basis="y"]')]);
3687
3946
  if (line && originMarker && xMarker && yMarker) {
3688
3947
  const origin = originMarker.getBoundingClientRect();
3689
3948
  const xPoint = xMarker.getBoundingClientRect();
@@ -3767,7 +4026,7 @@ function nearestTextPositionInLine(line, x, y) {
3767
4026
  nearest = { position: { node, offset }, distance: candidate.distance };
3768
4027
  }
3769
4028
  }
3770
- return _nullishCoalesce(_optionalChain([nearest, 'optionalAccess', _92 => _92.position]), () => ( null));
4029
+ return _nullishCoalesce(_optionalChain([nearest, 'optionalAccess', _95 => _95.position]), () => ( null));
3771
4030
  }
3772
4031
  function nearestTextPositionInProjection(contentEl, canvas, x, y, eventTarget) {
3773
4032
  if (contentEl.dataset.vectoContentGrid !== void 0) {
@@ -3781,7 +4040,7 @@ function nearestTextPositionInProjection(contentEl, canvas, x, y, eventTarget) {
3781
4040
  }
3782
4041
  targetLine = targetLine.parentElement;
3783
4042
  }
3784
- if (_optionalChain([targetLine, 'optionalAccess', _93 => _93.parentElement]) === contentEl) {
4043
+ if (_optionalChain([targetLine, 'optionalAccess', _96 => _96.parentElement]) === contentEl) {
3785
4044
  return nearestTextPositionInLine(targetLine, x, y);
3786
4045
  }
3787
4046
  let bestLine = null;
@@ -3843,13 +4102,13 @@ function extendSelection(selection, anchor, focus) {
3843
4102
  try {
3844
4103
  selection.setBaseAndExtent(anchor.node, anchor.offset, focus.node, focus.offset);
3845
4104
  return;
3846
- } catch (e15) {
4105
+ } catch (e16) {
3847
4106
  }
3848
4107
  try {
3849
4108
  selection.collapse(anchor.node, anchor.offset);
3850
4109
  selection.extend(focus.node, focus.offset);
3851
4110
  return;
3852
- } catch (e16) {
4111
+ } catch (e17) {
3853
4112
  }
3854
4113
  const anchorRange = document.createRange();
3855
4114
  anchorRange.setStart(anchor.node, anchor.offset);
@@ -4043,7 +4302,7 @@ var Scene = (_class15 = class _Scene {
4043
4302
  __init108() {this.forcedColorsChangeHandler = null}
4044
4303
  /** True when the OS asks for reduced motion and we respect it. Read by the animation drivers. */
4045
4304
  get prefersReducedMotion() {
4046
- return this.respectReducedMotion && !!_optionalChain([this, 'access', _94 => _94.reducedMotionQuery, 'optionalAccess', _95 => _95.matches]);
4305
+ return this.respectReducedMotion && !!_optionalChain([this, 'access', _97 => _97.reducedMotionQuery, 'optionalAccess', _98 => _98.matches]);
4047
4306
  }
4048
4307
  /**
4049
4308
  * True when the OS is in a forced-colors mode (Windows High Contrast, and the
@@ -4054,7 +4313,7 @@ var Scene = (_class15 = class _Scene {
4054
4313
  * when the setting toggles.
4055
4314
  */
4056
4315
  get forcedColors() {
4057
- return !!_optionalChain([this, 'access', _96 => _96.forcedColorsQuery, 'optionalAccess', _97 => _97.matches]);
4316
+ return !!_optionalChain([this, 'access', _99 => _99.forcedColorsQuery, 'optionalAccess', _100 => _100.matches]);
4058
4317
  }
4059
4318
  // --- domain: a11y-projection — sync throttle and shadow-DOM state ---
4060
4319
  /**
@@ -4062,21 +4321,41 @@ var Scene = (_class15 = class _Scene {
4062
4321
  * frame. See {@link SceneOptions.a11ySyncInterval}.
4063
4322
  */
4064
4323
  __init109() {this.a11ySyncInterval = 0}
4324
+ /**
4325
+ * Per-node semantic projection policy (RFC3 §5, CTX-0599). Consulted once
4326
+ * per node by {@link shouldProjectA11y}; the default projects everything
4327
+ * the legacy predicate projects, so reassigning nothing changes nothing.
4328
+ * See {@link SceneOptions.semanticProjectionPolicy}.
4329
+ */
4330
+ __init110() {this.semanticProjectionPolicy = DEFAULT_SEMANTIC_PROJECTION_POLICY}
4331
+ /**
4332
+ * Consecutive syncs an `'auto'`-policy node must keep voting for the other
4333
+ * backend before its sticky resolution flips (RFC4 §3 rule 3, CTX-0601).
4334
+ * Documented tunable — see {@link PROJECTION_AUTO_HYSTERESIS_FRAMES}.
4335
+ */
4336
+ __init111() {this.projectionHysteresisFrames = PROJECTION_AUTO_HYSTERESIS_FRAMES}
4337
+ /**
4338
+ * Maximum `'auto'`-resolved DOM residents per scene per frame (RFC4 §4
4339
+ * particle-row backstop: bulk-count nodes stay canvas). Explicit `'dom'`
4340
+ * requests bypass it. Documented tunable — see
4341
+ * {@link PROJECTION_AUTO_DOM_BUDGET}.
4342
+ */
4343
+ __init112() {this.projectionAutoDomBudget = PROJECTION_AUTO_DOM_BUDGET}
4065
4344
  /** Timestamp of the last a11y sync, for throttling. */
4066
- __init110() {this.lastA11ySync = -Infinity}
4345
+ __init113() {this.lastA11ySync = -Infinity}
4067
4346
  /** True if we skipped an a11y sync during animation and need to sync when at rest. */
4068
- __init111() {this.a11yPendingSyncAfterAnimation = false}
4347
+ __init114() {this.a11yPendingSyncAfterAnimation = false}
4069
4348
  // A11y / Automation Layer. `null` in non-DOM (SSR/Node) environments — the
4070
4349
  // whole projection degrades to a no-op so the engine's logic stays usable
4071
4350
  // server-side (e.g. headless layout / vector export) without jsdom.
4072
4351
 
4073
- __init112() {this.a11yElements = /* @__PURE__ */ new Map()}
4352
+ __init115() {this.a11yElements = /* @__PURE__ */ new Map()}
4074
4353
  /** Elements that already have the synthetic Enter/Space activation handler
4075
4354
  * (see the syncA11y refresh pass) — install-once bookkeeping (#694). */
4076
- __init113() {this.keyboardActivatedA11y = /* @__PURE__ */ new WeakSet()}
4355
+ __init116() {this.keyboardActivatedA11y = /* @__PURE__ */ new WeakSet()}
4077
4356
  // --- domain: content-projection — projected DOM, sync state, calibration, selection ---
4078
4357
  /** DOM nodes mirroring static text content, keyed by entity id. */
4079
- __init114() {this.contentElements = /* @__PURE__ */ new Map()}
4358
+ __init117() {this.contentElements = /* @__PURE__ */ new Map()}
4080
4359
  /**
4081
4360
  * What the last completed content-projection sync was built from, per entity.
4082
4361
  *
@@ -4085,9 +4364,9 @@ var Scene = (_class15 = class _Scene {
4085
4364
  * build. Only populated for entities that opt in via
4086
4365
  * {@link Entity.getContentEpoch}. (carryctx CTX-0199)
4087
4366
  */
4088
- __init115() {this.contentSyncState = /* @__PURE__ */ new Map()}
4367
+ __init118() {this.contentSyncState = /* @__PURE__ */ new Map()}
4089
4368
  /** Invalidates grid font calibration after browser font availability changes. */
4090
- __init116() {this.contentFontEpoch = 0}
4369
+ __init119() {this.contentFontEpoch = 0}
4091
4370
  /**
4092
4371
  * Bumped whenever the viewport itself changes shape, which re-tiers blocks
4093
4372
  * without moving any of them.
@@ -4100,7 +4379,7 @@ var Scene = (_class15 = class _Scene {
4100
4379
  * carriers it no longer needs. Scrolling needs no such epoch: it moves the root,
4101
4380
  * so every block's parent world transform changes and the check fails honestly.
4102
4381
  */
4103
- __init117() {this.contentViewportEpoch = 0}
4382
+ __init120() {this.contentViewportEpoch = 0}
4104
4383
  /**
4105
4384
  * One-entry memo for a parent's world transform, held as scalars.
4106
4385
  *
@@ -4115,16 +4394,16 @@ var Scene = (_class15 = class _Scene {
4115
4394
  * sync, so a memo can never survive into a frame in which the parent has
4116
4395
  * moved.
4117
4396
  */
4118
- __init118() {this._pwNode = null}
4119
- __init119() {this._pwSerial = -1}
4120
- __init120() {this._pwa = 1}
4121
- __init121() {this._pwb = 0}
4122
- __init122() {this._pwc = 0}
4123
- __init123() {this._pwd = 1}
4124
- __init124() {this._pwe = 0}
4125
- __init125() {this._pwf = 0}
4397
+ __init121() {this._pwNode = null}
4398
+ __init122() {this._pwSerial = -1}
4399
+ __init123() {this._pwa = 1}
4400
+ __init124() {this._pwb = 0}
4401
+ __init125() {this._pwc = 0}
4402
+ __init126() {this._pwd = 1}
4403
+ __init127() {this._pwe = 0}
4404
+ __init128() {this._pwf = 0}
4126
4405
  /** Incremented once per top-level `syncA11y`, invalidating `_pwNode`. */
4127
- __init126() {this._syncSerial = 0}
4406
+ __init129() {this._syncSerial = 0}
4128
4407
  /**
4129
4408
  * Disables the settled-walk fast path in {@link syncContentProjection}.
4130
4409
  *
@@ -4136,38 +4415,38 @@ var Scene = (_class15 = class _Scene {
4136
4415
  * Not part of the public API and not documented as an option: turning it on only
4137
4416
  * makes a settled document slower, never more correct.
4138
4417
  */
4139
- __init127() {this.disableSettledFastPath = false}
4418
+ __init130() {this.disableSettledFastPath = false}
4140
4419
  /** Cached Canvas-to-client scale for the current font/viewport epoch. */
4141
- __init128() {this.contentMetricScaleEpoch = -1}
4142
- __init129() {this.contentMetricScaleX = 1}
4143
- __init130() {this.contentProjectionEnabled = true}
4420
+ __init131() {this.contentMetricScaleEpoch = -1}
4421
+ __init132() {this.contentMetricScaleX = 1}
4422
+ __init133() {this.contentProjectionEnabled = true}
4144
4423
  // Virtualization margin (px) for the content-projection CARRIER band;
4145
4424
  // `undefined` → one viewport height, resolved at sync time. `Infinity` is
4146
4425
  // unsupported here: it unwindows every carrier, which is O(total glyphs).
4147
- __init131() {this.contentProjectionMargin = void 0}
4426
+ __init134() {this.contentProjectionMargin = void 0}
4148
4427
  // Virtualization margin (px) for the SEMANTIC tier — whether a block has
4149
4428
  // any projected DOM. `undefined` → falls back to contentProjectionMargin, so
4150
4429
  // the default keeps one gate. `Infinity` = every block keeps resident text.
4151
- __init132() {this.contentSemanticMargin = void 0}
4430
+ __init135() {this.contentSemanticMargin = void 0}
4152
4431
  // How many coarse-tier blocks may be MATERIALIZED per sync, spreading the
4153
4432
  // resident tier's document-open cost across frames. `Infinity` = one
4154
4433
  // synchronous pass.
4155
- __init133() {this.contentSemanticBudget = DEFAULT_CONTENT_SEMANTIC_BUDGET}
4434
+ __init136() {this.contentSemanticBudget = DEFAULT_CONTENT_SEMANTIC_BUDGET}
4156
4435
  // Remaining materializations in the CURRENT sync. Reset at the start of each
4157
4436
  // a11y walk; decremented per coarse block that creates its element.
4158
- __init134() {this.contentSemanticBudgetLeft = 0}
4437
+ __init137() {this.contentSemanticBudgetLeft = 0}
4159
4438
  // Set when a sync deferred at least one block, so the scene knows to keep
4160
4439
  // drawing frames until the resident tier is complete. Without it a static
4161
4440
  // scene in `onDemand` mode would stop rendering with the document half
4162
4441
  // materialized and never finish.
4163
- __init135() {this.contentSemanticDeferred = false}
4164
- __init136() {this.contentSelectionEndListener = null}
4442
+ __init138() {this.contentSemanticDeferred = false}
4443
+ __init139() {this.contentSelectionEndListener = null}
4165
4444
  // --- domain: render-scheduler — per-frame walk flags ---
4166
4445
  // Animation/interactive flags collected during the render walk (tree-walk
4167
4446
  // fusion): the loop reads last frame's answers instead of re-walking the
4168
4447
  // tree up to 4× per tick. Start true so the first tick stays conservative.
4169
- __init137() {this.frameHadAnimation = true}
4170
- __init138() {this.frameHadInteractive = true}
4448
+ __init140() {this.frameHadAnimation = true}
4449
+ __init141() {this.frameHadInteractive = true}
4171
4450
  // --- domain: context-and-resize — resize handler, DPR query, canvas observer ---
4172
4451
 
4173
4452
  /** Active `(resolution: Ndppx)` media query watching for a runtime DPR change
@@ -4175,17 +4454,17 @@ var Scene = (_class15 = class _Scene {
4175
4454
  * can be re-scaled — otherwise it stays rasterized at the old DPR and blurs.
4176
4455
  * A resolution media query only fires when leaving its exact value, so the
4177
4456
  * handler re-arms a fresh query for the new DPR each time. */
4178
- __init139() {this.dprMediaQuery = null}
4457
+ __init142() {this.dprMediaQuery = null}
4179
4458
  /** For embedded (`disableWindowResize`) scenes: observes the canvas element so
4180
4459
  * a CSS/layout-driven size change re-runs `resize()`. A window `resize`
4181
4460
  * listener never fires for these (the window isn't what changed), so without
4182
4461
  * this an embedded canvas stayed at its initial size forever. */
4183
- __init140() {this.canvasResizeObserver = null}
4184
- __init141() {this.dprChangeHandler = null}
4185
- __init142() {this.dprPollInterval = null}
4186
- __init143() {this.lastDpr = 1}
4462
+ __init143() {this.canvasResizeObserver = null}
4463
+ __init144() {this.dprChangeHandler = null}
4464
+ __init145() {this.dprPollInterval = null}
4465
+ __init146() {this.lastDpr = 1}
4187
4466
  // --- domain: a11y-projection — focus, overlay geometry, DOM ordering, portals ---
4188
- __init144() {this.focusedA11yElement = null}
4467
+ __init147() {this.focusedA11yElement = null}
4189
4468
  /**
4190
4469
  * Canvas box geometry: the CSS↔logical mapping, overlay layer alignment, and
4191
4470
  * the DPR math (extraction 5, `DEC-0024`).
@@ -4212,7 +4491,7 @@ var Scene = (_class15 = class _Scene {
4212
4491
  /** Shadow elements the pointer is currently inside. Lets a removal that happens
4213
4492
  * mid-hover synthesize the `pointerleave` the browser never sends for a
4214
4493
  * detached element, so the entity doesn't keep its hover state. */
4215
- __init145() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
4494
+ __init148() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
4216
4495
  /**
4217
4496
  * Entity ids the application has pinned via {@link requestA11yProjection}.
4218
4497
  *
@@ -4220,13 +4499,13 @@ var Scene = (_class15 = class _Scene {
4220
4499
  * a stale id simply never matches. Cleared per-entity by
4221
4500
  * {@link releaseA11yProjection}.
4222
4501
  */
4223
- __init146() {this.a11yProjectionRequests = /* @__PURE__ */ new Set()}
4502
+ __init149() {this.a11yProjectionRequests = /* @__PURE__ */ new Set()}
4224
4503
  /** Persistent tabindex=-1 element in a11yRoot. When the focused a11y mirror is
4225
4504
  * pruned (virtualization/streaming/removal) while it holds focus, we move
4226
4505
  * focus here instead of letting the browser drop it to <body> — keeping the
4227
4506
  * screen-reader virtual cursor inside the scene's a11y region. */
4228
- __init147() {this.focusSentinel = null}
4229
- __init148() {this.caretBlinkTimer = null}
4507
+ __init150() {this.focusSentinel = null}
4508
+ __init151() {this.caretBlinkTimer = null}
4230
4509
  /**
4231
4510
  * The projected DOM's ordering engine — the reorder flag, the per-pass scratch
4232
4511
  * collections, the visual reading-order sort and the cursor-based
@@ -4236,7 +4515,7 @@ var Scene = (_class15 = class _Scene {
4236
4515
  * delegate here; the collect-and-prune walk stays behind because it needs
4237
4516
  * {@link shouldProjectA11y} and the focus/caret state.
4238
4517
  */
4239
- __init149() {this.a11yOrder = new A11yProjectionManager()}
4518
+ __init152() {this.a11yOrder = new A11yProjectionManager()}
4240
4519
  /**
4241
4520
  * The content projection's selection preservation and grid calibration
4242
4521
  * (extraction 3, `DEC-0022`).
@@ -4288,11 +4567,35 @@ var Scene = (_class15 = class _Scene {
4288
4567
  set a11yNeedsReorder(value) {
4289
4568
  this.a11yOrder.needsReorder = value;
4290
4569
  }
4291
- __init150() {this.portalRoot = null}
4292
- __init151() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
4293
- __init152() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
4294
- __init153() {this.portalEntities = /* @__PURE__ */ new Map()}
4295
- __init154() {this.renderOrderCounter = 0}
4570
+ __init153() {this.portalRoot = null}
4571
+ __init154() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
4572
+ __init155() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
4573
+ __init156() {this.portalEntities = /* @__PURE__ */ new Map()}
4574
+ /**
4575
+ * Generic projection backends (RFC1 §5 vocabulary, `tree/scene/ProjectionBackend.ts`).
4576
+ * Render-independent: the interface exchanges only node identity, world
4577
+ * matrix, and lifecycle calls, so registering a DOM backend adds no
4578
+ * `HTMLElement` surface to core. `@vectojs/dom` owns the only implementation.
4579
+ */
4580
+ __init157() {this.projectionBackends = []}
4581
+ /** Per-frame seen-sets driving {@link pruneProjectionBackends} (portal-precedent). */
4582
+ __init158() {this.domSeenPrevFrame = /* @__PURE__ */ new Set()}
4583
+ __init159() {this.domSeenThisFrame = /* @__PURE__ */ new Set()}
4584
+ __init160() {this.domSeenNodes = /* @__PURE__ */ new Map()}
4585
+ /**
4586
+ * Negotiation state (RFC4 §3, CTX-0601): last resolution per node (the
4587
+ * per-scene queryable surface behind {@link getProjectionResolutions}),
4588
+ * hysteresis votes, per-kind capability overrides, and explicit gesture
4589
+ * pins. Plain data only — no `HTMLElement` surface in core.
4590
+ */
4591
+ __init161() {this.projectionResolutions = /* @__PURE__ */ new Map()}
4592
+ __init162() {this.projectionHysteresis = /* @__PURE__ */ new Map()}
4593
+ __init163() {this.projectionCapabilityOverrides = /* @__PURE__ */ new Map()}
4594
+ __init164() {this.projectionGesturePins = /* @__PURE__ */ new Map()}
4595
+ /** Frame id the `'auto'` DOM budget count belongs to (bulk backstop). */
4596
+ __init165() {this.projectionBudgetFrame = -1}
4597
+ __init166() {this.projectionDomAutoCount = 0}
4598
+ __init167() {this.renderOrderCounter = 0}
4296
4599
  // --- domain: render-scheduler — authoritative frame counter ---
4297
4600
  /**
4298
4601
  * Monotonic render-frame counter, bumped once per authoritative `render()`
@@ -4302,7 +4605,7 @@ var Scene = (_class15 = class _Scene {
4302
4605
  * back to the ancestor walk. Public for the same reason `Entity._getTrig`/
4303
4606
  * `_setWorldCache` are: it is a cross-class render-internal contract.
4304
4607
  */
4305
- __init155() {this.currentFrame = 0}
4608
+ __init168() {this.currentFrame = 0}
4306
4609
  // --- domain: wasm-backend-facade — EXTRACTED (see scene/WasmBackendFacade.ts) ---
4307
4610
  /**
4308
4611
  * The four invisible WASM accelerators and the resident transform store.
@@ -4372,8 +4675,8 @@ var Scene = (_class15 = class _Scene {
4372
4675
  // Stays on `Scene`: it walks `root` *and* `overlayRoot` for entities the
4373
4676
  // render walk consumes, so only its cache key is store state. The facade owns
4374
4677
  // the key; the walk stays here.
4375
- __init156() {this._computeEntities = []}
4376
- __init157() {this._computeEntitiesVersion = -1}
4678
+ __init169() {this._computeEntities = []}
4679
+ __init170() {this._computeEntitiesVersion = -1}
4377
4680
  /** Invalidate the resident WASM store layout; the next wasm-mode frame rebuilds
4378
4681
  * it. Called by `Entity.add`/`remove` (topology changes only). */
4379
4682
  markStructureChanged() {
@@ -4612,7 +4915,7 @@ var Scene = (_class15 = class _Scene {
4612
4915
  get animBatchedLastFrame() {
4613
4916
  return this._wasmBackend.animBatchedLastFrame;
4614
4917
  }
4615
- __init158() {this.animGate = {
4918
+ __init171() {this.animGate = {
4616
4919
  spring: 128,
4617
4920
  tween: 256,
4618
4921
  mixed: 128
@@ -4708,7 +5011,7 @@ var Scene = (_class15 = class _Scene {
4708
5011
  * `tickDrivers()`'s per-frame stamp would skip it a full frame — a one-frame
4709
5012
  * JS/WASM divergence. Written in {@link render} next to `_tickBatchedDrivers`.
4710
5013
  */
4711
- __init159() {this._updateWalkDt = null}
5014
+ __init172() {this._updateWalkDt = null}
4712
5015
  // --- domain: render-scheduler — batched driver tick (extraction 6), reads the anim backend ---
4713
5016
  /**
4714
5017
  * Advance every registered entity's active drivers for this frame, batching
@@ -4736,25 +5039,25 @@ var Scene = (_class15 = class _Scene {
4736
5039
  * sync, so retaining the order prevents a newly opened overlay from spending
4737
5040
  * its first frame below previously projected controls.
4738
5041
  */
4739
- __init160() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
5042
+ __init173() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
4740
5043
  // --- domain: context-and-resize — GL/GPU canvases, device state, pointer, size warnings ---
4741
5044
  // Optional WebGL point-cloud layer (see SceneOptions.pointBackend).
4742
- __init161() {this.pointRenderer = null}
4743
- __init162() {this.glCanvas = null}
4744
- __init163() {this.glContextLostHandler = null}
4745
- __init164() {this.glContextRestoredHandler = null}
5045
+ __init174() {this.pointRenderer = null}
5046
+ __init175() {this.glCanvas = null}
5047
+ __init176() {this.glContextLostHandler = null}
5048
+ __init177() {this.glContextRestoredHandler = null}
4746
5049
 
4747
5050
 
4748
5051
 
4749
- __init165() {this.disableWindowResize = false}
5052
+ __init178() {this.disableWindowResize = false}
4750
5053
  /** See {@link SceneOptions.maxDPR}. `undefined` = uncapped (real DPR). */
4751
5054
 
4752
5055
  // WebGPU properties
4753
- __init166() {this.destroyed = false}
4754
- __init167() {this.device = null}
4755
- __init168() {this.deviceLost = false}
4756
- __init169() {this.particleBackend = "auto"}
4757
- __init170() {this._webgpuDisabled = false}
5056
+ __init179() {this.destroyed = false}
5057
+ __init180() {this.device = null}
5058
+ __init181() {this.deviceLost = false}
5059
+ __init182() {this.particleBackend = "auto"}
5060
+ __init183() {this._webgpuDisabled = false}
4758
5061
  get webgpuDisabled() {
4759
5062
  return this._webgpuDisabled || this.particleBackend === "cpu";
4760
5063
  }
@@ -4768,7 +5071,7 @@ var Scene = (_class15 = class _Scene {
4768
5071
  * active.
4769
5072
  */
4770
5073
  get webglDrawStats() {
4771
- return _nullishCoalesce(_optionalChain([this, 'access', _98 => _98.pointRenderer, 'optionalAccess', _99 => _99.stats, 'optionalCall', _100 => _100()]), () => ( null));
5074
+ return _nullishCoalesce(_optionalChain([this, 'access', _101 => _101.pointRenderer, 'optionalAccess', _102 => _102.stats, 'optionalCall', _103 => _103()]), () => ( null));
4772
5075
  }
4773
5076
  /**
4774
5077
  * Whether a WebGPU device is currently live for particle compute.
@@ -4782,28 +5085,28 @@ var Scene = (_class15 = class _Scene {
4782
5085
  set webgpuDisabled(value) {
4783
5086
  this._webgpuDisabled = value;
4784
5087
  }
4785
- __init171() {this.recoveryTimerId = null}
4786
- __init172() {this.manager = null}
4787
- __init173() {this.initializingWebGPU = false}
4788
- __init174() {this.gpuCanvas = null}
4789
- __init175() {this.gpuContext = null}
5088
+ __init184() {this.recoveryTimerId = null}
5089
+ __init185() {this.manager = null}
5090
+ __init186() {this.initializingWebGPU = false}
5091
+ __init187() {this.gpuCanvas = null}
5092
+ __init188() {this.gpuContext = null}
4790
5093
  /** True while the GPU canvas holds a presented particle frame (needs clearing when they leave). */
4791
- __init176() {this.gpuHasContent = false}
4792
- __init177() {this.mouseX = -9999}
4793
- __init178() {this.mouseY = -9999}
4794
- __init179() {this.pointerMoveListener = null}
4795
- __init180() {this.pointerLeaveListener = null}
5094
+ __init189() {this.gpuHasContent = false}
5095
+ __init190() {this.mouseX = -9999}
5096
+ __init191() {this.mouseY = -9999}
5097
+ __init192() {this.pointerMoveListener = null}
5098
+ __init193() {this.pointerLeaveListener = null}
4796
5099
  /** Element the pointer listeners are bound to (parent container if present,
4797
5100
  * else the canvas). Stored so `destroy()` detaches from the same element. */
4798
- __init181() {this.pointerEventTarget = null}
5101
+ __init194() {this.pointerEventTarget = null}
4799
5102
  // --- scene-level keyboard channel state (see on/off/registerShortcut) ---
4800
- __init182() {this.keydownHandlers = []}
4801
- __init183() {this.keyupHandlers = []}
5103
+ __init195() {this.keydownHandlers = []}
5104
+ __init196() {this.keyupHandlers = []}
4802
5105
  /** Chord-normalized shortcut table, matched on keydown only. */
4803
- __init184() {this.shortcuts = []}
4804
- __init185() {this.windowKeyDownHandler = null}
4805
- __init186() {this.windowKeyUpHandler = null}
4806
- __init187() {this.hasWarnedZeroSize = false}
5106
+ __init197() {this.shortcuts = []}
5107
+ __init198() {this.windowKeyDownHandler = null}
5108
+ __init199() {this.windowKeyUpHandler = null}
5109
+ __init200() {this.hasWarnedZeroSize = false}
4807
5110
  /**
4808
5111
  * Latch for {@link Scene.resize}'s invalid-dimension warning.
4809
5112
  *
@@ -4813,8 +5116,8 @@ var Scene = (_class15 = class _Scene {
4813
5116
  * because `resize()` is commonly driven from a `ResizeObserver`, which would
4814
5117
  * otherwise warn on every frame of a drag.
4815
5118
  */
4816
- __init188() {this.hasWarnedInvalidResize = false}
4817
- __init189() {this.fontLoadHandler = null}
5119
+ __init201() {this.hasWarnedInvalidResize = false}
5120
+ __init202() {this.fontLoadHandler = null}
4818
5121
  // --- domain: scene-facade — dev-mode diagnostics ---
4819
5122
  // ── Dev-mode warning infrastructure ──────────────────────────────
4820
5123
  //
@@ -4844,12 +5147,12 @@ var Scene = (_class15 = class _Scene {
4844
5147
  static _devModeDetected() {
4845
5148
  if (_Scene._devMode) return true;
4846
5149
  const gp = typeof globalThis !== "undefined" ? globalThis : void 0;
4847
- if (_optionalChain([gp, 'optionalAccess', _101 => _101.__DEV__])) return true;
4848
- if (_optionalChain([gp, 'optionalAccess', _102 => _102.process, 'optionalAccess', _103 => _103.env, 'optionalAccess', _104 => _104.NODE_ENV]) === "development") return true;
5150
+ if (_optionalChain([gp, 'optionalAccess', _104 => _104.__DEV__])) return true;
5151
+ if (_optionalChain([gp, 'optionalAccess', _105 => _105.process, 'optionalAccess', _106 => _106.env, 'optionalAccess', _107 => _107.NODE_ENV]) === "development") return true;
4849
5152
  return false;
4850
5153
  }
4851
5154
 
4852
- __init190() {this._devFrameCount = 0}
5155
+ __init203() {this._devFrameCount = 0}
4853
5156
  _devWarn(message) {
4854
5157
  if (!this._devActive) return;
4855
5158
  console.warn(`[vectojs/dev] ${message}`);
@@ -4907,9 +5210,9 @@ var Scene = (_class15 = class _Scene {
4907
5210
  let checked = 0;
4908
5211
  const walkProjections = (node) => {
4909
5212
  if (checked > 10) return;
4910
- const proj = _optionalChain([node, 'access', _105 => _105.getContentProjection, 'optionalCall', _106 => _106()]);
4911
- if (_optionalChain([proj, 'optionalAccess', _107 => _107.text]) && proj.selectable !== false) {
4912
- const el = _optionalChain([this, 'access', _108 => _108.contentElements, 'optionalAccess', _109 => _109.get, 'call', _110 => _110(node.id)]);
5213
+ const proj = _optionalChain([node, 'access', _108 => _108.getContentProjection, 'optionalCall', _109 => _109()]);
5214
+ if (_optionalChain([proj, 'optionalAccess', _110 => _110.text]) && proj.selectable !== false) {
5215
+ const el = _optionalChain([this, 'access', _111 => _111.contentElements, 'optionalAccess', _112 => _112.get, 'call', _113 => _113(node.id)]);
4913
5216
  if (el) {
4914
5217
  const projectedText = el.textContent || "";
4915
5218
  const windowed = el.dataset.vectoProjectionWindow !== void 0;
@@ -4927,14 +5230,14 @@ var Scene = (_class15 = class _Scene {
4927
5230
  walkProjections(this.root);
4928
5231
  }
4929
5232
  // --- domain: scene-facade — construction (wires every collaborator) ---
4930
- constructor(canvas, options = {}) {;_class15.prototype.__init88.call(this);_class15.prototype.__init89.call(this);_class15.prototype.__init90.call(this);_class15.prototype.__init91.call(this);_class15.prototype.__init92.call(this);_class15.prototype.__init93.call(this);_class15.prototype.__init94.call(this);_class15.prototype.__init95.call(this);_class15.prototype.__init96.call(this);_class15.prototype.__init97.call(this);_class15.prototype.__init98.call(this);_class15.prototype.__init99.call(this);_class15.prototype.__init100.call(this);_class15.prototype.__init101.call(this);_class15.prototype.__init102.call(this);_class15.prototype.__init103.call(this);_class15.prototype.__init104.call(this);_class15.prototype.__init105.call(this);_class15.prototype.__init106.call(this);_class15.prototype.__init107.call(this);_class15.prototype.__init108.call(this);_class15.prototype.__init109.call(this);_class15.prototype.__init110.call(this);_class15.prototype.__init111.call(this);_class15.prototype.__init112.call(this);_class15.prototype.__init113.call(this);_class15.prototype.__init114.call(this);_class15.prototype.__init115.call(this);_class15.prototype.__init116.call(this);_class15.prototype.__init117.call(this);_class15.prototype.__init118.call(this);_class15.prototype.__init119.call(this);_class15.prototype.__init120.call(this);_class15.prototype.__init121.call(this);_class15.prototype.__init122.call(this);_class15.prototype.__init123.call(this);_class15.prototype.__init124.call(this);_class15.prototype.__init125.call(this);_class15.prototype.__init126.call(this);_class15.prototype.__init127.call(this);_class15.prototype.__init128.call(this);_class15.prototype.__init129.call(this);_class15.prototype.__init130.call(this);_class15.prototype.__init131.call(this);_class15.prototype.__init132.call(this);_class15.prototype.__init133.call(this);_class15.prototype.__init134.call(this);_class15.prototype.__init135.call(this);_class15.prototype.__init136.call(this);_class15.prototype.__init137.call(this);_class15.prototype.__init138.call(this);_class15.prototype.__init139.call(this);_class15.prototype.__init140.call(this);_class15.prototype.__init141.call(this);_class15.prototype.__init142.call(this);_class15.prototype.__init143.call(this);_class15.prototype.__init144.call(this);_class15.prototype.__init145.call(this);_class15.prototype.__init146.call(this);_class15.prototype.__init147.call(this);_class15.prototype.__init148.call(this);_class15.prototype.__init149.call(this);_class15.prototype.__init150.call(this);_class15.prototype.__init151.call(this);_class15.prototype.__init152.call(this);_class15.prototype.__init153.call(this);_class15.prototype.__init154.call(this);_class15.prototype.__init155.call(this);_class15.prototype.__init156.call(this);_class15.prototype.__init157.call(this);_class15.prototype.__init158.call(this);_class15.prototype.__init159.call(this);_class15.prototype.__init160.call(this);_class15.prototype.__init161.call(this);_class15.prototype.__init162.call(this);_class15.prototype.__init163.call(this);_class15.prototype.__init164.call(this);_class15.prototype.__init165.call(this);_class15.prototype.__init166.call(this);_class15.prototype.__init167.call(this);_class15.prototype.__init168.call(this);_class15.prototype.__init169.call(this);_class15.prototype.__init170.call(this);_class15.prototype.__init171.call(this);_class15.prototype.__init172.call(this);_class15.prototype.__init173.call(this);_class15.prototype.__init174.call(this);_class15.prototype.__init175.call(this);_class15.prototype.__init176.call(this);_class15.prototype.__init177.call(this);_class15.prototype.__init178.call(this);_class15.prototype.__init179.call(this);_class15.prototype.__init180.call(this);_class15.prototype.__init181.call(this);_class15.prototype.__init182.call(this);_class15.prototype.__init183.call(this);_class15.prototype.__init184.call(this);_class15.prototype.__init185.call(this);_class15.prototype.__init186.call(this);_class15.prototype.__init187.call(this);_class15.prototype.__init188.call(this);_class15.prototype.__init189.call(this);_class15.prototype.__init190.call(this);
5233
+ constructor(canvas, options = {}) {;_class15.prototype.__init88.call(this);_class15.prototype.__init89.call(this);_class15.prototype.__init90.call(this);_class15.prototype.__init91.call(this);_class15.prototype.__init92.call(this);_class15.prototype.__init93.call(this);_class15.prototype.__init94.call(this);_class15.prototype.__init95.call(this);_class15.prototype.__init96.call(this);_class15.prototype.__init97.call(this);_class15.prototype.__init98.call(this);_class15.prototype.__init99.call(this);_class15.prototype.__init100.call(this);_class15.prototype.__init101.call(this);_class15.prototype.__init102.call(this);_class15.prototype.__init103.call(this);_class15.prototype.__init104.call(this);_class15.prototype.__init105.call(this);_class15.prototype.__init106.call(this);_class15.prototype.__init107.call(this);_class15.prototype.__init108.call(this);_class15.prototype.__init109.call(this);_class15.prototype.__init110.call(this);_class15.prototype.__init111.call(this);_class15.prototype.__init112.call(this);_class15.prototype.__init113.call(this);_class15.prototype.__init114.call(this);_class15.prototype.__init115.call(this);_class15.prototype.__init116.call(this);_class15.prototype.__init117.call(this);_class15.prototype.__init118.call(this);_class15.prototype.__init119.call(this);_class15.prototype.__init120.call(this);_class15.prototype.__init121.call(this);_class15.prototype.__init122.call(this);_class15.prototype.__init123.call(this);_class15.prototype.__init124.call(this);_class15.prototype.__init125.call(this);_class15.prototype.__init126.call(this);_class15.prototype.__init127.call(this);_class15.prototype.__init128.call(this);_class15.prototype.__init129.call(this);_class15.prototype.__init130.call(this);_class15.prototype.__init131.call(this);_class15.prototype.__init132.call(this);_class15.prototype.__init133.call(this);_class15.prototype.__init134.call(this);_class15.prototype.__init135.call(this);_class15.prototype.__init136.call(this);_class15.prototype.__init137.call(this);_class15.prototype.__init138.call(this);_class15.prototype.__init139.call(this);_class15.prototype.__init140.call(this);_class15.prototype.__init141.call(this);_class15.prototype.__init142.call(this);_class15.prototype.__init143.call(this);_class15.prototype.__init144.call(this);_class15.prototype.__init145.call(this);_class15.prototype.__init146.call(this);_class15.prototype.__init147.call(this);_class15.prototype.__init148.call(this);_class15.prototype.__init149.call(this);_class15.prototype.__init150.call(this);_class15.prototype.__init151.call(this);_class15.prototype.__init152.call(this);_class15.prototype.__init153.call(this);_class15.prototype.__init154.call(this);_class15.prototype.__init155.call(this);_class15.prototype.__init156.call(this);_class15.prototype.__init157.call(this);_class15.prototype.__init158.call(this);_class15.prototype.__init159.call(this);_class15.prototype.__init160.call(this);_class15.prototype.__init161.call(this);_class15.prototype.__init162.call(this);_class15.prototype.__init163.call(this);_class15.prototype.__init164.call(this);_class15.prototype.__init165.call(this);_class15.prototype.__init166.call(this);_class15.prototype.__init167.call(this);_class15.prototype.__init168.call(this);_class15.prototype.__init169.call(this);_class15.prototype.__init170.call(this);_class15.prototype.__init171.call(this);_class15.prototype.__init172.call(this);_class15.prototype.__init173.call(this);_class15.prototype.__init174.call(this);_class15.prototype.__init175.call(this);_class15.prototype.__init176.call(this);_class15.prototype.__init177.call(this);_class15.prototype.__init178.call(this);_class15.prototype.__init179.call(this);_class15.prototype.__init180.call(this);_class15.prototype.__init181.call(this);_class15.prototype.__init182.call(this);_class15.prototype.__init183.call(this);_class15.prototype.__init184.call(this);_class15.prototype.__init185.call(this);_class15.prototype.__init186.call(this);_class15.prototype.__init187.call(this);_class15.prototype.__init188.call(this);_class15.prototype.__init189.call(this);_class15.prototype.__init190.call(this);_class15.prototype.__init191.call(this);_class15.prototype.__init192.call(this);_class15.prototype.__init193.call(this);_class15.prototype.__init194.call(this);_class15.prototype.__init195.call(this);_class15.prototype.__init196.call(this);_class15.prototype.__init197.call(this);_class15.prototype.__init198.call(this);_class15.prototype.__init199.call(this);_class15.prototype.__init200.call(this);_class15.prototype.__init201.call(this);_class15.prototype.__init202.call(this);_class15.prototype.__init203.call(this);
4931
5234
  this.canvas = canvas;
4932
5235
  this.debugA11y = _nullishCoalesce(options.debugA11y, () => ( false));
4933
5236
  this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
4934
5237
  this.maxDPR = options.maxDPR;
4935
5238
  if (this.disableWindowResize) {
4936
- const styleWidth = parseInlinePx(_optionalChain([canvas, 'access', _111 => _111.style, 'optionalAccess', _112 => _112.width]));
4937
- const styleHeight = parseInlinePx(_optionalChain([canvas, 'access', _113 => _113.style, 'optionalAccess', _114 => _114.height]));
5239
+ const styleWidth = parseInlinePx(_optionalChain([canvas, 'access', _114 => _114.style, 'optionalAccess', _115 => _115.width]));
5240
+ const styleHeight = parseInlinePx(_optionalChain([canvas, 'access', _116 => _116.style, 'optionalAccess', _117 => _117.height]));
4938
5241
  this.width = _nullishCoalesce(styleWidth, () => ( (canvas.width || canvas.clientWidth || 0)));
4939
5242
  this.height = _nullishCoalesce(styleHeight, () => ( (canvas.height || canvas.clientHeight || 0)));
4940
5243
  } else {
@@ -4947,7 +5250,7 @@ var Scene = (_class15 = class _Scene {
4947
5250
  }
4948
5251
  }
4949
5252
  const globalProcess = typeof globalThis !== "undefined" ? globalThis.process : void 0;
4950
- const isTest = globalProcess && (_optionalChain([globalProcess, 'access', _115 => _115.env, 'optionalAccess', _116 => _116.NODE_ENV]) === "test" || _optionalChain([globalProcess, 'access', _117 => _117.env, 'optionalAccess', _118 => _118.VITEST]) === "true");
5253
+ const isTest = globalProcess && (_optionalChain([globalProcess, 'access', _118 => _118.env, 'optionalAccess', _119 => _119.NODE_ENV]) === "test" || _optionalChain([globalProcess, 'access', _120 => _120.env, 'optionalAccess', _121 => _121.VITEST]) === "true");
4951
5254
  this.maxFPS = _nullishCoalesce(options.maxFPS, () => ( (isTest ? 0 : 60)));
4952
5255
  this.respectReducedMotion = _nullishCoalesce(options.respectReducedMotion, () => ( true));
4953
5256
  this.autoThrottle = _nullishCoalesce(options.autoThrottle, () => ( true));
@@ -4955,6 +5258,9 @@ var Scene = (_class15 = class _Scene {
4955
5258
  this.phases.userTiming = _nullishCoalesce(options.userTiming, () => ( false));
4956
5259
  this.particleBackend = _nullishCoalesce(options.particleBackend, () => ( "auto"));
4957
5260
  this.a11ySyncInterval = _nullishCoalesce(options.a11ySyncInterval, () => ( 0));
5261
+ this.semanticProjectionPolicy = _nullishCoalesce(options.semanticProjectionPolicy, () => ( DEFAULT_SEMANTIC_PROJECTION_POLICY));
5262
+ this.projectionHysteresisFrames = _nullishCoalesce(options.projectionHysteresisFrames, () => ( PROJECTION_AUTO_HYSTERESIS_FRAMES));
5263
+ this.projectionAutoDomBudget = _nullishCoalesce(options.projectionAutoDomBudget, () => ( PROJECTION_AUTO_DOM_BUDGET));
4958
5264
  this.contentProjectionEnabled = _nullishCoalesce(options.contentProjection, () => ( true));
4959
5265
  this.contentProjectionMargin = options.contentProjectionMargin;
4960
5266
  this.contentSemanticMargin = options.contentSemanticMargin;
@@ -4968,9 +5274,9 @@ var Scene = (_class15 = class _Scene {
4968
5274
  if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
4969
5275
  this.forcedColorsQuery = window.matchMedia("(forced-colors: active)");
4970
5276
  this.forcedColorsChangeHandler = () => this.markDirty();
4971
- _optionalChain([this, 'access', _119 => _119.forcedColorsQuery, 'access', _120 => _120.addEventListener, 'optionalCall', _121 => _121("change", this.forcedColorsChangeHandler)]);
5277
+ _optionalChain([this, 'access', _122 => _122.forcedColorsQuery, 'access', _123 => _123.addEventListener, 'optionalCall', _124 => _124("change", this.forcedColorsChangeHandler)]);
4972
5278
  }
4973
- this.root = new class RootEntity extends _chunkPOPXUPKRjs.Entity {
5279
+ this.root = new class RootEntity extends _chunkHC5EZOZJjs.Entity {
4974
5280
  isPointInside() {
4975
5281
  return false;
4976
5282
  }
@@ -4980,7 +5286,7 @@ var Scene = (_class15 = class _Scene {
4980
5286
  }("root");
4981
5287
  this.root._scene = this;
4982
5288
  this._wasmBackend = new WasmBackendFacade(this.root);
4983
- this.overlayRoot = new class OverlayRoot extends _chunkPOPXUPKRjs.Entity {
5289
+ this.overlayRoot = new class OverlayRoot extends _chunkHC5EZOZJjs.Entity {
4984
5290
  isPointInside() {
4985
5291
  return false;
4986
5292
  }
@@ -4999,9 +5305,9 @@ var Scene = (_class15 = class _Scene {
4999
5305
  this.maxDPR
5000
5306
  );
5001
5307
  }
5002
- _optionalChain([this, 'access', _122 => _122.renderer, 'access', _123 => _123.onContextRestored, 'optionalCall', _124 => _124(() => {
5308
+ _optionalChain([this, 'access', _125 => _125.renderer, 'access', _126 => _126.onContextRestored, 'optionalCall', _127 => _127(() => {
5003
5309
  this.markDirty();
5004
- if (_optionalChain([this, 'access', _125 => _125.renderer, 'access', _126 => _126.isContextLost, 'optionalCall', _127 => _127()]) !== true) this.render(this.renderer);
5310
+ if (_optionalChain([this, 'access', _128 => _128.renderer, 'access', _129 => _129.isContextLost, 'optionalCall', _130 => _130()]) !== true) this.render(this.renderer);
5005
5311
  })]);
5006
5312
  if (typeof document !== "undefined") {
5007
5313
  this.a11yRoot = document.createElement("div");
@@ -5174,7 +5480,7 @@ var Scene = (_class15 = class _Scene {
5174
5480
  watchDevicePixelRatio() {
5175
5481
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
5176
5482
  if (this.dprMediaQuery && this.dprChangeHandler) {
5177
- _optionalChain([this, 'access', _128 => _128.dprMediaQuery, 'access', _129 => _129.removeEventListener, 'optionalCall', _130 => _130("change", this.dprChangeHandler)]);
5483
+ _optionalChain([this, 'access', _131 => _131.dprMediaQuery, 'access', _132 => _132.removeEventListener, 'optionalCall', _133 => _133("change", this.dprChangeHandler)]);
5178
5484
  }
5179
5485
  const raw = window.devicePixelRatio;
5180
5486
  const dpr = Number.isFinite(raw) && raw > 0 ? raw : 1;
@@ -5193,13 +5499,13 @@ var Scene = (_class15 = class _Scene {
5193
5499
  console.warn("[VectoJS] DPR resize failed", err);
5194
5500
  }
5195
5501
  try {
5196
- if (_optionalChain([this, 'access', _131 => _131.renderer, 'access', _132 => _132.isContextLost, 'optionalCall', _133 => _133()]) !== true) this.render(this.renderer);
5502
+ if (_optionalChain([this, 'access', _134 => _134.renderer, 'access', _135 => _135.isContextLost, 'optionalCall', _136 => _136()]) !== true) this.render(this.renderer);
5197
5503
  } catch (err) {
5198
5504
  console.warn("[VectoJS] DPR repaint failed", err);
5199
5505
  }
5200
5506
  this.watchDevicePixelRatio();
5201
5507
  };
5202
- _optionalChain([query, 'access', _134 => _134.addEventListener, 'optionalCall', _135 => _135("change", handler)]);
5508
+ _optionalChain([query, 'access', _137 => _137.addEventListener, 'optionalCall', _138 => _138("change", handler)]);
5203
5509
  this.dprMediaQuery = query;
5204
5510
  this.dprChangeHandler = handler;
5205
5511
  this.lastDpr = dpr;
@@ -5215,7 +5521,7 @@ var Scene = (_class15 = class _Scene {
5215
5521
  console.warn("[VectoJS] DPR poll resize failed", err);
5216
5522
  }
5217
5523
  try {
5218
- if (_optionalChain([this, 'access', _136 => _136.renderer, 'access', _137 => _137.isContextLost, 'optionalCall', _138 => _138()]) !== true) this.render(this.renderer);
5524
+ if (_optionalChain([this, 'access', _139 => _139.renderer, 'access', _140 => _140.isContextLost, 'optionalCall', _141 => _141()]) !== true) this.render(this.renderer);
5219
5525
  } catch (err) {
5220
5526
  console.warn("[VectoJS] DPR poll repaint failed", err);
5221
5527
  }
@@ -5239,7 +5545,7 @@ var Scene = (_class15 = class _Scene {
5239
5545
  if (typeof gl.addEventListener !== "function") return;
5240
5546
  this.glContextLostHandler = (e) => {
5241
5547
  e.preventDefault();
5242
- _optionalChain([this, 'access', _139 => _139.pointRenderer, 'optionalAccess', _140 => _140.destroy, 'call', _141 => _141()]);
5548
+ _optionalChain([this, 'access', _142 => _142.pointRenderer, 'optionalAccess', _143 => _143.destroy, 'call', _144 => _144()]);
5243
5549
  this.pointRenderer = null;
5244
5550
  };
5245
5551
  this.glContextRestoredHandler = () => {
@@ -5273,7 +5579,7 @@ var Scene = (_class15 = class _Scene {
5273
5579
  if (this.width > 0 && this.height > 0) return;
5274
5580
  if (typeof ResizeObserver === "undefined") return;
5275
5581
  const observer = new ResizeObserver((entries) => {
5276
- const box = _optionalChain([entries, 'access', _142 => _142[0], 'optionalAccess', _143 => _143.contentRect]);
5582
+ const box = _optionalChain([entries, 'access', _145 => _145[0], 'optionalAccess', _146 => _146.contentRect]);
5277
5583
  if (!box || !(box.width > 0) || !(box.height > 0)) return;
5278
5584
  observer.disconnect();
5279
5585
  this.canvasResizeObserver = null;
@@ -5306,6 +5612,37 @@ var Scene = (_class15 = class _Scene {
5306
5612
  findEntityAt(x, y) {
5307
5613
  return this._hitTester.findEntityAt(x, y, this.currentFrame, this.width, this.height);
5308
5614
  }
5615
+ // --- domain: hit-test — merged HitResult query (RFC5 §2, CTX-0600) ---
5616
+ /**
5617
+ * The merged hit list for a scene-space point: the canvas spatial test
5618
+ * plus caller-observed DOM-native candidates (mirror / portal /
5619
+ * `dom-visual` extension point for CTX-0598) as ONE ordered candidate
5620
+ * list (`HitResult`, overlay order authoritative).
5621
+ *
5622
+ * Query API alongside {@link findEntityAt} — no dispatch change:
5623
+ * `findEntityAt` keeps its single topmost answer byte-for-byte, keyboard
5624
+ * and AT flows are untouched. For browser pointer coordinates use
5625
+ * {@link findHitsAtClient}, which maps through {@link clientToScene} first.
5626
+ */
5627
+ findHitsAt(x, y, domCandidates = []) {
5628
+ return this._hitTester.findHitsAt(
5629
+ x,
5630
+ y,
5631
+ this.currentFrame,
5632
+ this.width,
5633
+ this.height,
5634
+ domCandidates
5635
+ );
5636
+ }
5637
+ /**
5638
+ * {@link findHitsAt} for browser viewport coordinates: maps through
5639
+ * {@link clientToScene} so coordinates enter in scene space (RFC5 §2
5640
+ * rule 3) and backend attribution follows.
5641
+ */
5642
+ findHitsAtClient(clientX, clientY, domCandidates = []) {
5643
+ const point = this.clientToScene(clientX, clientY);
5644
+ return this.findHitsAt(point.x, point.y, domCandidates);
5645
+ }
5309
5646
  // --- domain: hit-test — client-to-scene mapping ---
5310
5647
  /** Convert browser viewport coordinates into this Scene's logical coordinates. */
5311
5648
  clientToScene(clientX, clientY) {
@@ -5346,6 +5683,15 @@ var Scene = (_class15 = class _Scene {
5346
5683
  this.activePortalsThisFrame.delete(node.id);
5347
5684
  this.activePortalsPrevFrame.delete(node.id);
5348
5685
  }
5686
+ if (node.domPolicy === "dom" || node.domResident) {
5687
+ for (const backend of this.projectionBackends) backend.unmount(node);
5688
+ this.domSeenThisFrame.delete(node.id);
5689
+ this.domSeenPrevFrame.delete(node.id);
5690
+ this.domSeenNodes.delete(node.id);
5691
+ }
5692
+ this.projectionResolutions.delete(node.id);
5693
+ this.projectionHysteresis.delete(node.id);
5694
+ this.projectionGesturePins.delete(node.id);
5349
5695
  const contentEl = this.contentElements.get(node.id);
5350
5696
  if (contentEl) {
5351
5697
  this._contentProjection.clearGridState(node.id, contentEl);
@@ -5365,7 +5711,7 @@ var Scene = (_class15 = class _Scene {
5365
5711
  }
5366
5712
  if (this.hoveredA11yElements.has(el)) {
5367
5713
  this.hoveredA11yElements.delete(el);
5368
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("pointerleave", node, void 0, false));
5714
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("pointerleave", node, void 0, false));
5369
5715
  }
5370
5716
  this.preserveFocusOnRemoval(el);
5371
5717
  el.remove();
@@ -5465,7 +5811,7 @@ var Scene = (_class15 = class _Scene {
5465
5811
  this.canvasResizeObserver = null;
5466
5812
  }
5467
5813
  if (this.dprMediaQuery && this.dprChangeHandler) {
5468
- _optionalChain([this, 'access', _144 => _144.dprMediaQuery, 'access', _145 => _145.removeEventListener, 'optionalCall', _146 => _146("change", this.dprChangeHandler)]);
5814
+ _optionalChain([this, 'access', _147 => _147.dprMediaQuery, 'access', _148 => _148.removeEventListener, 'optionalCall', _149 => _149("change", this.dprChangeHandler)]);
5469
5815
  this.dprMediaQuery = null;
5470
5816
  this.dprChangeHandler = null;
5471
5817
  }
@@ -5474,7 +5820,7 @@ var Scene = (_class15 = class _Scene {
5474
5820
  this.dprPollInterval = null;
5475
5821
  }
5476
5822
  if (this.forcedColorsQuery && this.forcedColorsChangeHandler) {
5477
- _optionalChain([this, 'access', _147 => _147.forcedColorsQuery, 'access', _148 => _148.removeEventListener, 'optionalCall', _149 => _149("change", this.forcedColorsChangeHandler)]);
5823
+ _optionalChain([this, 'access', _150 => _150.forcedColorsQuery, 'access', _151 => _151.removeEventListener, 'optionalCall', _152 => _152("change", this.forcedColorsChangeHandler)]);
5478
5824
  this.forcedColorsQuery = null;
5479
5825
  this.forcedColorsChangeHandler = null;
5480
5826
  }
@@ -5505,9 +5851,9 @@ var Scene = (_class15 = class _Scene {
5505
5851
  }
5506
5852
  this.pointerEventTarget = null;
5507
5853
  }
5508
- _optionalChain([this, 'access', _150 => _150.a11yRoot, 'optionalAccess', _151 => _151.remove, 'call', _152 => _152()]);
5854
+ _optionalChain([this, 'access', _153 => _153.a11yRoot, 'optionalAccess', _154 => _154.remove, 'call', _155 => _155()]);
5509
5855
  this.focusSentinel = null;
5510
- _optionalChain([this, 'access', _153 => _153.portalRoot, 'optionalAccess', _154 => _154.remove, 'call', _155 => _155()]);
5856
+ _optionalChain([this, 'access', _156 => _156.portalRoot, 'optionalAccess', _157 => _157.remove, 'call', _158 => _158()]);
5511
5857
  this.a11yElements.clear();
5512
5858
  for (const el of this.contentElements.values()) el.remove();
5513
5859
  this.contentElements.clear();
@@ -5523,10 +5869,10 @@ var Scene = (_class15 = class _Scene {
5523
5869
  }
5524
5870
  this.glContextLostHandler = null;
5525
5871
  this.glContextRestoredHandler = null;
5526
- _optionalChain([this, 'access', _156 => _156.pointRenderer, 'optionalAccess', _157 => _157.destroy, 'call', _158 => _158()]);
5527
- _optionalChain([this, 'access', _159 => _159.renderer, 'access', _160 => _160.dispose, 'optionalCall', _161 => _161()]);
5528
- _optionalChain([this, 'access', _162 => _162.glCanvas, 'optionalAccess', _163 => _163.remove, 'call', _164 => _164()]);
5529
- _optionalChain([this, 'access', _165 => _165.gpuCanvas, 'optionalAccess', _166 => _166.remove, 'call', _167 => _167()]);
5872
+ _optionalChain([this, 'access', _159 => _159.pointRenderer, 'optionalAccess', _160 => _160.destroy, 'call', _161 => _161()]);
5873
+ _optionalChain([this, 'access', _162 => _162.renderer, 'access', _163 => _163.dispose, 'optionalCall', _164 => _164()]);
5874
+ _optionalChain([this, 'access', _165 => _165.glCanvas, 'optionalAccess', _166 => _166.remove, 'call', _167 => _167()]);
5875
+ _optionalChain([this, 'access', _168 => _168.gpuCanvas, 'optionalAccess', _169 => _169.remove, 'call', _170 => _170()]);
5530
5876
  this.gpuCanvas = null;
5531
5877
  this.gpuContext = null;
5532
5878
  if (this.recoveryTimerId) {
@@ -5538,7 +5884,7 @@ var Scene = (_class15 = class _Scene {
5538
5884
  this.manager = null;
5539
5885
  }
5540
5886
  if (this.device) {
5541
- _optionalChain([this, 'access', _168 => _168.device, 'access', _169 => _169.destroy, 'optionalCall', _170 => _170()]);
5887
+ _optionalChain([this, 'access', _171 => _171.device, 'access', _172 => _172.destroy, 'optionalCall', _173 => _173()]);
5542
5888
  this.device = null;
5543
5889
  }
5544
5890
  }
@@ -5955,14 +6301,47 @@ var Scene = (_class15 = class _Scene {
5955
6301
  shouldProjectA11y(node) {
5956
6302
  if (!node.interactive) return false;
5957
6303
  if (!(node.width > 0 || node.a11yFullViewport)) return false;
6304
+ if (node.domPolicy === "dom" || this.resolveProjectionFor(node) === "dom") return false;
5958
6305
  switch (node.a11yProjection) {
5959
6306
  case "never":
5960
6307
  return false;
5961
6308
  case "onDemand":
5962
- return this.a11yEngaged(node);
6309
+ return this.a11yEngaged(node) && this.resolveSemanticProjection(node);
5963
6310
  default:
5964
- return true;
6311
+ return this.resolveSemanticProjection(node);
6312
+ }
6313
+ }
6314
+ /**
6315
+ * Policy half of {@link shouldProjectA11y} (RFC3 §5, CTX-0599).
6316
+ *
6317
+ * The legacy gates above (interactive, box, `a11yProjection` engagement)
6318
+ * are unchanged; this only maps a {@link SemanticProjectionPolicy} decision
6319
+ * onto project/suppress. Out of the box the default policy returns
6320
+ * `'project'`, so behaviour is identical to having no policy.
6321
+ *
6322
+ * `'defer-to-browser'` always falls back to projection today: no deferral
6323
+ * backend exists (`supportsHTMLInCanvas()` is `false`), so deferring would
6324
+ * silently drop semantics — the exact failure RFC §3 rules out. When a
6325
+ * backend lands, only allow-listed plain display text
6326
+ * ({@link isDeferrableSemanticNode}; never controls) may actually defer. A
6327
+ * throwing policy likewise falls back to projection: a policy must never be
6328
+ * able to drop semantics by accident.
6329
+ */
6330
+ resolveSemanticProjection(node) {
6331
+ const policy = _nullishCoalesce(this.semanticProjectionPolicy, () => ( DEFAULT_SEMANTIC_PROJECTION_POLICY));
6332
+ let decision;
6333
+ try {
6334
+ decision = policy.choose(node, getSemanticProjectionCapabilities(), {
6335
+ hasDOM: this.a11yRoot !== null
6336
+ });
6337
+ } catch (e18) {
6338
+ decision = "project";
6339
+ }
6340
+ if (decision === "never") return false;
6341
+ if (decision === "defer-to-browser" && isDeferrableSemanticNode(node) && supportsHTMLInCanvas()) {
6342
+ return false;
5965
6343
  }
6344
+ return true;
5966
6345
  }
5967
6346
  /**
5968
6347
  * Whether an `a11yProjection: 'onDemand'` entity is currently engaged enough to
@@ -5991,7 +6370,7 @@ var Scene = (_class15 = class _Scene {
5991
6370
  if (this.mouseX > -9e3 && !this.projectsSelectableText(node)) {
5992
6371
  if (node.isPointInside(this.mouseX, this.mouseY)) return true;
5993
6372
  }
5994
- const existing = _optionalChain([this, 'access', _171 => _171.a11yElements, 'optionalAccess', _172 => _172.get, 'call', _173 => _173(node.id)]);
6373
+ const existing = _optionalChain([this, 'access', _174 => _174.a11yElements, 'optionalAccess', _175 => _175.get, 'call', _176 => _176(node.id)]);
5995
6374
  if (existing && typeof document !== "undefined" && document.activeElement === existing) {
5996
6375
  return true;
5997
6376
  }
@@ -6005,8 +6384,8 @@ var Scene = (_class15 = class _Scene {
6005
6384
  * selection.
6006
6385
  */
6007
6386
  projectsSelectableText(node) {
6008
- const projection = _optionalChain([node, 'access', _174 => _174.getContentProjection, 'optionalCall', _175 => _175()]);
6009
- return !!_optionalChain([projection, 'optionalAccess', _176 => _176.text]) && projection.selectable !== false;
6387
+ const projection = _optionalChain([node, 'access', _177 => _177.getContentProjection, 'optionalCall', _178 => _178()]);
6388
+ return !!_optionalChain([projection, 'optionalAccess', _179 => _179.text]) && projection.selectable !== false;
6010
6389
  }
6011
6390
  /**
6012
6391
  * Keep `entity`'s a11y shadow node projected while it has
@@ -6101,20 +6480,20 @@ var Scene = (_class15 = class _Scene {
6101
6480
  el.style.background = "transparent";
6102
6481
  }
6103
6482
  el.addEventListener("click", (e) => {
6104
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("click", node, e));
6483
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("click", node, e));
6105
6484
  });
6106
6485
  el.addEventListener("dblclick", (e) => {
6107
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("dblclick", node, e));
6486
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("dblclick", node, e));
6108
6487
  });
6109
6488
  el.addEventListener("mouseenter", (e) => {
6110
6489
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.2)";
6111
6490
  this.hoveredA11yElements.add(el);
6112
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("hover", node, e, false));
6491
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("hover", node, e, false));
6113
6492
  });
6114
6493
  el.addEventListener("mouseleave", (e) => {
6115
6494
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.05)";
6116
6495
  this.hoveredA11yElements.delete(el);
6117
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("pointerleave", node, e, false));
6496
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("pointerleave", node, e, false));
6118
6497
  });
6119
6498
  const capEl = el;
6120
6499
  const releasePointer = (event) => {
@@ -6132,24 +6511,27 @@ var Scene = (_class15 = class _Scene {
6132
6511
  if (e.target === capEl && typeof capEl.setPointerCapture === "function") {
6133
6512
  capEl.setPointerCapture(e.pointerId);
6134
6513
  }
6135
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("pointerdown", node, e));
6514
+ this.pinProjectionForGesture(node);
6515
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("pointerdown", node, e));
6136
6516
  });
6137
6517
  el.addEventListener("pointerup", (e) => {
6138
6518
  releasePointer(e);
6139
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("pointerup", node, e));
6519
+ this.unpinProjectionForGesture(node);
6520
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("pointerup", node, e));
6140
6521
  });
6141
6522
  el.addEventListener("pointercancel", (e) => {
6142
6523
  releasePointer(e);
6143
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("pointercancel", node, e));
6524
+ this.unpinProjectionForGesture(node);
6525
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("pointercancel", node, e));
6144
6526
  });
6145
6527
  el.addEventListener(
6146
6528
  "pointermove",
6147
- (e) => node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("pointermove", node, e))
6529
+ (e) => node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("pointermove", node, e))
6148
6530
  );
6149
6531
  el.addEventListener(
6150
6532
  "wheel",
6151
6533
  (e) => {
6152
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("wheel", node, e));
6534
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("wheel", node, e));
6153
6535
  },
6154
6536
  { passive: false }
6155
6537
  );
@@ -6174,10 +6556,10 @@ var Scene = (_class15 = class _Scene {
6174
6556
  );
6175
6557
  emitInitialScroll = emitScroll;
6176
6558
  el.addEventListener("keydown", (e) => {
6177
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("keydown", node, e));
6559
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("keydown", node, e));
6178
6560
  });
6179
6561
  el.addEventListener("keyup", (e) => {
6180
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("keyup", node, e));
6562
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("keyup", node, e));
6181
6563
  });
6182
6564
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
6183
6565
  const input = el;
@@ -6208,7 +6590,7 @@ var Scene = (_class15 = class _Scene {
6208
6590
  el.addEventListener("compositionupdate", (e) => {
6209
6591
  const data = _nullishCoalesce(e.data, () => ( ""));
6210
6592
  composition = {
6211
- start: _nullishCoalesce(_optionalChain([composition, 'optionalAccess', _177 => _177.start]), () => ( 0)),
6593
+ start: _nullishCoalesce(_optionalChain([composition, 'optionalAccess', _180 => _180.start]), () => ( 0)),
6212
6594
  length: data.length
6213
6595
  };
6214
6596
  forward();
@@ -6262,14 +6644,14 @@ var Scene = (_class15 = class _Scene {
6262
6644
  el.addEventListener("keydown", (e) => {
6263
6645
  if (e.key === "Enter") {
6264
6646
  e.preventDefault();
6265
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("click", node, e));
6647
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("click", node, e));
6266
6648
  } else if (e.key === " ") {
6267
6649
  e.preventDefault();
6268
6650
  }
6269
6651
  });
6270
6652
  el.addEventListener("keyup", (e) => {
6271
6653
  if (e.key === " ") {
6272
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("click", node, e));
6654
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("click", node, e));
6273
6655
  }
6274
6656
  });
6275
6657
  this.keyboardActivatedA11y.add(el);
@@ -6459,7 +6841,7 @@ var Scene = (_class15 = class _Scene {
6459
6841
  const display = visible ? "" : "none";
6460
6842
  if (el.style.display !== display) el.style.display = display;
6461
6843
  }
6462
- _optionalChain([emitInitialScroll, 'optionalCall', _178 => _178()]);
6844
+ _optionalChain([emitInitialScroll, 'optionalCall', _181 => _181()]);
6463
6845
  }
6464
6846
  if (this.phases.enabled) {
6465
6847
  const projectionStart = performance.now();
@@ -6689,7 +7071,7 @@ var Scene = (_class15 = class _Scene {
6689
7071
  el.addEventListener(
6690
7072
  "wheel",
6691
7073
  (e2) => {
6692
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("wheel", node, e2));
7074
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("wheel", node, e2));
6693
7075
  },
6694
7076
  { passive: false }
6695
7077
  );
@@ -6920,8 +7302,8 @@ var Scene = (_class15 = class _Scene {
6920
7302
  next.f = f2;
6921
7303
  next.tier = tier;
6922
7304
  next.hasBand = lineBand !== null;
6923
- next.bandMin = _nullishCoalesce(_optionalChain([lineBand, 'optionalAccess', _179 => _179.minY]), () => ( 0));
6924
- next.bandMax = _nullishCoalesce(_optionalChain([lineBand, 'optionalAccess', _180 => _180.maxY]), () => ( 0));
7305
+ next.bandMin = _nullishCoalesce(_optionalChain([lineBand, 'optionalAccess', _182 => _182.minY]), () => ( 0));
7306
+ next.bandMax = _nullishCoalesce(_optionalChain([lineBand, 'optionalAccess', _183 => _183.maxY]), () => ( 0));
6925
7307
  next.visible = visible;
6926
7308
  next.width = node.width;
6927
7309
  next.height = node.height;
@@ -7100,6 +7482,197 @@ var Scene = (_class15 = class _Scene {
7100
7482
  this.activePortalsPrevFrame = new Set(this.activePortalsThisFrame);
7101
7483
  this.activePortalsThisFrame.clear();
7102
7484
  }
7485
+ /**
7486
+ * Register a generic projection backend (RFC1 §5, RFC2 P1 CTX-0598).
7487
+ * Registration is idempotent per backend instance. Core drives
7488
+ * mount/update/unmount from the render walk; the backend owns all
7489
+ * medium-specific state.
7490
+ */
7491
+ addProjectionBackend(backend) {
7492
+ if (!this.projectionBackends.includes(backend)) this.projectionBackends.push(backend);
7493
+ return this;
7494
+ }
7495
+ /**
7496
+ * Remove a previously registered projection backend by instance or
7497
+ * {@link ProjectionBackendKind | kind}. Removing does not unmount resident
7498
+ * nodes — unmount the backend first if teardown order matters.
7499
+ */
7500
+ removeProjectionBackend(backend) {
7501
+ this.projectionBackends = this.projectionBackends.filter(
7502
+ (b) => typeof backend === "string" ? b.kind !== backend : b !== backend
7503
+ );
7504
+ return this;
7505
+ }
7506
+ /**
7507
+ * Scene-level projection capabilities (RFC4 §3 rule 2, CTX-0601).
7508
+ *
7509
+ * Plain-data feature detection in the shape of the proposed
7510
+ * `scene.inputCapabilities` (input-dispatch-contract-v2 §4): apps and
7511
+ * devtools query where a node _would_ materialize and why, without touching
7512
+ * any backend. Additive only — no existing surface moves.
7513
+ */
7514
+ get projectionCapabilities() {
7515
+ return {
7516
+ hasDOM: typeof document !== "undefined",
7517
+ domBackendMounted: this.projectionBackends.some((b) => b.kind === "dom"),
7518
+ backends: this.projectionBackends.map((b) => b.kind),
7519
+ autoHysteresisFrames: this.projectionHysteresisFrames,
7520
+ autoDomBudget: this.projectionAutoDomBudget
7521
+ };
7522
+ }
7523
+ /**
7524
+ * Register (or replace) the Capability Matrix row for one `domKind`
7525
+ * (RFC4 §4, CTX-0601). Custom kinds start at the unknown-kind default
7526
+ * (`unsupported-kind` → canvas); registering a row is how a custom backend
7527
+ * kind opts into `'auto'` resolution and how tests drive oscillating costs.
7528
+ */
7529
+ registerProjectionCapability(domKind, row) {
7530
+ this.projectionCapabilityOverrides.set(domKind, row);
7531
+ return this;
7532
+ }
7533
+ /** Matrix row for a node's `domKind` (override wins, unknown → canvas). */
7534
+ getProjectionCapability(node) {
7535
+ return _nullishCoalesce(_nullishCoalesce(this.projectionCapabilityOverrides.get(node.domKind), () => ( DEFAULT_PROJECTION_CAPABILITIES[node.domKind])), () => ( UNKNOWN_PROJECTION_CAPABILITY));
7536
+ }
7537
+ /**
7538
+ * Pin a node's backend for the duration of an active gesture (RFC4 §5 +
7539
+ * input-dispatch-contract-v2 §4 gesture stickiness: no mid-gesture handoff).
7540
+ * The a11y-mirror pointerdown/up/cancel listeners maintain this; DOM-side
7541
+ * gestures are consulted through
7542
+ * {@link ProjectionBackend.hasActiveGesture | backend.hasActiveGesture}.
7543
+ * Refcounted so multi-pointer gestures on one node unpin exactly once.
7544
+ */
7545
+ pinProjectionForGesture(node) {
7546
+ this.projectionGesturePins.set(node.id, (_nullishCoalesce(this.projectionGesturePins.get(node.id), () => ( 0))) + 1);
7547
+ }
7548
+ /** Release one gesture pin taken by {@link pinProjectionForGesture}. */
7549
+ unpinProjectionForGesture(node) {
7550
+ const count = (_nullishCoalesce(this.projectionGesturePins.get(node.id), () => ( 0))) - 1;
7551
+ if (count <= 0) this.projectionGesturePins.delete(node.id);
7552
+ else this.projectionGesturePins.set(node.id, count);
7553
+ }
7554
+ /** Whether `node` currently owns an active gesture on a core mirror. */
7555
+ isProjectionPinned(node) {
7556
+ return (_nullishCoalesce(this.projectionGesturePins.get(node.id), () => ( 0))) > 0;
7557
+ }
7558
+ /**
7559
+ * Last negotiation for one node, if it was ever resolved on this scene.
7560
+ * Accepts the node or its id; returns the stored record (not a copy — treat
7561
+ * as read-only).
7562
+ */
7563
+ getProjectionResolution(node) {
7564
+ return this.projectionResolutions.get(typeof node === "string" ? node : node.id);
7565
+ }
7566
+ /** Every node's last negotiation, in first-resolution order. */
7567
+ getProjectionResolutions() {
7568
+ return [...this.projectionResolutions.values()];
7569
+ }
7570
+ /**
7571
+ * Resolve one node's `domPolicy` to its effective backend (RFC4 §3, CTX-0601).
7572
+ *
7573
+ * Memoized per main frame (`currentFrame`, bumped once per authoritative
7574
+ * render): the render walk and the later a11y sync must agree within a
7575
+ * frame, or a node could lose its mirror the same frame it gains an element.
7576
+ * `'auto'` applies, in order: no-DOM short-circuit → pure negotiation →
7577
+ * gesture/focus hard bars (a pinned node keeps its current backend, never
7578
+ * flips mid-gesture, focus is preserved-or-moved never dropped to `body` per
7579
+ * input-dispatch-contract-v2 §4) → hysteresis stickiness → bulk budget.
7580
+ * Every outcome is recorded on the per-scene queryable surface
7581
+ * ({@link getProjectionResolutions}) with its reason — fallbacks are
7582
+ * reported, not silent (§3 rule 2).
7583
+ */
7584
+ resolveProjectionFor(node) {
7585
+ const want = node.domPolicy;
7586
+ if (this.projectionBackends.length === 0 && want === "canvas") return "canvas";
7587
+ const memo = this.projectionHysteresis.get(node.id);
7588
+ if (memo && memo.lastFrame === this.currentFrame) return memo.resolved;
7589
+ const hasDOM = typeof document !== "undefined";
7590
+ const domBackendMounted = this.projectionBackends.some((b) => b.kind === "dom");
7591
+ const outcome = resolveProjection(want, this.getProjectionCapability(node), {
7592
+ hasDOM,
7593
+ domBackendMounted
7594
+ });
7595
+ let resolved = outcome.resolved;
7596
+ let reason = outcome.reason;
7597
+ if (want === "auto" && memo !== void 0) {
7598
+ if (resolved !== memo.resolved) {
7599
+ const gesturePinned = this.isProjectionPinned(node) || this.projectionBackends.some((b) => _optionalChain([b, 'access', _184 => _184.hasActiveGesture, 'optionalCall', _185 => _185(node)]) === true);
7600
+ if (gesturePinned) {
7601
+ resolved = memo.resolved;
7602
+ reason = "active-gesture";
7603
+ } else if (this.ownsProjectionFocus(node)) {
7604
+ resolved = memo.resolved;
7605
+ reason = "focus-pinned";
7606
+ } else {
7607
+ const vote = hysteresisVote(
7608
+ memo.resolved,
7609
+ resolved,
7610
+ memo.consecutive,
7611
+ this.projectionHysteresisFrames
7612
+ );
7613
+ resolved = vote.resolved;
7614
+ memo.consecutive = vote.consecutive;
7615
+ reason = vote.flipped ? reason : "hysteresis";
7616
+ }
7617
+ } else {
7618
+ memo.consecutive = 0;
7619
+ }
7620
+ }
7621
+ if (want === "auto" && resolved === "dom") {
7622
+ if (this.projectionBudgetFrame !== this.currentFrame) {
7623
+ this.projectionBudgetFrame = this.currentFrame;
7624
+ this.projectionDomAutoCount = 0;
7625
+ }
7626
+ this.projectionDomAutoCount += 1;
7627
+ if (this.projectionDomAutoCount > this.projectionAutoDomBudget) {
7628
+ resolved = "canvas";
7629
+ reason = "bulk-budget";
7630
+ }
7631
+ }
7632
+ this.projectionHysteresis.set(node.id, {
7633
+ resolved,
7634
+ consecutive: _nullishCoalesce(_optionalChain([memo, 'optionalAccess', _186 => _186.consecutive]), () => ( 0)),
7635
+ lastFrame: this.currentFrame
7636
+ });
7637
+ const prev = this.projectionResolutions.get(node.id);
7638
+ if (!prev || prev.want !== want || prev.resolved !== resolved || prev.reason !== reason) {
7639
+ this.projectionResolutions.set(node.id, { nodeId: node.id, want, resolved, reason });
7640
+ }
7641
+ return resolved;
7642
+ }
7643
+ /**
7644
+ * Whether `node`'s a11y mirror currently owns browser focus (RFC4 §5 focus
7645
+ * rule, input-dispatch-contract-v2 §4: focus stays DOM-based; flipping
7646
+ * `projection` must preserve or deliberately move focus, never drop it to
7647
+ * `body`). A focused node keeps its backend until blur — the actual move, if
7648
+ * any, goes through the existing sentinels (`preserveFocusOnRemoval`, the
7649
+ * DOM backend's own fallback), never a bare removal.
7650
+ */
7651
+ ownsProjectionFocus(node) {
7652
+ if (typeof document === "undefined") return false;
7653
+ const mirror = this.a11yElements.get(node.id);
7654
+ if (!mirror || mirror !== this.focusedA11yElement) return false;
7655
+ return document.activeElement === mirror;
7656
+ }
7657
+ /**
7658
+ * RFC2 P1 (CTX-0598): unmount DOM-projection residents the walk did not see
7659
+ * this frame (viewport-culled, removed without `remove()`, policy-flipped).
7660
+ * Mirrors {@link reconcilePortals} above; unmount is idempotent per the
7661
+ * `ProjectionBackend` contract so notifying every backend is safe.
7662
+ */
7663
+ pruneProjectionBackends() {
7664
+ for (const id of this.domSeenPrevFrame) {
7665
+ if (!this.domSeenThisFrame.has(id)) {
7666
+ const node = this.domSeenNodes.get(id);
7667
+ if (node) {
7668
+ for (const backend of this.projectionBackends) backend.unmount(node);
7669
+ this.domSeenNodes.delete(id);
7670
+ }
7671
+ }
7672
+ }
7673
+ this.domSeenPrevFrame = new Set(this.domSeenThisFrame);
7674
+ this.domSeenThisFrame.clear();
7675
+ }
7103
7676
  // --- domain: render-scheduler — the loop and the render walk ---
7104
7677
  /**
7105
7678
  * The frame-rate cap actually in effect: the explicit {@link maxFPS}, further
@@ -7107,7 +7680,7 @@ var Scene = (_class15 = class _Scene {
7107
7680
  * (and {@link respectReducedMotion} is on). `0` means uncapped.
7108
7681
  */
7109
7682
  effectiveMaxFPS() {
7110
- const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access', _181 => _181.reducedMotionQuery, 'optionalAccess', _182 => _182.matches]);
7683
+ const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access', _187 => _187.reducedMotionQuery, 'optionalAccess', _188 => _188.matches]);
7111
7684
  if (reduced)
7112
7685
  return this.maxFPS > 0 ? Math.min(this.maxFPS, REDUCED_MOTION_FPS) : REDUCED_MOTION_FPS;
7113
7686
  return this.maxFPS;
@@ -7177,6 +7750,12 @@ var Scene = (_class15 = class _Scene {
7177
7750
  /**
7178
7751
  * Render the entire scene graph onto the specified renderer.
7179
7752
  *
7753
+ * This is the `CanvasProjection` row of {@link ProjectionBackend}:
7754
+ * the per-frame materialization of scene semantics into pixels through the
7755
+ * backend-agnostic `IRenderer` contract. (The `A11yProjection` /
7756
+ * `ContentProjection` rows run on the same cadence via {@link Scene.syncA11y}
7757
+ * below; the `DOMProjection` row does not exist yet — RFC2/CTX-0598.)
7758
+ *
7180
7759
  * Main-frame causal order is a correctness contract:
7181
7760
  *
7182
7761
  * 1. Browser/input callbacks finish before the scheduled frame begins.
@@ -7202,7 +7781,7 @@ var Scene = (_class15 = class _Scene {
7202
7781
  * @param time - Current absolute time in milliseconds (default 0).
7203
7782
  */
7204
7783
  render(renderer, dt = 0, time = 0) {
7205
- if (_optionalChain([renderer, 'access', _183 => _183.isContextLost, 'optionalCall', _184 => _184()])) return;
7784
+ if (_optionalChain([renderer, 'access', _189 => _189.isContextLost, 'optionalCall', _190 => _190()])) return;
7206
7785
  const isMainRenderer = renderer === this.renderer;
7207
7786
  if (isMainRenderer && this.a11yRoot && this.canvas.parentElement) {
7208
7787
  const parentStyle = this.canvas.parentElement.style;
@@ -7352,14 +7931,14 @@ var Scene = (_class15 = class _Scene {
7352
7931
  }
7353
7932
  renderer.clear();
7354
7933
  if (isMainRenderer) {
7355
- _optionalChain([this, 'access', _185 => _185.pointRenderer, 'optionalAccess', _186 => _186.begin, 'call', _187 => _187()]);
7934
+ _optionalChain([this, 'access', _191 => _191.pointRenderer, 'optionalAccess', _192 => _192.begin, 'call', _193 => _193()]);
7356
7935
  }
7357
7936
  const vw = this.width;
7358
7937
  const vh = this.height;
7359
7938
  let walkHadAnimation = false;
7360
7939
  let walkHadInteractive = false;
7361
7940
  const runUpdate = (node) => {
7362
- const overridesUpdate = node.update !== _chunkPOPXUPKRjs.Entity.prototype.update;
7941
+ const overridesUpdate = node.update !== _chunkHC5EZOZJjs.Entity.prototype.update;
7363
7942
  let pending = node.hasPendingAnimations();
7364
7943
  if (pending || overridesUpdate) {
7365
7944
  node.update(dt, time);
@@ -7368,7 +7947,7 @@ var Scene = (_class15 = class _Scene {
7368
7947
  if (pending) walkHadAnimation = true;
7369
7948
  if (!walkHadInteractive && node.interactive) walkHadInteractive = true;
7370
7949
  if (this._devActive && this._devFrameCount % 120 === 0) {
7371
- if (overridesUpdate && node.hasPendingAnimations === _chunkPOPXUPKRjs.Entity.prototype.hasPendingAnimations) {
7950
+ if (overridesUpdate && node.hasPendingAnimations === _chunkHC5EZOZJjs.Entity.prototype.hasPendingAnimations) {
7372
7951
  this._devWarn(
7373
7952
  `Entity "${node.id}" overrides update() but not hasPendingAnimations(). Custom motion in update() without overriding hasPendingAnimations() causes the idle throttle to slow the animation. Override hasPendingAnimations() to return true while motion is in flight.`
7374
7953
  );
@@ -7469,6 +8048,16 @@ var Scene = (_class15 = class _Scene {
7469
8048
  visible = maxX >= 0 && minX <= vw && maxY >= 0 && minY <= vh;
7470
8049
  }
7471
8050
  if (!visible && node.children.length === 0) return;
8051
+ if (isMainRenderer && this.projectionBackends.length > 0 && this.resolveProjectionFor(node) === "dom" && visible) {
8052
+ for (const backend of this.projectionBackends) {
8053
+ backend.update(node, { a, b, c, d, e: te, f: tf });
8054
+ }
8055
+ if (node.domResident) {
8056
+ this.domSeenThisFrame.add(node.id);
8057
+ this.domSeenNodes.set(node.id, node);
8058
+ if (node.children.length === 0) return;
8059
+ }
8060
+ }
7472
8061
  if (node.children.length === 0 && node.scaleX === node.scaleY) {
7473
8062
  const bc = node.getBatchCircle();
7474
8063
  if (bc) {
@@ -7598,14 +8187,15 @@ var Scene = (_class15 = class _Scene {
7598
8187
  this.frameHadAnimation = walkHadAnimation;
7599
8188
  this.frameHadInteractive = walkHadInteractive;
7600
8189
  this.reconcilePortals();
8190
+ this.pruneProjectionBackends();
7601
8191
  }
7602
8192
  const flushTiming = this.phases.userTiming ? beginVectoUserTiming(VECTO_USER_TIMING.scene.flush) : null;
7603
8193
  const flushT0 = this.phases.enabled ? performance.now() : 0;
7604
8194
  renderer.flush();
7605
8195
  if (isMainRenderer) {
7606
- _optionalChain([this, 'access', _188 => _188.pointRenderer, 'optionalAccess', _189 => _189.flush, 'call', _190 => _190()]);
8196
+ _optionalChain([this, 'access', _194 => _194.pointRenderer, 'optionalAccess', _195 => _195.flush, 'call', _196 => _196()]);
7607
8197
  }
7608
- _optionalChain([renderer, 'access', _191 => _191.present, 'optionalCall', _192 => _192()]);
8198
+ _optionalChain([renderer, 'access', _197 => _197.present, 'optionalCall', _198 => _198()]);
7609
8199
  if (this.phases.enabled) this.phases.record("flush", performance.now() - flushT0);
7610
8200
  if (flushTiming) endVectoUserTiming(flushTiming);
7611
8201
  if (this._devActive) {
@@ -7696,7 +8286,7 @@ var Scene = (_class15 = class _Scene {
7696
8286
  });
7697
8287
  pass.end();
7698
8288
  this.device.queue.submit([encoder.finish()]);
7699
- } catch (e17) {
8289
+ } catch (e19) {
7700
8290
  }
7701
8291
  this.gpuHasContent = false;
7702
8292
  }
@@ -7831,20 +8421,20 @@ function defaultMeasurer() {
7831
8421
  sharedMeasurer ??= _layout.resolveGlyphMeasurer.call(void 0, "sans-serif");
7832
8422
  return sharedMeasurer;
7833
8423
  }
7834
- var TextEntity = (_class16 = class extends _chunkPOPXUPKRjs.Entity {
8424
+ var TextEntity = (_class16 = class extends _chunkHC5EZOZJjs.Entity {
7835
8425
 
7836
8426
 
7837
8427
 
7838
8428
 
7839
- __init191() {this.nodes = []}
8429
+ __init204() {this.nodes = []}
7840
8430
 
7841
- __init192() {this.fillStyle = "#94a3b8"}
7842
- __init193() {this.strokeStyle = null}
7843
- __init194() {this.hoveredFillStyle = "#ffffff"}
7844
- __init195() {this.lineWidth = 1}
7845
- __init196() {this.isHovered = false}
8431
+ __init205() {this.fillStyle = "#94a3b8"}
8432
+ __init206() {this.strokeStyle = null}
8433
+ __init207() {this.hoveredFillStyle = "#ffffff"}
8434
+ __init208() {this.lineWidth = 1}
8435
+ __init209() {this.isHovered = false}
7846
8436
  /** Bumped by {@link applyLayout}; read by `Scene` to skip an unchanged sync. */
7847
- __init197() {this.contentEpoch = 0}
8437
+ __init210() {this.contentEpoch = 0}
7848
8438
  /**
7849
8439
  * Visual lines with real canvas geometry, rebuilt by {@link applyLayout}
7850
8440
  * exactly when `this.nodes` changes.
@@ -7856,9 +8446,9 @@ var TextEntity = (_class16 = class extends _chunkPOPXUPKRjs.Entity {
7856
8446
  * ~0.35 em per line for a 24 px font, Firefox). Per-line carriers pin the DOM
7857
8447
  * baseline to the canvas baseline by construction, same as `ui/Text`.
7858
8448
  */
7859
- __init198() {this.projectionLines = []}
8449
+ __init211() {this.projectionLines = []}
7860
8450
  constructor(text, atlas, maxWidth, fontSize = 24) {
7861
- super();_class16.prototype.__init191.call(this);_class16.prototype.__init192.call(this);_class16.prototype.__init193.call(this);_class16.prototype.__init194.call(this);_class16.prototype.__init195.call(this);_class16.prototype.__init196.call(this);_class16.prototype.__init197.call(this);_class16.prototype.__init198.call(this);;
8451
+ super();_class16.prototype.__init204.call(this);_class16.prototype.__init205.call(this);_class16.prototype.__init206.call(this);_class16.prototype.__init207.call(this);_class16.prototype.__init208.call(this);_class16.prototype.__init209.call(this);_class16.prototype.__init210.call(this);_class16.prototype.__init211.call(this);;
7862
8452
  this.text = text;
7863
8453
  this.atlas = atlas;
7864
8454
  this.fontSize = fontSize;
@@ -8034,18 +8624,250 @@ var TextEntity = (_class16 = class extends _chunkPOPXUPKRjs.Entity {
8034
8624
  }
8035
8625
  }, _class16);
8036
8626
 
8627
+ // src/components/VirtualizedSetAggregate.ts
8628
+ var VirtualizedSetAggregate = (_class17 = class extends _chunkHC5EZOZJjs.Entity {
8629
+
8630
+
8631
+
8632
+
8633
+
8634
+
8635
+
8636
+ /** First item index currently bound to the pool (the scroll window). */
8637
+ __init212() {this._visibleStart = 0}
8638
+ /** Item id owning the roving tab stop / keyboard focus. */
8639
+ __init213() {this._activeId = null}
8640
+ __init214() {this._selectedId = null}
8641
+ /** One focusable hotspot per visible slot; re-bound, never rebuilt per item. */
8642
+ __init215() {this._hotspots = []}
8643
+ constructor(opts = {}) {
8644
+ super();_class17.prototype.__init212.call(this);_class17.prototype.__init213.call(this);_class17.prototype.__init214.call(this);_class17.prototype.__init215.call(this);;
8645
+ this._items = _nullishCoalesce(opts.items, () => ( []));
8646
+ this._label = _nullishCoalesce(opts.label, () => ( "Items"));
8647
+ this._role = _nullishCoalesce(opts.role, () => ( "list"));
8648
+ this._itemRole = _nullishCoalesce(opts.itemRole, () => ( "listitem"));
8649
+ this._rowHeight = _nullishCoalesce(opts.rowHeight, () => ( 28));
8650
+ this._visibleCapacity = opts.visibleCapacity;
8651
+ this._onActivate = opts.onActivate;
8652
+ this.width = _nullishCoalesce(opts.width, () => ( 0));
8653
+ this.height = _nullishCoalesce(opts.height, () => ( 0));
8654
+ this.interactive = true;
8655
+ this._syncHotspots();
8656
+ }
8657
+ /** Full item count — the number the container label states. */
8658
+ get itemCount() {
8659
+ return this._items.length;
8660
+ }
8661
+ /** Live pool size (bounded by the visible capacity, never by the item count). */
8662
+ get poolSize() {
8663
+ return this._hotspots.length;
8664
+ }
8665
+ /** First item index currently bound to the pool. */
8666
+ get visibleStart() {
8667
+ return this._visibleStart;
8668
+ }
8669
+ getA11yAttributes() {
8670
+ return {
8671
+ role: this._role,
8672
+ label: `${this._label}, ${this._items.length} items`,
8673
+ pointerEvents: "none"
8674
+ };
8675
+ }
8676
+ render(_renderer) {
8677
+ }
8678
+ /** Axis-aligned hit-test against the container box (the `UIComponent` default). */
8679
+ isPointInside(globalX, globalY) {
8680
+ const local = this.worldToLocal(globalX, globalY);
8681
+ if (!local) return false;
8682
+ return local.x >= 0 && local.x <= this.width && local.y >= 0 && local.y <= this.height;
8683
+ }
8684
+ /** Replace the item set; the pool re-binds and the label count follows. */
8685
+ setItems(items) {
8686
+ this._items = items;
8687
+ if (this._activeId !== null && !items.some((item) => item.id === this._activeId)) {
8688
+ this._activeId = null;
8689
+ }
8690
+ if (this._selectedId !== null && !items.some((item) => item.id === this._selectedId)) {
8691
+ this._selectedId = null;
8692
+ }
8693
+ this._visibleStart = Math.max(0, Math.min(this._visibleStart, Math.max(0, items.length - 1)));
8694
+ this._syncHotspots();
8695
+ _optionalChain([this, 'access', _199 => _199.scene, 'optionalAccess', _200 => _200.markDirty, 'call', _201 => _201()]);
8696
+ }
8697
+ /** Move the pool window to start at `start` (the scroll position). */
8698
+ setVisibleStart(start) {
8699
+ const clamped = Math.max(0, Math.min(start, Math.max(0, this._items.length - 1)));
8700
+ if (clamped === this._visibleStart) return;
8701
+ this._visibleStart = clamped;
8702
+ this._syncHotspots();
8703
+ _optionalChain([this, 'access', _202 => _202.scene, 'optionalAccess', _203 => _203.markDirty, 'call', _204 => _204()]);
8704
+ }
8705
+ /**
8706
+ * Keep one hotspot per visible slot, positioned over it. The pool is sized
8707
+ * to the viewport (capacity), each slot re-bound to whatever item currently
8708
+ * occupies it — the `Tree._syncHotspots` shape, minus tree structure.
8709
+ */
8710
+ _syncHotspots() {
8711
+ const capacity = _nullishCoalesce(this._visibleCapacity, () => ( Math.max(1, Math.ceil(this.height / this._rowHeight))));
8712
+ const need = Math.max(0, Math.min(capacity, this._items.length - this._visibleStart));
8713
+ while (this._hotspots.length < need) {
8714
+ const hotspot = new AggregateItemHotspot(this);
8715
+ this._hotspots.push(hotspot);
8716
+ this.add(hotspot);
8717
+ }
8718
+ while (this._hotspots.length > need) {
8719
+ const hotspot = this._hotspots.pop();
8720
+ _optionalChain([this, 'access', _205 => _205.scene, 'optionalAccess', _206 => _206.detachA11y, 'call', _207 => _207(hotspot)]);
8721
+ this.remove(hotspot);
8722
+ }
8723
+ for (let slot = 0; slot < need; slot++) {
8724
+ const index = this._visibleStart + slot;
8725
+ const hotspot = this._hotspots[slot];
8726
+ hotspot.bind(index, this._items[index]);
8727
+ hotspot.x = 0;
8728
+ hotspot.y = slot * this._rowHeight;
8729
+ hotspot.width = this.width;
8730
+ hotspot.height = this._rowHeight;
8731
+ }
8732
+ }
8733
+ /** Whether `id` owns the roving tab stop: active, else selected, else first. */
8734
+ isTabStop(id) {
8735
+ const anchor = _nullishCoalesce(_nullishCoalesce((this._activeId !== null && this._items.some((item) => item.id === this._activeId) ? this._activeId : null), () => ( (this._selectedId !== null && this._items.some((item) => item.id === this._selectedId) ? this._selectedId : null))), () => ( _optionalChain([this, 'access', _208 => _208._items, 'access', _209 => _209[0], 'optionalAccess', _210 => _210.id])));
8736
+ return id === anchor;
8737
+ }
8738
+ isSelected(id) {
8739
+ return this._selectedId === id;
8740
+ }
8741
+ /** Item role the hotspots announce (kept on the parent: one source of truth). */
8742
+ get itemRole() {
8743
+ return this._itemRole;
8744
+ }
8745
+ /** Activate an item (pointer tap-through or Enter/Space): selects and notifies. */
8746
+ activateItem(id, focusIt = false) {
8747
+ const index = this._items.findIndex((item) => item.id === id);
8748
+ if (index === -1) return;
8749
+ this._activeId = id;
8750
+ this._selectedId = id;
8751
+ _optionalChain([this, 'access', _211 => _211._onActivate, 'optionalCall', _212 => _212(this._items[index], index)]);
8752
+ if (focusIt) this.focusItem(id);
8753
+ _optionalChain([this, 'access', _213 => _213.scene, 'optionalAccess', _214 => _214.markDirty, 'call', _215 => _215()]);
8754
+ }
8755
+ /** Move keyboard focus to the hotspot currently bound to `id`, if any. */
8756
+ focusItem(id) {
8757
+ _optionalChain([this, 'access', _216 => _216._hotspots, 'access', _217 => _217.find, 'call', _218 => _218((hotspot) => hotspot.itemId === id), 'optionalAccess', _219 => _219.focus, 'call', _220 => _220()]);
8758
+ }
8759
+ /**
8760
+ * Aggregate keyboard model: Up/Down move the active item (scrolling the pool
8761
+ * window so it stays bound), Home/End jump, Enter/Space activate. The active
8762
+ * item is focused as it moves so focus is never stranded on a re-bound slot.
8763
+ */
8764
+ handleItemKey(e, id) {
8765
+ const index = this._items.findIndex((item) => item.id === id);
8766
+ if (index === -1) return;
8767
+ let next = -1;
8768
+ switch (e.key) {
8769
+ case "ArrowDown":
8770
+ next = Math.min(index + 1, this._items.length - 1);
8771
+ break;
8772
+ case "ArrowUp":
8773
+ next = Math.max(index - 1, 0);
8774
+ break;
8775
+ case "Home":
8776
+ next = 0;
8777
+ break;
8778
+ case "End":
8779
+ next = this._items.length - 1;
8780
+ break;
8781
+ case "Enter":
8782
+ case " ":
8783
+ e.preventDefault();
8784
+ this.activateItem(id, true);
8785
+ return;
8786
+ default:
8787
+ return;
8788
+ }
8789
+ e.preventDefault();
8790
+ this._activeId = this._items[next].id;
8791
+ this._ensureVisible(next);
8792
+ this.focusItem(this._activeId);
8793
+ _optionalChain([this, 'access', _221 => _221.scene, 'optionalAccess', _222 => _222.markDirty, 'call', _223 => _223()]);
8794
+ }
8795
+ /** Scroll the pool window just enough to keep `index` bound. */
8796
+ _ensureVisible(index) {
8797
+ const capacity = _nullishCoalesce(this._visibleCapacity, () => ( Math.max(1, Math.ceil(this.height / this._rowHeight))));
8798
+ if (index < this._visibleStart) this.setVisibleStart(index);
8799
+ else if (index >= this._visibleStart + capacity) {
8800
+ this.setVisibleStart(index - capacity + 1);
8801
+ } else {
8802
+ this._syncHotspots();
8803
+ }
8804
+ }
8805
+ update(_dt, _time) {
8806
+ super.update(_dt, _time);
8807
+ this._syncHotspots();
8808
+ }
8809
+ }, _class17);
8810
+ var AggregateItemHotspot = (_class18 = class extends _chunkHC5EZOZJjs.Entity {
8811
+ constructor(aggregate) {
8812
+ super();_class18.prototype.__init216.call(this);_class18.prototype.__init217.call(this);_class18.prototype.__init218.call(this);;
8813
+ this.aggregate = aggregate;
8814
+ this.interactive = true;
8815
+ this.on("click", () => this.aggregate.activateItem(this.itemId, true));
8816
+ this.on("keydown", (e) => this.aggregate.handleItemKey(e, this.itemId));
8817
+ }
8818
+
8819
+ __init216() {this.itemId = ""}
8820
+ __init217() {this._index = 0}
8821
+ __init218() {this._label = ""}
8822
+ bind(index, item) {
8823
+ this._index = index;
8824
+ this.itemId = item.id;
8825
+ this._label = item.label;
8826
+ }
8827
+ /** The aggregate positions and sizes one pooled hotspot per visible slot. */
8828
+ getLayoutControlledProperties() {
8829
+ return ["x", "y", "width", "height"];
8830
+ }
8831
+ getA11yAttributes() {
8832
+ return {
8833
+ role: this.aggregate.itemRole,
8834
+ label: this._label,
8835
+ // 1-based position within the FULL set, not the pool: without these a
8836
+ // virtualized window announces the pool position as the set position.
8837
+ posInSet: this._index + 1,
8838
+ setSize: this.aggregate.itemCount,
8839
+ selected: this.aggregate.isSelected(this.itemId),
8840
+ // Roving tabindex: only the active item is a tab stop; arrows move within.
8841
+ tabIndex: this.aggregate.isTabStop(this.itemId) ? 0 : -1,
8842
+ // The owner keeps the pointer (canvas handling or an underlying
8843
+ // selectable-text mirror); this hotspot exists for semantics + keyboard
8844
+ // focus, so it opts out of hit-testing. Keyboard focus and
8845
+ // AT-synthesized `click` still work under `pointer-events:none`.
8846
+ pointerEvents: "none"
8847
+ };
8848
+ }
8849
+ render() {
8850
+ }
8851
+ /** Axis-aligned hit-test against the hotspot slot (the `UIComponent` default). */
8852
+ isPointInside(globalX, globalY) {
8853
+ const local = this.worldToLocal(globalX, globalY);
8854
+ if (!local) return false;
8855
+ return local.x >= 0 && local.x <= this.width && local.y >= 0 && local.y <= this.height;
8856
+ }
8857
+ }, _class18);
8858
+
8037
8859
  // src/components/GridTextEntity.ts
8038
- var GridTextEntity = (_class17 = class extends _chunkPOPXUPKRjs.Entity {
8860
+ var GridTextEntity = (_class19 = class extends _chunkHC5EZOZJjs.Entity {
8039
8861
 
8040
- __init199() {this.fillStyle = "#ffffff"}
8041
- __init200() {this.grid = []}
8862
+ __init219() {this.fillStyle = "#ffffff"}
8863
+ __init220() {this.grid = []}
8042
8864
  // Array of rows
8043
- __init201() {this.cols = 0}
8044
- __init202() {this.rows = 0}
8865
+ __init221() {this.cols = 0}
8866
+ __init222() {this.rows = 0}
8045
8867
 
8046
8868
 
8047
8869
  constructor(_atlas, fontSize = 10) {
8048
- super();_class17.prototype.__init199.call(this);_class17.prototype.__init200.call(this);_class17.prototype.__init201.call(this);_class17.prototype.__init202.call(this);;
8870
+ super();_class19.prototype.__init219.call(this);_class19.prototype.__init220.call(this);_class19.prototype.__init221.call(this);_class19.prototype.__init222.call(this);;
8049
8871
  this.fontSize = fontSize;
8050
8872
  this.charWidth = fontSize * 1;
8051
8873
  this.charHeight = fontSize * 1.1;
@@ -8054,7 +8876,7 @@ var GridTextEntity = (_class17 = class extends _chunkPOPXUPKRjs.Entity {
8054
8876
  updateGrid(ascii) {
8055
8877
  this.grid = ascii;
8056
8878
  this.rows = ascii.length;
8057
- this.cols = ascii.reduce((widest, row) => Math.max(widest, _nullishCoalesce(_optionalChain([row, 'optionalAccess', _193 => _193.length]), () => ( 0))), 0);
8879
+ this.cols = ascii.reduce((widest, row) => Math.max(widest, _nullishCoalesce(_optionalChain([row, 'optionalAccess', _224 => _224.length]), () => ( 0))), 0);
8058
8880
  }
8059
8881
  isPointInside(_globalX, _globalY) {
8060
8882
  return false;
@@ -8076,7 +8898,7 @@ var GridTextEntity = (_class17 = class extends _chunkPOPXUPKRjs.Entity {
8076
8898
  }
8077
8899
  }
8078
8900
  }
8079
- }, _class17);
8901
+ }, _class19);
8080
8902
 
8081
8903
  // src/components/SplineEntity.ts
8082
8904
  function polySegmentToBezier(seg) {
@@ -8129,7 +8951,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
8129
8951
  const ey = py - cy;
8130
8952
  return ex * ex + ey * ey;
8131
8953
  }
8132
- var SplineEntity = (_class18 = class extends _chunkPOPXUPKRjs.Entity {
8954
+ var SplineEntity = (_class20 = class extends _chunkHC5EZOZJjs.Entity {
8133
8955
 
8134
8956
 
8135
8957
 
@@ -8137,17 +8959,17 @@ var SplineEntity = (_class18 = class extends _chunkPOPXUPKRjs.Entity {
8137
8959
 
8138
8960
 
8139
8961
 
8140
- __init203() {this.offscreen = null}
8141
- __init204() {this.baked = false}
8962
+ __init223() {this.offscreen = null}
8963
+ __init224() {this.baked = false}
8142
8964
  /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
8143
- __init205() {this.bakedWidth = 0}
8144
- __init206() {this.bakedHeight = 0}
8965
+ __init225() {this.bakedWidth = 0}
8966
+ __init226() {this.bakedHeight = 0}
8145
8967
  /**
8146
8968
  * Device-pixel ratio the bitmap was rasterized at. Tracked so a DPR change
8147
8969
  * (browser zoom, a monitor move, a renderer whose clamp changed) re-bakes
8148
8970
  * instead of blitting a bitmap at the wrong device density forever.
8149
8971
  */
8150
- __init207() {this.bakedDPR = 0}
8972
+ __init227() {this.bakedDPR = 0}
8151
8973
  /**
8152
8974
  * Gradient strokes can't be baked to a solid-color bitmap; they render
8153
8975
  * per-frame. Derived from `_doc`, so it is NOT readonly — assigning a new
@@ -8155,7 +8977,7 @@ var SplineEntity = (_class18 = class extends _chunkPOPXUPKRjs.Entity {
8155
8977
  */
8156
8978
 
8157
8979
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
8158
- __init208() {this.polylines = null}
8980
+ __init228() {this.polylines = null}
8159
8981
  /**
8160
8982
  * The spline document to render. Assigning a new document invalidates all
8161
8983
  * caches (baked canvas, flattened polylines, bounds).
@@ -8189,9 +9011,9 @@ var SplineEntity = (_class18 = class extends _chunkPOPXUPKRjs.Entity {
8189
9011
  * local bounds after painting the curves. Useful for drag feedback and
8190
9012
  * debugging hit areas. Defaults to `false`.
8191
9013
  */
8192
- __init209() {this.showBounds = false}
9014
+ __init229() {this.showBounds = false}
8193
9015
  constructor(doc, opts = {}) {
8194
- super();_class18.prototype.__init203.call(this);_class18.prototype.__init204.call(this);_class18.prototype.__init205.call(this);_class18.prototype.__init206.call(this);_class18.prototype.__init207.call(this);_class18.prototype.__init208.call(this);_class18.prototype.__init209.call(this);;
9016
+ super();_class20.prototype.__init223.call(this);_class20.prototype.__init224.call(this);_class20.prototype.__init225.call(this);_class20.prototype.__init226.call(this);_class20.prototype.__init227.call(this);_class20.prototype.__init228.call(this);_class20.prototype.__init229.call(this);;
8195
9017
  this._doc = doc;
8196
9018
  this._lineWidth = _nullishCoalesce(opts.lineWidth, () => ( 2));
8197
9019
  this.cache = _nullishCoalesce(opts.cache, () => ( true));
@@ -8211,7 +9033,7 @@ var SplineEntity = (_class18 = class extends _chunkPOPXUPKRjs.Entity {
8211
9033
  */
8212
9034
  computeContainsGradient() {
8213
9035
  const isGradient = (c) => c !== null && !Array.isArray(c);
8214
- return (_nullishCoalesce(_optionalChain([this, 'access', _194 => _194._doc, 'access', _195 => _195.equations, 'optionalAccess', _196 => _196.some, 'call', _197 => _197((eq) => isGradient(eq.color_rgb))]), () => ( false))) || (_nullishCoalesce(_optionalChain([this, 'access', _198 => _198._doc, 'access', _199 => _199.paths, 'optionalAccess', _200 => _200.some, 'call', _201 => _201((p) => isGradient(p.color_rgb))]), () => ( false)));
9036
+ return (_nullishCoalesce(_optionalChain([this, 'access', _225 => _225._doc, 'access', _226 => _226.equations, 'optionalAccess', _227 => _227.some, 'call', _228 => _228((eq) => isGradient(eq.color_rgb))]), () => ( false))) || (_nullishCoalesce(_optionalChain([this, 'access', _229 => _229._doc, 'access', _230 => _230.paths, 'optionalAccess', _231 => _231.some, 'call', _232 => _232((p) => isGradient(p.color_rgb))]), () => ( false)));
8215
9037
  }
8216
9038
  /** Clear cached baked canvas and hit-test polylines. */
8217
9039
  invalidateCache() {
@@ -8450,14 +9272,14 @@ var SplineEntity = (_class18 = class extends _chunkPOPXUPKRjs.Entity {
8450
9272
  r.stroke("rgba(0, 150, 255, 0.8)", 2);
8451
9273
  }
8452
9274
  }
8453
- }, _class18);
9275
+ }, _class20);
8454
9276
  async function loadSpline(url) {
8455
9277
  const res = await fetch(url);
8456
9278
  return await res.json();
8457
9279
  }
8458
9280
 
8459
9281
  // src/components/Rect.ts
8460
- var Rect = class extends _chunkPOPXUPKRjs.Entity {
9282
+ var Rect = class extends _chunkHC5EZOZJjs.Entity {
8461
9283
 
8462
9284
 
8463
9285
 
@@ -8513,7 +9335,7 @@ var Rect = class extends _chunkPOPXUPKRjs.Entity {
8513
9335
  };
8514
9336
 
8515
9337
  // src/components/Circle.ts
8516
- var Circle = class extends _chunkPOPXUPKRjs.Entity {
9338
+ var Circle = class extends _chunkHC5EZOZJjs.Entity {
8517
9339
 
8518
9340
 
8519
9341
 
@@ -8577,7 +9399,7 @@ var Circle = class extends _chunkPOPXUPKRjs.Entity {
8577
9399
  };
8578
9400
 
8579
9401
  // src/components/Group.ts
8580
- var Group = class extends _chunkPOPXUPKRjs.Entity {
9402
+ var Group = class extends _chunkHC5EZOZJjs.Entity {
8581
9403
  constructor(...children) {
8582
9404
  super();
8583
9405
  if (children.length > 0) this.add(...children);
@@ -8596,21 +9418,21 @@ var _math = require('@vectojs/math'); _createStarExport(_math);
8596
9418
 
8597
9419
 
8598
9420
  // src/tree/DOMPortalEntity.ts
8599
- var DOMPortalEntity = (_class19 = class extends _chunkPOPXUPKRjs.Entity {
9421
+ var DOMPortalEntity = (_class21 = class extends _chunkHC5EZOZJjs.Entity {
8600
9422
 
8601
- __init210() {this.isDOMPortal = true}
8602
- __init211() {this.domListeners = []}
8603
- __init212() {this.resizeObserver = null}
8604
- __init213() {this.domBound = false}
8605
- __init214() {this.cachedWidth = 100}
8606
- __init215() {this.cachedHeight = 100}
8607
- __init216() {this.lastWidth = ""}
8608
- __init217() {this.lastHeight = ""}
8609
- __init218() {this.lastTransform = ""}
8610
- __init219() {this.lastZIndex = ""}
8611
- __init220() {this.lastOpacity = ""}
9423
+ __init230() {this.isDOMPortal = true}
9424
+ __init231() {this.domListeners = []}
9425
+ __init232() {this.resizeObserver = null}
9426
+ __init233() {this.domBound = false}
9427
+ __init234() {this.cachedWidth = 100}
9428
+ __init235() {this.cachedHeight = 100}
9429
+ __init236() {this.lastWidth = ""}
9430
+ __init237() {this.lastHeight = ""}
9431
+ __init238() {this.lastTransform = ""}
9432
+ __init239() {this.lastZIndex = ""}
9433
+ __init240() {this.lastOpacity = ""}
8612
9434
  constructor(domElement, width, height, id) {
8613
- super(id);_class19.prototype.__init210.call(this);_class19.prototype.__init211.call(this);_class19.prototype.__init212.call(this);_class19.prototype.__init213.call(this);_class19.prototype.__init214.call(this);_class19.prototype.__init215.call(this);_class19.prototype.__init216.call(this);_class19.prototype.__init217.call(this);_class19.prototype.__init218.call(this);_class19.prototype.__init219.call(this);_class19.prototype.__init220.call(this);;
9435
+ super(id);_class21.prototype.__init230.call(this);_class21.prototype.__init231.call(this);_class21.prototype.__init232.call(this);_class21.prototype.__init233.call(this);_class21.prototype.__init234.call(this);_class21.prototype.__init235.call(this);_class21.prototype.__init236.call(this);_class21.prototype.__init237.call(this);_class21.prototype.__init238.call(this);_class21.prototype.__init239.call(this);_class21.prototype.__init240.call(this);;
8614
9436
  this.domElement = domElement;
8615
9437
  this.width = _nullishCoalesce(width, () => ( 0));
8616
9438
  this.height = _nullishCoalesce(height, () => ( 0));
@@ -8652,7 +9474,7 @@ var DOMPortalEntity = (_class19 = class extends _chunkPOPXUPKRjs.Entity {
8652
9474
  ];
8653
9475
  for (const type of events) {
8654
9476
  const handler = (e) => {
8655
- this.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)(type, this, e));
9477
+ this.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)(type, this, e));
8656
9478
  };
8657
9479
  this.domElement.addEventListener(type, handler);
8658
9480
  this.domListeners.push({ type, handler, capture: false });
@@ -8663,7 +9485,7 @@ var DOMPortalEntity = (_class19 = class extends _chunkPOPXUPKRjs.Entity {
8663
9485
  ];
8664
9486
  for (const { native, vecto } of hoverEvents) {
8665
9487
  const handler = (e) => {
8666
- this.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)(vecto, this, e, false));
9488
+ this.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)(vecto, this, e, false));
8667
9489
  };
8668
9490
  this.domElement.addEventListener(native, handler);
8669
9491
  this.domListeners.push({ type: native, handler, capture: false });
@@ -8671,7 +9493,7 @@ var DOMPortalEntity = (_class19 = class extends _chunkPOPXUPKRjs.Entity {
8671
9493
  const focusEvents = ["focus", "blur"];
8672
9494
  for (const type of focusEvents) {
8673
9495
  const handler = (e) => {
8674
- this.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)(type, this, e, true));
9496
+ this.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)(type, this, e, true));
8675
9497
  };
8676
9498
  this.domElement.addEventListener(type, handler, true);
8677
9499
  this.domListeners.push({ type, handler, capture: true });
@@ -8719,7 +9541,7 @@ var DOMPortalEntity = (_class19 = class extends _chunkPOPXUPKRjs.Entity {
8719
9541
  }
8720
9542
  super.destroy();
8721
9543
  }
8722
- }, _class19);
9544
+ }, _class21);
8723
9545
 
8724
9546
  // src/index.ts
8725
9547
  Scene.registerWebGLPointRendererCreator(_chunkBKXERWADjs.createWebGLPointRenderer);
@@ -8772,4 +9594,16 @@ Scene.registerWebGPUParticleSystemManager(_chunkBKXERWADjs.WebGPUParticleSystemM
8772
9594
 
8773
9595
 
8774
9596
 
8775
- exports.CanvasRenderer = _chunkBKXERWADjs.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DEFAULT_CONTENT_SEMANTIC_BUDGET = DEFAULT_CONTENT_SEMANTIC_BUDGET; exports.DOMPortalEntity = DOMPortalEntity; exports.Entity = _chunkPOPXUPKRjs.Entity; exports.GlyphRasterAtlas = _chunkBKXERWADjs.GlyphRasterAtlas; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.KEYBOARD_OWNING_ROLES = KEYBOARD_OWNING_ROLES; exports.MSDFTextEntity = _chunkPOPXUPKRjs.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.SCENE_OPTION_KEYS = SCENE_OPTION_KEYS; exports.SVGEntity = _chunkPOPXUPKRjs.SVGEntity; exports.SVGRenderer = _chunkBKXERWADjs.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkBKXERWADjs.TextRasterCache; exports.VECTO_USER_TIMING = VECTO_USER_TIMING; exports.VectoJSEvent = _chunkPOPXUPKRjs.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkBKXERWADjs.WebGPUParticleSystemManager; exports.beginVectoUserTiming = beginVectoUserTiming; exports.contentLineInHint = _chunkPOPXUPKRjs.contentLineInHint; exports.createWebGLPointRenderer = _chunkBKXERWADjs.createWebGLPointRenderer; exports.endVectoUserTiming = endVectoUserTiming; exports.installRendererDevTraps = _chunkBKXERWADjs.installRendererDevTraps; exports.isRendererDevMode = _chunkBKXERWADjs.isRendererDevMode; exports.isSafeUrl = _chunkBKXERWADjs.isSafeUrl; exports.loadSpline = loadSpline; exports.measureVectoUserTiming = measureVectoUserTiming; exports.normalizeChord = normalizeChord; exports.ownsKeyboard = ownsKeyboard; exports.parseColorToRGBA = _chunkBKXERWADjs.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkBKXERWADjs.sanitizeUrl; exports.setRendererDevMode = _chunkBKXERWADjs.setRendererDevMode;
9597
+
9598
+
9599
+
9600
+
9601
+
9602
+
9603
+
9604
+
9605
+
9606
+
9607
+
9608
+
9609
+ exports.CanvasRenderer = _chunkBKXERWADjs.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DEFAULT_CONTENT_SEMANTIC_BUDGET = DEFAULT_CONTENT_SEMANTIC_BUDGET; exports.DEFAULT_PROJECTION_CAPABILITIES = DEFAULT_PROJECTION_CAPABILITIES; exports.DEFAULT_SEMANTIC_PROJECTION_POLICY = DEFAULT_SEMANTIC_PROJECTION_POLICY; exports.DOMPortalEntity = DOMPortalEntity; exports.Entity = _chunkHC5EZOZJjs.Entity; exports.GlyphRasterAtlas = _chunkBKXERWADjs.GlyphRasterAtlas; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.KEYBOARD_OWNING_ROLES = KEYBOARD_OWNING_ROLES; exports.MSDFTextEntity = _chunkHC5EZOZJjs.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.PROJECTION_AUTO_DOM_BUDGET = PROJECTION_AUTO_DOM_BUDGET; exports.PROJECTION_AUTO_HYSTERESIS_FRAMES = PROJECTION_AUTO_HYSTERESIS_FRAMES; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.Rect = Rect; exports.SCENE_OPTION_KEYS = SCENE_OPTION_KEYS; exports.SVGEntity = _chunkHC5EZOZJjs.SVGEntity; exports.SVGRenderer = _chunkBKXERWADjs.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkBKXERWADjs.TextRasterCache; exports.UNKNOWN_PROJECTION_CAPABILITY = UNKNOWN_PROJECTION_CAPABILITY; exports.VECTO_USER_TIMING = VECTO_USER_TIMING; exports.VectoJSEvent = _chunkHC5EZOZJjs.VectoJSEvent; exports.VirtualizedSetAggregate = VirtualizedSetAggregate; exports.WebGPUParticleSystemManager = _chunkBKXERWADjs.WebGPUParticleSystemManager; exports.beginVectoUserTiming = beginVectoUserTiming; exports.contentLineInHint = _chunkHC5EZOZJjs.contentLineInHint; exports.createWebGLPointRenderer = _chunkBKXERWADjs.createWebGLPointRenderer; exports.describeHTMLInCanvasSupport = describeHTMLInCanvasSupport; exports.endVectoUserTiming = endVectoUserTiming; exports.getSemanticProjectionCapabilities = getSemanticProjectionCapabilities; exports.hysteresisVote = hysteresisVote; exports.installRendererDevTraps = _chunkBKXERWADjs.installRendererDevTraps; exports.isDeferrableSemanticNode = isDeferrableSemanticNode; exports.isRendererDevMode = _chunkBKXERWADjs.isRendererDevMode; exports.isSafeUrl = _chunkBKXERWADjs.isSafeUrl; exports.loadSpline = loadSpline; exports.measureVectoUserTiming = measureVectoUserTiming; exports.normalizeChord = normalizeChord; exports.ownsKeyboard = ownsKeyboard; exports.parseColorToRGBA = _chunkBKXERWADjs.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.resolveProjection = resolveProjection; exports.sanitizeUrl = _chunkBKXERWADjs.sanitizeUrl; exports.setRendererDevMode = _chunkBKXERWADjs.setRendererDevMode; exports.supportsHTMLInCanvas = supportsHTMLInCanvas;