@vectojs/core 1.29.0 → 1.30.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.
@@ -12,6 +12,10 @@ var ANIMATABLE_PROPS = /* @__PURE__ */ new Set([
12
12
  "rotation",
13
13
  "opacity"
14
14
  ]);
15
+ function contentLineInHint(hint, y, height) {
16
+ if (hint?.minY === void 0 || hint.maxY === void 0) return true;
17
+ return y + height >= hint.minY && y <= hint.maxY;
18
+ }
15
19
  var VectoJSEvent = class {
16
20
  /** The event name. */
17
21
  type;
@@ -251,6 +255,42 @@ var Entity = class {
251
255
  * nodes, so on-top components stay clickable.
252
256
  */
253
257
  a11yFullViewport = false;
258
+ /**
259
+ * When this entity's a11y shadow node is materialized.
260
+ *
261
+ * `'eager'` (the default) keeps today's behaviour: a shadow node exists for as
262
+ * long as the entity is `interactive` with a box. That is right for a button or
263
+ * a link, and wrong for thousands of ephemeral, individually-meaningless
264
+ * entities — particles, danmaku, graph nodes — where it produces one DOM node
265
+ * per entity every frame.
266
+ *
267
+ * Measured on 5,000 moving interactive entities (`benchmarks/lazy-a11y/`):
268
+ * eager costs **72.2 ms/frame on Chrome and 114.3 ms on Firefox**, missing even
269
+ * 60 Hz, against **1.55/1.63 ms** for the same scene with one node projected —
270
+ * within noise of the 1.26/1.65 ms floor of projecting nothing at all.
271
+ *
272
+ * `'onDemand'` projects a node only while {@link Scene} considers the entity
273
+ * *engaged*: it is focused, it is the current pointer target, or it has been
274
+ * explicitly requested via {@link Scene.requestA11yProjection}. Crucially the
275
+ * trigger is not hover alone — a keyboard or assistive-technology user
276
+ * generates no hover, so a hover-only gate would remove exactly those users'
277
+ * access. Engagement therefore includes focus and an explicit request, and the
278
+ * entity stays hit-testable on canvas throughout, so a click still reaches it
279
+ * and promotes it.
280
+ *
281
+ * `'never'` suppresses the node entirely. Prefer `interactive = false` unless
282
+ * the entity genuinely needs pointer events without any semantic presence;
283
+ * this exists so a purely decorative interactive surface can opt out without
284
+ * losing canvas hit-testing.
285
+ *
286
+ * **This does not replace an aggregate description.** A thousand `'onDemand'`
287
+ * danmaku are individually reachable but say nothing collectively. The proven
288
+ * pattern is one aggregate live region (`role: 'status'`, `a11yFullViewport`)
289
+ * plus a small pool of persistent hotspots for the current selection — see
290
+ * `vectojs-native/danmaku`. Use `'onDemand'` to stop paying per entity, not as
291
+ * the whole accessibility story.
292
+ */
293
+ a11yProjection = "eager";
254
294
  /**
255
295
  * Hide this entity AND its whole subtree from the accessibility/automation
256
296
  * projection, regardless of each node's own `interactive` flag.
@@ -1045,9 +1085,14 @@ var Entity = class {
1045
1085
  * `selectable` is set — natively selectable. Returns `null` by default.
1046
1086
  * Read on the a11y sync cadence, so text changes propagate automatically.
1047
1087
  *
1088
+ * @param hint - Optional advice about which part of the entity is worth
1089
+ * describing. Purely an optimization: ignoring it is always correct, which
1090
+ * is why it is a parameter rather than a required contract change. See
1091
+ * {@link ContentProjectionHint}.
1048
1092
  * @returns The projection descriptor, or `null` to project nothing.
1049
1093
  */
1050
- getContentProjection() {
1094
+ getContentProjection(hint) {
1095
+ void hint;
1051
1096
  return null;
1052
1097
  }
1053
1098
  /**
@@ -1634,6 +1679,7 @@ var SVGEntity = class extends Entity {
1634
1679
  };
1635
1680
 
1636
1681
  export {
1682
+ contentLineInHint,
1637
1683
  VectoJSEvent,
1638
1684
  Entity,
1639
1685
  MSDFTextEntity,
@@ -14,6 +14,10 @@ var ANIMATABLE_PROPS = /* @__PURE__ */ new Set([
14
14
  "rotation",
15
15
  "opacity"
16
16
  ]);
17
+ function contentLineInHint(hint, y, height) {
18
+ if (_optionalChain([hint, 'optionalAccess', _ => _.minY]) === void 0 || hint.maxY === void 0) return true;
19
+ return y + height >= hint.minY && y <= hint.maxY;
20
+ }
17
21
  var VectoJSEvent = (_class = class {
18
22
  /** The event name. */
19
23
 
@@ -47,7 +51,7 @@ var VectoJSEvent = (_class = class {
47
51
  }
48
52
  /** Forward to the native event's `preventDefault` (e.g. stop page scroll). */
49
53
  preventDefault() {
50
- _optionalChain([this, 'access', _ => _.nativeEvent, 'optionalAccess', _2 => _2.preventDefault, 'optionalCall', _3 => _3()]);
54
+ _optionalChain([this, 'access', _2 => _2.nativeEvent, 'optionalAccess', _3 => _3.preventDefault, 'optionalCall', _4 => _4()]);
51
55
  }
52
56
  /** Whether {@link stopPropagation} has been called. */
53
57
  get propagationStopped() {
@@ -59,72 +63,72 @@ var VectoJSEvent = (_class = class {
59
63
  }
60
64
  /** Whether the native event's default action was prevented. */
61
65
  get defaultPrevented() {
62
- return !!_optionalChain([this, 'access', _4 => _4.nativeEvent, 'optionalAccess', _5 => _5.defaultPrevented]);
66
+ return !!_optionalChain([this, 'access', _5 => _5.nativeEvent, 'optionalAccess', _6 => _6.defaultPrevented]);
63
67
  }
64
68
  /** Native horizontal wheel delta, if this wraps a `WheelEvent`. */
65
69
  get deltaX() {
66
- return _optionalChain([this, 'access', _6 => _6.nativeEvent, 'optionalAccess', _7 => _7.deltaX]);
70
+ return _optionalChain([this, 'access', _7 => _7.nativeEvent, 'optionalAccess', _8 => _8.deltaX]);
67
71
  }
68
72
  /** Native vertical wheel delta, if this wraps a `WheelEvent`. */
69
73
  get deltaY() {
70
- return _optionalChain([this, 'access', _8 => _8.nativeEvent, 'optionalAccess', _9 => _9.deltaY]);
74
+ return _optionalChain([this, 'access', _9 => _9.nativeEvent, 'optionalAccess', _10 => _10.deltaY]);
71
75
  }
72
76
  /** Native pointer X, if this wraps a pointer/mouse event. */
73
77
  get clientX() {
74
- return _optionalChain([this, 'access', _10 => _10.nativeEvent, 'optionalAccess', _11 => _11.clientX]);
78
+ return _optionalChain([this, 'access', _11 => _11.nativeEvent, 'optionalAccess', _12 => _12.clientX]);
75
79
  }
76
80
  /** Native pointer Y, if this wraps a pointer/mouse event. */
77
81
  get clientY() {
78
- return _optionalChain([this, 'access', _12 => _12.nativeEvent, 'optionalAccess', _13 => _13.clientY]);
82
+ return _optionalChain([this, 'access', _13 => _13.nativeEvent, 'optionalAccess', _14 => _14.clientY]);
79
83
  }
80
84
  get resolvedScenePoint() {
81
85
  if (this.explicitScenePoint) return this.explicitScenePoint;
82
86
  const native = this.nativeEvent;
83
- if (_optionalChain([native, 'optionalAccess', _14 => _14.vectoSceneX]) !== void 0 && native.vectoSceneY !== void 0) {
87
+ if (_optionalChain([native, 'optionalAccess', _15 => _15.vectoSceneX]) !== void 0 && native.vectoSceneY !== void 0) {
84
88
  return { x: native.vectoSceneX, y: native.vectoSceneY };
85
89
  }
86
- if (_optionalChain([native, 'optionalAccess', _15 => _15.clientX]) === void 0 || native.clientY === void 0) return void 0;
90
+ if (_optionalChain([native, 'optionalAccess', _16 => _16.clientX]) === void 0 || native.clientY === void 0) return void 0;
87
91
  const scene = this.target.scene;
88
- return _nullishCoalesce(_optionalChain([scene, 'optionalAccess', _16 => _16.clientToScene, 'optionalCall', _17 => _17(native.clientX, native.clientY)]), () => ( {
92
+ return _nullishCoalesce(_optionalChain([scene, 'optionalAccess', _17 => _17.clientToScene, 'optionalCall', _18 => _18(native.clientX, native.clientY)]), () => ( {
89
93
  x: native.clientX,
90
94
  y: native.clientY
91
95
  }));
92
96
  }
93
97
  /** Pointer X in the Scene's logical coordinate space. */
94
98
  get sceneX() {
95
- return _optionalChain([this, 'access', _18 => _18.resolvedScenePoint, 'optionalAccess', _19 => _19.x]);
99
+ return _optionalChain([this, 'access', _19 => _19.resolvedScenePoint, 'optionalAccess', _20 => _20.x]);
96
100
  }
97
101
  /** Pointer Y in the Scene's logical coordinate space. */
98
102
  get sceneY() {
99
- return _optionalChain([this, 'access', _20 => _20.resolvedScenePoint, 'optionalAccess', _21 => _21.y]);
103
+ return _optionalChain([this, 'access', _21 => _21.resolvedScenePoint, 'optionalAccess', _22 => _22.y]);
100
104
  }
101
105
  /** Pointer X local to the entity whose listener is currently running. */
102
106
  get localX() {
103
107
  const point = this.resolvedScenePoint;
104
108
  if (!point) return void 0;
105
- return _optionalChain([this, 'access', _22 => _22.currentTarget, 'access', _23 => _23.worldToLocal, 'call', _24 => _24(point.x, point.y), 'optionalAccess', _25 => _25.x]);
109
+ return _optionalChain([this, 'access', _23 => _23.currentTarget, 'access', _24 => _24.worldToLocal, 'call', _25 => _25(point.x, point.y), 'optionalAccess', _26 => _26.x]);
106
110
  }
107
111
  /** Pointer Y local to the entity whose listener is currently running. */
108
112
  get localY() {
109
113
  const point = this.resolvedScenePoint;
110
114
  if (!point) return void 0;
111
- return _optionalChain([this, 'access', _26 => _26.currentTarget, 'access', _27 => _27.worldToLocal, 'call', _28 => _28(point.x, point.y), 'optionalAccess', _29 => _29.y]);
115
+ return _optionalChain([this, 'access', _27 => _27.currentTarget, 'access', _28 => _28.worldToLocal, 'call', _29 => _29(point.x, point.y), 'optionalAccess', _30 => _30.y]);
112
116
  }
113
117
  get shiftKey() {
114
- return !!_optionalChain([this, 'access', _30 => _30.nativeEvent, 'optionalAccess', _31 => _31.shiftKey]);
118
+ return !!_optionalChain([this, 'access', _31 => _31.nativeEvent, 'optionalAccess', _32 => _32.shiftKey]);
115
119
  }
116
120
  get ctrlKey() {
117
- return !!_optionalChain([this, 'access', _32 => _32.nativeEvent, 'optionalAccess', _33 => _33.ctrlKey]);
121
+ return !!_optionalChain([this, 'access', _33 => _33.nativeEvent, 'optionalAccess', _34 => _34.ctrlKey]);
118
122
  }
119
123
  get altKey() {
120
- return !!_optionalChain([this, 'access', _34 => _34.nativeEvent, 'optionalAccess', _35 => _35.altKey]);
124
+ return !!_optionalChain([this, 'access', _35 => _35.nativeEvent, 'optionalAccess', _36 => _36.altKey]);
121
125
  }
122
126
  get metaKey() {
123
- return !!_optionalChain([this, 'access', _36 => _36.nativeEvent, 'optionalAccess', _37 => _37.metaKey]);
127
+ return !!_optionalChain([this, 'access', _37 => _37.nativeEvent, 'optionalAccess', _38 => _38.metaKey]);
124
128
  }
125
129
  /** Native key, if this wraps a keyboard event. */
126
130
  get key() {
127
- return _optionalChain([this, 'access', _38 => _38.nativeEvent, 'optionalAccess', _39 => _39.key]);
131
+ return _optionalChain([this, 'access', _39 => _39.nativeEvent, 'optionalAccess', _40 => _40.key]);
128
132
  }
129
133
  }, _class);
130
134
  var Entity = (_class2 = class {
@@ -253,6 +257,42 @@ var Entity = (_class2 = class {
253
257
  * nodes, so on-top components stay clickable.
254
258
  */
255
259
  __init33() {this.a11yFullViewport = false}
260
+ /**
261
+ * When this entity's a11y shadow node is materialized.
262
+ *
263
+ * `'eager'` (the default) keeps today's behaviour: a shadow node exists for as
264
+ * long as the entity is `interactive` with a box. That is right for a button or
265
+ * a link, and wrong for thousands of ephemeral, individually-meaningless
266
+ * entities — particles, danmaku, graph nodes — where it produces one DOM node
267
+ * per entity every frame.
268
+ *
269
+ * Measured on 5,000 moving interactive entities (`benchmarks/lazy-a11y/`):
270
+ * eager costs **72.2 ms/frame on Chrome and 114.3 ms on Firefox**, missing even
271
+ * 60 Hz, against **1.55/1.63 ms** for the same scene with one node projected —
272
+ * within noise of the 1.26/1.65 ms floor of projecting nothing at all.
273
+ *
274
+ * `'onDemand'` projects a node only while {@link Scene} considers the entity
275
+ * *engaged*: it is focused, it is the current pointer target, or it has been
276
+ * explicitly requested via {@link Scene.requestA11yProjection}. Crucially the
277
+ * trigger is not hover alone — a keyboard or assistive-technology user
278
+ * generates no hover, so a hover-only gate would remove exactly those users'
279
+ * access. Engagement therefore includes focus and an explicit request, and the
280
+ * entity stays hit-testable on canvas throughout, so a click still reaches it
281
+ * and promotes it.
282
+ *
283
+ * `'never'` suppresses the node entirely. Prefer `interactive = false` unless
284
+ * the entity genuinely needs pointer events without any semantic presence;
285
+ * this exists so a purely decorative interactive surface can opt out without
286
+ * losing canvas hit-testing.
287
+ *
288
+ * **This does not replace an aggregate description.** A thousand `'onDemand'`
289
+ * danmaku are individually reachable but say nothing collectively. The proven
290
+ * pattern is one aggregate live region (`role: 'status'`, `a11yFullViewport`)
291
+ * plus a small pool of persistent hotspots for the current selection — see
292
+ * `vectojs-native/danmaku`. Use `'onDemand'` to stop paying per entity, not as
293
+ * the whole accessibility story.
294
+ */
295
+ __init34() {this.a11yProjection = "eager"}
256
296
  /**
257
297
  * Hide this entity AND its whole subtree from the accessibility/automation
258
298
  * projection, regardless of each node's own `interactive` flag.
@@ -270,21 +310,21 @@ var Entity = (_class2 = class {
270
310
  * `=== 0` test never fires; a threshold would instead silently un-project a
271
311
  * faint-but-live control.
272
312
  */
273
- __init34() {this.a11yHidden = false}
313
+ __init35() {this.a11yHidden = false}
274
314
  /**
275
315
  * Clip this node's children to its local box (`[0,0]–[width,height]`) while
276
316
  * rendering. Combined with translating a content child, this is how
277
317
  * scroll/overflow containers (e.g. `ScrollView`) keep their content inside a
278
318
  * fixed viewport. Off by default (children render unclipped). Canvas2D only.
279
319
  */
280
- __init35() {this.clipChildren = false}
320
+ __init36() {this.clipChildren = false}
281
321
  // Lazily allocated (see _drivers above). Most entities never register a
282
322
  // listener or an imperative animate() tween.
283
- __init36() {this.listeners = null}
323
+ __init37() {this.listeners = null}
284
324
  /** Capture-phase listeners (fired root→target before bubble). */
285
- __init37() {this.captureListeners = null}
286
- __init38() {this.animations = null}
287
- constructor(id) {;_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this);_class2.prototype.__init6.call(this);_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);
325
+ __init38() {this.captureListeners = null}
326
+ __init39() {this.animations = null}
327
+ constructor(id) {;_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this);_class2.prototype.__init6.call(this);_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);
288
328
  this.id = id || `entity_${Math.random().toString(36).substring(2, 9)}`;
289
329
  }
290
330
  /**
@@ -318,7 +358,7 @@ var Entity = (_class2 = class {
318
358
  const s = this.scene;
319
359
  if (s) {
320
360
  s.a11yNeedsReorder = true;
321
- _optionalChain([s, 'access', _40 => _40.markStructureChanged, 'optionalCall', _41 => _41()]);
361
+ _optionalChain([s, 'access', _41 => _41.markStructureChanged, 'optionalCall', _42 => _42()]);
322
362
  s.markDirty({ entity: this.id, reason: "child-added" });
323
363
  child._notifyMounted();
324
364
  }
@@ -348,7 +388,7 @@ var Entity = (_class2 = class {
348
388
  if (s) {
349
389
  s.detachA11y(child);
350
390
  s.a11yNeedsReorder = true;
351
- _optionalChain([s, 'access', _42 => _42.markStructureChanged, 'optionalCall', _43 => _43()]);
391
+ _optionalChain([s, 'access', _43 => _43.markStructureChanged, 'optionalCall', _44 => _44()]);
352
392
  s.markDirty({ entity: this.id, reason: "child-removed" });
353
393
  }
354
394
  }
@@ -403,7 +443,7 @@ var Entity = (_class2 = class {
403
443
  startTime: -1,
404
444
  startProps: {}
405
445
  });
406
- _optionalChain([this, 'access', _44 => _44.scene, 'optionalAccess', _45 => _45.markDirty, 'call', _46 => _46({ entity: this.id, reason: "animation-start" })]);
446
+ _optionalChain([this, 'access', _45 => _45.scene, 'optionalAccess', _46 => _46.markDirty, 'call', _47 => _47({ entity: this.id, reason: "animation-start" })]);
407
447
  return this;
408
448
  }
409
449
  /** Write a driver-computed value to a backing field without re-triggering the setter. */
@@ -450,26 +490,26 @@ var Entity = (_class2 = class {
450
490
  * that need to seed a starting state (e.g. the presence helper's enter `from`).
451
491
  */
452
492
  setImmediate(prop, v) {
453
- const existing = _optionalChain([this, 'access', _47 => _47._drivers, 'optionalAccess', _48 => _48.get, 'call', _49 => _49(prop)]);
493
+ const existing = _optionalChain([this, 'access', _48 => _48._drivers, 'optionalAccess', _49 => _49.get, 'call', _50 => _50(prop)]);
454
494
  if (existing) this._settleDriver(existing);
455
- _optionalChain([this, 'access', _50 => _50._drivers, 'optionalAccess', _51 => _51.delete, 'call', _52 => _52(prop)]);
495
+ _optionalChain([this, 'access', _51 => _51._drivers, 'optionalAccess', _52 => _52.delete, 'call', _53 => _53(prop)]);
456
496
  this._applyAnimated(prop, v);
457
497
  }
458
498
  _settleDriver(driver) {
459
499
  const active = driver;
460
500
  const onDone = active.onDone;
461
501
  active.onDone = void 0;
462
- _optionalChain([onDone, 'optionalCall', _53 => _53()]);
502
+ _optionalChain([onDone, 'optionalCall', _54 => _54()]);
463
503
  }
464
504
  _spawnDriver(prop, to, cfg) {
465
- if (prop !== "opacity" && _optionalChain([this, 'access', _54 => _54.scene, 'optionalAccess', _55 => _55.prefersReducedMotion])) {
466
- const existing2 = _optionalChain([this, 'access', _56 => _56._drivers, 'optionalAccess', _57 => _57.get, 'call', _58 => _58(prop)]);
505
+ if (prop !== "opacity" && _optionalChain([this, 'access', _55 => _55.scene, 'optionalAccess', _56 => _56.prefersReducedMotion])) {
506
+ const existing2 = _optionalChain([this, 'access', _57 => _57._drivers, 'optionalAccess', _58 => _58.get, 'call', _59 => _59(prop)]);
467
507
  if (existing2) this._settleDriver(existing2);
468
- _optionalChain([this, 'access', _59 => _59._drivers, 'optionalAccess', _60 => _60.delete, 'call', _61 => _61(prop)]);
508
+ _optionalChain([this, 'access', _60 => _60._drivers, 'optionalAccess', _61 => _61.delete, 'call', _62 => _62(prop)]);
469
509
  this._applyAnimated(prop, to);
470
510
  return;
471
511
  }
472
- const existing = _optionalChain([this, 'access', _62 => _62._drivers, 'optionalAccess', _63 => _63.get, 'call', _64 => _64(prop)]);
512
+ const existing = _optionalChain([this, 'access', _63 => _63._drivers, 'optionalAccess', _64 => _64.get, 'call', _65 => _65(prop)]);
473
513
  if (existing) {
474
514
  this._settleDriver(existing);
475
515
  existing.retarget(to);
@@ -478,12 +518,12 @@ var Entity = (_class2 = class {
478
518
  const from = this._currentOf(prop);
479
519
  const driver = _animation.isTweenConfig.call(void 0, cfg) ? new (0, _animation.TweenDriver)(from, to, cfg) : new (0, _animation.SpringDriver)(from, to, cfg === "spring" ? {} : cfg);
480
520
  (this._drivers ??= /* @__PURE__ */ new Map()).set(prop, driver);
481
- _optionalChain([this, 'access', _65 => _65.scene, 'optionalAccess', _66 => _66.markDirty, 'call', _67 => _67({ entity: this.id, reason: "driver-added" })]);
482
- _optionalChain([this, 'access', _68 => _68.scene, 'optionalAccess', _69 => _69._registerActiveDriverEntity, 'call', _70 => _70(this)]);
521
+ _optionalChain([this, 'access', _66 => _66.scene, 'optionalAccess', _67 => _67.markDirty, 'call', _68 => _68({ entity: this.id, reason: "driver-added" })]);
522
+ _optionalChain([this, 'access', _69 => _69.scene, 'optionalAccess', _70 => _70._registerActiveDriverEntity, 'call', _71 => _71(this)]);
483
523
  }
484
524
  /** Assignment path when a declarative transition is configured for `prop`. */
485
525
  _animateProp(prop, to) {
486
- const cfg = _optionalChain([this, 'access', _71 => _71._transitions, 'optionalAccess', _72 => _72.get, 'call', _73 => _73(prop)]);
526
+ const cfg = _optionalChain([this, 'access', _72 => _72._transitions, 'optionalAccess', _73 => _73.get, 'call', _74 => _74(prop)]);
487
527
  if (!cfg) {
488
528
  this._applyAnimated(prop, to);
489
529
  return;
@@ -512,7 +552,7 @@ var Entity = (_class2 = class {
512
552
  entries.map(
513
553
  (e) => new Promise((resolve) => {
514
554
  this._spawnDriver(e[0], e[1], cfg);
515
- const d = _optionalChain([this, 'access', _74 => _74._drivers, 'optionalAccess', _75 => _75.get, 'call', _76 => _76(e[0])]);
555
+ const d = _optionalChain([this, 'access', _75 => _75._drivers, 'optionalAccess', _76 => _76.get, 'call', _77 => _77(e[0])]);
516
556
  if (!d)
517
557
  resolve();
518
558
  else d.onDone = resolve;
@@ -534,7 +574,7 @@ var Entity = (_class2 = class {
534
574
  this._applyAnimated(prop, driver.value);
535
575
  }
536
576
  }
537
- _optionalChain([this, 'access', _77 => _77.scene, 'optionalAccess', _78 => _78.markDirty, 'call', _79 => _79({ entity: this.id, reason: "driver-tick" })]);
577
+ _optionalChain([this, 'access', _78 => _78.scene, 'optionalAccess', _79 => _79.markDirty, 'call', _80 => _80({ entity: this.id, reason: "driver-tick" })]);
538
578
  }
539
579
  /**
540
580
  * Internal: this entity's active-driver map (read-only view), or `null` if
@@ -559,7 +599,7 @@ var Entity = (_class2 = class {
559
599
  if (driver.isDone()) {
560
600
  this._applyAnimated(prop, driver.target);
561
601
  this._settleDriver(driver);
562
- _optionalChain([this, 'access', _80 => _80._drivers, 'optionalAccess', _81 => _81.delete, 'call', _82 => _82(prop)]);
602
+ _optionalChain([this, 'access', _81 => _81._drivers, 'optionalAccess', _82 => _82.delete, 'call', _83 => _83(prop)]);
563
603
  } else {
564
604
  this._applyAnimated(prop, driver.value);
565
605
  }
@@ -620,7 +660,7 @@ var Entity = (_class2 = class {
620
660
  * @example entity.on('click', (e) => console.log('clicked', e));
621
661
  */
622
662
  on(event, callback, options) {
623
- const map = _optionalChain([options, 'optionalAccess', _83 => _83.capture]) ? this.captureListeners ??= /* @__PURE__ */ new Map() : this.listeners ??= /* @__PURE__ */ new Map();
663
+ const map = _optionalChain([options, 'optionalAccess', _84 => _84.capture]) ? this.captureListeners ??= /* @__PURE__ */ new Map() : this.listeners ??= /* @__PURE__ */ new Map();
624
664
  if (!map.has(event)) {
625
665
  map.set(event, []);
626
666
  }
@@ -636,7 +676,7 @@ var Entity = (_class2 = class {
636
676
  * @returns `this` for method chaining.
637
677
  */
638
678
  off(event, callback, options) {
639
- const handlers = _optionalChain([(_optionalChain([options, 'optionalAccess', _84 => _84.capture]) ? this.captureListeners : this.listeners), 'optionalAccess', _85 => _85.get, 'call', _86 => _86(event)]);
679
+ const handlers = _optionalChain([(_optionalChain([options, 'optionalAccess', _85 => _85.capture]) ? this.captureListeners : this.listeners), 'optionalAccess', _86 => _86.get, 'call', _87 => _87(event)]);
640
680
  if (handlers) {
641
681
  const idx = handlers.indexOf(callback);
642
682
  if (idx !== -1) handlers.splice(idx, 1);
@@ -673,8 +713,8 @@ var Entity = (_class2 = class {
673
713
  }
674
714
  this._drivers.clear();
675
715
  }
676
- _optionalChain([this, 'access', _87 => _87.listeners, 'optionalAccess', _88 => _88.clear, 'call', _89 => _89()]);
677
- _optionalChain([this, 'access', _90 => _90.captureListeners, 'optionalAccess', _91 => _91.clear, 'call', _92 => _92()]);
716
+ _optionalChain([this, 'access', _88 => _88.listeners, 'optionalAccess', _89 => _89.clear, 'call', _90 => _90()]);
717
+ _optionalChain([this, 'access', _91 => _91.captureListeners, 'optionalAccess', _92 => _92.clear, 'call', _93 => _93()]);
678
718
  if (this.parent) {
679
719
  this.parent.remove(this);
680
720
  }
@@ -689,7 +729,7 @@ var Entity = (_class2 = class {
689
729
  * @param payload - Arbitrary data forwarded to each listener.
690
730
  */
691
731
  emit(event, payload) {
692
- const handlers = _optionalChain([this, 'access', _93 => _93.listeners, 'optionalAccess', _94 => _94.get, 'call', _95 => _95(event)]);
732
+ const handlers = _optionalChain([this, 'access', _94 => _94.listeners, 'optionalAccess', _95 => _95.get, 'call', _96 => _96(event)]);
693
733
  if (handlers) {
694
734
  handlers.forEach((h) => h(payload));
695
735
  }
@@ -703,19 +743,19 @@ var Entity = (_class2 = class {
703
743
  * with `requestAnimationFrame(() => document.getElementById(id)?.focus())`.
704
744
  */
705
745
  focus() {
706
- const el = _optionalChain([this, 'access', _96 => _96.scene, 'optionalAccess', _97 => _97.getA11yElement, 'call', _98 => _98(this.id)]);
746
+ const el = _optionalChain([this, 'access', _97 => _97.scene, 'optionalAccess', _98 => _98.getA11yElement, 'call', _99 => _99(this.id)]);
707
747
  if (el) {
708
748
  el.focus();
709
749
  return;
710
750
  }
711
751
  requestAnimationFrame(() => {
712
- const retry = _optionalChain([this, 'access', _99 => _99.scene, 'optionalAccess', _100 => _100.getA11yElement, 'call', _101 => _101(this.id)]);
752
+ const retry = _optionalChain([this, 'access', _100 => _100.scene, 'optionalAccess', _101 => _101.getA11yElement, 'call', _102 => _102(this.id)]);
713
753
  if (retry) retry.focus();
714
754
  });
715
755
  }
716
756
  /** Run one node's listeners for the event, honoring stopImmediatePropagation. */
717
757
  fireListeners(node, map, event) {
718
- const handlers = _optionalChain([map, 'optionalAccess', _102 => _102.get, 'call', _103 => _103(event.type)]);
758
+ const handlers = _optionalChain([map, 'optionalAccess', _103 => _103.get, 'call', _104 => _104(event.type)]);
719
759
  if (!handlers) return;
720
760
  event.currentTarget = node;
721
761
  for (const h of handlers.slice()) {
@@ -1047,9 +1087,14 @@ var Entity = (_class2 = class {
1047
1087
  * `selectable` is set — natively selectable. Returns `null` by default.
1048
1088
  * Read on the a11y sync cadence, so text changes propagate automatically.
1049
1089
  *
1090
+ * @param hint - Optional advice about which part of the entity is worth
1091
+ * describing. Purely an optimization: ignoring it is always correct, which
1092
+ * is why it is a parameter rather than a required contract change. See
1093
+ * {@link ContentProjectionHint}.
1050
1094
  * @returns The projection descriptor, or `null` to project nothing.
1051
1095
  */
1052
- getContentProjection() {
1096
+ getContentProjection(hint) {
1097
+ void hint;
1053
1098
  return null;
1054
1099
  }
1055
1100
  /**
@@ -1069,7 +1114,7 @@ var Entity = (_class2 = class {
1069
1114
  * @returns `true` if at least one animation or property driver remains.
1070
1115
  */
1071
1116
  hasPendingAnimations() {
1072
- return (_nullishCoalesce(_optionalChain([this, 'access', _104 => _104.animations, 'optionalAccess', _105 => _105.length]), () => ( 0))) > 0 || (_nullishCoalesce(_optionalChain([this, 'access', _106 => _106._drivers, 'optionalAccess', _107 => _107.size]), () => ( 0))) > 0;
1117
+ return (_nullishCoalesce(_optionalChain([this, 'access', _105 => _105.animations, 'optionalAccess', _106 => _106.length]), () => ( 0))) > 0 || (_nullishCoalesce(_optionalChain([this, 'access', _107 => _107._drivers, 'optionalAccess', _108 => _108.size]), () => ( 0))) > 0;
1073
1118
  }
1074
1119
  }, _class2);
1075
1120
 
@@ -1091,20 +1136,20 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1091
1136
  // joined by soft hyphens (U+00AD); the worker then treats those as break
1092
1137
  // opportunities. `text` keeps the original string for a11y/content
1093
1138
  // projection; `layoutText` is the soft-hyphen-annotated string sent to layout.
1094
- __init39() {this.hyphenator = null}
1095
- __init40() {this.layoutText = ""}
1096
- __init41() {this.text = ""}
1097
- __init42() {this.lastRenderedSeqId = 0}
1139
+ __init40() {this.hyphenator = null}
1140
+ __init41() {this.layoutText = ""}
1141
+ __init42() {this.text = ""}
1142
+ __init43() {this.lastRenderedSeqId = 0}
1098
1143
  // Atlas-decode subscription (see watchAtlasDecode). Held so `destroy()` can
1099
1144
  // release it: the handler closes over `this`, so leaving it attached to a
1100
1145
  // long-lived shared atlas image would retain the whole entity.
1101
- __init43() {this.atlasDecodeTarget = null}
1102
- __init44() {this.atlasDecodeHandler = null}
1103
- __init45() {this.rgbColorCache = /* @__PURE__ */ new Map()}
1104
- __init46() {this.fontStringCache = []}
1105
- __init47() {this.layoutResult = null}
1146
+ __init44() {this.atlasDecodeTarget = null}
1147
+ __init45() {this.atlasDecodeHandler = null}
1148
+ __init46() {this.rgbColorCache = /* @__PURE__ */ new Map()}
1149
+ __init47() {this.fontStringCache = []}
1150
+ __init48() {this.layoutResult = null}
1106
1151
  constructor(text, options) {
1107
- super();_class3.prototype.__init39.call(this);_class3.prototype.__init40.call(this);_class3.prototype.__init41.call(this);_class3.prototype.__init42.call(this);_class3.prototype.__init43.call(this);_class3.prototype.__init44.call(this);_class3.prototype.__init45.call(this);_class3.prototype.__init46.call(this);_class3.prototype.__init47.call(this);;
1152
+ super();_class3.prototype.__init40.call(this);_class3.prototype.__init41.call(this);_class3.prototype.__init42.call(this);_class3.prototype.__init43.call(this);_class3.prototype.__init44.call(this);_class3.prototype.__init45.call(this);_class3.prototype.__init46.call(this);_class3.prototype.__init47.call(this);_class3.prototype.__init48.call(this);;
1108
1153
  this.font = options.font;
1109
1154
  this.texture = options.texture;
1110
1155
  this.fallbackFont = _nullishCoalesce(options.fallbackFont, () => ( "sans-serif"));
@@ -1149,7 +1194,7 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1149
1194
  const target = this.texture;
1150
1195
  this.atlasDecodeHandler = () => {
1151
1196
  this.detachAtlasDecodeListener();
1152
- _optionalChain([this, 'access', _108 => _108.scene, 'optionalAccess', _109 => _109.markDirty, 'call', _110 => _110()]);
1197
+ _optionalChain([this, 'access', _109 => _109.scene, 'optionalAccess', _110 => _110.markDirty, 'call', _111 => _111()]);
1153
1198
  };
1154
1199
  target.addEventListener("load", this.atlasDecodeHandler);
1155
1200
  target.addEventListener("error", this.atlasDecodeHandler);
@@ -1227,7 +1272,7 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1227
1272
  if (res.seqId < this.lastRenderedSeqId) return;
1228
1273
  this.lastRenderedSeqId = res.seqId;
1229
1274
  this.layoutResult = res;
1230
- _optionalChain([this, 'access', _111 => _111.scene, 'optionalAccess', _112 => _112.markDirty, 'call', _113 => _113()]);
1275
+ _optionalChain([this, 'access', _112 => _112.scene, 'optionalAccess', _113 => _113.markDirty, 'call', _114 => _114()]);
1231
1276
  }
1232
1277
  });
1233
1278
  }
@@ -1376,23 +1421,23 @@ var SVGEntity = (_class4 = class extends Entity {
1376
1421
  * rasterized. Set to `'transparent'` to opt out and keep the box empty.
1377
1422
  * Default `'rgba(248,113,113,0.9)'`.
1378
1423
  */
1379
- __init48() {this.fallbackStroke = "rgba(248,113,113,0.9)"}
1424
+ __init49() {this.fallbackStroke = "rgba(248,113,113,0.9)"}
1380
1425
  /** Fill behind the fallback marker. Default `'rgba(248,113,113,0.12)'`. */
1381
- __init49() {this.fallbackFill = "rgba(248,113,113,0.12)"}
1382
- __init50() {this.svgSource = ""}
1383
- __init51() {this.imageBitmap = null}
1384
- __init52() {this.imageElement = null}
1385
- __init53() {this.blobURL = null}
1386
- __init54() {this.currentImg = null}
1387
- __init55() {this.lodTimeout = null}
1388
- __init56() {this.rasterFailed = false}
1389
- __init57() {this.cachedDoc = null}
1390
- __init58() {this.baseWidth = 100}
1391
- __init59() {this.baseHeight = 100}
1392
- __init60() {this.lastRasterizedScale = 1}
1393
- __init61() {this.targetScale = 1}
1426
+ __init50() {this.fallbackFill = "rgba(248,113,113,0.12)"}
1427
+ __init51() {this.svgSource = ""}
1428
+ __init52() {this.imageBitmap = null}
1429
+ __init53() {this.imageElement = null}
1430
+ __init54() {this.blobURL = null}
1431
+ __init55() {this.currentImg = null}
1432
+ __init56() {this.lodTimeout = null}
1433
+ __init57() {this.rasterFailed = false}
1434
+ __init58() {this.cachedDoc = null}
1435
+ __init59() {this.baseWidth = 100}
1436
+ __init60() {this.baseHeight = 100}
1437
+ __init61() {this.lastRasterizedScale = 1}
1438
+ __init62() {this.targetScale = 1}
1394
1439
  constructor(svgSource, id) {
1395
- super(id);_class4.prototype.__init48.call(this);_class4.prototype.__init49.call(this);_class4.prototype.__init50.call(this);_class4.prototype.__init51.call(this);_class4.prototype.__init52.call(this);_class4.prototype.__init53.call(this);_class4.prototype.__init54.call(this);_class4.prototype.__init55.call(this);_class4.prototype.__init56.call(this);_class4.prototype.__init57.call(this);_class4.prototype.__init58.call(this);_class4.prototype.__init59.call(this);_class4.prototype.__init60.call(this);_class4.prototype.__init61.call(this);;
1440
+ super(id);_class4.prototype.__init49.call(this);_class4.prototype.__init50.call(this);_class4.prototype.__init51.call(this);_class4.prototype.__init52.call(this);_class4.prototype.__init53.call(this);_class4.prototype.__init54.call(this);_class4.prototype.__init55.call(this);_class4.prototype.__init56.call(this);_class4.prototype.__init57.call(this);_class4.prototype.__init58.call(this);_class4.prototype.__init59.call(this);_class4.prototype.__init60.call(this);_class4.prototype.__init61.call(this);_class4.prototype.__init62.call(this);;
1396
1441
  this.setSVGSource(svgSource);
1397
1442
  }
1398
1443
  setSVGSource(svgSource) {
@@ -1640,4 +1685,5 @@ var SVGEntity = (_class4 = class extends Entity {
1640
1685
 
1641
1686
 
1642
1687
 
1643
- exports.VectoJSEvent = VectoJSEvent; exports.Entity = Entity; exports.MSDFTextEntity = MSDFTextEntity; exports.SVGEntity = SVGEntity;
1688
+
1689
+ exports.contentLineInHint = contentLineInHint; exports.VectoJSEvent = VectoJSEvent; exports.Entity = Entity; exports.MSDFTextEntity = MSDFTextEntity; exports.SVGEntity = SVGEntity;