@vectojs/core 1.14.0 → 1.16.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.
Files changed (52) hide show
  1. package/dist/{chunk-XIEQHSBB.mjs → chunk-AQTO7OSU.mjs} +253 -496
  2. package/dist/{chunk-2Z23LTH3.js → chunk-DUYB4GX4.js} +310 -551
  3. package/dist/{chunk-BEUIB3U7.js → chunk-IYPLG4Z4.js} +93 -43
  4. package/dist/{chunk-L5BCKFQE.mjs → chunk-JFU56BX4.mjs} +48 -0
  5. package/dist/index.d.ts +4 -12
  6. package/dist/index.js +2155 -456
  7. package/dist/index.mjs +1977 -279
  8. package/dist/layout/index.d.ts +1 -3
  9. package/dist/layout.js +3 -15
  10. package/dist/layout.mjs +2 -16
  11. package/dist/renderer/CanvasRenderer.d.ts +22 -0
  12. package/dist/renderer/IRenderer.d.ts +13 -0
  13. package/dist/renderer.js +4 -3
  14. package/dist/renderer.mjs +1 -1
  15. package/dist/text/MSDFTextEntity.d.ts +30 -1
  16. package/dist/text/index.d.ts +1 -5
  17. package/dist/text.js +5 -15
  18. package/dist/text.mjs +6 -17
  19. package/dist/tree/ComputeParticleEntity.d.ts +18 -0
  20. package/dist/tree/DOMPortalEntity.d.ts +18 -0
  21. package/dist/tree/Entity.d.ts +122 -6
  22. package/dist/tree/Scene.d.ts +380 -0
  23. package/dist/wasm/anim-backend.d.ts +86 -0
  24. package/dist/wasm/asset.d.ts +25 -0
  25. package/dist/wasm/asset.js +7 -0
  26. package/dist/wasm/asset.mjs +5 -0
  27. package/dist/wasm/backend.d.ts +154 -0
  28. package/dist/wasm/hit-backend.d.ts +92 -0
  29. package/dist/wasm/hit-store.d.ts +48 -0
  30. package/dist/wasm/particle-backend.d.ts +104 -0
  31. package/dist/wasm/scene-store.d.ts +28 -0
  32. package/dist/wasm/soa.d.ts +146 -0
  33. package/dist/wasm/vectojs_core.wasm +0 -0
  34. package/package.json +17 -8
  35. package/dist/animation/drivers.d.ts +0 -48
  36. package/dist/animation/easing.d.ts +0 -16
  37. package/dist/chunk-4AR425AR.js +0 -1121
  38. package/dist/chunk-BA5HUUDF.js +0 -760
  39. package/dist/chunk-IESDTEJ4.mjs +0 -1121
  40. package/dist/chunk-X7I465AQ.mjs +0 -760
  41. package/dist/layout/LayoutEngine.d.ts +0 -289
  42. package/dist/layout/LayoutWorker.d.ts +0 -23
  43. package/dist/layout/LayoutWorkerManager.d.ts +0 -26
  44. package/dist/layout/LayoutWorkerSource.d.ts +0 -1
  45. package/dist/layout/measure.d.ts +0 -20
  46. package/dist/math/SpatialHashGrid.d.ts +0 -53
  47. package/dist/math/SpringPhysics.d.ts +0 -13
  48. package/dist/text/ArabicShaper.d.ts +0 -10
  49. package/dist/text/BidiResolver.d.ts +0 -5
  50. package/dist/text/MSDFFont.d.ts +0 -129
  51. package/dist/text/PreparedContentGrid.d.ts +0 -60
  52. package/dist/text/Typography.d.ts +0 -11
@@ -1,3 +1 @@
1
- export * from './LayoutEngine';
2
- export * from './LayoutWorkerManager';
3
- export * from './measure';
1
+ export * from '@vectojs/layout';
package/dist/layout.js CHANGED
@@ -1,16 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _createStarExport(obj) { Object.keys(obj) .filter((key) => key !== "default" && key !== "__esModule") .forEach((key) => { if (exports.hasOwnProperty(key)) { return; } Object.defineProperty(exports, key, {enumerable: true, configurable: true, get: () => obj[key]}); }); }const __vecto_cjs_url=require("url").pathToFileURL(__filename).href;
2
2
 
3
-
4
-
5
-
6
- var _chunkBA5HUUDFjs = require('./chunk-BA5HUUDF.js');
7
-
8
-
9
- var _chunk4AR425ARjs = require('./chunk-4AR425AR.js');
10
-
11
-
12
-
13
-
14
-
15
-
16
- exports.LayoutEngine = _chunkBA5HUUDFjs.LayoutEngine; exports.LayoutResultBuffer = _chunkBA5HUUDFjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunk4AR425ARjs.LayoutWorkerManager; exports.computeLineSegments = _chunkBA5HUUDFjs.computeLineSegments; exports.createCanvasMeasurer = _chunkBA5HUUDFjs.createCanvasMeasurer;
3
+ // src/layout/index.ts
4
+ var _layout = require('@vectojs/layout'); _createStarExport(_layout);
package/dist/layout.mjs CHANGED
@@ -1,16 +1,2 @@
1
- import {
2
- LayoutEngine,
3
- LayoutResultBuffer,
4
- computeLineSegments,
5
- createCanvasMeasurer
6
- } from "./chunk-X7I465AQ.mjs";
7
- import {
8
- LayoutWorkerManager
9
- } from "./chunk-IESDTEJ4.mjs";
10
- export {
11
- LayoutEngine,
12
- LayoutResultBuffer,
13
- LayoutWorkerManager,
14
- computeLineSegments,
15
- createCanvasMeasurer
16
- };
1
+ // src/layout/index.ts
2
+ export * from "@vectojs/layout";
@@ -3,6 +3,14 @@ export declare class CanvasRenderer implements IRenderer {
3
3
  private ctx;
4
4
  private width;
5
5
  private height;
6
+ private canvas;
7
+ /** True between a `contextlost` and its `contextrestored` — the 2D context is
8
+ * unusable, so draw calls are skipped until it comes back. Canvas2D context
9
+ * loss is rare (GPU reset / memory pressure) but a real browser event. */
10
+ private contextLost;
11
+ /** Invoked after the context is restored + re-initialized, so the owner
12
+ * (`Scene`) can repaint the now-blank canvas. Set via {@link onContextRestored}. */
13
+ private contextRestoredCb;
6
14
  /**
7
15
  * Cap on the effective device pixel ratio applied by the constructor and
8
16
  * {@link resize}. `undefined` (default) uses the real, uncapped
@@ -38,6 +46,17 @@ export declare class CanvasRenderer implements IRenderer {
38
46
  width: number;
39
47
  height: number;
40
48
  }, maxDPR?: number);
49
+ /** Register a callback fired after a lost 2D context is restored + re-scaled,
50
+ * so the owner can repaint (the restored canvas comes back cleared). */
51
+ onContextRestored(cb: () => void): void;
52
+ /**
53
+ * Handle Canvas2D context loss/restore (GPU reset, memory pressure). The
54
+ * `contextlost` handler MUST call `preventDefault()` or the browser never
55
+ * fires `contextrestored`; while lost, draw calls are skipped. On restore we
56
+ * re-acquire the 2D context, re-apply the DPR scale, drop cached style, and
57
+ * notify the owner to repaint.
58
+ */
59
+ private setupContextLossRecovery;
41
60
  /**
42
61
  * Expose the underlying `CanvasRenderingContext2D` for operations not
43
62
  * covered by the {@link IRenderer} interface.
@@ -56,6 +75,9 @@ export declare class CanvasRenderer implements IRenderer {
56
75
  * @param height - New logical height in CSS pixels.
57
76
  */
58
77
  resize(width: number, height: number): void;
78
+ /** Whether the 2D context is currently lost (drawing is a no-op until it is
79
+ * restored). The owner skips its render pass while this is true. */
80
+ isContextLost(): boolean;
59
81
  /** @inheritdoc */
60
82
  clear(): void;
61
83
  /** @inheritdoc */
@@ -191,4 +191,17 @@ export interface IRenderer {
191
191
  * successful teardown must be a silent no-op, not throw.
192
192
  */
193
193
  dispose?(): void;
194
+ /**
195
+ * Whether the renderer's drawing context is currently lost (e.g. a Canvas2D
196
+ * `contextlost` before its `contextrestored`, or a WebGL context loss). While
197
+ * true the renderer's draw calls are no-ops and the owner should skip its
198
+ * render pass. Optional — a renderer that can't lose its context omits it.
199
+ */
200
+ isContextLost?(): boolean;
201
+ /**
202
+ * Register a callback invoked after a lost context is restored and
203
+ * re-initialized, so the owner can repaint (a restored canvas comes back
204
+ * cleared). Optional, paired with {@link isContextLost}.
205
+ */
206
+ onContextRestored?(cb: () => void): void;
194
207
  }
package/dist/renderer.js CHANGED
@@ -1,16 +1,17 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});const __vecto_cjs_url=require("url").pathToFileURL(__filename).href;
2
2
 
3
3
 
4
4
 
5
5
 
6
6
 
7
7
 
8
- var _chunkBEUIB3U7js = require('./chunk-BEUIB3U7.js');
9
8
 
9
+ var _chunkIYPLG4Z4js = require('./chunk-IYPLG4Z4.js');
10
10
 
11
11
 
12
12
 
13
13
 
14
14
 
15
15
 
16
- exports.CanvasRenderer = _chunkBEUIB3U7js.CanvasRenderer; exports.SVGRenderer = _chunkBEUIB3U7js.SVGRenderer; exports.TextRasterCache = _chunkBEUIB3U7js.TextRasterCache; exports.WebGPUParticleSystemManager = _chunkBEUIB3U7js.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkBEUIB3U7js.createWebGLPointRenderer; exports.parseColorToRGBA = _chunkBEUIB3U7js.parseColorToRGBA;
16
+
17
+ exports.CanvasRenderer = _chunkIYPLG4Z4js.CanvasRenderer; exports.SVGRenderer = _chunkIYPLG4Z4js.SVGRenderer; exports.TextRasterCache = _chunkIYPLG4Z4js.TextRasterCache; exports.WebGPUParticleSystemManager = _chunkIYPLG4Z4js.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkIYPLG4Z4js.createWebGLPointRenderer; exports.parseColorToRGBA = _chunkIYPLG4Z4js.parseColorToRGBA;
package/dist/renderer.mjs CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  WebGPUParticleSystemManager,
6
6
  createWebGLPointRenderer,
7
7
  parseColorToRGBA
8
- } from "./chunk-L5BCKFQE.mjs";
8
+ } from "./chunk-JFU56BX4.mjs";
9
9
  export {
10
10
  CanvasRenderer,
11
11
  SVGRenderer,
@@ -1,5 +1,5 @@
1
1
  import { Entity, type ContentProjection } from '../tree/Entity';
2
- import { MSDFFont } from './MSDFFont';
2
+ import { MSDFFont } from '@vectojs/text';
3
3
  export interface MSDFTextEntityOptions {
4
4
  font: MSDFFont;
5
5
  texture: TexImageSource;
@@ -12,6 +12,12 @@ export interface MSDFTextEntityOptions {
12
12
  maxWidth?: number;
13
13
  /** Layout height limit in logical pixels. Defaults to 1000. */
14
14
  maxHeight?: number;
15
+ /**
16
+ * Horizontal alignment. `'justify'` stretches every wrapped line flush to
17
+ * {@link maxWidth} (the paragraph-final and newline-ended lines stay ragged);
18
+ * `'left'` (default) leaves them ragged.
19
+ */
20
+ textAlign?: 'left' | 'justify';
15
21
  }
16
22
  export declare class MSDFTextEntity extends Entity {
17
23
  private font;
@@ -23,6 +29,9 @@ export declare class MSDFTextEntity extends Entity {
23
29
  private lineHeight?;
24
30
  private maxWidth;
25
31
  private maxHeight;
32
+ private textAlign;
33
+ private hyphenator;
34
+ private layoutText;
26
35
  private text;
27
36
  private lastRenderedSeqId;
28
37
  private rgbColorCache;
@@ -31,7 +40,27 @@ export declare class MSDFTextEntity extends Entity {
31
40
  constructor(text: string, options: MSDFTextEntityOptions);
32
41
  /** Change the wrap boundary and re-run layout for the current text. */
33
42
  setMaxWidth(maxWidth: number): void;
43
+ /**
44
+ * Set horizontal alignment (`'justify'` stretches wrapped lines flush to
45
+ * {@link setMaxWidth}'s width; the last line stays ragged) and re-run layout.
46
+ */
47
+ setTextAlign(align: 'left' | 'justify'): void;
48
+ /**
49
+ * Plug a hyphenator (word → parts). Break opportunities are inserted as soft
50
+ * hyphens (U+00AD) into the string sent to layout, so a word that doesn't fit
51
+ * can break with a visible hyphen. Soft hyphens already present in the text
52
+ * work without one. Pass `null` to disable. The original text is preserved
53
+ * for accessibility — only the layout string carries the hyphens.
54
+ */
55
+ setHyphenator(fn: ((word: string) => string[]) | null): void;
34
56
  setText(text: string): void;
57
+ /**
58
+ * Recompute {@link layoutText} from {@link text}: with a hyphenator active,
59
+ * split each whitespace-delimited word and rejoin its parts with U+00AD so
60
+ * the worker sees the break opportunities. Without one, the layout string is
61
+ * the text unchanged.
62
+ */
63
+ private rebuildLayoutText;
35
64
  private queueLayout;
36
65
  /**
37
66
  * Mirror the rendered text into the DOM content layer: find-in-page, screen
@@ -1,7 +1,3 @@
1
- export * from './ArabicShaper';
2
- export * from './BidiResolver';
3
- export * from './PreparedContentGrid';
4
- export * from './MSDFFont';
1
+ export * from '@vectojs/text';
5
2
  export * from './MSDFTextEntity';
6
3
  export * from './SVGEntity';
7
- export * from './Typography';
package/dist/text.js CHANGED
@@ -1,22 +1,12 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _createStarExport(obj) { Object.keys(obj) .filter((key) => key !== "default" && key !== "__esModule") .forEach((key) => { if (exports.hasOwnProperty(key)) { return; } Object.defineProperty(exports, key, {enumerable: true, configurable: true, get: () => obj[key]}); }); }const __vecto_cjs_url=require("url").pathToFileURL(__filename).href;
2
2
 
3
3
 
4
4
 
5
+ var _chunkDUYB4GX4js = require('./chunk-DUYB4GX4.js');
5
6
 
7
+ // src/text/index.ts
8
+ var _text = require('@vectojs/text'); _createStarExport(_text);
6
9
 
7
10
 
8
- var _chunk2Z23LTH3js = require('./chunk-2Z23LTH3.js');
9
11
 
10
-
11
-
12
- var _chunk4AR425ARjs = require('./chunk-4AR425AR.js');
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
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;
12
+ exports.MSDFTextEntity = _chunkDUYB4GX4js.MSDFTextEntity; exports.SVGEntity = _chunkDUYB4GX4js.SVGEntity;
package/dist/text.mjs CHANGED
@@ -1,22 +1,11 @@
1
1
  import {
2
- MSDFFont,
3
2
  MSDFTextEntity,
4
- SVGEntity,
5
- clearCssLineBoxMetrics,
6
- cssLineBoxBaseline,
7
- prepareContentGrid
8
- } from "./chunk-XIEQHSBB.mjs";
9
- import {
10
- ArabicShaper,
11
- BidiResolver
12
- } from "./chunk-IESDTEJ4.mjs";
3
+ SVGEntity
4
+ } from "./chunk-AQTO7OSU.mjs";
5
+
6
+ // src/text/index.ts
7
+ export * from "@vectojs/text";
13
8
  export {
14
- ArabicShaper,
15
- BidiResolver,
16
- MSDFFont,
17
9
  MSDFTextEntity,
18
- SVGEntity,
19
- clearCssLineBoxMetrics,
20
- cssLineBoxBaseline,
21
- prepareContentGrid
10
+ SVGEntity
22
11
  };
@@ -1,5 +1,6 @@
1
1
  import { Entity } from './Entity';
2
2
  import { IRenderer } from '../renderer/IRenderer';
3
+ import type { ParticleBackend } from '../wasm/particle-backend';
3
4
  /**
4
5
  * Options for configuring a {@link ComputeParticleEntity}.
5
6
  */
@@ -60,6 +61,10 @@ export declare class ComputeParticleEntity extends Entity {
60
61
  computeBindGroup: any;
61
62
  /** WebGPU bind group for the render pass (usually same as compute). */
62
63
  renderBindGroup: any;
64
+ /** When the last simulation step ran through the WASM backend, its fused
65
+ * pending-animation flag; `null` when the last step used the JS `updateCPU`
66
+ * path (so `hasPendingAnimations` falls back to its own scan). */
67
+ private _wasmPending;
63
68
  constructor(options?: ComputeParticleOptions);
64
69
  /**
65
70
  * Disperses all particles randomly across the specified screen bounds.
@@ -127,6 +132,19 @@ export declare class ComputeParticleEntity extends Entity {
127
132
  * @param height - Boundary height.
128
133
  */
129
134
  updateCPU(dt: number, mouseX: number, mouseY: number, width: number, height: number): void;
135
+ /**
136
+ * Advance the simulation one step through the WASM particle kernel: transpose
137
+ * this entity's AoS buffer into the backend's SoA views, run `particle_step`,
138
+ * and scatter position/velocity/life back. Produces an f32 result (matching
139
+ * the WGSL shader) that differs from {@link updateCPU}'s f64 by <1 ULP/step —
140
+ * the accepted CPU-vs-GPU-class divergence. Caches the kernel's fused
141
+ * pending-animation flag so {@link hasPendingAnimations} needs no second scan.
142
+ *
143
+ * The backend holds one resident SoA store, so a Scene with multiple particle
144
+ * entities reuses it sequentially — origin is therefore re-gathered each call
145
+ * (not upload-once), a couple of extra f32 reads per particle.
146
+ */
147
+ stepWithBackend(backend: ParticleBackend, dt: number, mouseX: number, mouseY: number, width: number, height: number): void;
130
148
  destroy(): void;
131
149
  /**
132
150
  * Frees all GPU resources allocated for WebGPU simulation.
@@ -4,6 +4,7 @@ export declare class DOMPortalEntity extends Entity {
4
4
  isDOMPortal: boolean;
5
5
  private domListeners;
6
6
  private resizeObserver;
7
+ private domBound;
7
8
  cachedWidth: number;
8
9
  cachedHeight: number;
9
10
  lastWidth: string;
@@ -12,6 +13,23 @@ export declare class DOMPortalEntity extends Entity {
12
13
  lastZIndex: string;
13
14
  lastOpacity: string;
14
15
  constructor(domElement: HTMLElement, width?: number, height?: number, id?: string);
16
+ /**
17
+ * Attach the ResizeObserver + DOM event listeners that bridge native DOM
18
+ * events into the Vecto event system. Idempotent: safe to call every frame
19
+ * from the projection path (`Scene.syncPortalGeometry`), which is what lets a
20
+ * portal survive a `scene.remove()` -> re-add cycle — {@link releaseDOMBindings}
21
+ * tears these down on removal and the next projection re-attaches them.
22
+ */
23
+ attachDOMBindings(): void;
24
+ /**
25
+ * Disconnect the ResizeObserver and remove the DOM event listeners, without
26
+ * touching the scene graph or the element's DOM parentage. Called on the
27
+ * `scene.remove()` path so a detached portal doesn't leak an observer that
28
+ * keeps its element alive and firing; the element itself is removed from the
29
+ * document by the scene's portal pruning. Re-attached lazily on the next
30
+ * projection frame via {@link attachDOMBindings}.
31
+ */
32
+ releaseDOMBindings(): void;
15
33
  isPointInside(globalX: number, globalY: number): boolean;
16
34
  add(..._children: Entity[]): this;
17
35
  render(): void;
@@ -1,5 +1,5 @@
1
- import { type MotionConfig, type TweenConfig, type SpringConfig } from '../animation/drivers';
2
- import type { PreparedContentGrid } from '../text/PreparedContentGrid';
1
+ import { type PropertyDriver, type MotionConfig, type TweenConfig, type SpringConfig } from '@vectojs/animation';
2
+ import type { PreparedContentGrid } from '@vectojs/text';
3
3
  /** A numeric transform/visual property that participates in the animation system. */
4
4
  export type AnimatableProp = 'x' | 'y' | 'scaleX' | 'scaleY' | 'rotation' | 'opacity';
5
5
  /**
@@ -65,6 +65,21 @@ export interface ContentProjectionRun {
65
65
  text: string;
66
66
  /** CSS font shorthand matching the canvas run. */
67
67
  font?: string;
68
+ /**
69
+ * Absolute local x of this run within the entity. Set it (with {@link width})
70
+ * for justified or otherwise non-naturally-spaced text: the Scene then places
71
+ * each run as a positioned carrier (`inline-block` + relative `left`) at the
72
+ * exact canvas x, so the DOM selection box overlaps the drawn glyphs instead
73
+ * of drifting under the browser's own inter-word spacing. When omitted, runs
74
+ * flow naturally (the default for left-aligned text).
75
+ */
76
+ x?: number;
77
+ /**
78
+ * Advance (width in px) the canvas used for this run, including any widened
79
+ * trailing gap for justify. Used only alongside {@link x} to size the
80
+ * positioned carrier so the next run starts flush at its own `x`.
81
+ */
82
+ width?: number;
68
83
  }
69
84
  export interface ContentProjectionLine {
70
85
  /** Text for browser find-in-page and native selection. */
@@ -186,6 +201,30 @@ export interface A11yAttributes {
186
201
  activedescendant?: string;
187
202
  valuemin?: string;
188
203
  valuemax?: string;
204
+ /**
205
+ * ARIA live-region politeness (`aria-live`). Set on the container whose text
206
+ * changes as content streams in (chat message, toast, async validation
207
+ * summary) so a screen reader announces updates without moving focus.
208
+ * `'polite'` waits for a pause; `'assertive'` interrupts. WCAG 4.1.3.
209
+ */
210
+ live?: 'off' | 'polite' | 'assertive';
211
+ /** `aria-atomic`: announce the whole region on change, not just the diff. */
212
+ atomic?: boolean;
213
+ /** `aria-relevant`: which mutation types to announce (e.g. `'additions text'`). */
214
+ relevant?: string;
215
+ /** `aria-labelledby`: id(s) of the element(s) that label this one. */
216
+ labelledby?: string;
217
+ /** `aria-describedby`: id(s) of the element(s) that describe this one. */
218
+ describedby?: string;
219
+ /** `aria-required`: the field must be filled before submit. */
220
+ required?: boolean;
221
+ /** `aria-invalid`: the field's current value fails validation. */
222
+ invalid?: boolean;
223
+ /** `aria-level`: hierarchical level (headings, tree items, etc.). */
224
+ level?: number;
225
+ /** `aria-modal`: marks a `role="dialog"` as modal so assistive tech confines
226
+ * reading to it. Set on a modal dialog's shell. */
227
+ ariaModal?: 'true' | 'false';
189
228
  /** Explicit native editor typography; ignored for non-input elements. */
190
229
  textInputStyle?: TextInputStyle;
191
230
  }
@@ -295,6 +334,18 @@ export declare abstract class Entity {
295
334
  private _transitions;
296
335
  private _drivers;
297
336
  private _mounted;
337
+ private _destroyed;
338
+ _driversTickedFrame: number;
339
+ private readonly _trig;
340
+ private _trigRotation;
341
+ private _wa;
342
+ private _wb;
343
+ private _wc;
344
+ private _wd;
345
+ private _we;
346
+ private _wf;
347
+ private _worldFrame;
348
+ _storeSlot: number;
298
349
  get x(): number;
299
350
  set x(v: number);
300
351
  get y(): number;
@@ -330,9 +381,9 @@ export declare abstract class Entity {
330
381
  * fixed viewport. Off by default (children render unclipped). Canvas2D only.
331
382
  */
332
383
  clipChildren: boolean;
333
- protected listeners: Map<VectoEvent, Array<(e: any) => void>>;
384
+ protected listeners: Map<VectoEvent, Array<(e: any) => void>> | null;
334
385
  /** Capture-phase listeners (fired root→target before bubble). */
335
- protected captureListeners: Map<VectoEvent, Array<(e: any) => void>>;
386
+ protected captureListeners: Map<VectoEvent, Array<(e: any) => void>> | null;
336
387
  private animations;
337
388
  constructor(id?: string);
338
389
  /**
@@ -422,6 +473,26 @@ export declare abstract class Entity {
422
473
  private _driveTo;
423
474
  /** Advance active property drivers one frame. Call from update(). */
424
475
  protected tickDrivers(dt: number): void;
476
+ /**
477
+ * Internal: this entity's active-driver map (read-only view), or `null` if
478
+ * it has none. Called only by Scene's batched WASM animation pass, never
479
+ * application code. Returns the Map directly (not a callback iteration) so
480
+ * a caller can `for...of` it with zero per-entity closure allocation — the
481
+ * integrated benchmark (benchmarks/anim-wasm-scene) found a fresh callback
482
+ * per entity per frame was a real cost, not a negligible one.
483
+ */
484
+ _driverEntries(): ReadonlyMap<AnimatableProp, PropertyDriver> | null;
485
+ /**
486
+ * Internal: finalize one driver that was ALREADY advanced externally this
487
+ * frame (e.g. by Scene's batched WASM tick via `driver.syncExternal`, or a
488
+ * direct `driver.tick()` call for a driver the batch can't offload) —
489
+ * exactly mirrors tickDrivers()'s own per-driver completion logic, so a
490
+ * driver behaves identically regardless of which path ticked it. Called
491
+ * only by Scene, never application code.
492
+ */
493
+ _applyDriverTick(prop: AnimatableProp, driver: PropertyDriver): void;
494
+ /** Internal: true if this entity currently has any active property driver. */
495
+ _hasActiveDrivers(): boolean;
425
496
  /**
426
497
  * Advance the entity's internal state for one frame.
427
498
  *
@@ -456,8 +527,21 @@ export declare abstract class Entity {
456
527
  */
457
528
  off(event: VectoEvent, callback: (e: any) => void, options?: ListenerOptions): this;
458
529
  /**
459
- * Tear down this entity: clear all animations, event listeners, and detach
460
- * from parent. Call before discarding an entity to prevent memory leaks.
530
+ * Tear down this entity **and its entire subtree**: recursively destroy every
531
+ * descendant (leaf-first), clear all animations, event listeners, and property
532
+ * drivers, then detach from the parent. Call before discarding an entity to
533
+ * prevent memory leaks.
534
+ *
535
+ * Recursing here is what frees a subtree's GPU buffers, layout workers, and DOM
536
+ * observers when an app does `entity.destroy()` or `scene.remove(subtree)` on a
537
+ * route change — without it, only the root's own state was released and every
538
+ * descendant (and its subclass resources) was stranded. Subclasses that own
539
+ * external resources override `destroy()`, free their resource, then call
540
+ * `super.destroy()`; because children don't depend on a parent's resource, the
541
+ * order (parent resource first, then descendants) is safe.
542
+ *
543
+ * Idempotent and re-entrancy safe: a second call is a no-op, and because each
544
+ * child is detached as it is destroyed, destroying a subtree never double-frees.
461
545
  */
462
546
  destroy(): void;
463
547
  /**
@@ -498,10 +582,42 @@ export declare abstract class Entity {
498
582
  * @returns World-space {@link Point} for this entity.
499
583
  */
500
584
  getGlobalPosition(): Point;
585
+ /**
586
+ * Internal: read this entity's cached world matrix into `out` — the SAME
587
+ * cache {@link getWorldTransform} reads — without allocating a wrapper
588
+ * object. Returns `false` (leaving `out` untouched) when the cache isn't
589
+ * valid for `frame` (typically the caller's `scene.currentFrame`), exactly
590
+ * mirroring {@link getWorldTransform}'s own validity check; the caller
591
+ * falls back to {@link getWorldTransform}'s full walk in that case. Exists
592
+ * so a per-entity gather that runs every entity through this (e.g. G3's
593
+ * `gatherHitAABBs`) pays for six scalar reads instead of one object
594
+ * allocation per entity per call — the exact class of per-frame garbage
595
+ * the G2 integrated benchmark found dominating its own gather cost.
596
+ */
597
+ _readWorldCache(frame: number, out: AffineTransform): boolean;
501
598
  /**
502
599
  * Return the exact accumulated Canvas `T * S * R` transform for this entity.
503
600
  */
504
601
  getWorldTransform(): AffineTransform;
602
+ /**
603
+ * Return this entity's cached `{ cos, sin }` of its current rotation,
604
+ * recomputing only when `rotation` has actually changed since the last call.
605
+ * The same object identity is returned across calls with an unchanged
606
+ * rotation, so callers must treat it as read-only. Used by the render walk
607
+ * and {@link getWorldTransform} to avoid V8's comparatively slow Math.cos/sin
608
+ * (a software libm call, ~2.5x slower than other engines) per entity/frame.
609
+ */
610
+ _getTrig(): Readonly<{
611
+ cos: number;
612
+ sin: number;
613
+ }>;
614
+ /**
615
+ * Store the world matrix Scene computed for this entity during the render
616
+ * walk, stamped with the frame it belongs to. {@link getWorldTransform}
617
+ * returns it verbatim while `frame === scene.currentFrame`. Internal: called
618
+ * only by Scene's renderer, never by application code.
619
+ */
620
+ _setWorldCache(a: number, b: number, c: number, d: number, e: number, f: number, frame: number): void;
505
621
  /** Convert a point from this entity's local space to Scene/world space. */
506
622
  localToWorld(localX: number, localY: number): Point;
507
623
  /**