@vectojs/core 1.16.2 → 1.16.3

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
@@ -2162,17 +2162,17 @@ var Scene = (_class6 = class _Scene {
2162
2162
  this._devFrameCount++;
2163
2163
  if (this._devFrameCount % 120 !== 0) return;
2164
2164
  if (this.a11yElements) {
2165
- let interactiveCount = 0;
2165
+ let projectableCount = 0;
2166
2166
  const walk = (node) => {
2167
- if (node.interactive && node.width > 0) interactiveCount++;
2167
+ if (this.shouldProjectA11y(node)) projectableCount++;
2168
2168
  for (const c of node.children) walk(c);
2169
2169
  };
2170
2170
  walk(this.root);
2171
2171
  for (const c of this.overlayRoot.children) walk(c);
2172
2172
  const shadowCount = this.a11yElements.size;
2173
- if (shadowCount > interactiveCount + 2) {
2173
+ if (shadowCount > projectableCount) {
2174
2174
  this._devWarn(
2175
- `a11yElements (${shadowCount}) exceeds interactive entities (${interactiveCount}). Call scene.detachA11y(entity) before removing interactive children from the tree, or their shadow nodes leak.`
2175
+ `a11yElements (${shadowCount}) exceeds projectable entities (${projectableCount}). Call scene.detachA11y(entity) before removing interactive children from the tree, or their shadow nodes leak.`
2176
2176
  );
2177
2177
  }
2178
2178
  }
@@ -2643,7 +2643,19 @@ var Scene = (_class6 = class _Scene {
2643
2643
  * without removing it from the scene graph. Components that manage dynamic
2644
2644
  * interactive *child* entities (e.g. a {@link Entity}'s per-link hotspots) call
2645
2645
  * this before discarding those children so their shadow `<a>`/controls don't
2646
- * leak (the per-frame `syncA11y` only creates/updates, it never prunes).
2646
+ * leak.
2647
+ *
2648
+ * `syncA11y` itself only creates and updates, never prunes — but it is always
2649
+ * followed by `enforceA11yDomOrder`, whose prune pass removes any element
2650
+ * whose entity is no longer reachable in the tree or no longer satisfies
2651
+ * {@link shouldProjectA11y}. So an entity that is `remove()`d, or whose
2652
+ * `interactive` flips to `false`, has its element torn down on the next synced
2653
+ * frame without any explicit call.
2654
+ *
2655
+ * This method is for the case that pass cannot see: a child dropped from a
2656
+ * component's own bookkeeping while still parented, or one discarded before
2657
+ * the next sync runs. Calling it is always safe and is the right habit for
2658
+ * pooled children.
2647
2659
  *
2648
2660
  * @param entity - The subtree whose shadow nodes should be removed.
2649
2661
  */
@@ -2932,12 +2944,34 @@ var Scene = (_class6 = class _Scene {
2932
2944
  }
2933
2945
  if (element.getAttribute(name) !== value) element.setAttribute(name, value);
2934
2946
  }
2947
+ /**
2948
+ * Whether `node` should have an a11y shadow element projected for it.
2949
+ *
2950
+ * The single authority for that decision. It was previously inlined verbatim
2951
+ * at four call sites — `syncA11y` (create/update), `enforceA11yDomOrder`
2952
+ * (which ids survive pruning), `getA11yTree` (the public snapshot) and
2953
+ * `render` (z-index / reading-order assignment). Four copies of one predicate
2954
+ * is a standing correctness hazard: if any of them drifts, elements either
2955
+ * leak (created but never marked active, so pruned every frame and rebuilt) or
2956
+ * go missing from the semantic tree while still present in the DOM.
2957
+ *
2958
+ * A box is required because a zero-size element is unfocusable and
2959
+ * unhittable; `a11yFullViewport` is the deliberate exception, since those
2960
+ * nodes are boundless interaction surfaces mounted behind everything else.
2961
+ *
2962
+ * Keep this the only place the rule is written. A planned per-entity
2963
+ * `a11yProjection` mode ('eager' | 'onDemand' | 'never') extends exactly this
2964
+ * predicate, which is only tractable while it has one home.
2965
+ */
2966
+ shouldProjectA11y(node) {
2967
+ return node.interactive && (node.width > 0 || node.a11yFullViewport);
2968
+ }
2935
2969
  syncA11y(node) {
2936
2970
  if (!this.a11yRoot) return;
2937
2971
  if (node.isDOMPortal) {
2938
2972
  return;
2939
2973
  }
2940
- if (node.interactive && (node.width > 0 || node.a11yFullViewport)) {
2974
+ if (this.shouldProjectA11y(node)) {
2941
2975
  let el = this.a11yElements.get(node.id);
2942
2976
  const attrs = node.getA11yAttributes();
2943
2977
  const expectedTag = attrs.tag || "div";
@@ -3713,7 +3747,7 @@ var Scene = (_class6 = class _Scene {
3713
3747
  if (node.a11yFullViewport) this.fullViewportElements.push(contentEl);
3714
3748
  else this.normalElements.push(contentEl);
3715
3749
  }
3716
- if (node.interactive && (node.width > 0 || node.a11yFullViewport)) {
3750
+ if (this.shouldProjectA11y(node)) {
3717
3751
  const el = this.a11yElements.get(node.id);
3718
3752
  if (el) {
3719
3753
  this.activeIds.add(node.id);
@@ -3852,7 +3886,7 @@ var Scene = (_class6 = class _Scene {
3852
3886
  const traverse = (node, parentNode) => {
3853
3887
  if (node.isDOMPortal) return;
3854
3888
  let currentA11yNode = null;
3855
- if (node.interactive && (node.width > 0 || node.a11yFullViewport)) {
3889
+ if (this.shouldProjectA11y(node)) {
3856
3890
  const el = this.a11yElements.get(node.id);
3857
3891
  if (el) {
3858
3892
  const attrs = node.getA11yAttributes();
@@ -4222,7 +4256,7 @@ var Scene = (_class6 = class _Scene {
4222
4256
  const orthogonalTolerance = Math.max(1, worldScaleX * worldScaleY) * 1e-6;
4223
4257
  const isSimilarityTransform = Number.isFinite(worldScaleX) && Number.isFinite(worldScaleY) && Math.abs(worldScaleX - worldScaleY) <= scaleTolerance && Math.abs(a * c + b * d) <= orthogonalTolerance;
4224
4258
  const a11yEl = isMainRenderer ? this.a11yElements.get(node.id) : void 0;
4225
- const willProjectA11y = isMainRenderer && node.interactive && (node.width > 0 || node.a11yFullViewport);
4259
+ const willProjectA11y = isMainRenderer && this.shouldProjectA11y(node);
4226
4260
  if (a11yEl || willProjectA11y) {
4227
4261
  const renderOrder = this.renderOrderCounter++;
4228
4262
  if (willProjectA11y) this.a11yRenderOrders.set(node.id, renderOrder);
package/dist/index.mjs CHANGED
@@ -2161,17 +2161,17 @@ var Scene = class _Scene {
2161
2161
  this._devFrameCount++;
2162
2162
  if (this._devFrameCount % 120 !== 0) return;
2163
2163
  if (this.a11yElements) {
2164
- let interactiveCount = 0;
2164
+ let projectableCount = 0;
2165
2165
  const walk = (node) => {
2166
- if (node.interactive && node.width > 0) interactiveCount++;
2166
+ if (this.shouldProjectA11y(node)) projectableCount++;
2167
2167
  for (const c of node.children) walk(c);
2168
2168
  };
2169
2169
  walk(this.root);
2170
2170
  for (const c of this.overlayRoot.children) walk(c);
2171
2171
  const shadowCount = this.a11yElements.size;
2172
- if (shadowCount > interactiveCount + 2) {
2172
+ if (shadowCount > projectableCount) {
2173
2173
  this._devWarn(
2174
- `a11yElements (${shadowCount}) exceeds interactive entities (${interactiveCount}). Call scene.detachA11y(entity) before removing interactive children from the tree, or their shadow nodes leak.`
2174
+ `a11yElements (${shadowCount}) exceeds projectable entities (${projectableCount}). Call scene.detachA11y(entity) before removing interactive children from the tree, or their shadow nodes leak.`
2175
2175
  );
2176
2176
  }
2177
2177
  }
@@ -2642,7 +2642,19 @@ var Scene = class _Scene {
2642
2642
  * without removing it from the scene graph. Components that manage dynamic
2643
2643
  * interactive *child* entities (e.g. a {@link Entity}'s per-link hotspots) call
2644
2644
  * this before discarding those children so their shadow `<a>`/controls don't
2645
- * leak (the per-frame `syncA11y` only creates/updates, it never prunes).
2645
+ * leak.
2646
+ *
2647
+ * `syncA11y` itself only creates and updates, never prunes — but it is always
2648
+ * followed by `enforceA11yDomOrder`, whose prune pass removes any element
2649
+ * whose entity is no longer reachable in the tree or no longer satisfies
2650
+ * {@link shouldProjectA11y}. So an entity that is `remove()`d, or whose
2651
+ * `interactive` flips to `false`, has its element torn down on the next synced
2652
+ * frame without any explicit call.
2653
+ *
2654
+ * This method is for the case that pass cannot see: a child dropped from a
2655
+ * component's own bookkeeping while still parented, or one discarded before
2656
+ * the next sync runs. Calling it is always safe and is the right habit for
2657
+ * pooled children.
2646
2658
  *
2647
2659
  * @param entity - The subtree whose shadow nodes should be removed.
2648
2660
  */
@@ -2931,12 +2943,34 @@ var Scene = class _Scene {
2931
2943
  }
2932
2944
  if (element.getAttribute(name) !== value) element.setAttribute(name, value);
2933
2945
  }
2946
+ /**
2947
+ * Whether `node` should have an a11y shadow element projected for it.
2948
+ *
2949
+ * The single authority for that decision. It was previously inlined verbatim
2950
+ * at four call sites — `syncA11y` (create/update), `enforceA11yDomOrder`
2951
+ * (which ids survive pruning), `getA11yTree` (the public snapshot) and
2952
+ * `render` (z-index / reading-order assignment). Four copies of one predicate
2953
+ * is a standing correctness hazard: if any of them drifts, elements either
2954
+ * leak (created but never marked active, so pruned every frame and rebuilt) or
2955
+ * go missing from the semantic tree while still present in the DOM.
2956
+ *
2957
+ * A box is required because a zero-size element is unfocusable and
2958
+ * unhittable; `a11yFullViewport` is the deliberate exception, since those
2959
+ * nodes are boundless interaction surfaces mounted behind everything else.
2960
+ *
2961
+ * Keep this the only place the rule is written. A planned per-entity
2962
+ * `a11yProjection` mode ('eager' | 'onDemand' | 'never') extends exactly this
2963
+ * predicate, which is only tractable while it has one home.
2964
+ */
2965
+ shouldProjectA11y(node) {
2966
+ return node.interactive && (node.width > 0 || node.a11yFullViewport);
2967
+ }
2934
2968
  syncA11y(node) {
2935
2969
  if (!this.a11yRoot) return;
2936
2970
  if (node.isDOMPortal) {
2937
2971
  return;
2938
2972
  }
2939
- if (node.interactive && (node.width > 0 || node.a11yFullViewport)) {
2973
+ if (this.shouldProjectA11y(node)) {
2940
2974
  let el = this.a11yElements.get(node.id);
2941
2975
  const attrs = node.getA11yAttributes();
2942
2976
  const expectedTag = attrs.tag || "div";
@@ -3712,7 +3746,7 @@ var Scene = class _Scene {
3712
3746
  if (node.a11yFullViewport) this.fullViewportElements.push(contentEl);
3713
3747
  else this.normalElements.push(contentEl);
3714
3748
  }
3715
- if (node.interactive && (node.width > 0 || node.a11yFullViewport)) {
3749
+ if (this.shouldProjectA11y(node)) {
3716
3750
  const el = this.a11yElements.get(node.id);
3717
3751
  if (el) {
3718
3752
  this.activeIds.add(node.id);
@@ -3851,7 +3885,7 @@ var Scene = class _Scene {
3851
3885
  const traverse = (node, parentNode) => {
3852
3886
  if (node.isDOMPortal) return;
3853
3887
  let currentA11yNode = null;
3854
- if (node.interactive && (node.width > 0 || node.a11yFullViewport)) {
3888
+ if (this.shouldProjectA11y(node)) {
3855
3889
  const el = this.a11yElements.get(node.id);
3856
3890
  if (el) {
3857
3891
  const attrs = node.getA11yAttributes();
@@ -4221,7 +4255,7 @@ var Scene = class _Scene {
4221
4255
  const orthogonalTolerance = Math.max(1, worldScaleX * worldScaleY) * 1e-6;
4222
4256
  const isSimilarityTransform = Number.isFinite(worldScaleX) && Number.isFinite(worldScaleY) && Math.abs(worldScaleX - worldScaleY) <= scaleTolerance && Math.abs(a * c + b * d) <= orthogonalTolerance;
4223
4257
  const a11yEl = isMainRenderer ? this.a11yElements.get(node.id) : void 0;
4224
- const willProjectA11y = isMainRenderer && node.interactive && (node.width > 0 || node.a11yFullViewport);
4258
+ const willProjectA11y = isMainRenderer && this.shouldProjectA11y(node);
4225
4259
  if (a11yEl || willProjectA11y) {
4226
4260
  const renderOrder = this.renderOrderCounter++;
4227
4261
  if (willProjectA11y) this.a11yRenderOrders.set(node.id, renderOrder);
@@ -673,7 +673,19 @@ export declare class Scene {
673
673
  * without removing it from the scene graph. Components that manage dynamic
674
674
  * interactive *child* entities (e.g. a {@link Entity}'s per-link hotspots) call
675
675
  * this before discarding those children so their shadow `<a>`/controls don't
676
- * leak (the per-frame `syncA11y` only creates/updates, it never prunes).
676
+ * leak.
677
+ *
678
+ * `syncA11y` itself only creates and updates, never prunes — but it is always
679
+ * followed by `enforceA11yDomOrder`, whose prune pass removes any element
680
+ * whose entity is no longer reachable in the tree or no longer satisfies
681
+ * {@link shouldProjectA11y}. So an entity that is `remove()`d, or whose
682
+ * `interactive` flips to `false`, has its element torn down on the next synced
683
+ * frame without any explicit call.
684
+ *
685
+ * This method is for the case that pass cannot see: a child dropped from a
686
+ * component's own bookkeeping while still parented, or one discarded before
687
+ * the next sync runs. Calling it is always safe and is the right habit for
688
+ * pooled children.
677
689
  *
678
690
  * @param entity - The subtree whose shadow nodes should be removed.
679
691
  */
@@ -756,6 +768,26 @@ export declare class Scene {
756
768
  /** True when any node in the subtree has a pending animation. */
757
769
  /** True when any node in the subtree is interactive (drives a11y sync). */
758
770
  private syncOptionalAttribute;
771
+ /**
772
+ * Whether `node` should have an a11y shadow element projected for it.
773
+ *
774
+ * The single authority for that decision. It was previously inlined verbatim
775
+ * at four call sites — `syncA11y` (create/update), `enforceA11yDomOrder`
776
+ * (which ids survive pruning), `getA11yTree` (the public snapshot) and
777
+ * `render` (z-index / reading-order assignment). Four copies of one predicate
778
+ * is a standing correctness hazard: if any of them drifts, elements either
779
+ * leak (created but never marked active, so pruned every frame and rebuilt) or
780
+ * go missing from the semantic tree while still present in the DOM.
781
+ *
782
+ * A box is required because a zero-size element is unfocusable and
783
+ * unhittable; `a11yFullViewport` is the deliberate exception, since those
784
+ * nodes are boundless interaction surfaces mounted behind everything else.
785
+ *
786
+ * Keep this the only place the rule is written. A planned per-entity
787
+ * `a11yProjection` mode ('eager' | 'onDemand' | 'never') extends exactly this
788
+ * predicate, which is only tractable while it has one home.
789
+ */
790
+ private shouldProjectA11y;
759
791
  private syncA11y;
760
792
  /**
761
793
  * Mirror one entity's static text ({@link Entity.getContentProjection}) as a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "1.16.2",
3
+ "version": "1.16.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },