@vectojs/core 1.39.0 → 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
 
@@ -12,14 +12,14 @@
12
12
 
13
13
 
14
14
 
15
- var _chunkKRUDTGNRjs = require('./chunk-KRUDTGNR.js');
15
+ var _chunkBKXERWADjs = require('./chunk-BKXERWAD.js');
16
16
 
17
17
 
18
18
 
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
 
@@ -1070,8 +1070,11 @@ var CanvasGeometry = (_class3 = class {
1070
1070
  /** Effective device pixel ratio, matching CanvasRenderer: real DPR clamped to
1071
1071
  * `maxDPR` when set. */
1072
1072
  effectiveDPR(maxDPR) {
1073
- const real = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
1074
- return maxDPR !== void 0 ? Math.min(real, maxDPR) : real;
1073
+ const raw = typeof window !== "undefined" ? window.devicePixelRatio : 1;
1074
+ const real = Number.isFinite(raw) && raw > 0 ? raw : 1;
1075
+ if (maxDPR === void 0) return real;
1076
+ if (!Number.isFinite(maxDPR) || maxDPR <= 0) return real;
1077
+ return Math.min(real, maxDPR);
1075
1078
  }
1076
1079
  /** Size the WebGPU particle canvas: backing store at logical × DPR, CSS box at
1077
1080
  * the logical size. Sizing the backing store in logical px (the old
@@ -1596,6 +1599,62 @@ var HitTester = (_class6 = class {
1596
1599
  findEntityAt(x, y, frame, width, height) {
1597
1600
  const overlayHit = this.findHitRecursively(this.overlayRoot, x, y);
1598
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) {
1599
1658
  if (this.backends.hit && this.ensureHitGrid(frame, width, height)) {
1600
1659
  return this.findEntityAtWasm(x, y);
1601
1660
  }
@@ -3499,6 +3558,207 @@ function normalizeChord(input) {
3499
3558
  return ordered.join("+");
3500
3559
  }
3501
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
+
3502
3762
  // src/tree/Scene.ts
3503
3763
  var RANGE_VALUE_ROLES = /* @__PURE__ */ new Set(["slider", "spinbutton", "progressbar", "scrollbar", "meter"]);
3504
3764
  var INTERACTIVE_A11Y_ROLES = /* @__PURE__ */ new Set([
@@ -3522,7 +3782,8 @@ var KEYBOARD_OWNING_ROLES = /* @__PURE__ */ new Set([
3522
3782
  ]);
3523
3783
  function ownsKeyboard(el) {
3524
3784
  if (!el) return false;
3525
- if (el === document.body || el === document.documentElement) return false;
3785
+ if (typeof document !== "undefined" && (el === document.body || el === document.documentElement))
3786
+ return false;
3526
3787
  if (el.hasAttribute("data-vecto-a11y-root")) return false;
3527
3788
  const tag = el.tagName;
3528
3789
  if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
@@ -3562,6 +3823,7 @@ var SCENE_OPTION_KEYS = [
3562
3823
  "renderer",
3563
3824
  "renderMode",
3564
3825
  "respectReducedMotion",
3826
+ "semanticProjectionPolicy",
3565
3827
  "userTiming"
3566
3828
  ];
3567
3829
  var SCENE_FIELD_NOT_OPTION = {
@@ -3678,9 +3940,9 @@ function parseCssMatrix(transform) {
3678
3940
  }
3679
3941
  function clientToGridLocal(contentEl, canvas, clientX, clientY) {
3680
3942
  const line = contentEl.querySelector("[data-vecto-grid-line]");
3681
- const originMarker = _optionalChain([line, 'optionalAccess', _86 => _86.querySelector, 'call', _87 => _87('[data-vecto-grid-basis="origin"]')]);
3682
- const xMarker = _optionalChain([line, 'optionalAccess', _88 => _88.querySelector, 'call', _89 => _89('[data-vecto-grid-basis="x"]')]);
3683
- 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"]')]);
3684
3946
  if (line && originMarker && xMarker && yMarker) {
3685
3947
  const origin = originMarker.getBoundingClientRect();
3686
3948
  const xPoint = xMarker.getBoundingClientRect();
@@ -3764,7 +4026,7 @@ function nearestTextPositionInLine(line, x, y) {
3764
4026
  nearest = { position: { node, offset }, distance: candidate.distance };
3765
4027
  }
3766
4028
  }
3767
- return _nullishCoalesce(_optionalChain([nearest, 'optionalAccess', _92 => _92.position]), () => ( null));
4029
+ return _nullishCoalesce(_optionalChain([nearest, 'optionalAccess', _95 => _95.position]), () => ( null));
3768
4030
  }
3769
4031
  function nearestTextPositionInProjection(contentEl, canvas, x, y, eventTarget) {
3770
4032
  if (contentEl.dataset.vectoContentGrid !== void 0) {
@@ -3778,7 +4040,7 @@ function nearestTextPositionInProjection(contentEl, canvas, x, y, eventTarget) {
3778
4040
  }
3779
4041
  targetLine = targetLine.parentElement;
3780
4042
  }
3781
- if (_optionalChain([targetLine, 'optionalAccess', _93 => _93.parentElement]) === contentEl) {
4043
+ if (_optionalChain([targetLine, 'optionalAccess', _96 => _96.parentElement]) === contentEl) {
3782
4044
  return nearestTextPositionInLine(targetLine, x, y);
3783
4045
  }
3784
4046
  let bestLine = null;
@@ -3840,13 +4102,13 @@ function extendSelection(selection, anchor, focus) {
3840
4102
  try {
3841
4103
  selection.setBaseAndExtent(anchor.node, anchor.offset, focus.node, focus.offset);
3842
4104
  return;
3843
- } catch (e15) {
4105
+ } catch (e16) {
3844
4106
  }
3845
4107
  try {
3846
4108
  selection.collapse(anchor.node, anchor.offset);
3847
4109
  selection.extend(focus.node, focus.offset);
3848
4110
  return;
3849
- } catch (e16) {
4111
+ } catch (e17) {
3850
4112
  }
3851
4113
  const anchorRange = document.createRange();
3852
4114
  anchorRange.setStart(anchor.node, anchor.offset);
@@ -4040,7 +4302,7 @@ var Scene = (_class15 = class _Scene {
4040
4302
  __init108() {this.forcedColorsChangeHandler = null}
4041
4303
  /** True when the OS asks for reduced motion and we respect it. Read by the animation drivers. */
4042
4304
  get prefersReducedMotion() {
4043
- 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]);
4044
4306
  }
4045
4307
  /**
4046
4308
  * True when the OS is in a forced-colors mode (Windows High Contrast, and the
@@ -4051,7 +4313,7 @@ var Scene = (_class15 = class _Scene {
4051
4313
  * when the setting toggles.
4052
4314
  */
4053
4315
  get forcedColors() {
4054
- return !!_optionalChain([this, 'access', _96 => _96.forcedColorsQuery, 'optionalAccess', _97 => _97.matches]);
4316
+ return !!_optionalChain([this, 'access', _99 => _99.forcedColorsQuery, 'optionalAccess', _100 => _100.matches]);
4055
4317
  }
4056
4318
  // --- domain: a11y-projection — sync throttle and shadow-DOM state ---
4057
4319
  /**
@@ -4059,21 +4321,41 @@ var Scene = (_class15 = class _Scene {
4059
4321
  * frame. See {@link SceneOptions.a11ySyncInterval}.
4060
4322
  */
4061
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}
4062
4344
  /** Timestamp of the last a11y sync, for throttling. */
4063
- __init110() {this.lastA11ySync = -Infinity}
4345
+ __init113() {this.lastA11ySync = -Infinity}
4064
4346
  /** True if we skipped an a11y sync during animation and need to sync when at rest. */
4065
- __init111() {this.a11yPendingSyncAfterAnimation = false}
4347
+ __init114() {this.a11yPendingSyncAfterAnimation = false}
4066
4348
  // A11y / Automation Layer. `null` in non-DOM (SSR/Node) environments — the
4067
4349
  // whole projection degrades to a no-op so the engine's logic stays usable
4068
4350
  // server-side (e.g. headless layout / vector export) without jsdom.
4069
4351
 
4070
- __init112() {this.a11yElements = /* @__PURE__ */ new Map()}
4352
+ __init115() {this.a11yElements = /* @__PURE__ */ new Map()}
4071
4353
  /** Elements that already have the synthetic Enter/Space activation handler
4072
4354
  * (see the syncA11y refresh pass) — install-once bookkeeping (#694). */
4073
- __init113() {this.keyboardActivatedA11y = /* @__PURE__ */ new WeakSet()}
4355
+ __init116() {this.keyboardActivatedA11y = /* @__PURE__ */ new WeakSet()}
4074
4356
  // --- domain: content-projection — projected DOM, sync state, calibration, selection ---
4075
4357
  /** DOM nodes mirroring static text content, keyed by entity id. */
4076
- __init114() {this.contentElements = /* @__PURE__ */ new Map()}
4358
+ __init117() {this.contentElements = /* @__PURE__ */ new Map()}
4077
4359
  /**
4078
4360
  * What the last completed content-projection sync was built from, per entity.
4079
4361
  *
@@ -4082,9 +4364,9 @@ var Scene = (_class15 = class _Scene {
4082
4364
  * build. Only populated for entities that opt in via
4083
4365
  * {@link Entity.getContentEpoch}. (carryctx CTX-0199)
4084
4366
  */
4085
- __init115() {this.contentSyncState = /* @__PURE__ */ new Map()}
4367
+ __init118() {this.contentSyncState = /* @__PURE__ */ new Map()}
4086
4368
  /** Invalidates grid font calibration after browser font availability changes. */
4087
- __init116() {this.contentFontEpoch = 0}
4369
+ __init119() {this.contentFontEpoch = 0}
4088
4370
  /**
4089
4371
  * Bumped whenever the viewport itself changes shape, which re-tiers blocks
4090
4372
  * without moving any of them.
@@ -4097,7 +4379,7 @@ var Scene = (_class15 = class _Scene {
4097
4379
  * carriers it no longer needs. Scrolling needs no such epoch: it moves the root,
4098
4380
  * so every block's parent world transform changes and the check fails honestly.
4099
4381
  */
4100
- __init117() {this.contentViewportEpoch = 0}
4382
+ __init120() {this.contentViewportEpoch = 0}
4101
4383
  /**
4102
4384
  * One-entry memo for a parent's world transform, held as scalars.
4103
4385
  *
@@ -4112,16 +4394,16 @@ var Scene = (_class15 = class _Scene {
4112
4394
  * sync, so a memo can never survive into a frame in which the parent has
4113
4395
  * moved.
4114
4396
  */
4115
- __init118() {this._pwNode = null}
4116
- __init119() {this._pwSerial = -1}
4117
- __init120() {this._pwa = 1}
4118
- __init121() {this._pwb = 0}
4119
- __init122() {this._pwc = 0}
4120
- __init123() {this._pwd = 1}
4121
- __init124() {this._pwe = 0}
4122
- __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}
4123
4405
  /** Incremented once per top-level `syncA11y`, invalidating `_pwNode`. */
4124
- __init126() {this._syncSerial = 0}
4406
+ __init129() {this._syncSerial = 0}
4125
4407
  /**
4126
4408
  * Disables the settled-walk fast path in {@link syncContentProjection}.
4127
4409
  *
@@ -4133,38 +4415,38 @@ var Scene = (_class15 = class _Scene {
4133
4415
  * Not part of the public API and not documented as an option: turning it on only
4134
4416
  * makes a settled document slower, never more correct.
4135
4417
  */
4136
- __init127() {this.disableSettledFastPath = false}
4418
+ __init130() {this.disableSettledFastPath = false}
4137
4419
  /** Cached Canvas-to-client scale for the current font/viewport epoch. */
4138
- __init128() {this.contentMetricScaleEpoch = -1}
4139
- __init129() {this.contentMetricScaleX = 1}
4140
- __init130() {this.contentProjectionEnabled = true}
4420
+ __init131() {this.contentMetricScaleEpoch = -1}
4421
+ __init132() {this.contentMetricScaleX = 1}
4422
+ __init133() {this.contentProjectionEnabled = true}
4141
4423
  // Virtualization margin (px) for the content-projection CARRIER band;
4142
4424
  // `undefined` → one viewport height, resolved at sync time. `Infinity` is
4143
4425
  // unsupported here: it unwindows every carrier, which is O(total glyphs).
4144
- __init131() {this.contentProjectionMargin = void 0}
4426
+ __init134() {this.contentProjectionMargin = void 0}
4145
4427
  // Virtualization margin (px) for the SEMANTIC tier — whether a block has
4146
4428
  // any projected DOM. `undefined` → falls back to contentProjectionMargin, so
4147
4429
  // the default keeps one gate. `Infinity` = every block keeps resident text.
4148
- __init132() {this.contentSemanticMargin = void 0}
4430
+ __init135() {this.contentSemanticMargin = void 0}
4149
4431
  // How many coarse-tier blocks may be MATERIALIZED per sync, spreading the
4150
4432
  // resident tier's document-open cost across frames. `Infinity` = one
4151
4433
  // synchronous pass.
4152
- __init133() {this.contentSemanticBudget = DEFAULT_CONTENT_SEMANTIC_BUDGET}
4434
+ __init136() {this.contentSemanticBudget = DEFAULT_CONTENT_SEMANTIC_BUDGET}
4153
4435
  // Remaining materializations in the CURRENT sync. Reset at the start of each
4154
4436
  // a11y walk; decremented per coarse block that creates its element.
4155
- __init134() {this.contentSemanticBudgetLeft = 0}
4437
+ __init137() {this.contentSemanticBudgetLeft = 0}
4156
4438
  // Set when a sync deferred at least one block, so the scene knows to keep
4157
4439
  // drawing frames until the resident tier is complete. Without it a static
4158
4440
  // scene in `onDemand` mode would stop rendering with the document half
4159
4441
  // materialized and never finish.
4160
- __init135() {this.contentSemanticDeferred = false}
4161
- __init136() {this.contentSelectionEndListener = null}
4442
+ __init138() {this.contentSemanticDeferred = false}
4443
+ __init139() {this.contentSelectionEndListener = null}
4162
4444
  // --- domain: render-scheduler — per-frame walk flags ---
4163
4445
  // Animation/interactive flags collected during the render walk (tree-walk
4164
4446
  // fusion): the loop reads last frame's answers instead of re-walking the
4165
4447
  // tree up to 4× per tick. Start true so the first tick stays conservative.
4166
- __init137() {this.frameHadAnimation = true}
4167
- __init138() {this.frameHadInteractive = true}
4448
+ __init140() {this.frameHadAnimation = true}
4449
+ __init141() {this.frameHadInteractive = true}
4168
4450
  // --- domain: context-and-resize — resize handler, DPR query, canvas observer ---
4169
4451
 
4170
4452
  /** Active `(resolution: Ndppx)` media query watching for a runtime DPR change
@@ -4172,15 +4454,17 @@ var Scene = (_class15 = class _Scene {
4172
4454
  * can be re-scaled — otherwise it stays rasterized at the old DPR and blurs.
4173
4455
  * A resolution media query only fires when leaving its exact value, so the
4174
4456
  * handler re-arms a fresh query for the new DPR each time. */
4175
- __init139() {this.dprMediaQuery = null}
4457
+ __init142() {this.dprMediaQuery = null}
4176
4458
  /** For embedded (`disableWindowResize`) scenes: observes the canvas element so
4177
4459
  * a CSS/layout-driven size change re-runs `resize()`. A window `resize`
4178
4460
  * listener never fires for these (the window isn't what changed), so without
4179
4461
  * this an embedded canvas stayed at its initial size forever. */
4180
- __init140() {this.canvasResizeObserver = null}
4181
- __init141() {this.dprChangeHandler = null}
4462
+ __init143() {this.canvasResizeObserver = null}
4463
+ __init144() {this.dprChangeHandler = null}
4464
+ __init145() {this.dprPollInterval = null}
4465
+ __init146() {this.lastDpr = 1}
4182
4466
  // --- domain: a11y-projection — focus, overlay geometry, DOM ordering, portals ---
4183
- __init142() {this.focusedA11yElement = null}
4467
+ __init147() {this.focusedA11yElement = null}
4184
4468
  /**
4185
4469
  * Canvas box geometry: the CSS↔logical mapping, overlay layer alignment, and
4186
4470
  * the DPR math (extraction 5, `DEC-0024`).
@@ -4207,7 +4491,7 @@ var Scene = (_class15 = class _Scene {
4207
4491
  /** Shadow elements the pointer is currently inside. Lets a removal that happens
4208
4492
  * mid-hover synthesize the `pointerleave` the browser never sends for a
4209
4493
  * detached element, so the entity doesn't keep its hover state. */
4210
- __init143() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
4494
+ __init148() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
4211
4495
  /**
4212
4496
  * Entity ids the application has pinned via {@link requestA11yProjection}.
4213
4497
  *
@@ -4215,13 +4499,13 @@ var Scene = (_class15 = class _Scene {
4215
4499
  * a stale id simply never matches. Cleared per-entity by
4216
4500
  * {@link releaseA11yProjection}.
4217
4501
  */
4218
- __init144() {this.a11yProjectionRequests = /* @__PURE__ */ new Set()}
4502
+ __init149() {this.a11yProjectionRequests = /* @__PURE__ */ new Set()}
4219
4503
  /** Persistent tabindex=-1 element in a11yRoot. When the focused a11y mirror is
4220
4504
  * pruned (virtualization/streaming/removal) while it holds focus, we move
4221
4505
  * focus here instead of letting the browser drop it to <body> — keeping the
4222
4506
  * screen-reader virtual cursor inside the scene's a11y region. */
4223
- __init145() {this.focusSentinel = null}
4224
- __init146() {this.caretBlinkTimer = null}
4507
+ __init150() {this.focusSentinel = null}
4508
+ __init151() {this.caretBlinkTimer = null}
4225
4509
  /**
4226
4510
  * The projected DOM's ordering engine — the reorder flag, the per-pass scratch
4227
4511
  * collections, the visual reading-order sort and the cursor-based
@@ -4231,7 +4515,7 @@ var Scene = (_class15 = class _Scene {
4231
4515
  * delegate here; the collect-and-prune walk stays behind because it needs
4232
4516
  * {@link shouldProjectA11y} and the focus/caret state.
4233
4517
  */
4234
- __init147() {this.a11yOrder = new A11yProjectionManager()}
4518
+ __init152() {this.a11yOrder = new A11yProjectionManager()}
4235
4519
  /**
4236
4520
  * The content projection's selection preservation and grid calibration
4237
4521
  * (extraction 3, `DEC-0022`).
@@ -4283,11 +4567,35 @@ var Scene = (_class15 = class _Scene {
4283
4567
  set a11yNeedsReorder(value) {
4284
4568
  this.a11yOrder.needsReorder = value;
4285
4569
  }
4286
- __init148() {this.portalRoot = null}
4287
- __init149() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
4288
- __init150() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
4289
- __init151() {this.portalEntities = /* @__PURE__ */ new Map()}
4290
- __init152() {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}
4291
4599
  // --- domain: render-scheduler — authoritative frame counter ---
4292
4600
  /**
4293
4601
  * Monotonic render-frame counter, bumped once per authoritative `render()`
@@ -4297,7 +4605,7 @@ var Scene = (_class15 = class _Scene {
4297
4605
  * back to the ancestor walk. Public for the same reason `Entity._getTrig`/
4298
4606
  * `_setWorldCache` are: it is a cross-class render-internal contract.
4299
4607
  */
4300
- __init153() {this.currentFrame = 0}
4608
+ __init168() {this.currentFrame = 0}
4301
4609
  // --- domain: wasm-backend-facade — EXTRACTED (see scene/WasmBackendFacade.ts) ---
4302
4610
  /**
4303
4611
  * The four invisible WASM accelerators and the resident transform store.
@@ -4367,8 +4675,8 @@ var Scene = (_class15 = class _Scene {
4367
4675
  // Stays on `Scene`: it walks `root` *and* `overlayRoot` for entities the
4368
4676
  // render walk consumes, so only its cache key is store state. The facade owns
4369
4677
  // the key; the walk stays here.
4370
- __init154() {this._computeEntities = []}
4371
- __init155() {this._computeEntitiesVersion = -1}
4678
+ __init169() {this._computeEntities = []}
4679
+ __init170() {this._computeEntitiesVersion = -1}
4372
4680
  /** Invalidate the resident WASM store layout; the next wasm-mode frame rebuilds
4373
4681
  * it. Called by `Entity.add`/`remove` (topology changes only). */
4374
4682
  markStructureChanged() {
@@ -4607,7 +4915,7 @@ var Scene = (_class15 = class _Scene {
4607
4915
  get animBatchedLastFrame() {
4608
4916
  return this._wasmBackend.animBatchedLastFrame;
4609
4917
  }
4610
- __init156() {this.animGate = {
4918
+ __init171() {this.animGate = {
4611
4919
  spring: 128,
4612
4920
  tween: 256,
4613
4921
  mixed: 128
@@ -4703,7 +5011,7 @@ var Scene = (_class15 = class _Scene {
4703
5011
  * `tickDrivers()`'s per-frame stamp would skip it a full frame — a one-frame
4704
5012
  * JS/WASM divergence. Written in {@link render} next to `_tickBatchedDrivers`.
4705
5013
  */
4706
- __init157() {this._updateWalkDt = null}
5014
+ __init172() {this._updateWalkDt = null}
4707
5015
  // --- domain: render-scheduler — batched driver tick (extraction 6), reads the anim backend ---
4708
5016
  /**
4709
5017
  * Advance every registered entity's active drivers for this frame, batching
@@ -4731,25 +5039,25 @@ var Scene = (_class15 = class _Scene {
4731
5039
  * sync, so retaining the order prevents a newly opened overlay from spending
4732
5040
  * its first frame below previously projected controls.
4733
5041
  */
4734
- __init158() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
5042
+ __init173() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
4735
5043
  // --- domain: context-and-resize — GL/GPU canvases, device state, pointer, size warnings ---
4736
5044
  // Optional WebGL point-cloud layer (see SceneOptions.pointBackend).
4737
- __init159() {this.pointRenderer = null}
4738
- __init160() {this.glCanvas = null}
4739
- __init161() {this.glContextLostHandler = null}
4740
- __init162() {this.glContextRestoredHandler = null}
5045
+ __init174() {this.pointRenderer = null}
5046
+ __init175() {this.glCanvas = null}
5047
+ __init176() {this.glContextLostHandler = null}
5048
+ __init177() {this.glContextRestoredHandler = null}
4741
5049
 
4742
5050
 
4743
5051
 
4744
- __init163() {this.disableWindowResize = false}
5052
+ __init178() {this.disableWindowResize = false}
4745
5053
  /** See {@link SceneOptions.maxDPR}. `undefined` = uncapped (real DPR). */
4746
5054
 
4747
5055
  // WebGPU properties
4748
- __init164() {this.destroyed = false}
4749
- __init165() {this.device = null}
4750
- __init166() {this.deviceLost = false}
4751
- __init167() {this.particleBackend = "auto"}
4752
- __init168() {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}
4753
5061
  get webgpuDisabled() {
4754
5062
  return this._webgpuDisabled || this.particleBackend === "cpu";
4755
5063
  }
@@ -4763,7 +5071,7 @@ var Scene = (_class15 = class _Scene {
4763
5071
  * active.
4764
5072
  */
4765
5073
  get webglDrawStats() {
4766
- 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));
4767
5075
  }
4768
5076
  /**
4769
5077
  * Whether a WebGPU device is currently live for particle compute.
@@ -4777,28 +5085,28 @@ var Scene = (_class15 = class _Scene {
4777
5085
  set webgpuDisabled(value) {
4778
5086
  this._webgpuDisabled = value;
4779
5087
  }
4780
- __init169() {this.recoveryTimerId = null}
4781
- __init170() {this.manager = null}
4782
- __init171() {this.initializingWebGPU = false}
4783
- __init172() {this.gpuCanvas = null}
4784
- __init173() {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}
4785
5093
  /** True while the GPU canvas holds a presented particle frame (needs clearing when they leave). */
4786
- __init174() {this.gpuHasContent = false}
4787
- __init175() {this.mouseX = -9999}
4788
- __init176() {this.mouseY = -9999}
4789
- __init177() {this.pointerMoveListener = null}
4790
- __init178() {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}
4791
5099
  /** Element the pointer listeners are bound to (parent container if present,
4792
5100
  * else the canvas). Stored so `destroy()` detaches from the same element. */
4793
- __init179() {this.pointerEventTarget = null}
5101
+ __init194() {this.pointerEventTarget = null}
4794
5102
  // --- scene-level keyboard channel state (see on/off/registerShortcut) ---
4795
- __init180() {this.keydownHandlers = []}
4796
- __init181() {this.keyupHandlers = []}
5103
+ __init195() {this.keydownHandlers = []}
5104
+ __init196() {this.keyupHandlers = []}
4797
5105
  /** Chord-normalized shortcut table, matched on keydown only. */
4798
- __init182() {this.shortcuts = []}
4799
- __init183() {this.windowKeyDownHandler = null}
4800
- __init184() {this.windowKeyUpHandler = null}
4801
- __init185() {this.hasWarnedZeroSize = false}
5106
+ __init197() {this.shortcuts = []}
5107
+ __init198() {this.windowKeyDownHandler = null}
5108
+ __init199() {this.windowKeyUpHandler = null}
5109
+ __init200() {this.hasWarnedZeroSize = false}
4802
5110
  /**
4803
5111
  * Latch for {@link Scene.resize}'s invalid-dimension warning.
4804
5112
  *
@@ -4808,8 +5116,8 @@ var Scene = (_class15 = class _Scene {
4808
5116
  * because `resize()` is commonly driven from a `ResizeObserver`, which would
4809
5117
  * otherwise warn on every frame of a drag.
4810
5118
  */
4811
- __init186() {this.hasWarnedInvalidResize = false}
4812
- __init187() {this.fontLoadHandler = null}
5119
+ __init201() {this.hasWarnedInvalidResize = false}
5120
+ __init202() {this.fontLoadHandler = null}
4813
5121
  // --- domain: scene-facade — dev-mode diagnostics ---
4814
5122
  // ── Dev-mode warning infrastructure ──────────────────────────────
4815
5123
  //
@@ -4834,17 +5142,17 @@ var Scene = (_class15 = class _Scene {
4834
5142
  }
4835
5143
  static set devMode(active) {
4836
5144
  _Scene._devMode = active;
4837
- _chunkKRUDTGNRjs.setRendererDevMode.call(void 0, active);
5145
+ _chunkBKXERWADjs.setRendererDevMode.call(void 0, active);
4838
5146
  }
4839
5147
  static _devModeDetected() {
4840
5148
  if (_Scene._devMode) return true;
4841
5149
  const gp = typeof globalThis !== "undefined" ? globalThis : void 0;
4842
- if (_optionalChain([gp, 'optionalAccess', _101 => _101.__DEV__])) return true;
4843
- 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;
4844
5152
  return false;
4845
5153
  }
4846
5154
 
4847
- __init188() {this._devFrameCount = 0}
5155
+ __init203() {this._devFrameCount = 0}
4848
5156
  _devWarn(message) {
4849
5157
  if (!this._devActive) return;
4850
5158
  console.warn(`[vectojs/dev] ${message}`);
@@ -4902,9 +5210,9 @@ var Scene = (_class15 = class _Scene {
4902
5210
  let checked = 0;
4903
5211
  const walkProjections = (node) => {
4904
5212
  if (checked > 10) return;
4905
- const proj = _optionalChain([node, 'access', _105 => _105.getContentProjection, 'optionalCall', _106 => _106()]);
4906
- if (_optionalChain([proj, 'optionalAccess', _107 => _107.text]) && proj.selectable !== false) {
4907
- 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)]);
4908
5216
  if (el) {
4909
5217
  const projectedText = el.textContent || "";
4910
5218
  const windowed = el.dataset.vectoProjectionWindow !== void 0;
@@ -4922,22 +5230,27 @@ var Scene = (_class15 = class _Scene {
4922
5230
  walkProjections(this.root);
4923
5231
  }
4924
5232
  // --- domain: scene-facade — construction (wires every collaborator) ---
4925
- 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);
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);
4926
5234
  this.canvas = canvas;
4927
5235
  this.debugA11y = _nullishCoalesce(options.debugA11y, () => ( false));
4928
5236
  this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
4929
5237
  this.maxDPR = options.maxDPR;
4930
5238
  if (this.disableWindowResize) {
4931
- const styleWidth = parseInlinePx(_optionalChain([canvas, 'access', _111 => _111.style, 'optionalAccess', _112 => _112.width]));
4932
- 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]));
4933
5241
  this.width = _nullishCoalesce(styleWidth, () => ( (canvas.width || canvas.clientWidth || 0)));
4934
5242
  this.height = _nullishCoalesce(styleHeight, () => ( (canvas.height || canvas.clientHeight || 0)));
4935
5243
  } else {
4936
- this.width = typeof window !== "undefined" ? window.innerWidth : canvas.clientWidth || canvas.width || 800;
4937
- this.height = typeof window !== "undefined" ? window.innerHeight : canvas.clientHeight || canvas.height || 600;
5244
+ if (typeof canvas.isConnected === "boolean" && !canvas.isConnected) {
5245
+ this.width = 0;
5246
+ this.height = 0;
5247
+ } else {
5248
+ this.width = typeof window !== "undefined" ? window.innerWidth : canvas.clientWidth || canvas.width || 800;
5249
+ this.height = typeof window !== "undefined" ? window.innerHeight : canvas.clientHeight || canvas.height || 600;
5250
+ }
4938
5251
  }
4939
5252
  const globalProcess = typeof globalThis !== "undefined" ? globalThis.process : void 0;
4940
- 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");
4941
5254
  this.maxFPS = _nullishCoalesce(options.maxFPS, () => ( (isTest ? 0 : 60)));
4942
5255
  this.respectReducedMotion = _nullishCoalesce(options.respectReducedMotion, () => ( true));
4943
5256
  this.autoThrottle = _nullishCoalesce(options.autoThrottle, () => ( true));
@@ -4945,6 +5258,9 @@ var Scene = (_class15 = class _Scene {
4945
5258
  this.phases.userTiming = _nullishCoalesce(options.userTiming, () => ( false));
4946
5259
  this.particleBackend = _nullishCoalesce(options.particleBackend, () => ( "auto"));
4947
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));
4948
5264
  this.contentProjectionEnabled = _nullishCoalesce(options.contentProjection, () => ( true));
4949
5265
  this.contentProjectionMargin = options.contentProjectionMargin;
4950
5266
  this.contentSemanticMargin = options.contentSemanticMargin;
@@ -4952,15 +5268,15 @@ var Scene = (_class15 = class _Scene {
4952
5268
  this.readingDirection = _nullishCoalesce(options.readingDirection, () => ( "ltr"));
4953
5269
  this.renderMode = _nullishCoalesce(options.renderMode, () => ( "always"));
4954
5270
  this._devActive = _Scene._devModeDetected();
4955
- _chunkKRUDTGNRjs.setRendererDevMode.call(void 0, this._devActive);
5271
+ _chunkBKXERWADjs.setRendererDevMode.call(void 0, this._devActive);
4956
5272
  this._warnUnknownOptions(options);
4957
5273
  this.reducedMotionQuery = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
4958
5274
  if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
4959
5275
  this.forcedColorsQuery = window.matchMedia("(forced-colors: active)");
4960
5276
  this.forcedColorsChangeHandler = () => this.markDirty();
4961
- _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)]);
4962
5278
  }
4963
- this.root = new class RootEntity extends _chunkPOPXUPKRjs.Entity {
5279
+ this.root = new class RootEntity extends _chunkHC5EZOZJjs.Entity {
4964
5280
  isPointInside() {
4965
5281
  return false;
4966
5282
  }
@@ -4970,7 +5286,7 @@ var Scene = (_class15 = class _Scene {
4970
5286
  }("root");
4971
5287
  this.root._scene = this;
4972
5288
  this._wasmBackend = new WasmBackendFacade(this.root);
4973
- this.overlayRoot = new class OverlayRoot extends _chunkPOPXUPKRjs.Entity {
5289
+ this.overlayRoot = new class OverlayRoot extends _chunkHC5EZOZJjs.Entity {
4974
5290
  isPointInside() {
4975
5291
  return false;
4976
5292
  }
@@ -4983,15 +5299,15 @@ var Scene = (_class15 = class _Scene {
4983
5299
  if (options.renderer) {
4984
5300
  this.renderer = options.renderer;
4985
5301
  } else {
4986
- this.renderer = new (0, _chunkKRUDTGNRjs.CanvasRenderer)(
5302
+ this.renderer = new (0, _chunkBKXERWADjs.CanvasRenderer)(
4987
5303
  canvas,
4988
5304
  this.disableWindowResize ? { width: this.width, height: this.height } : void 0,
4989
5305
  this.maxDPR
4990
5306
  );
4991
5307
  }
4992
- _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(() => {
4993
5309
  this.markDirty();
4994
- 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);
4995
5311
  })]);
4996
5312
  if (typeof document !== "undefined") {
4997
5313
  this.a11yRoot = document.createElement("div");
@@ -5164,18 +5480,54 @@ var Scene = (_class15 = class _Scene {
5164
5480
  watchDevicePixelRatio() {
5165
5481
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
5166
5482
  if (this.dprMediaQuery && this.dprChangeHandler) {
5167
- _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)]);
5168
5484
  }
5169
- const dpr = window.devicePixelRatio || 1;
5485
+ const raw = window.devicePixelRatio;
5486
+ const dpr = Number.isFinite(raw) && raw > 0 ? raw : 1;
5170
5487
  const query = window.matchMedia(`(resolution: ${dpr}dppx)`);
5171
5488
  const handler = () => {
5172
- this.resize(this.width, this.height);
5173
- if (_optionalChain([this, 'access', _131 => _131.renderer, 'access', _132 => _132.isContextLost, 'optionalCall', _133 => _133()]) !== true) this.render(this.renderer);
5489
+ const curRaw = window.devicePixelRatio;
5490
+ const cur = Number.isFinite(curRaw) && curRaw > 0 ? curRaw : 1;
5491
+ if (Math.abs(cur - dpr) <= 1e-3) {
5492
+ this.watchDevicePixelRatio();
5493
+ return;
5494
+ }
5495
+ this.lastDpr = cur;
5496
+ try {
5497
+ this.resize(this.width, this.height);
5498
+ } catch (err) {
5499
+ console.warn("[VectoJS] DPR resize failed", err);
5500
+ }
5501
+ try {
5502
+ if (_optionalChain([this, 'access', _134 => _134.renderer, 'access', _135 => _135.isContextLost, 'optionalCall', _136 => _136()]) !== true) this.render(this.renderer);
5503
+ } catch (err) {
5504
+ console.warn("[VectoJS] DPR repaint failed", err);
5505
+ }
5174
5506
  this.watchDevicePixelRatio();
5175
5507
  };
5176
- _optionalChain([query, 'access', _134 => _134.addEventListener, 'optionalCall', _135 => _135("change", handler)]);
5508
+ _optionalChain([query, 'access', _137 => _137.addEventListener, 'optionalCall', _138 => _138("change", handler)]);
5177
5509
  this.dprMediaQuery = query;
5178
5510
  this.dprChangeHandler = handler;
5511
+ this.lastDpr = dpr;
5512
+ if (this.dprPollInterval === null && typeof setInterval === "function") {
5513
+ this.dprPollInterval = setInterval(() => {
5514
+ const rawPoll = window.devicePixelRatio;
5515
+ const curPoll = Number.isFinite(rawPoll) && rawPoll > 0 ? rawPoll : 1;
5516
+ if (Math.abs(curPoll - this.lastDpr) <= 1e-3) return;
5517
+ this.lastDpr = curPoll;
5518
+ try {
5519
+ this.resize(this.width, this.height);
5520
+ } catch (err) {
5521
+ console.warn("[VectoJS] DPR poll resize failed", err);
5522
+ }
5523
+ try {
5524
+ if (_optionalChain([this, 'access', _139 => _139.renderer, 'access', _140 => _140.isContextLost, 'optionalCall', _141 => _141()]) !== true) this.render(this.renderer);
5525
+ } catch (err) {
5526
+ console.warn("[VectoJS] DPR poll repaint failed", err);
5527
+ }
5528
+ this.watchDevicePixelRatio();
5529
+ }, 1e3);
5530
+ }
5179
5531
  }
5180
5532
  /**
5181
5533
  * Recover the WebGL point layer from a GPU context loss (driver TDR reset,
@@ -5193,7 +5545,7 @@ var Scene = (_class15 = class _Scene {
5193
5545
  if (typeof gl.addEventListener !== "function") return;
5194
5546
  this.glContextLostHandler = (e) => {
5195
5547
  e.preventDefault();
5196
- _optionalChain([this, 'access', _136 => _136.pointRenderer, 'optionalAccess', _137 => _137.destroy, 'call', _138 => _138()]);
5548
+ _optionalChain([this, 'access', _142 => _142.pointRenderer, 'optionalAccess', _143 => _143.destroy, 'call', _144 => _144()]);
5197
5549
  this.pointRenderer = null;
5198
5550
  };
5199
5551
  this.glContextRestoredHandler = () => {
@@ -5209,6 +5561,35 @@ var Scene = (_class15 = class _Scene {
5209
5561
  gl.addEventListener("webglcontextlost", this.glContextLostHandler);
5210
5562
  gl.addEventListener("webglcontextrestored", this.glContextRestoredHandler);
5211
5563
  }
5564
+ /**
5565
+ * One-shot adoption of the window viewport once an initially-detached canvas
5566
+ * gains layout (#817). A full-window scene constructed before its canvas is
5567
+ * attached starts at 0×0, and no window `resize` fires on attachment — so
5568
+ * without this latch the scene would stay unsized forever. The first nonzero
5569
+ * layout box adopts `window.innerWidth/innerHeight` (the same sizing the
5570
+ * window resize handler applies, keeping the full-window contract), then the
5571
+ * observer disconnects: steady-state sizing stays exactly as if the canvas
5572
+ * had been attached before construction. An explicit user `resize()` between
5573
+ * construction and attachment is respected and skips adoption.
5574
+ *
5575
+ * No-op when the scene already has a size (attached at construction) or when
5576
+ * `ResizeObserver` is unavailable (the caller then drives sizing explicitly).
5577
+ */
5578
+ armAttachmentViewportLatch() {
5579
+ if (this.width > 0 && this.height > 0) return;
5580
+ if (typeof ResizeObserver === "undefined") return;
5581
+ const observer = new ResizeObserver((entries) => {
5582
+ const box = _optionalChain([entries, 'access', _145 => _145[0], 'optionalAccess', _146 => _146.contentRect]);
5583
+ if (!box || !(box.width > 0) || !(box.height > 0)) return;
5584
+ observer.disconnect();
5585
+ this.canvasResizeObserver = null;
5586
+ if (this.width === 0 || this.height === 0) {
5587
+ this.resize(window.innerWidth, window.innerHeight);
5588
+ }
5589
+ });
5590
+ this.canvasResizeObserver = observer;
5591
+ observer.observe(this.canvas);
5592
+ }
5212
5593
  // --- domain: scene-facade — renderer accessor ---
5213
5594
  /**
5214
5595
  * Expose the underlying {@link IRenderer} for advanced direct-draw operations.
@@ -5231,6 +5612,37 @@ var Scene = (_class15 = class _Scene {
5231
5612
  findEntityAt(x, y) {
5232
5613
  return this._hitTester.findEntityAt(x, y, this.currentFrame, this.width, this.height);
5233
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
+ }
5234
5646
  // --- domain: hit-test — client-to-scene mapping ---
5235
5647
  /** Convert browser viewport coordinates into this Scene's logical coordinates. */
5236
5648
  clientToScene(clientX, clientY) {
@@ -5271,6 +5683,15 @@ var Scene = (_class15 = class _Scene {
5271
5683
  this.activePortalsThisFrame.delete(node.id);
5272
5684
  this.activePortalsPrevFrame.delete(node.id);
5273
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);
5274
5695
  const contentEl = this.contentElements.get(node.id);
5275
5696
  if (contentEl) {
5276
5697
  this._contentProjection.clearGridState(node.id, contentEl);
@@ -5290,7 +5711,7 @@ var Scene = (_class15 = class _Scene {
5290
5711
  }
5291
5712
  if (this.hoveredA11yElements.has(el)) {
5292
5713
  this.hoveredA11yElements.delete(el);
5293
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("pointerleave", node, void 0, false));
5714
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("pointerleave", node, void 0, false));
5294
5715
  }
5295
5716
  this.preserveFocusOnRemoval(el);
5296
5717
  el.remove();
@@ -5390,12 +5811,16 @@ var Scene = (_class15 = class _Scene {
5390
5811
  this.canvasResizeObserver = null;
5391
5812
  }
5392
5813
  if (this.dprMediaQuery && this.dprChangeHandler) {
5393
- _optionalChain([this, 'access', _139 => _139.dprMediaQuery, 'access', _140 => _140.removeEventListener, 'optionalCall', _141 => _141("change", this.dprChangeHandler)]);
5814
+ _optionalChain([this, 'access', _147 => _147.dprMediaQuery, 'access', _148 => _148.removeEventListener, 'optionalCall', _149 => _149("change", this.dprChangeHandler)]);
5394
5815
  this.dprMediaQuery = null;
5395
5816
  this.dprChangeHandler = null;
5396
5817
  }
5818
+ if (this.dprPollInterval !== null) {
5819
+ clearInterval(this.dprPollInterval);
5820
+ this.dprPollInterval = null;
5821
+ }
5397
5822
  if (this.forcedColorsQuery && this.forcedColorsChangeHandler) {
5398
- _optionalChain([this, 'access', _142 => _142.forcedColorsQuery, 'access', _143 => _143.removeEventListener, 'optionalCall', _144 => _144("change", this.forcedColorsChangeHandler)]);
5823
+ _optionalChain([this, 'access', _150 => _150.forcedColorsQuery, 'access', _151 => _151.removeEventListener, 'optionalCall', _152 => _152("change", this.forcedColorsChangeHandler)]);
5399
5824
  this.forcedColorsQuery = null;
5400
5825
  this.forcedColorsChangeHandler = null;
5401
5826
  }
@@ -5426,9 +5851,9 @@ var Scene = (_class15 = class _Scene {
5426
5851
  }
5427
5852
  this.pointerEventTarget = null;
5428
5853
  }
5429
- _optionalChain([this, 'access', _145 => _145.a11yRoot, 'optionalAccess', _146 => _146.remove, 'call', _147 => _147()]);
5854
+ _optionalChain([this, 'access', _153 => _153.a11yRoot, 'optionalAccess', _154 => _154.remove, 'call', _155 => _155()]);
5430
5855
  this.focusSentinel = null;
5431
- _optionalChain([this, 'access', _148 => _148.portalRoot, 'optionalAccess', _149 => _149.remove, 'call', _150 => _150()]);
5856
+ _optionalChain([this, 'access', _156 => _156.portalRoot, 'optionalAccess', _157 => _157.remove, 'call', _158 => _158()]);
5432
5857
  this.a11yElements.clear();
5433
5858
  for (const el of this.contentElements.values()) el.remove();
5434
5859
  this.contentElements.clear();
@@ -5444,10 +5869,10 @@ var Scene = (_class15 = class _Scene {
5444
5869
  }
5445
5870
  this.glContextLostHandler = null;
5446
5871
  this.glContextRestoredHandler = null;
5447
- _optionalChain([this, 'access', _151 => _151.pointRenderer, 'optionalAccess', _152 => _152.destroy, 'call', _153 => _153()]);
5448
- _optionalChain([this, 'access', _154 => _154.renderer, 'access', _155 => _155.dispose, 'optionalCall', _156 => _156()]);
5449
- _optionalChain([this, 'access', _157 => _157.glCanvas, 'optionalAccess', _158 => _158.remove, 'call', _159 => _159()]);
5450
- _optionalChain([this, 'access', _160 => _160.gpuCanvas, 'optionalAccess', _161 => _161.remove, 'call', _162 => _162()]);
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()]);
5451
5876
  this.gpuCanvas = null;
5452
5877
  this.gpuContext = null;
5453
5878
  if (this.recoveryTimerId) {
@@ -5459,7 +5884,7 @@ var Scene = (_class15 = class _Scene {
5459
5884
  this.manager = null;
5460
5885
  }
5461
5886
  if (this.device) {
5462
- _optionalChain([this, 'access', _163 => _163.device, 'access', _164 => _164.destroy, 'optionalCall', _165 => _165()]);
5887
+ _optionalChain([this, 'access', _171 => _171.device, 'access', _172 => _172.destroy, 'optionalCall', _173 => _173()]);
5463
5888
  this.device = null;
5464
5889
  }
5465
5890
  }
@@ -5467,6 +5892,7 @@ var Scene = (_class15 = class _Scene {
5467
5892
  setupEvents() {
5468
5893
  if (typeof window !== "undefined" && !this.disableWindowResize) {
5469
5894
  window.addEventListener("resize", this.resizeHandler);
5895
+ this.armAttachmentViewportLatch();
5470
5896
  } else if (this.disableWindowResize && typeof ResizeObserver !== "undefined" && this.canvas && typeof this.canvas.getBoundingClientRect === "function") {
5471
5897
  this.canvasResizeObserver = new ResizeObserver((entries) => {
5472
5898
  const entry = entries[0];
@@ -5875,15 +6301,48 @@ var Scene = (_class15 = class _Scene {
5875
6301
  shouldProjectA11y(node) {
5876
6302
  if (!node.interactive) return false;
5877
6303
  if (!(node.width > 0 || node.a11yFullViewport)) return false;
6304
+ if (node.domPolicy === "dom" || this.resolveProjectionFor(node) === "dom") return false;
5878
6305
  switch (node.a11yProjection) {
5879
6306
  case "never":
5880
6307
  return false;
5881
6308
  case "onDemand":
5882
- return this.a11yEngaged(node);
6309
+ return this.a11yEngaged(node) && this.resolveSemanticProjection(node);
5883
6310
  default:
5884
- return true;
6311
+ return this.resolveSemanticProjection(node);
5885
6312
  }
5886
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;
6343
+ }
6344
+ return true;
6345
+ }
5887
6346
  /**
5888
6347
  * Whether an `a11yProjection: 'onDemand'` entity is currently engaged enough to
5889
6348
  * deserve a shadow node.
@@ -5911,7 +6370,7 @@ var Scene = (_class15 = class _Scene {
5911
6370
  if (this.mouseX > -9e3 && !this.projectsSelectableText(node)) {
5912
6371
  if (node.isPointInside(this.mouseX, this.mouseY)) return true;
5913
6372
  }
5914
- const existing = _optionalChain([this, 'access', _166 => _166.a11yElements, 'optionalAccess', _167 => _167.get, 'call', _168 => _168(node.id)]);
6373
+ const existing = _optionalChain([this, 'access', _174 => _174.a11yElements, 'optionalAccess', _175 => _175.get, 'call', _176 => _176(node.id)]);
5915
6374
  if (existing && typeof document !== "undefined" && document.activeElement === existing) {
5916
6375
  return true;
5917
6376
  }
@@ -5925,8 +6384,8 @@ var Scene = (_class15 = class _Scene {
5925
6384
  * selection.
5926
6385
  */
5927
6386
  projectsSelectableText(node) {
5928
- const projection = _optionalChain([node, 'access', _169 => _169.getContentProjection, 'optionalCall', _170 => _170()]);
5929
- return !!_optionalChain([projection, 'optionalAccess', _171 => _171.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;
5930
6389
  }
5931
6390
  /**
5932
6391
  * Keep `entity`'s a11y shadow node projected while it has
@@ -6021,20 +6480,20 @@ var Scene = (_class15 = class _Scene {
6021
6480
  el.style.background = "transparent";
6022
6481
  }
6023
6482
  el.addEventListener("click", (e) => {
6024
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("click", node, e));
6483
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("click", node, e));
6025
6484
  });
6026
6485
  el.addEventListener("dblclick", (e) => {
6027
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("dblclick", node, e));
6486
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("dblclick", node, e));
6028
6487
  });
6029
6488
  el.addEventListener("mouseenter", (e) => {
6030
6489
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.2)";
6031
6490
  this.hoveredA11yElements.add(el);
6032
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("hover", node, e, false));
6491
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("hover", node, e, false));
6033
6492
  });
6034
6493
  el.addEventListener("mouseleave", (e) => {
6035
6494
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.05)";
6036
6495
  this.hoveredA11yElements.delete(el);
6037
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("pointerleave", node, e, false));
6496
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("pointerleave", node, e, false));
6038
6497
  });
6039
6498
  const capEl = el;
6040
6499
  const releasePointer = (event) => {
@@ -6052,24 +6511,27 @@ var Scene = (_class15 = class _Scene {
6052
6511
  if (e.target === capEl && typeof capEl.setPointerCapture === "function") {
6053
6512
  capEl.setPointerCapture(e.pointerId);
6054
6513
  }
6055
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("pointerdown", node, e));
6514
+ this.pinProjectionForGesture(node);
6515
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("pointerdown", node, e));
6056
6516
  });
6057
6517
  el.addEventListener("pointerup", (e) => {
6058
6518
  releasePointer(e);
6059
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("pointerup", node, e));
6519
+ this.unpinProjectionForGesture(node);
6520
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("pointerup", node, e));
6060
6521
  });
6061
6522
  el.addEventListener("pointercancel", (e) => {
6062
6523
  releasePointer(e);
6063
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("pointercancel", node, e));
6524
+ this.unpinProjectionForGesture(node);
6525
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("pointercancel", node, e));
6064
6526
  });
6065
6527
  el.addEventListener(
6066
6528
  "pointermove",
6067
- (e) => node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("pointermove", node, e))
6529
+ (e) => node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("pointermove", node, e))
6068
6530
  );
6069
6531
  el.addEventListener(
6070
6532
  "wheel",
6071
6533
  (e) => {
6072
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("wheel", node, e));
6534
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("wheel", node, e));
6073
6535
  },
6074
6536
  { passive: false }
6075
6537
  );
@@ -6094,10 +6556,10 @@ var Scene = (_class15 = class _Scene {
6094
6556
  );
6095
6557
  emitInitialScroll = emitScroll;
6096
6558
  el.addEventListener("keydown", (e) => {
6097
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("keydown", node, e));
6559
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("keydown", node, e));
6098
6560
  });
6099
6561
  el.addEventListener("keyup", (e) => {
6100
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("keyup", node, e));
6562
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("keyup", node, e));
6101
6563
  });
6102
6564
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
6103
6565
  const input = el;
@@ -6128,7 +6590,7 @@ var Scene = (_class15 = class _Scene {
6128
6590
  el.addEventListener("compositionupdate", (e) => {
6129
6591
  const data = _nullishCoalesce(e.data, () => ( ""));
6130
6592
  composition = {
6131
- start: _nullishCoalesce(_optionalChain([composition, 'optionalAccess', _172 => _172.start]), () => ( 0)),
6593
+ start: _nullishCoalesce(_optionalChain([composition, 'optionalAccess', _180 => _180.start]), () => ( 0)),
6132
6594
  length: data.length
6133
6595
  };
6134
6596
  forward();
@@ -6182,14 +6644,14 @@ var Scene = (_class15 = class _Scene {
6182
6644
  el.addEventListener("keydown", (e) => {
6183
6645
  if (e.key === "Enter") {
6184
6646
  e.preventDefault();
6185
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("click", node, e));
6647
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("click", node, e));
6186
6648
  } else if (e.key === " ") {
6187
6649
  e.preventDefault();
6188
6650
  }
6189
6651
  });
6190
6652
  el.addEventListener("keyup", (e) => {
6191
6653
  if (e.key === " ") {
6192
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("click", node, e));
6654
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("click", node, e));
6193
6655
  }
6194
6656
  });
6195
6657
  this.keyboardActivatedA11y.add(el);
@@ -6210,7 +6672,7 @@ var Scene = (_class15 = class _Scene {
6210
6672
  this.syncOptionalAttribute(
6211
6673
  el,
6212
6674
  "href",
6213
- attrs.href === void 0 ? void 0 : _chunkKRUDTGNRjs.sanitizeUrl.call(void 0, attrs.href)
6675
+ attrs.href === void 0 ? void 0 : _chunkBKXERWADjs.sanitizeUrl.call(void 0, attrs.href)
6214
6676
  );
6215
6677
  this.syncOptionalAttribute(el, "target", attrs.target);
6216
6678
  }
@@ -6379,7 +6841,7 @@ var Scene = (_class15 = class _Scene {
6379
6841
  const display = visible ? "" : "none";
6380
6842
  if (el.style.display !== display) el.style.display = display;
6381
6843
  }
6382
- _optionalChain([emitInitialScroll, 'optionalCall', _173 => _173()]);
6844
+ _optionalChain([emitInitialScroll, 'optionalCall', _181 => _181()]);
6383
6845
  }
6384
6846
  if (this.phases.enabled) {
6385
6847
  const projectionStart = performance.now();
@@ -6609,7 +7071,7 @@ var Scene = (_class15 = class _Scene {
6609
7071
  el.addEventListener(
6610
7072
  "wheel",
6611
7073
  (e2) => {
6612
- node.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)("wheel", node, e2));
7074
+ node.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)("wheel", node, e2));
6613
7075
  },
6614
7076
  { passive: false }
6615
7077
  );
@@ -6840,8 +7302,8 @@ var Scene = (_class15 = class _Scene {
6840
7302
  next.f = f2;
6841
7303
  next.tier = tier;
6842
7304
  next.hasBand = lineBand !== null;
6843
- next.bandMin = _nullishCoalesce(_optionalChain([lineBand, 'optionalAccess', _174 => _174.minY]), () => ( 0));
6844
- next.bandMax = _nullishCoalesce(_optionalChain([lineBand, 'optionalAccess', _175 => _175.maxY]), () => ( 0));
7305
+ next.bandMin = _nullishCoalesce(_optionalChain([lineBand, 'optionalAccess', _182 => _182.minY]), () => ( 0));
7306
+ next.bandMax = _nullishCoalesce(_optionalChain([lineBand, 'optionalAccess', _183 => _183.maxY]), () => ( 0));
6845
7307
  next.visible = visible;
6846
7308
  next.width = node.width;
6847
7309
  next.height = node.height;
@@ -7020,6 +7482,197 @@ var Scene = (_class15 = class _Scene {
7020
7482
  this.activePortalsPrevFrame = new Set(this.activePortalsThisFrame);
7021
7483
  this.activePortalsThisFrame.clear();
7022
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
+ }
7023
7676
  // --- domain: render-scheduler — the loop and the render walk ---
7024
7677
  /**
7025
7678
  * The frame-rate cap actually in effect: the explicit {@link maxFPS}, further
@@ -7027,7 +7680,7 @@ var Scene = (_class15 = class _Scene {
7027
7680
  * (and {@link respectReducedMotion} is on). `0` means uncapped.
7028
7681
  */
7029
7682
  effectiveMaxFPS() {
7030
- const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access', _176 => _176.reducedMotionQuery, 'optionalAccess', _177 => _177.matches]);
7683
+ const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access', _187 => _187.reducedMotionQuery, 'optionalAccess', _188 => _188.matches]);
7031
7684
  if (reduced)
7032
7685
  return this.maxFPS > 0 ? Math.min(this.maxFPS, REDUCED_MOTION_FPS) : REDUCED_MOTION_FPS;
7033
7686
  return this.maxFPS;
@@ -7097,6 +7750,12 @@ var Scene = (_class15 = class _Scene {
7097
7750
  /**
7098
7751
  * Render the entire scene graph onto the specified renderer.
7099
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
+ *
7100
7759
  * Main-frame causal order is a correctness contract:
7101
7760
  *
7102
7761
  * 1. Browser/input callbacks finish before the scheduled frame begins.
@@ -7122,7 +7781,7 @@ var Scene = (_class15 = class _Scene {
7122
7781
  * @param time - Current absolute time in milliseconds (default 0).
7123
7782
  */
7124
7783
  render(renderer, dt = 0, time = 0) {
7125
- if (_optionalChain([renderer, 'access', _178 => _178.isContextLost, 'optionalCall', _179 => _179()])) return;
7784
+ if (_optionalChain([renderer, 'access', _189 => _189.isContextLost, 'optionalCall', _190 => _190()])) return;
7126
7785
  const isMainRenderer = renderer === this.renderer;
7127
7786
  if (isMainRenderer && this.a11yRoot && this.canvas.parentElement) {
7128
7787
  const parentStyle = this.canvas.parentElement.style;
@@ -7272,14 +7931,14 @@ var Scene = (_class15 = class _Scene {
7272
7931
  }
7273
7932
  renderer.clear();
7274
7933
  if (isMainRenderer) {
7275
- _optionalChain([this, 'access', _180 => _180.pointRenderer, 'optionalAccess', _181 => _181.begin, 'call', _182 => _182()]);
7934
+ _optionalChain([this, 'access', _191 => _191.pointRenderer, 'optionalAccess', _192 => _192.begin, 'call', _193 => _193()]);
7276
7935
  }
7277
7936
  const vw = this.width;
7278
7937
  const vh = this.height;
7279
7938
  let walkHadAnimation = false;
7280
7939
  let walkHadInteractive = false;
7281
7940
  const runUpdate = (node) => {
7282
- const overridesUpdate = node.update !== _chunkPOPXUPKRjs.Entity.prototype.update;
7941
+ const overridesUpdate = node.update !== _chunkHC5EZOZJjs.Entity.prototype.update;
7283
7942
  let pending = node.hasPendingAnimations();
7284
7943
  if (pending || overridesUpdate) {
7285
7944
  node.update(dt, time);
@@ -7288,7 +7947,7 @@ var Scene = (_class15 = class _Scene {
7288
7947
  if (pending) walkHadAnimation = true;
7289
7948
  if (!walkHadInteractive && node.interactive) walkHadInteractive = true;
7290
7949
  if (this._devActive && this._devFrameCount % 120 === 0) {
7291
- if (overridesUpdate && node.hasPendingAnimations === _chunkPOPXUPKRjs.Entity.prototype.hasPendingAnimations) {
7950
+ if (overridesUpdate && node.hasPendingAnimations === _chunkHC5EZOZJjs.Entity.prototype.hasPendingAnimations) {
7292
7951
  this._devWarn(
7293
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.`
7294
7953
  );
@@ -7389,6 +8048,16 @@ var Scene = (_class15 = class _Scene {
7389
8048
  visible = maxX >= 0 && minX <= vw && maxY >= 0 && minY <= vh;
7390
8049
  }
7391
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
+ }
7392
8061
  if (node.children.length === 0 && node.scaleX === node.scaleY) {
7393
8062
  const bc = node.getBatchCircle();
7394
8063
  if (bc) {
@@ -7518,14 +8187,15 @@ var Scene = (_class15 = class _Scene {
7518
8187
  this.frameHadAnimation = walkHadAnimation;
7519
8188
  this.frameHadInteractive = walkHadInteractive;
7520
8189
  this.reconcilePortals();
8190
+ this.pruneProjectionBackends();
7521
8191
  }
7522
8192
  const flushTiming = this.phases.userTiming ? beginVectoUserTiming(VECTO_USER_TIMING.scene.flush) : null;
7523
8193
  const flushT0 = this.phases.enabled ? performance.now() : 0;
7524
8194
  renderer.flush();
7525
8195
  if (isMainRenderer) {
7526
- _optionalChain([this, 'access', _183 => _183.pointRenderer, 'optionalAccess', _184 => _184.flush, 'call', _185 => _185()]);
8196
+ _optionalChain([this, 'access', _194 => _194.pointRenderer, 'optionalAccess', _195 => _195.flush, 'call', _196 => _196()]);
7527
8197
  }
7528
- _optionalChain([renderer, 'access', _186 => _186.present, 'optionalCall', _187 => _187()]);
8198
+ _optionalChain([renderer, 'access', _197 => _197.present, 'optionalCall', _198 => _198()]);
7529
8199
  if (this.phases.enabled) this.phases.record("flush", performance.now() - flushT0);
7530
8200
  if (flushTiming) endVectoUserTiming(flushTiming);
7531
8201
  if (this._devActive) {
@@ -7538,7 +8208,7 @@ var Scene = (_class15 = class _Scene {
7538
8208
  * Export the current scene state to a lightweight, flat SVG XML string.
7539
8209
  */
7540
8210
  toSVG() {
7541
- const renderer = new (0, _chunkKRUDTGNRjs.SVGRenderer)(this.width, this.height);
8211
+ const renderer = new (0, _chunkBKXERWADjs.SVGRenderer)(this.width, this.height);
7542
8212
  this.render(renderer, 0, 0);
7543
8213
  return renderer.toXMLString();
7544
8214
  }
@@ -7616,7 +8286,7 @@ var Scene = (_class15 = class _Scene {
7616
8286
  });
7617
8287
  pass.end();
7618
8288
  this.device.queue.submit([encoder.finish()]);
7619
- } catch (e17) {
8289
+ } catch (e19) {
7620
8290
  }
7621
8291
  this.gpuHasContent = false;
7622
8292
  }
@@ -7751,20 +8421,20 @@ function defaultMeasurer() {
7751
8421
  sharedMeasurer ??= _layout.resolveGlyphMeasurer.call(void 0, "sans-serif");
7752
8422
  return sharedMeasurer;
7753
8423
  }
7754
- var TextEntity = (_class16 = class extends _chunkPOPXUPKRjs.Entity {
8424
+ var TextEntity = (_class16 = class extends _chunkHC5EZOZJjs.Entity {
7755
8425
 
7756
8426
 
7757
8427
 
7758
8428
 
7759
- __init189() {this.nodes = []}
8429
+ __init204() {this.nodes = []}
7760
8430
 
7761
- __init190() {this.fillStyle = "#94a3b8"}
7762
- __init191() {this.strokeStyle = null}
7763
- __init192() {this.hoveredFillStyle = "#ffffff"}
7764
- __init193() {this.lineWidth = 1}
7765
- __init194() {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}
7766
8436
  /** Bumped by {@link applyLayout}; read by `Scene` to skip an unchanged sync. */
7767
- __init195() {this.contentEpoch = 0}
8437
+ __init210() {this.contentEpoch = 0}
7768
8438
  /**
7769
8439
  * Visual lines with real canvas geometry, rebuilt by {@link applyLayout}
7770
8440
  * exactly when `this.nodes` changes.
@@ -7776,9 +8446,9 @@ var TextEntity = (_class16 = class extends _chunkPOPXUPKRjs.Entity {
7776
8446
  * ~0.35 em per line for a 24 px font, Firefox). Per-line carriers pin the DOM
7777
8447
  * baseline to the canvas baseline by construction, same as `ui/Text`.
7778
8448
  */
7779
- __init196() {this.projectionLines = []}
8449
+ __init211() {this.projectionLines = []}
7780
8450
  constructor(text, atlas, maxWidth, fontSize = 24) {
7781
- super();_class16.prototype.__init189.call(this);_class16.prototype.__init190.call(this);_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);;
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);;
7782
8452
  this.text = text;
7783
8453
  this.atlas = atlas;
7784
8454
  this.fontSize = fontSize;
@@ -7954,18 +8624,250 @@ var TextEntity = (_class16 = class extends _chunkPOPXUPKRjs.Entity {
7954
8624
  }
7955
8625
  }, _class16);
7956
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
+
7957
8859
  // src/components/GridTextEntity.ts
7958
- var GridTextEntity = (_class17 = class extends _chunkPOPXUPKRjs.Entity {
8860
+ var GridTextEntity = (_class19 = class extends _chunkHC5EZOZJjs.Entity {
7959
8861
 
7960
- __init197() {this.fillStyle = "#ffffff"}
7961
- __init198() {this.grid = []}
8862
+ __init219() {this.fillStyle = "#ffffff"}
8863
+ __init220() {this.grid = []}
7962
8864
  // Array of rows
7963
- __init199() {this.cols = 0}
7964
- __init200() {this.rows = 0}
8865
+ __init221() {this.cols = 0}
8866
+ __init222() {this.rows = 0}
7965
8867
 
7966
8868
 
7967
8869
  constructor(_atlas, fontSize = 10) {
7968
- super();_class17.prototype.__init197.call(this);_class17.prototype.__init198.call(this);_class17.prototype.__init199.call(this);_class17.prototype.__init200.call(this);;
8870
+ super();_class19.prototype.__init219.call(this);_class19.prototype.__init220.call(this);_class19.prototype.__init221.call(this);_class19.prototype.__init222.call(this);;
7969
8871
  this.fontSize = fontSize;
7970
8872
  this.charWidth = fontSize * 1;
7971
8873
  this.charHeight = fontSize * 1.1;
@@ -7974,7 +8876,7 @@ var GridTextEntity = (_class17 = class extends _chunkPOPXUPKRjs.Entity {
7974
8876
  updateGrid(ascii) {
7975
8877
  this.grid = ascii;
7976
8878
  this.rows = ascii.length;
7977
- this.cols = ascii.reduce((widest, row) => Math.max(widest, _nullishCoalesce(_optionalChain([row, 'optionalAccess', _188 => _188.length]), () => ( 0))), 0);
8879
+ this.cols = ascii.reduce((widest, row) => Math.max(widest, _nullishCoalesce(_optionalChain([row, 'optionalAccess', _224 => _224.length]), () => ( 0))), 0);
7978
8880
  }
7979
8881
  isPointInside(_globalX, _globalY) {
7980
8882
  return false;
@@ -7996,7 +8898,7 @@ var GridTextEntity = (_class17 = class extends _chunkPOPXUPKRjs.Entity {
7996
8898
  }
7997
8899
  }
7998
8900
  }
7999
- }, _class17);
8901
+ }, _class19);
8000
8902
 
8001
8903
  // src/components/SplineEntity.ts
8002
8904
  function polySegmentToBezier(seg) {
@@ -8049,7 +8951,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
8049
8951
  const ey = py - cy;
8050
8952
  return ex * ex + ey * ey;
8051
8953
  }
8052
- var SplineEntity = (_class18 = class extends _chunkPOPXUPKRjs.Entity {
8954
+ var SplineEntity = (_class20 = class extends _chunkHC5EZOZJjs.Entity {
8053
8955
 
8054
8956
 
8055
8957
 
@@ -8057,17 +8959,17 @@ var SplineEntity = (_class18 = class extends _chunkPOPXUPKRjs.Entity {
8057
8959
 
8058
8960
 
8059
8961
 
8060
- __init201() {this.offscreen = null}
8061
- __init202() {this.baked = false}
8962
+ __init223() {this.offscreen = null}
8963
+ __init224() {this.baked = false}
8062
8964
  /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
8063
- __init203() {this.bakedWidth = 0}
8064
- __init204() {this.bakedHeight = 0}
8965
+ __init225() {this.bakedWidth = 0}
8966
+ __init226() {this.bakedHeight = 0}
8065
8967
  /**
8066
8968
  * Device-pixel ratio the bitmap was rasterized at. Tracked so a DPR change
8067
8969
  * (browser zoom, a monitor move, a renderer whose clamp changed) re-bakes
8068
8970
  * instead of blitting a bitmap at the wrong device density forever.
8069
8971
  */
8070
- __init205() {this.bakedDPR = 0}
8972
+ __init227() {this.bakedDPR = 0}
8071
8973
  /**
8072
8974
  * Gradient strokes can't be baked to a solid-color bitmap; they render
8073
8975
  * per-frame. Derived from `_doc`, so it is NOT readonly — assigning a new
@@ -8075,7 +8977,7 @@ var SplineEntity = (_class18 = class extends _chunkPOPXUPKRjs.Entity {
8075
8977
  */
8076
8978
 
8077
8979
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
8078
- __init206() {this.polylines = null}
8980
+ __init228() {this.polylines = null}
8079
8981
  /**
8080
8982
  * The spline document to render. Assigning a new document invalidates all
8081
8983
  * caches (baked canvas, flattened polylines, bounds).
@@ -8109,9 +9011,9 @@ var SplineEntity = (_class18 = class extends _chunkPOPXUPKRjs.Entity {
8109
9011
  * local bounds after painting the curves. Useful for drag feedback and
8110
9012
  * debugging hit areas. Defaults to `false`.
8111
9013
  */
8112
- __init207() {this.showBounds = false}
9014
+ __init229() {this.showBounds = false}
8113
9015
  constructor(doc, opts = {}) {
8114
- super();_class18.prototype.__init201.call(this);_class18.prototype.__init202.call(this);_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);;
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);;
8115
9017
  this._doc = doc;
8116
9018
  this._lineWidth = _nullishCoalesce(opts.lineWidth, () => ( 2));
8117
9019
  this.cache = _nullishCoalesce(opts.cache, () => ( true));
@@ -8131,7 +9033,7 @@ var SplineEntity = (_class18 = class extends _chunkPOPXUPKRjs.Entity {
8131
9033
  */
8132
9034
  computeContainsGradient() {
8133
9035
  const isGradient = (c) => c !== null && !Array.isArray(c);
8134
- return (_nullishCoalesce(_optionalChain([this, 'access', _189 => _189._doc, 'access', _190 => _190.equations, 'optionalAccess', _191 => _191.some, 'call', _192 => _192((eq) => isGradient(eq.color_rgb))]), () => ( false))) || (_nullishCoalesce(_optionalChain([this, 'access', _193 => _193._doc, 'access', _194 => _194.paths, 'optionalAccess', _195 => _195.some, 'call', _196 => _196((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)));
8135
9037
  }
8136
9038
  /** Clear cached baked canvas and hit-test polylines. */
8137
9039
  invalidateCache() {
@@ -8370,14 +9272,14 @@ var SplineEntity = (_class18 = class extends _chunkPOPXUPKRjs.Entity {
8370
9272
  r.stroke("rgba(0, 150, 255, 0.8)", 2);
8371
9273
  }
8372
9274
  }
8373
- }, _class18);
9275
+ }, _class20);
8374
9276
  async function loadSpline(url) {
8375
9277
  const res = await fetch(url);
8376
9278
  return await res.json();
8377
9279
  }
8378
9280
 
8379
9281
  // src/components/Rect.ts
8380
- var Rect = class extends _chunkPOPXUPKRjs.Entity {
9282
+ var Rect = class extends _chunkHC5EZOZJjs.Entity {
8381
9283
 
8382
9284
 
8383
9285
 
@@ -8433,7 +9335,7 @@ var Rect = class extends _chunkPOPXUPKRjs.Entity {
8433
9335
  };
8434
9336
 
8435
9337
  // src/components/Circle.ts
8436
- var Circle = class extends _chunkPOPXUPKRjs.Entity {
9338
+ var Circle = class extends _chunkHC5EZOZJjs.Entity {
8437
9339
 
8438
9340
 
8439
9341
 
@@ -8497,7 +9399,7 @@ var Circle = class extends _chunkPOPXUPKRjs.Entity {
8497
9399
  };
8498
9400
 
8499
9401
  // src/components/Group.ts
8500
- var Group = class extends _chunkPOPXUPKRjs.Entity {
9402
+ var Group = class extends _chunkHC5EZOZJjs.Entity {
8501
9403
  constructor(...children) {
8502
9404
  super();
8503
9405
  if (children.length > 0) this.add(...children);
@@ -8516,21 +9418,21 @@ var _math = require('@vectojs/math'); _createStarExport(_math);
8516
9418
 
8517
9419
 
8518
9420
  // src/tree/DOMPortalEntity.ts
8519
- var DOMPortalEntity = (_class19 = class extends _chunkPOPXUPKRjs.Entity {
9421
+ var DOMPortalEntity = (_class21 = class extends _chunkHC5EZOZJjs.Entity {
8520
9422
 
8521
- __init208() {this.isDOMPortal = true}
8522
- __init209() {this.domListeners = []}
8523
- __init210() {this.resizeObserver = null}
8524
- __init211() {this.domBound = false}
8525
- __init212() {this.cachedWidth = 100}
8526
- __init213() {this.cachedHeight = 100}
8527
- __init214() {this.lastWidth = ""}
8528
- __init215() {this.lastHeight = ""}
8529
- __init216() {this.lastTransform = ""}
8530
- __init217() {this.lastZIndex = ""}
8531
- __init218() {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 = ""}
8532
9434
  constructor(domElement, width, height, id) {
8533
- super(id);_class19.prototype.__init208.call(this);_class19.prototype.__init209.call(this);_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);;
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);;
8534
9436
  this.domElement = domElement;
8535
9437
  this.width = _nullishCoalesce(width, () => ( 0));
8536
9438
  this.height = _nullishCoalesce(height, () => ( 0));
@@ -8572,7 +9474,7 @@ var DOMPortalEntity = (_class19 = class extends _chunkPOPXUPKRjs.Entity {
8572
9474
  ];
8573
9475
  for (const type of events) {
8574
9476
  const handler = (e) => {
8575
- this.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)(type, this, e));
9477
+ this.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)(type, this, e));
8576
9478
  };
8577
9479
  this.domElement.addEventListener(type, handler);
8578
9480
  this.domListeners.push({ type, handler, capture: false });
@@ -8583,7 +9485,7 @@ var DOMPortalEntity = (_class19 = class extends _chunkPOPXUPKRjs.Entity {
8583
9485
  ];
8584
9486
  for (const { native, vecto } of hoverEvents) {
8585
9487
  const handler = (e) => {
8586
- this.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)(vecto, this, e, false));
9488
+ this.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)(vecto, this, e, false));
8587
9489
  };
8588
9490
  this.domElement.addEventListener(native, handler);
8589
9491
  this.domListeners.push({ type: native, handler, capture: false });
@@ -8591,7 +9493,7 @@ var DOMPortalEntity = (_class19 = class extends _chunkPOPXUPKRjs.Entity {
8591
9493
  const focusEvents = ["focus", "blur"];
8592
9494
  for (const type of focusEvents) {
8593
9495
  const handler = (e) => {
8594
- this.dispatchEvent(new (0, _chunkPOPXUPKRjs.VectoJSEvent)(type, this, e, true));
9496
+ this.dispatchEvent(new (0, _chunkHC5EZOZJjs.VectoJSEvent)(type, this, e, true));
8595
9497
  };
8596
9498
  this.domElement.addEventListener(type, handler, true);
8597
9499
  this.domListeners.push({ type, handler, capture: true });
@@ -8639,11 +9541,23 @@ var DOMPortalEntity = (_class19 = class extends _chunkPOPXUPKRjs.Entity {
8639
9541
  }
8640
9542
  super.destroy();
8641
9543
  }
8642
- }, _class19);
9544
+ }, _class21);
8643
9545
 
8644
9546
  // src/index.ts
8645
- Scene.registerWebGLPointRendererCreator(_chunkKRUDTGNRjs.createWebGLPointRenderer);
8646
- Scene.registerWebGPUParticleSystemManager(_chunkKRUDTGNRjs.WebGPUParticleSystemManager);
9547
+ Scene.registerWebGLPointRendererCreator(_chunkBKXERWADjs.createWebGLPointRenderer);
9548
+ Scene.registerWebGPUParticleSystemManager(_chunkBKXERWADjs.WebGPUParticleSystemManager);
9549
+
9550
+
9551
+
9552
+
9553
+
9554
+
9555
+
9556
+
9557
+
9558
+
9559
+
9560
+
8647
9561
 
8648
9562
 
8649
9563
 
@@ -8692,4 +9606,4 @@ Scene.registerWebGPUParticleSystemManager(_chunkKRUDTGNRjs.WebGPUParticleSystemM
8692
9606
 
8693
9607
 
8694
9608
 
8695
- exports.CanvasRenderer = _chunkKRUDTGNRjs.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 = _chunkKRUDTGNRjs.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 = _chunkKRUDTGNRjs.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkKRUDTGNRjs.TextRasterCache; exports.VECTO_USER_TIMING = VECTO_USER_TIMING; exports.VectoJSEvent = _chunkPOPXUPKRjs.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkKRUDTGNRjs.WebGPUParticleSystemManager; exports.beginVectoUserTiming = beginVectoUserTiming; exports.contentLineInHint = _chunkPOPXUPKRjs.contentLineInHint; exports.createWebGLPointRenderer = _chunkKRUDTGNRjs.createWebGLPointRenderer; exports.endVectoUserTiming = endVectoUserTiming; exports.installRendererDevTraps = _chunkKRUDTGNRjs.installRendererDevTraps; exports.isRendererDevMode = _chunkKRUDTGNRjs.isRendererDevMode; exports.isSafeUrl = _chunkKRUDTGNRjs.isSafeUrl; exports.loadSpline = loadSpline; exports.measureVectoUserTiming = measureVectoUserTiming; exports.normalizeChord = normalizeChord; exports.ownsKeyboard = ownsKeyboard; exports.parseColorToRGBA = _chunkKRUDTGNRjs.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkKRUDTGNRjs.sanitizeUrl; exports.setRendererDevMode = _chunkKRUDTGNRjs.setRendererDevMode;
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;