@vectojs/core 1.10.0 → 1.11.1

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.
@@ -710,6 +710,25 @@ var Entity = (_class4 = class {
710
710
  handlers.forEach((h) => h(payload));
711
711
  }
712
712
  }
713
+ /**
714
+ * Programmatically focus the entity's projected a11y shadow element, if one
715
+ * exists. After `scene.add(entity)` the shadow element is typically created
716
+ * on the next a11y sync (within one animation frame), so this method retries
717
+ * once on the next rAF if the element isn't immediately available — matching
718
+ * the timing pattern the prior workaround described in findings.md achieved
719
+ * with `requestAnimationFrame(() => document.getElementById(id)?.focus())`.
720
+ */
721
+ focus() {
722
+ const el = _optionalChain([this, 'access', _57 => _57.scene, 'optionalAccess', _58 => _58.getA11yElement, 'call', _59 => _59(this.id)]);
723
+ if (el) {
724
+ el.focus();
725
+ return;
726
+ }
727
+ requestAnimationFrame(() => {
728
+ const retry = _optionalChain([this, 'access', _60 => _60.scene, 'optionalAccess', _61 => _61.getA11yElement, 'call', _62 => _62(this.id)]);
729
+ if (retry) retry.focus();
730
+ });
731
+ }
713
732
  /** Run one node's listeners for the event, honoring stopImmediatePropagation. */
714
733
  fireListeners(node, map, event) {
715
734
  const handlers = map.get(event.type);
@@ -1141,7 +1160,7 @@ var MSDFTextEntity = (_class6 = class extends Entity {
1141
1160
  if (res.seqId < this.lastRenderedSeqId) return;
1142
1161
  this.lastRenderedSeqId = res.seqId;
1143
1162
  this.layoutResult = res;
1144
- _optionalChain([this, 'access', _57 => _57.scene, 'optionalAccess', _58 => _58.markDirty, 'call', _59 => _59()]);
1163
+ _optionalChain([this, 'access', _63 => _63.scene, 'optionalAccess', _64 => _64.markDirty, 'call', _65 => _65()]);
1145
1164
  }
1146
1165
  });
1147
1166
  }
@@ -710,6 +710,25 @@ var Entity = class {
710
710
  handlers.forEach((h) => h(payload));
711
711
  }
712
712
  }
713
+ /**
714
+ * Programmatically focus the entity's projected a11y shadow element, if one
715
+ * exists. After `scene.add(entity)` the shadow element is typically created
716
+ * on the next a11y sync (within one animation frame), so this method retries
717
+ * once on the next rAF if the element isn't immediately available — matching
718
+ * the timing pattern the prior workaround described in findings.md achieved
719
+ * with `requestAnimationFrame(() => document.getElementById(id)?.focus())`.
720
+ */
721
+ focus() {
722
+ const el = this.scene?.getA11yElement(this.id);
723
+ if (el) {
724
+ el.focus();
725
+ return;
726
+ }
727
+ requestAnimationFrame(() => {
728
+ const retry = this.scene?.getA11yElement(this.id);
729
+ if (retry) retry.focus();
730
+ });
731
+ }
713
732
  /** Run one node's listeners for the event, honoring stopImmediatePropagation. */
714
733
  fireListeners(node, map, event) {
715
734
  const handlers = map.get(event.type);
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ var _chunkORCPCIJ7js = require('./chunk-ORCPCIJ7.js');
27
27
 
28
28
 
29
29
 
30
- var _chunkGKLTTGBRjs = require('./chunk-GKLTTGBR.js');
30
+ var _chunk2Z23LTH3js = require('./chunk-2Z23LTH3.js');
31
31
 
32
32
 
33
33
 
@@ -44,7 +44,7 @@ var PARTICLE_OFFSET_ORIGIN_X = 4;
44
44
  var PARTICLE_OFFSET_ORIGIN_Y = 5;
45
45
  var PARTICLE_OFFSET_SIZE = 6;
46
46
  var PARTICLE_OFFSET_LIFE = 7;
47
- var ComputeParticleEntity = (_class = class extends _chunkGKLTTGBRjs.Entity {
47
+ var ComputeParticleEntity = (_class = class extends _chunk2Z23LTH3js.Entity {
48
48
 
49
49
 
50
50
 
@@ -701,48 +701,116 @@ var Scene = (_class2 = class _Scene {
701
701
  __init39() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
702
702
  __init40() {this.portalEntities = /* @__PURE__ */ new Map()}
703
703
  __init41() {this.renderOrderCounter = 0}
704
+ /**
705
+ * Authoritative paint order for semantic nodes discovered during the main
706
+ * render. A node may not have a DOM projection until the following a11y
707
+ * sync, so retaining the order prevents a newly opened overlay from spending
708
+ * its first frame below previously projected controls.
709
+ */
710
+ __init42() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
704
711
  // Optional WebGL point-cloud layer (see SceneOptions.pointBackend).
705
- __init42() {this.pointRenderer = null}
706
- __init43() {this.glCanvas = null}
712
+ __init43() {this.pointRenderer = null}
713
+ __init44() {this.glCanvas = null}
707
714
 
708
715
 
709
716
 
710
- __init44() {this.disableWindowResize = false}
717
+ __init45() {this.disableWindowResize = false}
711
718
  /** See {@link SceneOptions.maxDPR}. `undefined` = uncapped (real DPR). */
712
719
 
713
720
  // WebGPU properties
714
- __init45() {this.destroyed = false}
715
- __init46() {this.device = null}
716
- __init47() {this.deviceLost = false}
717
- __init48() {this.particleBackend = "auto"}
718
- __init49() {this._webgpuDisabled = false}
721
+ __init46() {this.destroyed = false}
722
+ __init47() {this.device = null}
723
+ __init48() {this.deviceLost = false}
724
+ __init49() {this.particleBackend = "auto"}
725
+ __init50() {this._webgpuDisabled = false}
719
726
  get webgpuDisabled() {
720
727
  return this._webgpuDisabled || this.particleBackend === "cpu";
721
728
  }
722
729
  set webgpuDisabled(value) {
723
730
  this._webgpuDisabled = value;
724
731
  }
725
- __init50() {this.recoveryTimerId = null}
726
- __init51() {this.manager = null}
727
- __init52() {this.initializingWebGPU = false}
728
- __init53() {this.gpuCanvas = null}
729
- __init54() {this.gpuContext = null}
732
+ __init51() {this.recoveryTimerId = null}
733
+ __init52() {this.manager = null}
734
+ __init53() {this.initializingWebGPU = false}
735
+ __init54() {this.gpuCanvas = null}
736
+ __init55() {this.gpuContext = null}
730
737
  /** True while the GPU canvas holds a presented particle frame (needs clearing when they leave). */
731
- __init55() {this.gpuHasContent = false}
732
- __init56() {this.mouseX = -9999}
733
- __init57() {this.mouseY = -9999}
734
- __init58() {this.pointerMoveListener = null}
735
- __init59() {this.pointerLeaveListener = null}
736
- __init60() {this.hasWarnedZeroSize = false}
737
- __init61() {this.fontLoadHandler = null}
738
- constructor(canvas, options = {}) {;_class2.prototype.__init7.call(this);_class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);_class2.prototype.__init10.call(this);_class2.prototype.__init11.call(this);_class2.prototype.__init12.call(this);_class2.prototype.__init13.call(this);_class2.prototype.__init14.call(this);_class2.prototype.__init15.call(this);_class2.prototype.__init16.call(this);_class2.prototype.__init17.call(this);_class2.prototype.__init18.call(this);_class2.prototype.__init19.call(this);_class2.prototype.__init20.call(this);_class2.prototype.__init21.call(this);_class2.prototype.__init22.call(this);_class2.prototype.__init23.call(this);_class2.prototype.__init24.call(this);_class2.prototype.__init25.call(this);_class2.prototype.__init26.call(this);_class2.prototype.__init27.call(this);_class2.prototype.__init28.call(this);_class2.prototype.__init29.call(this);_class2.prototype.__init30.call(this);_class2.prototype.__init31.call(this);_class2.prototype.__init32.call(this);_class2.prototype.__init33.call(this);_class2.prototype.__init34.call(this);_class2.prototype.__init35.call(this);_class2.prototype.__init36.call(this);_class2.prototype.__init37.call(this);_class2.prototype.__init38.call(this);_class2.prototype.__init39.call(this);_class2.prototype.__init40.call(this);_class2.prototype.__init41.call(this);_class2.prototype.__init42.call(this);_class2.prototype.__init43.call(this);_class2.prototype.__init44.call(this);_class2.prototype.__init45.call(this);_class2.prototype.__init46.call(this);_class2.prototype.__init47.call(this);_class2.prototype.__init48.call(this);_class2.prototype.__init49.call(this);_class2.prototype.__init50.call(this);_class2.prototype.__init51.call(this);_class2.prototype.__init52.call(this);_class2.prototype.__init53.call(this);_class2.prototype.__init54.call(this);_class2.prototype.__init55.call(this);_class2.prototype.__init56.call(this);_class2.prototype.__init57.call(this);_class2.prototype.__init58.call(this);_class2.prototype.__init59.call(this);_class2.prototype.__init60.call(this);_class2.prototype.__init61.call(this);
738
+ __init56() {this.gpuHasContent = false}
739
+ __init57() {this.mouseX = -9999}
740
+ __init58() {this.mouseY = -9999}
741
+ __init59() {this.pointerMoveListener = null}
742
+ __init60() {this.pointerLeaveListener = null}
743
+ __init61() {this.hasWarnedZeroSize = false}
744
+ __init62() {this.fontLoadHandler = null}
745
+ // ── Dev-mode warning infrastructure ──────────────────────────────
746
+ //
747
+ // Enable with `Scene.devMode = true` or by setting `globalThis.__DEV__`.
748
+ // Auto-detected when `NODE_ENV === 'development'`.
749
+ //
750
+ // Checks run once every ~120 frames (~2s at 60fps) to keep overhead
751
+ // negligible even when dev mode is on.
752
+ /** Toggle development-mode runtime warnings globally. */
753
+ static __initStatic3() {this.devMode = false}
754
+ static _devModeDetected() {
755
+ if (_Scene.devMode) return true;
756
+ const gp = typeof globalThis !== "undefined" ? globalThis : void 0;
757
+ if (_optionalChain([gp, 'optionalAccess', _23 => _23.__DEV__])) return true;
758
+ if (_optionalChain([gp, 'optionalAccess', _24 => _24.process, 'optionalAccess', _25 => _25.env, 'optionalAccess', _26 => _26.NODE_ENV]) === "development") return true;
759
+ return false;
760
+ }
761
+
762
+ __init63() {this._devFrameCount = 0}
763
+ _devWarn(message) {
764
+ if (!this._devActive) return;
765
+ console.warn(`[vectojs/dev] ${message}`);
766
+ }
767
+ /** @internal Periodic dev checks — called once per frame in dev mode. */
768
+ _devRunChecks() {
769
+ this._devFrameCount++;
770
+ if (this._devFrameCount % 120 !== 0) return;
771
+ if (this.a11yElements) {
772
+ let interactiveCount = 0;
773
+ const walk = (node) => {
774
+ if (node.interactive && node.width > 0) interactiveCount++;
775
+ for (const c of node.children) walk(c);
776
+ };
777
+ walk(this.root);
778
+ for (const c of this.overlayRoot.children) walk(c);
779
+ const shadowCount = this.a11yElements.size;
780
+ if (shadowCount > interactiveCount + 2) {
781
+ this._devWarn(
782
+ `a11yElements (${shadowCount}) exceeds interactive entities (${interactiveCount}). Call scene.detachA11y(entity) before removing interactive children from the tree, or their shadow nodes leak.`
783
+ );
784
+ }
785
+ }
786
+ let checked = 0;
787
+ const walkProjections = (node) => {
788
+ if (checked > 10) return;
789
+ const proj = _optionalChain([node, 'access', _27 => _27.getContentProjection, 'optionalCall', _28 => _28()]);
790
+ if (_optionalChain([proj, 'optionalAccess', _29 => _29.text]) && proj.selectable !== false) {
791
+ const el = _optionalChain([this, 'access', _30 => _30.contentElements, 'optionalAccess', _31 => _31.get, 'call', _32 => _32(node.id)]);
792
+ if (el) {
793
+ const projectedText = el.textContent || "";
794
+ if (projectedText !== "" && projectedText !== proj.text) {
795
+ this._devWarn(
796
+ `Content projection mismatch for entity "${node.id}": projection says "${proj.text.slice(0, 60)}" but DOM shows "${projectedText.slice(0, 60)}". Ensure getContentProjection() output matches what drawSelf renders.`
797
+ );
798
+ }
799
+ }
800
+ }
801
+ checked++;
802
+ for (const c of node.children) walkProjections(c);
803
+ };
804
+ walkProjections(this.root);
805
+ }
806
+ constructor(canvas, options = {}) {;_class2.prototype.__init7.call(this);_class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);_class2.prototype.__init10.call(this);_class2.prototype.__init11.call(this);_class2.prototype.__init12.call(this);_class2.prototype.__init13.call(this);_class2.prototype.__init14.call(this);_class2.prototype.__init15.call(this);_class2.prototype.__init16.call(this);_class2.prototype.__init17.call(this);_class2.prototype.__init18.call(this);_class2.prototype.__init19.call(this);_class2.prototype.__init20.call(this);_class2.prototype.__init21.call(this);_class2.prototype.__init22.call(this);_class2.prototype.__init23.call(this);_class2.prototype.__init24.call(this);_class2.prototype.__init25.call(this);_class2.prototype.__init26.call(this);_class2.prototype.__init27.call(this);_class2.prototype.__init28.call(this);_class2.prototype.__init29.call(this);_class2.prototype.__init30.call(this);_class2.prototype.__init31.call(this);_class2.prototype.__init32.call(this);_class2.prototype.__init33.call(this);_class2.prototype.__init34.call(this);_class2.prototype.__init35.call(this);_class2.prototype.__init36.call(this);_class2.prototype.__init37.call(this);_class2.prototype.__init38.call(this);_class2.prototype.__init39.call(this);_class2.prototype.__init40.call(this);_class2.prototype.__init41.call(this);_class2.prototype.__init42.call(this);_class2.prototype.__init43.call(this);_class2.prototype.__init44.call(this);_class2.prototype.__init45.call(this);_class2.prototype.__init46.call(this);_class2.prototype.__init47.call(this);_class2.prototype.__init48.call(this);_class2.prototype.__init49.call(this);_class2.prototype.__init50.call(this);_class2.prototype.__init51.call(this);_class2.prototype.__init52.call(this);_class2.prototype.__init53.call(this);_class2.prototype.__init54.call(this);_class2.prototype.__init55.call(this);_class2.prototype.__init56.call(this);_class2.prototype.__init57.call(this);_class2.prototype.__init58.call(this);_class2.prototype.__init59.call(this);_class2.prototype.__init60.call(this);_class2.prototype.__init61.call(this);_class2.prototype.__init62.call(this);_class2.prototype.__init63.call(this);
739
807
  this.canvas = canvas;
740
808
  this.debugA11y = _nullishCoalesce(options.debugA11y, () => ( false));
741
809
  this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
742
810
  this.maxDPR = options.maxDPR;
743
811
  if (this.disableWindowResize) {
744
- const styleWidth = parseInlinePx(_optionalChain([canvas, 'access', _23 => _23.style, 'optionalAccess', _24 => _24.width]));
745
- const styleHeight = parseInlinePx(_optionalChain([canvas, 'access', _25 => _25.style, 'optionalAccess', _26 => _26.height]));
812
+ const styleWidth = parseInlinePx(_optionalChain([canvas, 'access', _33 => _33.style, 'optionalAccess', _34 => _34.width]));
813
+ const styleHeight = parseInlinePx(_optionalChain([canvas, 'access', _35 => _35.style, 'optionalAccess', _36 => _36.height]));
746
814
  this.width = _nullishCoalesce(styleWidth, () => ( (canvas.width || canvas.clientWidth || 0)));
747
815
  this.height = _nullishCoalesce(styleHeight, () => ( (canvas.height || canvas.clientHeight || 0)));
748
816
  } else {
@@ -750,15 +818,16 @@ var Scene = (_class2 = class _Scene {
750
818
  this.height = typeof window !== "undefined" ? window.innerHeight : canvas.clientHeight || canvas.height || 600;
751
819
  }
752
820
  const globalProcess = typeof globalThis !== "undefined" ? globalThis.process : void 0;
753
- const isTest = globalProcess && (_optionalChain([globalProcess, 'access', _27 => _27.env, 'optionalAccess', _28 => _28.NODE_ENV]) === "test" || _optionalChain([globalProcess, 'access', _29 => _29.env, 'optionalAccess', _30 => _30.VITEST]) === "true");
821
+ const isTest = globalProcess && (_optionalChain([globalProcess, 'access', _37 => _37.env, 'optionalAccess', _38 => _38.NODE_ENV]) === "test" || _optionalChain([globalProcess, 'access', _39 => _39.env, 'optionalAccess', _40 => _40.VITEST]) === "true");
754
822
  this.maxFPS = _nullishCoalesce(options.maxFPS, () => ( (isTest ? 0 : 60)));
755
823
  this.respectReducedMotion = _nullishCoalesce(options.respectReducedMotion, () => ( true));
756
824
  this.autoThrottle = _nullishCoalesce(options.autoThrottle, () => ( true));
757
825
  this.particleBackend = _nullishCoalesce(options.particleBackend, () => ( "auto"));
758
826
  this.a11ySyncInterval = _nullishCoalesce(options.a11ySyncInterval, () => ( 0));
759
827
  this.contentProjectionEnabled = _nullishCoalesce(options.contentProjection, () => ( true));
828
+ this._devActive = _Scene._devModeDetected();
760
829
  this.reducedMotionQuery = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
761
- this.root = new class RootEntity extends _chunkGKLTTGBRjs.Entity {
830
+ this.root = new class RootEntity extends _chunk2Z23LTH3js.Entity {
762
831
  isPointInside() {
763
832
  return false;
764
833
  }
@@ -767,7 +836,7 @@ var Scene = (_class2 = class _Scene {
767
836
  }
768
837
  }("root");
769
838
  this.root._scene = this;
770
- this.overlayRoot = new class OverlayRoot extends _chunkGKLTTGBRjs.Entity {
839
+ this.overlayRoot = new class OverlayRoot extends _chunk2Z23LTH3js.Entity {
771
840
  isPointInside() {
772
841
  return false;
773
842
  }
@@ -920,7 +989,7 @@ var Scene = (_class2 = class _Scene {
920
989
  };
921
990
  if (typeof document !== "undefined" && document.fonts) {
922
991
  this.fontLoadHandler = () => {
923
- _chunkGKLTTGBRjs.clearCssLineBoxMetrics.call(void 0, );
992
+ _chunk2Z23LTH3js.clearCssLineBoxMetrics.call(void 0, );
924
993
  this.contentFontEpoch++;
925
994
  this.markDirty();
926
995
  };
@@ -936,10 +1005,10 @@ var Scene = (_class2 = class _Scene {
936
1005
  }
937
1006
  releaseContentSelectionForRebuild(el) {
938
1007
  const selection = typeof window !== "undefined" && typeof window.getSelection === "function" ? window.getSelection() : null;
939
- const ownsSelection = this.contentSelectionAnchor && el.contains(this.contentSelectionAnchor.node) || (_optionalChain([selection, 'optionalAccess', _31 => _31.anchorNode]) ? el.contains(selection.anchorNode) : false) || (_optionalChain([selection, 'optionalAccess', _32 => _32.focusNode]) ? el.contains(selection.focusNode) : false);
1008
+ const ownsSelection = this.contentSelectionAnchor && el.contains(this.contentSelectionAnchor.node) || (_optionalChain([selection, 'optionalAccess', _41 => _41.anchorNode]) ? el.contains(selection.anchorNode) : false) || (_optionalChain([selection, 'optionalAccess', _42 => _42.focusNode]) ? el.contains(selection.focusNode) : false);
940
1009
  if (!ownsSelection) return;
941
1010
  this.endContentSelectionDrag();
942
- _optionalChain([selection, 'optionalAccess', _33 => _33.removeAllRanges, 'call', _34 => _34()]);
1011
+ _optionalChain([selection, 'optionalAccess', _43 => _43.removeAllRanges, 'call', _44 => _44()]);
943
1012
  }
944
1013
  /**
945
1014
  * Expose the underlying {@link IRenderer} for advanced direct-draw operations.
@@ -951,7 +1020,7 @@ var Scene = (_class2 = class _Scene {
951
1020
  }
952
1021
  /** Convert browser viewport coordinates into this Scene's logical coordinates. */
953
1022
  clientToScene(clientX, clientY) {
954
- const rect = _optionalChain([this, 'access', _35 => _35.canvas, 'access', _36 => _36.getBoundingClientRect, 'optionalCall', _37 => _37()]);
1023
+ const rect = _optionalChain([this, 'access', _45 => _45.canvas, 'access', _46 => _46.getBoundingClientRect, 'optionalCall', _47 => _47()]);
955
1024
  if (!rect) return { x: clientX, y: clientY };
956
1025
  const cssWidth = rect.width || this.canvas.clientWidth || this.width;
957
1026
  const cssHeight = rect.height || this.canvas.clientHeight || this.height;
@@ -977,7 +1046,7 @@ var Scene = (_class2 = class _Scene {
977
1046
  cancelAnimationFrame(calibrationFrame);
978
1047
  }
979
1048
  this.contentGridCalibrationFrames.delete(entityId);
980
- _optionalChain([this, 'access', _38 => _38.contentGridCalibrationProbes, 'access', _39 => _39.get, 'call', _40 => _40(entityId), 'optionalAccess', _41 => _41.remove, 'call', _42 => _42()]);
1049
+ _optionalChain([this, 'access', _48 => _48.contentGridCalibrationProbes, 'access', _49 => _49.get, 'call', _50 => _50(entityId), 'optionalAccess', _51 => _51.remove, 'call', _52 => _52()]);
981
1050
  this.contentGridCalibrationProbes.delete(entityId);
982
1051
  delete el.dataset.vectoGridCalibrationPending;
983
1052
  delete el.dataset.vectoGridCalibration;
@@ -1093,8 +1162,8 @@ var Scene = (_class2 = class _Scene {
1093
1162
  this.canvas.removeEventListener("pointerleave", this.pointerLeaveListener);
1094
1163
  }
1095
1164
  }
1096
- _optionalChain([this, 'access', _43 => _43.a11yRoot, 'optionalAccess', _44 => _44.remove, 'call', _45 => _45()]);
1097
- _optionalChain([this, 'access', _46 => _46.portalRoot, 'optionalAccess', _47 => _47.remove, 'call', _48 => _48()]);
1165
+ _optionalChain([this, 'access', _53 => _53.a11yRoot, 'optionalAccess', _54 => _54.remove, 'call', _55 => _55()]);
1166
+ _optionalChain([this, 'access', _56 => _56.portalRoot, 'optionalAccess', _57 => _57.remove, 'call', _58 => _58()]);
1098
1167
  this.a11yElements.clear();
1099
1168
  for (const el of this.contentElements.values()) el.remove();
1100
1169
  this.contentElements.clear();
@@ -1107,10 +1176,10 @@ var Scene = (_class2 = class _Scene {
1107
1176
  for (const probe of this.contentGridCalibrationProbes.values()) probe.remove();
1108
1177
  this.contentGridCalibrationProbes.clear();
1109
1178
  this.endContentSelectionDrag();
1110
- _optionalChain([this, 'access', _49 => _49.pointRenderer, 'optionalAccess', _50 => _50.destroy, 'call', _51 => _51()]);
1111
- _optionalChain([this, 'access', _52 => _52.renderer, 'access', _53 => _53.dispose, 'optionalCall', _54 => _54()]);
1112
- _optionalChain([this, 'access', _55 => _55.glCanvas, 'optionalAccess', _56 => _56.remove, 'call', _57 => _57()]);
1113
- _optionalChain([this, 'access', _58 => _58.gpuCanvas, 'optionalAccess', _59 => _59.remove, 'call', _60 => _60()]);
1179
+ _optionalChain([this, 'access', _59 => _59.pointRenderer, 'optionalAccess', _60 => _60.destroy, 'call', _61 => _61()]);
1180
+ _optionalChain([this, 'access', _62 => _62.renderer, 'access', _63 => _63.dispose, 'optionalCall', _64 => _64()]);
1181
+ _optionalChain([this, 'access', _65 => _65.glCanvas, 'optionalAccess', _66 => _66.remove, 'call', _67 => _67()]);
1182
+ _optionalChain([this, 'access', _68 => _68.gpuCanvas, 'optionalAccess', _69 => _69.remove, 'call', _70 => _70()]);
1114
1183
  this.gpuCanvas = null;
1115
1184
  this.gpuContext = null;
1116
1185
  if (this.recoveryTimerId) {
@@ -1122,7 +1191,7 @@ var Scene = (_class2 = class _Scene {
1122
1191
  this.manager = null;
1123
1192
  }
1124
1193
  if (this.device) {
1125
- _optionalChain([this, 'access', _61 => _61.device, 'access', _62 => _62.destroy, 'optionalCall', _63 => _63()]);
1194
+ _optionalChain([this, 'access', _71 => _71.device, 'access', _72 => _72.destroy, 'optionalCall', _73 => _73()]);
1126
1195
  this.device = null;
1127
1196
  }
1128
1197
  }
@@ -1271,15 +1340,18 @@ var Scene = (_class2 = class _Scene {
1271
1340
  el.style.background = "transparent";
1272
1341
  }
1273
1342
  el.addEventListener("click", (e) => {
1274
- node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("click", node, e));
1343
+ node.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)("click", node, e));
1344
+ });
1345
+ el.addEventListener("dblclick", (e) => {
1346
+ node.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)("dblclick", node, e));
1275
1347
  });
1276
1348
  el.addEventListener("mouseenter", (e) => {
1277
1349
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.2)";
1278
- node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("hover", node, e, false));
1350
+ node.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)("hover", node, e, false));
1279
1351
  });
1280
1352
  el.addEventListener("mouseleave", (e) => {
1281
1353
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.05)";
1282
- node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("pointerleave", node, e, false));
1354
+ node.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)("pointerleave", node, e, false));
1283
1355
  });
1284
1356
  const capEl = el;
1285
1357
  const releasePointer = (event) => {
@@ -1295,32 +1367,32 @@ var Scene = (_class2 = class _Scene {
1295
1367
  };
1296
1368
  el.addEventListener("pointerdown", (e) => {
1297
1369
  if (typeof capEl.setPointerCapture === "function") capEl.setPointerCapture(e.pointerId);
1298
- node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("pointerdown", node, e));
1370
+ node.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)("pointerdown", node, e));
1299
1371
  });
1300
1372
  el.addEventListener("pointerup", (e) => {
1301
1373
  releasePointer(e);
1302
- node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("pointerup", node, e));
1374
+ node.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)("pointerup", node, e));
1303
1375
  });
1304
1376
  el.addEventListener("pointercancel", (e) => {
1305
1377
  releasePointer(e);
1306
- node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("pointercancel", node, e));
1378
+ node.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)("pointercancel", node, e));
1307
1379
  });
1308
1380
  el.addEventListener(
1309
1381
  "pointermove",
1310
- (e) => node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("pointermove", node, e))
1382
+ (e) => node.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)("pointermove", node, e))
1311
1383
  );
1312
1384
  el.addEventListener(
1313
1385
  "wheel",
1314
1386
  (e) => {
1315
- node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("wheel", node, e));
1387
+ node.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)("wheel", node, e));
1316
1388
  },
1317
1389
  { passive: false }
1318
1390
  );
1319
1391
  el.addEventListener("keydown", (e) => {
1320
- node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("keydown", node, e));
1392
+ node.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)("keydown", node, e));
1321
1393
  });
1322
1394
  el.addEventListener("keyup", (e) => {
1323
- node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("keyup", node, e));
1395
+ node.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)("keyup", node, e));
1324
1396
  });
1325
1397
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
1326
1398
  const input = el;
@@ -1347,7 +1419,7 @@ var Scene = (_class2 = class _Scene {
1347
1419
  });
1348
1420
  el.addEventListener("compositionupdate", (e) => {
1349
1421
  const data = _nullishCoalesce(e.data, () => ( ""));
1350
- composition = { start: _nullishCoalesce(_optionalChain([composition, 'optionalAccess', _64 => _64.start]), () => ( 0)), length: data.length };
1422
+ composition = { start: _nullishCoalesce(_optionalChain([composition, 'optionalAccess', _74 => _74.start]), () => ( 0)), length: data.length };
1351
1423
  forward();
1352
1424
  });
1353
1425
  el.addEventListener("compositionend", () => {
@@ -1380,7 +1452,7 @@ var Scene = (_class2 = class _Scene {
1380
1452
  el.addEventListener("keydown", (e) => {
1381
1453
  if (e.key === "Enter" || e.key === " ") {
1382
1454
  e.preventDefault();
1383
- node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("click", node, e));
1455
+ node.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)("click", node, e));
1384
1456
  }
1385
1457
  });
1386
1458
  }
@@ -1398,6 +1470,10 @@ var Scene = (_class2 = class _Scene {
1398
1470
  if (el.style.pointerEvents !== semanticPointerEvents) {
1399
1471
  el.style.pointerEvents = semanticPointerEvents;
1400
1472
  }
1473
+ const renderOrder = this.a11yRenderOrders.get(node.id);
1474
+ if (renderOrder !== void 0 && el.style.zIndex !== String(renderOrder)) {
1475
+ el.style.zIndex = String(renderOrder);
1476
+ }
1401
1477
  const implicitTabIndex = !isNativelyFocusable(el) && attrs.role && INTERACTIVE_A11Y_ROLES.has(attrs.role) ? 0 : null;
1402
1478
  const desiredTabIndex = _nullishCoalesce(attrs.tabIndex, () => ( implicitTabIndex));
1403
1479
  if (desiredTabIndex === null) {
@@ -1539,7 +1615,7 @@ var Scene = (_class2 = class _Scene {
1539
1615
  el.addEventListener(
1540
1616
  "wheel",
1541
1617
  (e2) => {
1542
- node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("wheel", node, e2));
1618
+ node.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)("wheel", node, e2));
1543
1619
  },
1544
1620
  { passive: false }
1545
1621
  );
@@ -1570,7 +1646,7 @@ var Scene = (_class2 = class _Scene {
1570
1646
  lineElement.style.position = "absolute";
1571
1647
  lineElement.dir = "auto";
1572
1648
  lineElement.style.left = `${line.x}px`;
1573
- lineElement.style.top = `${line.y + line.baseline - _chunkGKLTTGBRjs.cssLineBoxBaseline.call(void 0, lineFont, lineHeight2)}px`;
1649
+ lineElement.style.top = `${line.y + line.baseline - _chunk2Z23LTH3js.cssLineBoxBaseline.call(void 0, lineFont, lineHeight2)}px`;
1574
1650
  lineElement.style.whiteSpace = "pre";
1575
1651
  if (lineFont) lineElement.style.font = lineFont;
1576
1652
  lineElement.style.lineHeight = `${lineHeight2}px`;
@@ -1622,7 +1698,7 @@ var Scene = (_class2 = class _Scene {
1622
1698
  const { a, b, c, d, e, f } = node.getWorldTransform();
1623
1699
  const contentX = _nullishCoalesce(projection.contentX, () => ( 0));
1624
1700
  const contentY = _nullishCoalesce(projection.contentY, () => ( 0));
1625
- const baselineOffset = lines && lines.length > 0 ? 0 : projection.baseline === void 0 ? 0 : projection.baseline - _chunkGKLTTGBRjs.cssLineBoxBaseline.call(void 0, font, _nullishCoalesce(projection.lineHeight, () => ( 16)));
1701
+ const baselineOffset = lines && lines.length > 0 ? 0 : projection.baseline === void 0 ? 0 : projection.baseline - _chunk2Z23LTH3js.cssLineBoxBaseline.call(void 0, font, _nullishCoalesce(projection.lineHeight, () => ( 16)));
1626
1702
  const localY = contentY + baselineOffset;
1627
1703
  el.style.left = `${e + a * contentX + c * localY}px`;
1628
1704
  el.style.top = `${f + b * contentX + d * localY}px`;
@@ -1686,15 +1762,15 @@ var Scene = (_class2 = class _Scene {
1686
1762
  for (let lineIndex = 0; lineIndex < grid.lines.length; lineIndex++) {
1687
1763
  const gridLine = grid.lines[lineIndex];
1688
1764
  const projectedLine = projectionLines[lineIndex];
1689
- const lineHeight = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _65 => _65.lineHeight]), () => ( grid.lineHeight));
1690
- const baseline = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _66 => _66.baseline]), () => ( grid.baseline));
1691
- const lineFont = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _67 => _67.font]), () => ( grid.font));
1765
+ const lineHeight = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _75 => _75.lineHeight]), () => ( grid.lineHeight));
1766
+ const baseline = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _76 => _76.baseline]), () => ( grid.baseline));
1767
+ const lineFont = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _77 => _77.font]), () => ( grid.font));
1692
1768
  const lineElement = document.createElement("span");
1693
1769
  lineElement.dir = "ltr";
1694
1770
  lineElement.dataset.vectoGridLine = `${lineIndex}`;
1695
1771
  lineElement.style.position = "absolute";
1696
- lineElement.style.left = `${_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _68 => _68.x]), () => ( 0))}px`;
1697
- lineElement.style.top = `${(_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _69 => _69.y]), () => ( lineIndex * grid.lineHeight))) + baseline - _chunkGKLTTGBRjs.cssLineBoxBaseline.call(void 0, lineFont, lineHeight)}px`;
1772
+ lineElement.style.left = `${_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _78 => _78.x]), () => ( 0))}px`;
1773
+ lineElement.style.top = `${(_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _79 => _79.y]), () => ( lineIndex * grid.lineHeight))) + baseline - _chunk2Z23LTH3js.cssLineBoxBaseline.call(void 0, lineFont, lineHeight)}px`;
1698
1774
  lineElement.style.width = `${gridLine.width}px`;
1699
1775
  lineElement.style.height = `${lineHeight}px`;
1700
1776
  lineElement.style.whiteSpace = "pre";
@@ -1790,7 +1866,7 @@ var Scene = (_class2 = class _Scene {
1790
1866
  if (previous !== void 0 && typeof cancelAnimationFrame === "function") {
1791
1867
  cancelAnimationFrame(previous);
1792
1868
  }
1793
- _optionalChain([this, 'access', _70 => _70.contentGridCalibrationProbes, 'access', _71 => _71.get, 'call', _72 => _72(entityId), 'optionalAccess', _73 => _73.remove, 'call', _74 => _74()]);
1869
+ _optionalChain([this, 'access', _80 => _80.contentGridCalibrationProbes, 'access', _81 => _81.get, 'call', _82 => _82(entityId), 'optionalAccess', _83 => _83.remove, 'call', _84 => _84()]);
1794
1870
  this.contentGridCalibrationProbes.delete(entityId);
1795
1871
  const calibrationStart = typeof performance !== "undefined" ? performance.now() : 0;
1796
1872
  const probe = document.createElement("div");
@@ -1820,7 +1896,7 @@ var Scene = (_class2 = class _Scene {
1820
1896
  const sourceLength = Number(_nullishCoalesce(target.dataset.vectoGridSourceLength, () => ( 0)));
1821
1897
  const targetWidth = Number(_nullishCoalesce(target.dataset.vectoGridAdvance, () => ( 0)));
1822
1898
  if (sourceLength <= 0 || targetWidth <= 0) continue;
1823
- const sourceText = _nullishCoalesce(_optionalChain([target, 'access', _75 => _75.textContent, 'optionalAccess', _76 => _76.slice, 'call', _77 => _77(0, sourceLength)]), () => ( ""));
1899
+ const sourceText = _nullishCoalesce(_optionalChain([target, 'access', _85 => _85.textContent, 'optionalAccess', _86 => _86.slice, 'call', _87 => _87(0, sourceLength)]), () => ( ""));
1824
1900
  if (!sourceText) continue;
1825
1901
  const measurementKey = JSON.stringify([
1826
1902
  target.style.font,
@@ -1970,12 +2046,12 @@ var Scene = (_class2 = class _Scene {
1970
2046
  syncOverlayGeometry() {
1971
2047
  const parent = this.canvas.parentElement;
1972
2048
  if (!parent) return;
1973
- const canvasRect = _optionalChain([this, 'access', _78 => _78.canvas, 'access', _79 => _79.getBoundingClientRect, 'optionalCall', _80 => _80()]);
1974
- const parentRect = _optionalChain([parent, 'access', _81 => _81.getBoundingClientRect, 'optionalCall', _82 => _82()]);
1975
- const cssWidth = _optionalChain([canvasRect, 'optionalAccess', _83 => _83.width]) || this.canvas.clientWidth || this.width;
1976
- const cssHeight = _optionalChain([canvasRect, 'optionalAccess', _84 => _84.height]) || this.canvas.clientHeight || this.height;
1977
- const left = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _85 => _85.left]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _86 => _86.left]), () => ( 0))) - (parent.clientLeft || 0) + parent.scrollLeft;
1978
- const top = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _87 => _87.top]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _88 => _88.top]), () => ( 0))) - (parent.clientTop || 0) + parent.scrollTop;
2049
+ const canvasRect = _optionalChain([this, 'access', _88 => _88.canvas, 'access', _89 => _89.getBoundingClientRect, 'optionalCall', _90 => _90()]);
2050
+ const parentRect = _optionalChain([parent, 'access', _91 => _91.getBoundingClientRect, 'optionalCall', _92 => _92()]);
2051
+ const cssWidth = _optionalChain([canvasRect, 'optionalAccess', _93 => _93.width]) || this.canvas.clientWidth || this.width;
2052
+ const cssHeight = _optionalChain([canvasRect, 'optionalAccess', _94 => _94.height]) || this.canvas.clientHeight || this.height;
2053
+ const left = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _95 => _95.left]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _96 => _96.left]), () => ( 0))) - (parent.clientLeft || 0) + parent.scrollLeft;
2054
+ const top = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _97 => _97.top]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _98 => _98.top]), () => ( 0))) - (parent.clientTop || 0) + parent.scrollTop;
1979
2055
  const scaleX = this.width > 0 ? cssWidth / this.width : 1;
1980
2056
  const scaleY = this.height > 0 ? cssHeight / this.height : 1;
1981
2057
  for (const root of [this.a11yRoot, this.portalRoot]) {
@@ -2100,7 +2176,7 @@ var Scene = (_class2 = class _Scene {
2100
2176
  * (and {@link respectReducedMotion} is on). `0` means uncapped.
2101
2177
  */
2102
2178
  effectiveMaxFPS() {
2103
- const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access', _89 => _89.reducedMotionQuery, 'optionalAccess', _90 => _90.matches]);
2179
+ const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access', _99 => _99.reducedMotionQuery, 'optionalAccess', _100 => _100.matches]);
2104
2180
  if (reduced)
2105
2181
  return this.maxFPS > 0 ? Math.min(this.maxFPS, REDUCED_MOTION_FPS) : REDUCED_MOTION_FPS;
2106
2182
  return this.maxFPS;
@@ -2158,6 +2234,7 @@ var Scene = (_class2 = class _Scene {
2158
2234
  }
2159
2235
  if (isMainRenderer) {
2160
2236
  this.renderOrderCounter = 0;
2237
+ this.a11yRenderOrders.clear();
2161
2238
  this.activePortalsThisFrame.clear();
2162
2239
  }
2163
2240
  const computeEntities = [];
@@ -2278,7 +2355,7 @@ var Scene = (_class2 = class _Scene {
2278
2355
  }
2279
2356
  renderer.clear();
2280
2357
  if (isMainRenderer) {
2281
- _optionalChain([this, 'access', _91 => _91.pointRenderer, 'optionalAccess', _92 => _92.begin, 'call', _93 => _93()]);
2358
+ _optionalChain([this, 'access', _101 => _101.pointRenderer, 'optionalAccess', _102 => _102.begin, 'call', _103 => _103()]);
2282
2359
  }
2283
2360
  const vw = this.width;
2284
2361
  const vh = this.height;
@@ -2289,6 +2366,13 @@ var Scene = (_class2 = class _Scene {
2289
2366
  node.update(dt, time);
2290
2367
  if (!walkHadAnimation && node.hasPendingAnimations()) walkHadAnimation = true;
2291
2368
  if (!walkHadInteractive && node.interactive) walkHadInteractive = true;
2369
+ if (this._devActive && this._devFrameCount % 120 === 0) {
2370
+ if (node.update !== _chunk2Z23LTH3js.Entity.prototype.update && node.hasPendingAnimations === _chunk2Z23LTH3js.Entity.prototype.hasPendingAnimations) {
2371
+ this._devWarn(
2372
+ `Entity "${node.id}" overrides update() but not hasPendingAnimations(). Custom motion in update() without overriding hasPendingAnimations() causes the idle throttle to drop the animation to ~2fps. Override hasPendingAnimations() to return true while motion is in flight.`
2373
+ );
2374
+ }
2375
+ }
2292
2376
  }
2293
2377
  const cos = Math.cos(node.rotation);
2294
2378
  const sin = Math.sin(node.rotation);
@@ -2309,8 +2393,11 @@ var Scene = (_class2 = class _Scene {
2309
2393
  const orthogonalTolerance = Math.max(1, worldScaleX * worldScaleY) * 1e-6;
2310
2394
  const isSimilarityTransform = Number.isFinite(worldScaleX) && Number.isFinite(worldScaleY) && Math.abs(worldScaleX - worldScaleY) <= scaleTolerance && Math.abs(a * c + b * d) <= orthogonalTolerance;
2311
2395
  const a11yEl = isMainRenderer ? this.a11yElements.get(node.id) : void 0;
2312
- if (a11yEl) {
2313
- a11yEl.style.zIndex = String(this.renderOrderCounter++);
2396
+ const willProjectA11y = isMainRenderer && node.interactive && (node.width > 0 || node.a11yFullViewport);
2397
+ if (a11yEl || willProjectA11y) {
2398
+ const renderOrder = this.renderOrderCounter++;
2399
+ if (willProjectA11y) this.a11yRenderOrders.set(node.id, renderOrder);
2400
+ if (a11yEl) a11yEl.style.zIndex = String(renderOrder);
2314
2401
  }
2315
2402
  if (node.isDOMPortal) {
2316
2403
  if (isMainRenderer) {
@@ -2416,9 +2503,13 @@ var Scene = (_class2 = class _Scene {
2416
2503
  }
2417
2504
  renderer.flush();
2418
2505
  if (isMainRenderer) {
2419
- _optionalChain([this, 'access', _94 => _94.pointRenderer, 'optionalAccess', _95 => _95.flush, 'call', _96 => _96()]);
2506
+ _optionalChain([this, 'access', _104 => _104.pointRenderer, 'optionalAccess', _105 => _105.flush, 'call', _106 => _106()]);
2507
+ }
2508
+ _optionalChain([renderer, 'access', _107 => _107.present, 'optionalCall', _108 => _108()]);
2509
+ if (this._devActive) {
2510
+ this._devFrameCount++;
2511
+ this._devRunChecks();
2420
2512
  }
2421
- _optionalChain([renderer, 'access', _97 => _97.present, 'optionalCall', _98 => _98()]);
2422
2513
  }
2423
2514
  /**
2424
2515
  * Export the current scene state to a lightweight, flat SVG XML string.
@@ -2622,7 +2713,7 @@ var Scene = (_class2 = class _Scene {
2622
2713
  }
2623
2714
  return null;
2624
2715
  }
2625
- }, _class2.__initStatic(), _class2.__initStatic2(), _class2);
2716
+ }, _class2.__initStatic(), _class2.__initStatic2(), _class2.__initStatic3(), _class2);
2626
2717
 
2627
2718
  // src/components/TextEntity.ts
2628
2719
  var sharedMeasurer;
@@ -2630,20 +2721,20 @@ function defaultMeasurer() {
2630
2721
  if (sharedMeasurer === void 0) sharedMeasurer = _chunkBA5HUUDFjs.createCanvasMeasurer.call(void 0, "sans-serif");
2631
2722
  return sharedMeasurer;
2632
2723
  }
2633
- var TextEntity = (_class3 = class extends _chunkGKLTTGBRjs.Entity {
2724
+ var TextEntity = (_class3 = class extends _chunk2Z23LTH3js.Entity {
2634
2725
 
2635
2726
 
2636
2727
 
2637
2728
 
2638
- __init62() {this.nodes = []}
2729
+ __init64() {this.nodes = []}
2639
2730
 
2640
- __init63() {this.fillStyle = "#94a3b8"}
2641
- __init64() {this.strokeStyle = null}
2642
- __init65() {this.hoveredFillStyle = "#ffffff"}
2643
- __init66() {this.lineWidth = 1}
2644
- __init67() {this.isHovered = false}
2731
+ __init65() {this.fillStyle = "#94a3b8"}
2732
+ __init66() {this.strokeStyle = null}
2733
+ __init67() {this.hoveredFillStyle = "#ffffff"}
2734
+ __init68() {this.lineWidth = 1}
2735
+ __init69() {this.isHovered = false}
2645
2736
  constructor(text, atlas, maxWidth, fontSize = 24) {
2646
- super();_class3.prototype.__init62.call(this);_class3.prototype.__init63.call(this);_class3.prototype.__init64.call(this);_class3.prototype.__init65.call(this);_class3.prototype.__init66.call(this);_class3.prototype.__init67.call(this);;
2737
+ super();_class3.prototype.__init64.call(this);_class3.prototype.__init65.call(this);_class3.prototype.__init66.call(this);_class3.prototype.__init67.call(this);_class3.prototype.__init68.call(this);_class3.prototype.__init69.call(this);;
2647
2738
  this.text = text;
2648
2739
  this.atlas = atlas;
2649
2740
  this.fontSize = fontSize;
@@ -2756,17 +2847,17 @@ var TextEntity = (_class3 = class extends _chunkGKLTTGBRjs.Entity {
2756
2847
  }, _class3);
2757
2848
 
2758
2849
  // src/components/GridTextEntity.ts
2759
- var GridTextEntity = (_class4 = class extends _chunkGKLTTGBRjs.Entity {
2850
+ var GridTextEntity = (_class4 = class extends _chunk2Z23LTH3js.Entity {
2760
2851
 
2761
- __init68() {this.fillStyle = "#ffffff"}
2762
- __init69() {this.grid = []}
2852
+ __init70() {this.fillStyle = "#ffffff"}
2853
+ __init71() {this.grid = []}
2763
2854
  // Array of rows
2764
- __init70() {this.cols = 0}
2765
- __init71() {this.rows = 0}
2855
+ __init72() {this.cols = 0}
2856
+ __init73() {this.rows = 0}
2766
2857
 
2767
2858
 
2768
2859
  constructor(_atlas, fontSize = 10) {
2769
- super();_class4.prototype.__init68.call(this);_class4.prototype.__init69.call(this);_class4.prototype.__init70.call(this);_class4.prototype.__init71.call(this);;
2860
+ super();_class4.prototype.__init70.call(this);_class4.prototype.__init71.call(this);_class4.prototype.__init72.call(this);_class4.prototype.__init73.call(this);;
2770
2861
  this.fontSize = fontSize;
2771
2862
  this.charWidth = fontSize * 1;
2772
2863
  this.charHeight = fontSize * 1.1;
@@ -2775,7 +2866,7 @@ var GridTextEntity = (_class4 = class extends _chunkGKLTTGBRjs.Entity {
2775
2866
  updateGrid(ascii) {
2776
2867
  this.grid = ascii;
2777
2868
  this.rows = ascii.length;
2778
- this.cols = _optionalChain([ascii, 'access', _99 => _99[0], 'optionalAccess', _100 => _100.length]) || 0;
2869
+ this.cols = _optionalChain([ascii, 'access', _109 => _109[0], 'optionalAccess', _110 => _110.length]) || 0;
2779
2870
  }
2780
2871
  isPointInside(_globalX, _globalY) {
2781
2872
  return false;
@@ -2850,7 +2941,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
2850
2941
  const ey = py - cy;
2851
2942
  return ex * ex + ey * ey;
2852
2943
  }
2853
- var SplineEntity = (_class5 = class extends _chunkGKLTTGBRjs.Entity {
2944
+ var SplineEntity = (_class5 = class extends _chunk2Z23LTH3js.Entity {
2854
2945
 
2855
2946
 
2856
2947
 
@@ -2858,23 +2949,23 @@ var SplineEntity = (_class5 = class extends _chunkGKLTTGBRjs.Entity {
2858
2949
 
2859
2950
 
2860
2951
 
2861
- __init72() {this.offscreen = null}
2862
- __init73() {this.baked = false}
2952
+ __init74() {this.offscreen = null}
2953
+ __init75() {this.baked = false}
2863
2954
  /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
2864
- __init74() {this.bakedWidth = 0}
2865
- __init75() {this.bakedHeight = 0}
2955
+ __init76() {this.bakedWidth = 0}
2956
+ __init77() {this.bakedHeight = 0}
2866
2957
  /** Gradient strokes can't be baked to a solid-color bitmap; they render per-frame. */
2867
2958
 
2868
2959
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
2869
- __init76() {this.polylines = null}
2960
+ __init78() {this.polylines = null}
2870
2961
  /**
2871
2962
  * When `true`, the renderer draws a rounded-rect outline of the entity's
2872
2963
  * local bounds after painting the curves. Useful for drag feedback and
2873
2964
  * debugging hit areas. Defaults to `false`.
2874
2965
  */
2875
- __init77() {this.showBounds = false}
2966
+ __init79() {this.showBounds = false}
2876
2967
  constructor(doc, opts = {}) {
2877
- super();_class5.prototype.__init72.call(this);_class5.prototype.__init73.call(this);_class5.prototype.__init74.call(this);_class5.prototype.__init75.call(this);_class5.prototype.__init76.call(this);_class5.prototype.__init77.call(this);;
2968
+ super();_class5.prototype.__init74.call(this);_class5.prototype.__init75.call(this);_class5.prototype.__init76.call(this);_class5.prototype.__init77.call(this);_class5.prototype.__init78.call(this);_class5.prototype.__init79.call(this);;
2878
2969
  this.doc = doc;
2879
2970
  this.lineWidth = _nullishCoalesce(opts.lineWidth, () => ( 2));
2880
2971
  this.cache = _nullishCoalesce(opts.cache, () => ( true));
@@ -2885,7 +2976,7 @@ var SplineEntity = (_class5 = class extends _chunkGKLTTGBRjs.Entity {
2885
2976
  this.width = this.bounds.width;
2886
2977
  this.height = this.bounds.height;
2887
2978
  const isGradient = (c) => c !== null && !Array.isArray(c);
2888
- this.containsGradient = (_nullishCoalesce(_optionalChain([this, 'access', _101 => _101.doc, 'access', _102 => _102.equations, 'optionalAccess', _103 => _103.some, 'call', _104 => _104((eq) => isGradient(eq.color_rgb))]), () => ( false))) || (_nullishCoalesce(_optionalChain([this, 'access', _105 => _105.doc, 'access', _106 => _106.paths, 'optionalAccess', _107 => _107.some, 'call', _108 => _108((p) => isGradient(p.color_rgb))]), () => ( false)));
2979
+ this.containsGradient = (_nullishCoalesce(_optionalChain([this, 'access', _111 => _111.doc, 'access', _112 => _112.equations, 'optionalAccess', _113 => _113.some, 'call', _114 => _114((eq) => isGradient(eq.color_rgb))]), () => ( false))) || (_nullishCoalesce(_optionalChain([this, 'access', _115 => _115.doc, 'access', _116 => _116.paths, 'optionalAccess', _117 => _117.some, 'call', _118 => _118((p) => isGradient(p.color_rgb))]), () => ( false)));
2889
2980
  this.interactive = true;
2890
2981
  }
2891
2982
  computeBounds() {
@@ -3125,7 +3216,7 @@ async function loadSpline(url) {
3125
3216
  }
3126
3217
 
3127
3218
  // src/components/Rect.ts
3128
- var Rect = class extends _chunkGKLTTGBRjs.Entity {
3219
+ var Rect = class extends _chunk2Z23LTH3js.Entity {
3129
3220
 
3130
3221
 
3131
3222
 
@@ -3174,7 +3265,7 @@ var Rect = class extends _chunkGKLTTGBRjs.Entity {
3174
3265
  };
3175
3266
 
3176
3267
  // src/components/Circle.ts
3177
- var Circle = class extends _chunkGKLTTGBRjs.Entity {
3268
+ var Circle = class extends _chunk2Z23LTH3js.Entity {
3178
3269
 
3179
3270
 
3180
3271
 
@@ -3234,7 +3325,7 @@ var Circle = class extends _chunkGKLTTGBRjs.Entity {
3234
3325
  };
3235
3326
 
3236
3327
  // src/components/Group.ts
3237
- var Group = class extends _chunkGKLTTGBRjs.Entity {
3328
+ var Group = class extends _chunk2Z23LTH3js.Entity {
3238
3329
  constructor(...children) {
3239
3330
  super();
3240
3331
  if (children.length > 0) this.add(...children);
@@ -3249,9 +3340,9 @@ var Group = class extends _chunkGKLTTGBRjs.Entity {
3249
3340
  // src/math/SpatialHashGrid.ts
3250
3341
  var SpatialHashGrid = (_class6 = class {
3251
3342
 
3252
- __init78() {this.grid = /* @__PURE__ */ new Map()}
3253
- __init79() {this.entityCells = /* @__PURE__ */ new Map()}
3254
- constructor(cellSize = 64) {;_class6.prototype.__init78.call(this);_class6.prototype.__init79.call(this);
3343
+ __init80() {this.grid = /* @__PURE__ */ new Map()}
3344
+ __init81() {this.entityCells = /* @__PURE__ */ new Map()}
3345
+ constructor(cellSize = 64) {;_class6.prototype.__init80.call(this);_class6.prototype.__init81.call(this);
3255
3346
  this.cellSize = cellSize;
3256
3347
  }
3257
3348
  hash(cx, cy) {
@@ -3305,7 +3396,7 @@ var SpatialHashGrid = (_class6 = class {
3305
3396
  const keys = this.entityCells.get(id);
3306
3397
  if (!keys) return;
3307
3398
  for (const key of keys) {
3308
- _optionalChain([this, 'access', _109 => _109.grid, 'access', _110 => _110.get, 'call', _111 => _111(key), 'optionalAccess', _112 => _112.delete, 'call', _113 => _113(id)]);
3399
+ _optionalChain([this, 'access', _119 => _119.grid, 'access', _120 => _120.get, 'call', _121 => _121(key), 'optionalAccess', _122 => _122.delete, 'call', _123 => _123(id)]);
3309
3400
  }
3310
3401
  this.entityCells.delete(id);
3311
3402
  }
@@ -3341,20 +3432,20 @@ var SpatialHashGrid = (_class6 = class {
3341
3432
  }, _class6);
3342
3433
 
3343
3434
  // src/tree/DOMPortalEntity.ts
3344
- var DOMPortalEntity = (_class7 = class extends _chunkGKLTTGBRjs.Entity {
3435
+ var DOMPortalEntity = (_class7 = class extends _chunk2Z23LTH3js.Entity {
3345
3436
 
3346
- __init80() {this.isDOMPortal = true}
3347
- __init81() {this.domListeners = []}
3348
- __init82() {this.resizeObserver = null}
3349
- __init83() {this.cachedWidth = 100}
3350
- __init84() {this.cachedHeight = 100}
3351
- __init85() {this.lastWidth = ""}
3352
- __init86() {this.lastHeight = ""}
3353
- __init87() {this.lastTransform = ""}
3354
- __init88() {this.lastZIndex = ""}
3355
- __init89() {this.lastOpacity = ""}
3437
+ __init82() {this.isDOMPortal = true}
3438
+ __init83() {this.domListeners = []}
3439
+ __init84() {this.resizeObserver = null}
3440
+ __init85() {this.cachedWidth = 100}
3441
+ __init86() {this.cachedHeight = 100}
3442
+ __init87() {this.lastWidth = ""}
3443
+ __init88() {this.lastHeight = ""}
3444
+ __init89() {this.lastTransform = ""}
3445
+ __init90() {this.lastZIndex = ""}
3446
+ __init91() {this.lastOpacity = ""}
3356
3447
  constructor(domElement, width, height, id) {
3357
- super(id);_class7.prototype.__init80.call(this);_class7.prototype.__init81.call(this);_class7.prototype.__init82.call(this);_class7.prototype.__init83.call(this);_class7.prototype.__init84.call(this);_class7.prototype.__init85.call(this);_class7.prototype.__init86.call(this);_class7.prototype.__init87.call(this);_class7.prototype.__init88.call(this);_class7.prototype.__init89.call(this);;
3448
+ super(id);_class7.prototype.__init82.call(this);_class7.prototype.__init83.call(this);_class7.prototype.__init84.call(this);_class7.prototype.__init85.call(this);_class7.prototype.__init86.call(this);_class7.prototype.__init87.call(this);_class7.prototype.__init88.call(this);_class7.prototype.__init89.call(this);_class7.prototype.__init90.call(this);_class7.prototype.__init91.call(this);;
3358
3449
  this.domElement = domElement;
3359
3450
  this.width = _nullishCoalesce(width, () => ( 0));
3360
3451
  this.height = _nullishCoalesce(height, () => ( 0));
@@ -3383,7 +3474,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkGKLTTGBRjs.Entity {
3383
3474
  ];
3384
3475
  for (const type of events) {
3385
3476
  const handler = (e) => {
3386
- this.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)(type, this, e));
3477
+ this.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)(type, this, e));
3387
3478
  };
3388
3479
  this.domElement.addEventListener(type, handler);
3389
3480
  this.domListeners.push({ type, handler, capture: false });
@@ -3394,7 +3485,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkGKLTTGBRjs.Entity {
3394
3485
  ];
3395
3486
  for (const { native, vecto } of hoverEvents) {
3396
3487
  const handler = (e) => {
3397
- this.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)(vecto, this, e, false));
3488
+ this.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)(vecto, this, e, false));
3398
3489
  };
3399
3490
  this.domElement.addEventListener(native, handler);
3400
3491
  this.domListeners.push({ type: native, handler, capture: false });
@@ -3402,7 +3493,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkGKLTTGBRjs.Entity {
3402
3493
  const focusEvents = ["focus", "blur"];
3403
3494
  for (const type of focusEvents) {
3404
3495
  const handler = (e) => {
3405
- this.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)(type, this, e, true));
3496
+ this.dispatchEvent(new (0, _chunk2Z23LTH3js.VectoJSEvent)(type, this, e, true));
3406
3497
  };
3407
3498
  this.domElement.addEventListener(type, handler, true);
3408
3499
  this.domListeners.push({ type, handler, capture: true });
@@ -3493,4 +3584,4 @@ Scene.registerWebGPUParticleSystemManager(_chunkORCPCIJ7js.WebGPUParticleSystemM
3493
3584
 
3494
3585
 
3495
3586
 
3496
- exports.ArabicShaper = _chunk4AR425ARjs.ArabicShaper; exports.BidiResolver = _chunk4AR425ARjs.BidiResolver; exports.CanvasRenderer = _chunkORCPCIJ7js.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Easing = _chunkGKLTTGBRjs.Easing; exports.Entity = _chunkGKLTTGBRjs.Entity; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.LayoutEngine = _chunkBA5HUUDFjs.LayoutEngine; exports.LayoutResultBuffer = _chunkBA5HUUDFjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunk4AR425ARjs.LayoutWorkerManager; exports.MSDFFont = _chunkGKLTTGBRjs.MSDFFont; exports.MSDFTextEntity = _chunkGKLTTGBRjs.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.Rect = Rect; exports.SVGEntity = _chunkGKLTTGBRjs.SVGEntity; exports.SVGRenderer = _chunkORCPCIJ7js.SVGRenderer; exports.Scene = Scene; exports.SpatialHashGrid = SpatialHashGrid; exports.SplineEntity = SplineEntity; exports.SpringDriver = _chunkGKLTTGBRjs.SpringDriver; exports.SpringPhysics = _chunkGKLTTGBRjs.SpringPhysics; exports.TextEntity = TextEntity; exports.TweenDriver = _chunkGKLTTGBRjs.TweenDriver; exports.VectoJSEvent = _chunkGKLTTGBRjs.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkORCPCIJ7js.WebGPUParticleSystemManager; exports.clearCssLineBoxMetrics = _chunkGKLTTGBRjs.clearCssLineBoxMetrics; exports.computeLineSegments = _chunkBA5HUUDFjs.computeLineSegments; exports.createCanvasMeasurer = _chunkBA5HUUDFjs.createCanvasMeasurer; exports.createWebGLPointRenderer = _chunkORCPCIJ7js.createWebGLPointRenderer; exports.cssLineBoxBaseline = _chunkGKLTTGBRjs.cssLineBoxBaseline; exports.isSafeUrl = _chunkORCPCIJ7js.isSafeUrl; exports.isTweenConfig = _chunkGKLTTGBRjs.isTweenConfig; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkORCPCIJ7js.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.prepareContentGrid = _chunkGKLTTGBRjs.prepareContentGrid; exports.sanitizeUrl = _chunkORCPCIJ7js.sanitizeUrl;
3587
+ exports.ArabicShaper = _chunk4AR425ARjs.ArabicShaper; exports.BidiResolver = _chunk4AR425ARjs.BidiResolver; exports.CanvasRenderer = _chunkORCPCIJ7js.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Easing = _chunk2Z23LTH3js.Easing; exports.Entity = _chunk2Z23LTH3js.Entity; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.LayoutEngine = _chunkBA5HUUDFjs.LayoutEngine; exports.LayoutResultBuffer = _chunkBA5HUUDFjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunk4AR425ARjs.LayoutWorkerManager; exports.MSDFFont = _chunk2Z23LTH3js.MSDFFont; exports.MSDFTextEntity = _chunk2Z23LTH3js.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.Rect = Rect; exports.SVGEntity = _chunk2Z23LTH3js.SVGEntity; exports.SVGRenderer = _chunkORCPCIJ7js.SVGRenderer; exports.Scene = Scene; exports.SpatialHashGrid = SpatialHashGrid; exports.SplineEntity = SplineEntity; exports.SpringDriver = _chunk2Z23LTH3js.SpringDriver; exports.SpringPhysics = _chunk2Z23LTH3js.SpringPhysics; exports.TextEntity = TextEntity; exports.TweenDriver = _chunk2Z23LTH3js.TweenDriver; exports.VectoJSEvent = _chunk2Z23LTH3js.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkORCPCIJ7js.WebGPUParticleSystemManager; exports.clearCssLineBoxMetrics = _chunk2Z23LTH3js.clearCssLineBoxMetrics; exports.computeLineSegments = _chunkBA5HUUDFjs.computeLineSegments; exports.createCanvasMeasurer = _chunkBA5HUUDFjs.createCanvasMeasurer; exports.createWebGLPointRenderer = _chunkORCPCIJ7js.createWebGLPointRenderer; exports.cssLineBoxBaseline = _chunk2Z23LTH3js.cssLineBoxBaseline; exports.isSafeUrl = _chunkORCPCIJ7js.isSafeUrl; exports.isTweenConfig = _chunk2Z23LTH3js.isTweenConfig; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkORCPCIJ7js.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.prepareContentGrid = _chunk2Z23LTH3js.prepareContentGrid; exports.sanitizeUrl = _chunkORCPCIJ7js.sanitizeUrl;
package/dist/index.mjs CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  cssLineBoxBaseline,
28
28
  isTweenConfig,
29
29
  prepareContentGrid
30
- } from "./chunk-J2P6QZJV.mjs";
30
+ } from "./chunk-XIEQHSBB.mjs";
31
31
  import {
32
32
  ArabicShaper,
33
33
  BidiResolver,
@@ -701,6 +701,13 @@ var Scene = class _Scene {
701
701
  activePortalsPrevFrame = /* @__PURE__ */ new Set();
702
702
  portalEntities = /* @__PURE__ */ new Map();
703
703
  renderOrderCounter = 0;
704
+ /**
705
+ * Authoritative paint order for semantic nodes discovered during the main
706
+ * render. A node may not have a DOM projection until the following a11y
707
+ * sync, so retaining the order prevents a newly opened overlay from spending
708
+ * its first frame below previously projected controls.
709
+ */
710
+ a11yRenderOrders = /* @__PURE__ */ new Map();
704
711
  // Optional WebGL point-cloud layer (see SceneOptions.pointBackend).
705
712
  pointRenderer = null;
706
713
  glCanvas = null;
@@ -735,6 +742,67 @@ var Scene = class _Scene {
735
742
  pointerLeaveListener = null;
736
743
  hasWarnedZeroSize = false;
737
744
  fontLoadHandler = null;
745
+ // ── Dev-mode warning infrastructure ──────────────────────────────
746
+ //
747
+ // Enable with `Scene.devMode = true` or by setting `globalThis.__DEV__`.
748
+ // Auto-detected when `NODE_ENV === 'development'`.
749
+ //
750
+ // Checks run once every ~120 frames (~2s at 60fps) to keep overhead
751
+ // negligible even when dev mode is on.
752
+ /** Toggle development-mode runtime warnings globally. */
753
+ static devMode = false;
754
+ static _devModeDetected() {
755
+ if (_Scene.devMode) return true;
756
+ const gp = typeof globalThis !== "undefined" ? globalThis : void 0;
757
+ if (gp?.__DEV__) return true;
758
+ if (gp?.process?.env?.NODE_ENV === "development") return true;
759
+ return false;
760
+ }
761
+ _devActive;
762
+ _devFrameCount = 0;
763
+ _devWarn(message) {
764
+ if (!this._devActive) return;
765
+ console.warn(`[vectojs/dev] ${message}`);
766
+ }
767
+ /** @internal Periodic dev checks — called once per frame in dev mode. */
768
+ _devRunChecks() {
769
+ this._devFrameCount++;
770
+ if (this._devFrameCount % 120 !== 0) return;
771
+ if (this.a11yElements) {
772
+ let interactiveCount = 0;
773
+ const walk = (node) => {
774
+ if (node.interactive && node.width > 0) interactiveCount++;
775
+ for (const c of node.children) walk(c);
776
+ };
777
+ walk(this.root);
778
+ for (const c of this.overlayRoot.children) walk(c);
779
+ const shadowCount = this.a11yElements.size;
780
+ if (shadowCount > interactiveCount + 2) {
781
+ this._devWarn(
782
+ `a11yElements (${shadowCount}) exceeds interactive entities (${interactiveCount}). Call scene.detachA11y(entity) before removing interactive children from the tree, or their shadow nodes leak.`
783
+ );
784
+ }
785
+ }
786
+ let checked = 0;
787
+ const walkProjections = (node) => {
788
+ if (checked > 10) return;
789
+ const proj = node.getContentProjection?.();
790
+ if (proj?.text && proj.selectable !== false) {
791
+ const el = this.contentElements?.get(node.id);
792
+ if (el) {
793
+ const projectedText = el.textContent || "";
794
+ if (projectedText !== "" && projectedText !== proj.text) {
795
+ this._devWarn(
796
+ `Content projection mismatch for entity "${node.id}": projection says "${proj.text.slice(0, 60)}" but DOM shows "${projectedText.slice(0, 60)}". Ensure getContentProjection() output matches what drawSelf renders.`
797
+ );
798
+ }
799
+ }
800
+ }
801
+ checked++;
802
+ for (const c of node.children) walkProjections(c);
803
+ };
804
+ walkProjections(this.root);
805
+ }
738
806
  constructor(canvas, options = {}) {
739
807
  this.canvas = canvas;
740
808
  this.debugA11y = options.debugA11y ?? false;
@@ -757,6 +825,7 @@ var Scene = class _Scene {
757
825
  this.particleBackend = options.particleBackend ?? "auto";
758
826
  this.a11ySyncInterval = options.a11ySyncInterval ?? 0;
759
827
  this.contentProjectionEnabled = options.contentProjection ?? true;
828
+ this._devActive = _Scene._devModeDetected();
760
829
  this.reducedMotionQuery = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
761
830
  this.root = new class RootEntity extends Entity {
762
831
  isPointInside() {
@@ -1273,6 +1342,9 @@ var Scene = class _Scene {
1273
1342
  el.addEventListener("click", (e) => {
1274
1343
  node.dispatchEvent(new VectoJSEvent("click", node, e));
1275
1344
  });
1345
+ el.addEventListener("dblclick", (e) => {
1346
+ node.dispatchEvent(new VectoJSEvent("dblclick", node, e));
1347
+ });
1276
1348
  el.addEventListener("mouseenter", (e) => {
1277
1349
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.2)";
1278
1350
  node.dispatchEvent(new VectoJSEvent("hover", node, e, false));
@@ -1398,6 +1470,10 @@ var Scene = class _Scene {
1398
1470
  if (el.style.pointerEvents !== semanticPointerEvents) {
1399
1471
  el.style.pointerEvents = semanticPointerEvents;
1400
1472
  }
1473
+ const renderOrder = this.a11yRenderOrders.get(node.id);
1474
+ if (renderOrder !== void 0 && el.style.zIndex !== String(renderOrder)) {
1475
+ el.style.zIndex = String(renderOrder);
1476
+ }
1401
1477
  const implicitTabIndex = !isNativelyFocusable(el) && attrs.role && INTERACTIVE_A11Y_ROLES.has(attrs.role) ? 0 : null;
1402
1478
  const desiredTabIndex = attrs.tabIndex ?? implicitTabIndex;
1403
1479
  if (desiredTabIndex === null) {
@@ -2158,6 +2234,7 @@ var Scene = class _Scene {
2158
2234
  }
2159
2235
  if (isMainRenderer) {
2160
2236
  this.renderOrderCounter = 0;
2237
+ this.a11yRenderOrders.clear();
2161
2238
  this.activePortalsThisFrame.clear();
2162
2239
  }
2163
2240
  const computeEntities = [];
@@ -2289,6 +2366,13 @@ var Scene = class _Scene {
2289
2366
  node.update(dt, time);
2290
2367
  if (!walkHadAnimation && node.hasPendingAnimations()) walkHadAnimation = true;
2291
2368
  if (!walkHadInteractive && node.interactive) walkHadInteractive = true;
2369
+ if (this._devActive && this._devFrameCount % 120 === 0) {
2370
+ if (node.update !== Entity.prototype.update && node.hasPendingAnimations === Entity.prototype.hasPendingAnimations) {
2371
+ this._devWarn(
2372
+ `Entity "${node.id}" overrides update() but not hasPendingAnimations(). Custom motion in update() without overriding hasPendingAnimations() causes the idle throttle to drop the animation to ~2fps. Override hasPendingAnimations() to return true while motion is in flight.`
2373
+ );
2374
+ }
2375
+ }
2292
2376
  }
2293
2377
  const cos = Math.cos(node.rotation);
2294
2378
  const sin = Math.sin(node.rotation);
@@ -2309,8 +2393,11 @@ var Scene = class _Scene {
2309
2393
  const orthogonalTolerance = Math.max(1, worldScaleX * worldScaleY) * 1e-6;
2310
2394
  const isSimilarityTransform = Number.isFinite(worldScaleX) && Number.isFinite(worldScaleY) && Math.abs(worldScaleX - worldScaleY) <= scaleTolerance && Math.abs(a * c + b * d) <= orthogonalTolerance;
2311
2395
  const a11yEl = isMainRenderer ? this.a11yElements.get(node.id) : void 0;
2312
- if (a11yEl) {
2313
- a11yEl.style.zIndex = String(this.renderOrderCounter++);
2396
+ const willProjectA11y = isMainRenderer && node.interactive && (node.width > 0 || node.a11yFullViewport);
2397
+ if (a11yEl || willProjectA11y) {
2398
+ const renderOrder = this.renderOrderCounter++;
2399
+ if (willProjectA11y) this.a11yRenderOrders.set(node.id, renderOrder);
2400
+ if (a11yEl) a11yEl.style.zIndex = String(renderOrder);
2314
2401
  }
2315
2402
  if (node.isDOMPortal) {
2316
2403
  if (isMainRenderer) {
@@ -2419,6 +2506,10 @@ var Scene = class _Scene {
2419
2506
  this.pointRenderer?.flush();
2420
2507
  }
2421
2508
  renderer.present?.();
2509
+ if (this._devActive) {
2510
+ this._devFrameCount++;
2511
+ this._devRunChecks();
2512
+ }
2422
2513
  }
2423
2514
  /**
2424
2515
  * Export the current scene state to a lightweight, flat SVG XML string.
package/dist/text.js CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
 
7
7
 
8
- var _chunkGKLTTGBRjs = require('./chunk-GKLTTGBR.js');
8
+ var _chunk2Z23LTH3js = require('./chunk-2Z23LTH3.js');
9
9
 
10
10
 
11
11
 
@@ -19,4 +19,4 @@ var _chunk4AR425ARjs = require('./chunk-4AR425AR.js');
19
19
 
20
20
 
21
21
 
22
- exports.ArabicShaper = _chunk4AR425ARjs.ArabicShaper; exports.BidiResolver = _chunk4AR425ARjs.BidiResolver; exports.MSDFFont = _chunkGKLTTGBRjs.MSDFFont; exports.MSDFTextEntity = _chunkGKLTTGBRjs.MSDFTextEntity; exports.SVGEntity = _chunkGKLTTGBRjs.SVGEntity; exports.clearCssLineBoxMetrics = _chunkGKLTTGBRjs.clearCssLineBoxMetrics; exports.cssLineBoxBaseline = _chunkGKLTTGBRjs.cssLineBoxBaseline; exports.prepareContentGrid = _chunkGKLTTGBRjs.prepareContentGrid;
22
+ exports.ArabicShaper = _chunk4AR425ARjs.ArabicShaper; exports.BidiResolver = _chunk4AR425ARjs.BidiResolver; exports.MSDFFont = _chunk2Z23LTH3js.MSDFFont; exports.MSDFTextEntity = _chunk2Z23LTH3js.MSDFTextEntity; exports.SVGEntity = _chunk2Z23LTH3js.SVGEntity; exports.clearCssLineBoxMetrics = _chunk2Z23LTH3js.clearCssLineBoxMetrics; exports.cssLineBoxBaseline = _chunk2Z23LTH3js.cssLineBoxBaseline; exports.prepareContentGrid = _chunk2Z23LTH3js.prepareContentGrid;
package/dist/text.mjs CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  clearCssLineBoxMetrics,
6
6
  cssLineBoxBaseline,
7
7
  prepareContentGrid
8
- } from "./chunk-J2P6QZJV.mjs";
8
+ } from "./chunk-XIEQHSBB.mjs";
9
9
  import {
10
10
  ArabicShaper,
11
11
  BidiResolver
@@ -192,7 +192,7 @@ export interface A11yAttributes {
192
192
  /**
193
193
  * Union of all pointer/interaction events that can be emitted by an {@link Entity}.
194
194
  */
195
- export type VectoEvent = 'click' | 'hover' | 'pointerdown' | 'pointerup' | 'pointercancel' | 'pointermove' | 'pointerleave' | 'change' | 'focus' | 'blur' | 'wheel' | 'keydown' | 'keyup';
195
+ export type VectoEvent = 'click' | 'dblclick' | 'hover' | 'pointerdown' | 'pointerup' | 'pointercancel' | 'pointermove' | 'pointerleave' | 'change' | 'focus' | 'blur' | 'wheel' | 'keydown' | 'keyup';
196
196
  /** Options for {@link Entity.on} / {@link Entity.off}. */
197
197
  export interface ListenerOptions {
198
198
  /** Register the listener for the capture phase (root→target) instead of bubble. */
@@ -470,6 +470,15 @@ export declare abstract class Entity {
470
470
  * @param payload - Arbitrary data forwarded to each listener.
471
471
  */
472
472
  emit(event: VectoEvent, payload: any): void;
473
+ /**
474
+ * Programmatically focus the entity's projected a11y shadow element, if one
475
+ * exists. After `scene.add(entity)` the shadow element is typically created
476
+ * on the next a11y sync (within one animation frame), so this method retries
477
+ * once on the next rAF if the element isn't immediately available — matching
478
+ * the timing pattern the prior workaround described in findings.md achieved
479
+ * with `requestAnimationFrame(() => document.getElementById(id)?.focus())`.
480
+ */
481
+ focus(): void;
473
482
  /** Run one node's listeners for the event, honoring stopImmediatePropagation. */
474
483
  private fireListeners;
475
484
  /**
@@ -205,6 +205,13 @@ export declare class Scene {
205
205
  private activePortalsPrevFrame;
206
206
  private portalEntities;
207
207
  private renderOrderCounter;
208
+ /**
209
+ * Authoritative paint order for semantic nodes discovered during the main
210
+ * render. A node may not have a DOM projection until the following a11y
211
+ * sync, so retaining the order prevents a newly opened overlay from spending
212
+ * its first frame below previously projected controls.
213
+ */
214
+ private a11yRenderOrders;
208
215
  private pointRenderer;
209
216
  private glCanvas;
210
217
  private debugA11y;
@@ -233,6 +240,14 @@ export declare class Scene {
233
240
  private pointerLeaveListener;
234
241
  private hasWarnedZeroSize;
235
242
  private fontLoadHandler;
243
+ /** Toggle development-mode runtime warnings globally. */
244
+ static devMode: boolean;
245
+ private static _devModeDetected;
246
+ private _devActive;
247
+ private _devFrameCount;
248
+ private _devWarn;
249
+ /** @internal Periodic dev checks — called once per frame in dev mode. */
250
+ private _devRunChecks;
236
251
  constructor(canvas: HTMLCanvasElement, options?: SceneOptions);
237
252
  private endContentSelectionDrag;
238
253
  private releaseContentSelectionForRebuild;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "1.10.0",
3
+ "version": "1.11.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },