@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.
- package/dist/{chunk-XIEQHSBB.mjs → chunk-AQTO7OSU.mjs} +253 -496
- package/dist/{chunk-2Z23LTH3.js → chunk-DUYB4GX4.js} +310 -551
- package/dist/{chunk-BEUIB3U7.js → chunk-IYPLG4Z4.js} +93 -43
- package/dist/{chunk-L5BCKFQE.mjs → chunk-JFU56BX4.mjs} +48 -0
- package/dist/index.d.ts +4 -12
- package/dist/index.js +2155 -456
- package/dist/index.mjs +1977 -279
- package/dist/layout/index.d.ts +1 -3
- package/dist/layout.js +3 -15
- package/dist/layout.mjs +2 -16
- package/dist/renderer/CanvasRenderer.d.ts +22 -0
- package/dist/renderer/IRenderer.d.ts +13 -0
- package/dist/renderer.js +4 -3
- package/dist/renderer.mjs +1 -1
- package/dist/text/MSDFTextEntity.d.ts +30 -1
- package/dist/text/index.d.ts +1 -5
- package/dist/text.js +5 -15
- package/dist/text.mjs +6 -17
- package/dist/tree/ComputeParticleEntity.d.ts +18 -0
- package/dist/tree/DOMPortalEntity.d.ts +18 -0
- package/dist/tree/Entity.d.ts +122 -6
- package/dist/tree/Scene.d.ts +380 -0
- package/dist/wasm/anim-backend.d.ts +86 -0
- package/dist/wasm/asset.d.ts +25 -0
- package/dist/wasm/asset.js +7 -0
- package/dist/wasm/asset.mjs +5 -0
- package/dist/wasm/backend.d.ts +154 -0
- package/dist/wasm/hit-backend.d.ts +92 -0
- package/dist/wasm/hit-store.d.ts +48 -0
- package/dist/wasm/particle-backend.d.ts +104 -0
- package/dist/wasm/scene-store.d.ts +28 -0
- package/dist/wasm/soa.d.ts +146 -0
- package/dist/wasm/vectojs_core.wasm +0 -0
- package/package.json +17 -8
- package/dist/animation/drivers.d.ts +0 -48
- package/dist/animation/easing.d.ts +0 -16
- package/dist/chunk-4AR425AR.js +0 -1121
- package/dist/chunk-BA5HUUDF.js +0 -760
- package/dist/chunk-IESDTEJ4.mjs +0 -1121
- package/dist/chunk-X7I465AQ.mjs +0 -760
- package/dist/layout/LayoutEngine.d.ts +0 -289
- package/dist/layout/LayoutWorker.d.ts +0 -23
- package/dist/layout/LayoutWorkerManager.d.ts +0 -26
- package/dist/layout/LayoutWorkerSource.d.ts +0 -1
- package/dist/layout/measure.d.ts +0 -20
- package/dist/math/SpatialHashGrid.d.ts +0 -53
- package/dist/math/SpringPhysics.d.ts +0 -13
- package/dist/text/ArabicShaper.d.ts +0 -10
- package/dist/text/BidiResolver.d.ts +0 -5
- package/dist/text/MSDFFont.d.ts +0 -129
- package/dist/text/PreparedContentGrid.d.ts +0 -60
- package/dist/text/Typography.d.ts +0 -11
package/dist/tree/Scene.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export interface IWebGPUParticleSystemManager {
|
|
|
12
12
|
}
|
|
13
13
|
import { Entity } from './Entity';
|
|
14
14
|
import { IRenderer } from '../renderer/IRenderer';
|
|
15
|
+
import { type WasmModuleSource, type WasmTransformBackend } from '../wasm/backend';
|
|
16
|
+
import { type HitModuleSource, type HitTestBackend } from '../wasm/hit-backend';
|
|
17
|
+
import { type AnimModuleSource, type AnimBackend } from '../wasm/anim-backend';
|
|
18
|
+
import { type ParticleModuleSource, type ParticleBackend } from '../wasm/particle-backend';
|
|
15
19
|
/**
|
|
16
20
|
* Options for {@link Scene}.
|
|
17
21
|
*/
|
|
@@ -110,6 +114,15 @@ export interface SceneOptions {
|
|
|
110
114
|
* height (`undefined` → resolved to `Scene.height` at sync time).
|
|
111
115
|
*/
|
|
112
116
|
contentProjectionMargin?: number;
|
|
117
|
+
/**
|
|
118
|
+
* Reading direction used to order the accessibility/automation shadow tree so
|
|
119
|
+
* keyboard **tab order** and screen-reader traversal follow the *visual*
|
|
120
|
+
* reading order (top-to-bottom, then inline) rather than scene-graph
|
|
121
|
+
* insertion order — two entities added in any order but drawn left/right of
|
|
122
|
+
* each other should Tab left→right (`'ltr'`, default) or right→left
|
|
123
|
+
* (`'rtl'`). Also settable later via {@link Scene.readingDirection}.
|
|
124
|
+
*/
|
|
125
|
+
readingDirection?: 'ltr' | 'rtl';
|
|
113
126
|
}
|
|
114
127
|
/** Frame-rate the loop is capped to when the OS requests reduced motion. */
|
|
115
128
|
export declare const REDUCED_MOTION_FPS = 30;
|
|
@@ -162,12 +175,23 @@ export interface A11yTreeNode {
|
|
|
162
175
|
export declare class Scene {
|
|
163
176
|
private static webglCreator;
|
|
164
177
|
private static webgpuManagerClass;
|
|
178
|
+
/** Upper bound (ms) on a single frame's `dt`. Caps the giant elapsed gap a
|
|
179
|
+
* backgrounded/refocused tab produces so physics advances at most one slow
|
|
180
|
+
* frame instead of the whole idle duration (~100ms ≈ 6 frames at 60fps). */
|
|
181
|
+
private static readonly MAX_FRAME_DT;
|
|
165
182
|
static registerWebGLPointRendererCreator(creator: WebGLPointRendererCreator): void;
|
|
166
183
|
static registerWebGPUParticleSystemManager(managerClass: any): void;
|
|
167
184
|
private root;
|
|
168
185
|
overlayRoot: Entity;
|
|
169
186
|
private renderer;
|
|
170
187
|
private isRunning;
|
|
188
|
+
/** Whether the canvas is at least partially in the viewport. When it scrolls
|
|
189
|
+
* fully off-screen the rAF loop pauses (stops rescheduling) instead of
|
|
190
|
+
* burning frames on a scene nobody can see; an IntersectionObserver resumes
|
|
191
|
+
* it on re-entry. Defaults true (and stays true where IntersectionObserver
|
|
192
|
+
* is unavailable, e.g. SSR/jsdom, so behavior is unchanged there). */
|
|
193
|
+
private _canvasOnScreen;
|
|
194
|
+
private _canvasObserver;
|
|
171
195
|
private lastTime;
|
|
172
196
|
canvas: HTMLCanvasElement;
|
|
173
197
|
/**
|
|
@@ -201,10 +225,34 @@ export declare class Scene {
|
|
|
201
225
|
maxFPS: number;
|
|
202
226
|
/** Whether the OS prefers-reduced-motion setting auto-caps the loop. */
|
|
203
227
|
respectReducedMotion: boolean;
|
|
228
|
+
/**
|
|
229
|
+
* Reading direction for accessibility tab/traversal order (`'ltr'` default,
|
|
230
|
+
* `'rtl'`). Controls the inline sort within a visual row in
|
|
231
|
+
* {@link enforceA11yDomOrder}. Set at runtime to re-flow tab order on the
|
|
232
|
+
* next sync (also trips a reorder).
|
|
233
|
+
*/
|
|
234
|
+
get readingDirection(): 'ltr' | 'rtl';
|
|
235
|
+
set readingDirection(dir: 'ltr' | 'rtl');
|
|
236
|
+
private _readingDirection;
|
|
204
237
|
/** Cached media-query list; `.matches` is read live each frame. */
|
|
205
238
|
private reducedMotionQuery;
|
|
239
|
+
/** Cached `(forced-colors: active)` query (Windows High Contrast etc.). A
|
|
240
|
+
* canvas gets NO automatic forced-colors treatment from the browser (it's
|
|
241
|
+
* opaque pixels), so components must read {@link forcedColors} and repaint
|
|
242
|
+
* with system colors themselves; a change listener repaints idle scenes. */
|
|
243
|
+
private forcedColorsQuery;
|
|
244
|
+
private forcedColorsChangeHandler;
|
|
206
245
|
/** True when the OS asks for reduced motion and we respect it. Read by the animation drivers. */
|
|
207
246
|
get prefersReducedMotion(): boolean;
|
|
247
|
+
/**
|
|
248
|
+
* True when the OS is in a forced-colors mode (Windows High Contrast, and the
|
|
249
|
+
* `forced-colors: active` media feature generally). Canvas pixels are exempt
|
|
250
|
+
* from the browser's forced-colors remapping, so accessible components should
|
|
251
|
+
* read this and draw with CSS system colors (`CanvasText`, `Canvas`,
|
|
252
|
+
* `Highlight`, …) instead of their themed palette. Re-rendered automatically
|
|
253
|
+
* when the setting toggles.
|
|
254
|
+
*/
|
|
255
|
+
get forcedColors(): boolean;
|
|
208
256
|
/**
|
|
209
257
|
* Throttle interval (ms) for the a11y/automation shadow sync. `0` = every
|
|
210
258
|
* frame. See {@link SceneOptions.a11ySyncInterval}.
|
|
@@ -241,7 +289,32 @@ export declare class Scene {
|
|
|
241
289
|
private frameHadAnimation;
|
|
242
290
|
private frameHadInteractive;
|
|
243
291
|
private resizeHandler;
|
|
292
|
+
/** Active `(resolution: Ndppx)` media query watching for a runtime DPR change
|
|
293
|
+
* (window moved between monitors, browser zoom) so the canvas backing store
|
|
294
|
+
* can be re-scaled — otherwise it stays rasterized at the old DPR and blurs.
|
|
295
|
+
* A resolution media query only fires when leaving its exact value, so the
|
|
296
|
+
* handler re-arms a fresh query for the new DPR each time. */
|
|
297
|
+
private dprMediaQuery;
|
|
298
|
+
/** For embedded (`disableWindowResize`) scenes: observes the canvas element so
|
|
299
|
+
* a CSS/layout-driven size change re-runs `resize()`. A window `resize`
|
|
300
|
+
* listener never fires for these (the window isn't what changed), so without
|
|
301
|
+
* this an embedded canvas stayed at its initial size forever. */
|
|
302
|
+
private canvasResizeObserver;
|
|
303
|
+
private dprChangeHandler;
|
|
244
304
|
private focusedA11yElement;
|
|
305
|
+
/** Last geometry `syncOverlayGeometry` wrote, so an unchanged frame can skip the
|
|
306
|
+
* style writes entirely. Reset to `null` to force the next sync (a new overlay
|
|
307
|
+
* layer was created and has never been positioned). */
|
|
308
|
+
private _overlayGeometry;
|
|
309
|
+
/** Shadow elements the pointer is currently inside. Lets a removal that happens
|
|
310
|
+
* mid-hover synthesize the `pointerleave` the browser never sends for a
|
|
311
|
+
* detached element, so the entity doesn't keep its hover state. */
|
|
312
|
+
private readonly hoveredA11yElements;
|
|
313
|
+
/** Persistent tabindex=-1 element in a11yRoot. When the focused a11y mirror is
|
|
314
|
+
* pruned (virtualization/streaming/removal) while it holds focus, we move
|
|
315
|
+
* focus here instead of letting the browser drop it to <body> — keeping the
|
|
316
|
+
* screen-reader virtual cursor inside the scene's a11y region. */
|
|
317
|
+
private focusSentinel;
|
|
245
318
|
private caretBlinkTimer;
|
|
246
319
|
a11yNeedsReorder: boolean;
|
|
247
320
|
private portalRoot;
|
|
@@ -252,6 +325,223 @@ export declare class Scene {
|
|
|
252
325
|
private activePortalsPrevFrame;
|
|
253
326
|
private portalEntities;
|
|
254
327
|
private renderOrderCounter;
|
|
328
|
+
/**
|
|
329
|
+
* Monotonic render-frame counter, bumped once per authoritative `render()`
|
|
330
|
+
* pass. Entities stamp their per-frame world-matrix cache with this value and
|
|
331
|
+
* {@link Entity.getWorldTransform} trusts that cache only while it still
|
|
332
|
+
* matches, so a query outside the frame that produced it transparently falls
|
|
333
|
+
* back to the ancestor walk. Public for the same reason `Entity._getTrig`/
|
|
334
|
+
* `_setWorldCache` are: it is a cross-class render-internal contract.
|
|
335
|
+
*/
|
|
336
|
+
currentFrame: number;
|
|
337
|
+
private _wasm;
|
|
338
|
+
private _transformBackend;
|
|
339
|
+
private _treeStore;
|
|
340
|
+
private _slotEntity;
|
|
341
|
+
private _wasmInputs;
|
|
342
|
+
private _wasmWorld;
|
|
343
|
+
private _structureVersion;
|
|
344
|
+
private _storeStructureVersion;
|
|
345
|
+
private _computeEntities;
|
|
346
|
+
private _computeEntitiesVersion;
|
|
347
|
+
/** Invalidate the resident WASM store layout; the next wasm-mode frame rebuilds
|
|
348
|
+
* it. Called by `Entity.add`/`remove` (topology changes only). */
|
|
349
|
+
markStructureChanged(): void;
|
|
350
|
+
/** The tree's ComputeParticleEntity instances, cached per structure version so
|
|
351
|
+
* a compute-free scene doesn't re-walk the whole tree every frame. */
|
|
352
|
+
private _computeEntitiesFor;
|
|
353
|
+
/** Which backend composes world matrices for the main render walk. */
|
|
354
|
+
get transformBackend(): 'js' | 'wasm';
|
|
355
|
+
/**
|
|
356
|
+
* Install (or clear) a WASM transform backend. Passing a backend switches the
|
|
357
|
+
* main render walk onto it; passing `null` reverts to the JS path. Synchronous
|
|
358
|
+
* and safe to call between frames — the next `render()` picks it up. Prefer
|
|
359
|
+
* {@link enableWasmTransforms} for the normal async hot-swap.
|
|
360
|
+
*/
|
|
361
|
+
setTransformBackend(backend: WasmTransformBackend | null): void;
|
|
362
|
+
/**
|
|
363
|
+
* Asynchronously instantiate the WASM transform core and, on success, hot-swap
|
|
364
|
+
* the render walk onto it. Accepts whatever is convenient at the call site:
|
|
365
|
+
*
|
|
366
|
+
* ```ts
|
|
367
|
+
* // The common case — a bundler-emitted, co-located asset URL:
|
|
368
|
+
* await scene.enableWasmTransforms(new URL('./vectojs_core.wasm', import.meta.url));
|
|
369
|
+
* // …or a path string, a Response, or raw bytes you already have:
|
|
370
|
+
* await scene.enableWasmTransforms('/assets/vectojs_core.wasm');
|
|
371
|
+
* await scene.enableWasmTransforms(await fetch(url));
|
|
372
|
+
* await scene.enableWasmTransforms(myUint8Array);
|
|
373
|
+
* ```
|
|
374
|
+
*
|
|
375
|
+
* A URL/Response streams (compiles while it downloads, with a buffered
|
|
376
|
+
* fallback for a wrong MIME type); raw bytes instantiate directly. The Scene
|
|
377
|
+
* keeps rendering on the JS path until this resolves, and stays on JS if
|
|
378
|
+
* instantiation fails (CSP `wasm-unsafe-eval`, unsupported SIMD, corrupt or
|
|
379
|
+
* missing bytes, a 404) — failure is the default state, not an error path.
|
|
380
|
+
* Resolves `true` if WASM is now active, `false` if the JS path remains.
|
|
381
|
+
*/
|
|
382
|
+
enableWasmTransforms(source: WasmModuleSource): Promise<boolean>;
|
|
383
|
+
private _hitWasm;
|
|
384
|
+
private _hitGridFrame;
|
|
385
|
+
private _hitGridOk;
|
|
386
|
+
private _hitSlotEntity;
|
|
387
|
+
private _hitBoundless;
|
|
388
|
+
/** Which backend answers `findEntityAt` for the main tree. */
|
|
389
|
+
get hitTestBackend(): 'js' | 'wasm';
|
|
390
|
+
/** Install (or clear) a WASM hit-test backend directly. Prefer
|
|
391
|
+
* {@link enableWasmHitTest} for the normal async hot-swap. */
|
|
392
|
+
setHitTestBackend(backend: HitTestBackend | null): void;
|
|
393
|
+
/**
|
|
394
|
+
* Asynchronously instantiate the WASM hit-test core and, on success, hot-swap
|
|
395
|
+
* `findEntityAt` onto it. Accepts the same source shapes as
|
|
396
|
+
* {@link enableWasmTransforms} (URL, path string, Response, or raw bytes).
|
|
397
|
+
* Stays on the JS walk if instantiation fails — failure is the default
|
|
398
|
+
* state, not an error path. Resolves `true` if WASM is now active.
|
|
399
|
+
*/
|
|
400
|
+
enableWasmHitTest(source: HitModuleSource): Promise<boolean>;
|
|
401
|
+
/**
|
|
402
|
+
* Refresh the hit-test grid for the CURRENT tree state if it is stale (a
|
|
403
|
+
* structural or transform change may have happened since the last build —
|
|
404
|
+
* there is no cheap "nothing moved" shortcut for a spatial index the way
|
|
405
|
+
* there is for the transform store's topology-only run table, since ANY
|
|
406
|
+
* entity moving invalidates its AABB, not just add/remove/reparent; the
|
|
407
|
+
* measured build cost is cheap enough to redo per call). Returns `false`
|
|
408
|
+
* (grid untrustworthy — caller must use the JS walk) when there is no
|
|
409
|
+
* backend or the build overflowed its item budget.
|
|
410
|
+
*/
|
|
411
|
+
private _ensureHitGrid;
|
|
412
|
+
/**
|
|
413
|
+
* `findEntityAt`'s WASM-accelerated path for the main tree. Scans only the
|
|
414
|
+
* queried cell's candidates (confirming each against its own AABB and precise
|
|
415
|
+
* `isPointInside`) merged against the (typically empty or tiny) list of
|
|
416
|
+
* entities with no `getBounds()`, taking whichever confirmed match has the
|
|
417
|
+
* higher pre-order index — see hit-store.ts for why that is exactly
|
|
418
|
+
* equivalent to findHitRecursively's topmost-hit priority. Always
|
|
419
|
+
* conclusive: returns the correct entity or `null`, never "inconclusive".
|
|
420
|
+
*/
|
|
421
|
+
private _findEntityAtWasm;
|
|
422
|
+
private _animWasm;
|
|
423
|
+
private _activeDriverEntities;
|
|
424
|
+
private _springEntities;
|
|
425
|
+
private _springProps;
|
|
426
|
+
private _springDrivers;
|
|
427
|
+
private _tweenEntities;
|
|
428
|
+
private _tweenProps;
|
|
429
|
+
private _tweenDrivers;
|
|
430
|
+
/**
|
|
431
|
+
* Minimum number of batchable (spring, or named-easing tween) active drivers
|
|
432
|
+
* before a frame engages the WASM batch path at all; below it, every driver
|
|
433
|
+
* ticks on the normal JS per-entity path, unmodified.
|
|
434
|
+
*
|
|
435
|
+
* Re-measured on the INTEGRATED path (benchmarks/anim-wasm-scene, real
|
|
436
|
+
* Chrome 150 / Firefox 153, 2026-07-24 — correctness verified 0 mismatches
|
|
437
|
+
* across all three kinds before any of these numbers were trusted): the
|
|
438
|
+
* isolated kernel spike's "<100 drivers, wins everywhere" verdict did NOT
|
|
439
|
+
* survive integration, and neither did the first integrated pass's single
|
|
440
|
+
* gate-count verdict once broken out by driver kind. On Chrome, spring and
|
|
441
|
+
* mixed drivers are a real ~1.4–2.3× win from n=128 up through the tested
|
|
442
|
+
* ceiling of 16384, but pure-tween drivers are a LOSS at n=128 (0.71×,
|
|
443
|
+
* i.e. ~40% slower than the JS path) and only turn net-positive around
|
|
444
|
+
* n≈256 (1.52×). A single scalar gate can't be tight for spring/mixed
|
|
445
|
+
* without occasionally opening early on a tween-heavy scene and making it
|
|
446
|
+
* slower — 256 is chosen to keep the gate net-positive across all three
|
|
447
|
+
* kinds rather than optimal for any one of them; a kind-aware gate (see
|
|
448
|
+
* `_tickBatchedDrivers`'s per-kind arrays, which already separate spring
|
|
449
|
+
* from tween) would recover the 128–255 spring/mixed win without the
|
|
450
|
+
* tween regression, but that's a larger change than this measurement pass
|
|
451
|
+
* covers. On Firefox it is a net loss at every driver count measured, up
|
|
452
|
+
* to 16384 — not an allocation artifact (confirmed after removing all
|
|
453
|
+
* per-frame allocation from the gather/scatter path); SpiderMonkey's
|
|
454
|
+
* wasm-boundary/property-dispatch cost for this shape of call appears to
|
|
455
|
+
* structurally exceed the saving, at least at the scales tested here.
|
|
456
|
+
*
|
|
457
|
+
* 256 is set as a Chrome-oriented default so an app that opts in (this
|
|
458
|
+
* path is never engaged without an explicit {@link enableWasmAnimBatching}
|
|
459
|
+
* call) sees the gate open only where it reliably helps on Chromium,
|
|
460
|
+
* regardless of whether the scene's active drivers are spring, tween, or
|
|
461
|
+
* a mix of both. Unlike G1 (safe to default on everywhere) and G3 (opt-in,
|
|
462
|
+
* but a reliable win once its own gate condition holds), G2 has no
|
|
463
|
+
* threshold that is safe on every engine — raise or lower this per your
|
|
464
|
+
* own target browser mix and driver-kind distribution, or leave WASM
|
|
465
|
+
* animation batching disabled entirely on a Firefox-heavy audience.
|
|
466
|
+
*/
|
|
467
|
+
animDriverGateCount: number;
|
|
468
|
+
/** Which backend advances active property drivers on the current gate
|
|
469
|
+
* decision. Reflects only whether a backend is installed — the per-frame
|
|
470
|
+
* gate can still choose the JS path even when this reads `'wasm'`. */
|
|
471
|
+
get animBackend(): 'js' | 'wasm';
|
|
472
|
+
/** Install (or clear) a WASM batched-animation backend directly. Prefer
|
|
473
|
+
* {@link enableWasmAnimBatching} for the normal async hot-swap. */
|
|
474
|
+
setAnimBackend(backend: AnimBackend | null): void;
|
|
475
|
+
/**
|
|
476
|
+
* Asynchronously instantiate the WASM batched-animation core and, on
|
|
477
|
+
* success, make it available to the per-frame gate (see
|
|
478
|
+
* {@link animDriverGateCount}). Accepts the same source shapes as
|
|
479
|
+
* {@link enableWasmTransforms}. Stays on the JS tick loop if instantiation
|
|
480
|
+
* fails — failure is the default state, not an error path. Resolves `true`
|
|
481
|
+
* if WASM is now available (not necessarily active every frame).
|
|
482
|
+
*/
|
|
483
|
+
enableWasmAnimBatching(source: AnimModuleSource): Promise<boolean>;
|
|
484
|
+
private _particleWasm;
|
|
485
|
+
/** Which backend runs the CPU particle simulation. Reflects only whether a
|
|
486
|
+
* backend is installed (the WebGPU compute path, when active, is used first
|
|
487
|
+
* regardless). */
|
|
488
|
+
get particleSimBackend(): 'js' | 'wasm';
|
|
489
|
+
/** Install (or clear) a WASM particle backend directly. Prefer
|
|
490
|
+
* {@link enableWasmParticles} for the normal async hot-swap. */
|
|
491
|
+
setParticleBackend(backend: ParticleBackend | null): void;
|
|
492
|
+
/**
|
|
493
|
+
* Asynchronously instantiate the WASM particle core and, on success, use it
|
|
494
|
+
* for the CPU particle fallback. Accepts the same source shapes as
|
|
495
|
+
* {@link enableWasmTransforms}. Stays on the JS `updateCPU` path if
|
|
496
|
+
* instantiation fails — failure is the default state, not an error path.
|
|
497
|
+
* Resolves `true` if WASM is now active.
|
|
498
|
+
*/
|
|
499
|
+
enableWasmParticles(source: ParticleModuleSource): Promise<boolean>;
|
|
500
|
+
/** Internal: called by `Entity._spawnDriver` when a new property driver
|
|
501
|
+
* starts. See {@link _activeDriverEntities}. */
|
|
502
|
+
_registerActiveDriverEntity(entity: Entity): void;
|
|
503
|
+
/**
|
|
504
|
+
* Drop `entity` and its whole subtree from the batched-driver candidate set.
|
|
505
|
+
* Called by {@link remove}/{@link hideOverlay} on detach: without this a
|
|
506
|
+
* removed-but-still-animating entity stays pinned in the Set (a leak) and its
|
|
507
|
+
* drivers keep ticking every frame even though it is off-tree. If it is later
|
|
508
|
+
* re-added, {@link registerActiveDriverSubtree} re-registers any node that
|
|
509
|
+
* still has live drivers, so the motion resumes.
|
|
510
|
+
*/
|
|
511
|
+
private unregisterActiveDriverSubtree;
|
|
512
|
+
/**
|
|
513
|
+
* Re-register every node in `entity`'s subtree that still has live property
|
|
514
|
+
* drivers. Called by {@link add}/{@link showOverlay} so re-attaching a subtree
|
|
515
|
+
* that was removed mid-animation resumes its batched drivers (they were
|
|
516
|
+
* dropped from the candidate set on removal, but the driver state still lives
|
|
517
|
+
* on each entity).
|
|
518
|
+
*/
|
|
519
|
+
private registerActiveDriverSubtree;
|
|
520
|
+
/**
|
|
521
|
+
* Advance every registered entity's active drivers for this frame, batching
|
|
522
|
+
* whichever are batchable (`SpringDriver`; `TweenDriver` with a named
|
|
523
|
+
* easing) through one WASM call each when the driver-count gate is open, and
|
|
524
|
+
* ticking the rest (a `TweenDriver` using a custom `EasingFn`) directly in
|
|
525
|
+
* JS regardless of the gate. A "claimed" entity must have ALL its drivers
|
|
526
|
+
* advanced here so it can be safely stamped `_driversTickedFrame` — leaving
|
|
527
|
+
* one unclaimed would silently stall it, since `tickDrivers()` skips the
|
|
528
|
+
* whole entity once stamped.
|
|
529
|
+
*
|
|
530
|
+
* Must run before ANY entity's `update()`/`tickDrivers()` this frame (see
|
|
531
|
+
* the call site in {@link render}) — the same ordering constraint G1 Stage 4
|
|
532
|
+
* discovered: a value this pass writes must be final before anything reads
|
|
533
|
+
* it, including the JS-mode interleaved walk and the WASM-mode transform
|
|
534
|
+
* pre-pass.
|
|
535
|
+
*/
|
|
536
|
+
private _tickBatchedDrivers;
|
|
537
|
+
/**
|
|
538
|
+
* Compose the whole main tree's world matrices through the resident WASM store
|
|
539
|
+
* and return the world-matrix views for the render walk to read. Rebuilds the
|
|
540
|
+
* store layout (slots + runs) only when the tree structure changed since the
|
|
541
|
+
* last rebuild; otherwise it just gathers current transforms into the resident
|
|
542
|
+
* input view and runs the kernel. Returns `null` if there is no backend.
|
|
543
|
+
*/
|
|
544
|
+
private _syncWasmStore;
|
|
255
545
|
/**
|
|
256
546
|
* Authoritative paint order for semantic nodes discovered during the main
|
|
257
547
|
* render. A node may not have a DOM projection until the following a11y
|
|
@@ -261,6 +551,8 @@ export declare class Scene {
|
|
|
261
551
|
private a11yRenderOrders;
|
|
262
552
|
private pointRenderer;
|
|
263
553
|
private glCanvas;
|
|
554
|
+
private glContextLostHandler;
|
|
555
|
+
private glContextRestoredHandler;
|
|
264
556
|
private debugA11y;
|
|
265
557
|
width: number;
|
|
266
558
|
height: number;
|
|
@@ -285,6 +577,9 @@ export declare class Scene {
|
|
|
285
577
|
private mouseY;
|
|
286
578
|
private pointerMoveListener;
|
|
287
579
|
private pointerLeaveListener;
|
|
580
|
+
/** Element the pointer listeners are bound to (parent container if present,
|
|
581
|
+
* else the canvas). Stored so `destroy()` detaches from the same element. */
|
|
582
|
+
private pointerEventTarget;
|
|
288
583
|
private hasWarnedZeroSize;
|
|
289
584
|
private fontLoadHandler;
|
|
290
585
|
/** Toggle development-mode runtime warnings globally. */
|
|
@@ -296,8 +591,46 @@ export declare class Scene {
|
|
|
296
591
|
/** @internal Periodic dev checks — called once per frame in dev mode. */
|
|
297
592
|
private _devRunChecks;
|
|
298
593
|
constructor(canvas: HTMLCanvasElement, options?: SceneOptions);
|
|
594
|
+
/**
|
|
595
|
+
* Arm a `(resolution: Ndppx)` media query for the current devicePixelRatio and
|
|
596
|
+
* re-apply the canvas scale when it changes. Such a query only fires when the
|
|
597
|
+
* DPR leaves its exact value, so on each change the old query is detached and
|
|
598
|
+
* a fresh one is armed for the new DPR. Re-runs `resize(width, height)` (which
|
|
599
|
+
* re-scales the backing store via the renderer) so text/vectors stay crisp
|
|
600
|
+
* after a monitor move or zoom. No-op without `matchMedia`.
|
|
601
|
+
*/
|
|
602
|
+
private watchDevicePixelRatio;
|
|
603
|
+
/**
|
|
604
|
+
* Recover the WebGL point layer from a GPU context loss (driver TDR reset,
|
|
605
|
+
* tab backgrounded on mobile, GPU switch). Two things are required:
|
|
606
|
+
*
|
|
607
|
+
* 1. The `webglcontextlost` handler MUST call `preventDefault()`, or the
|
|
608
|
+
* browser never fires `webglcontextrestored` and the layer is blank
|
|
609
|
+
* forever. While lost, the old renderer's GL calls are silently ignored,
|
|
610
|
+
* so we drop it and the render loop simply skips the point layer.
|
|
611
|
+
* 2. On `webglcontextrestored`, all GL objects (programs, buffers, textures)
|
|
612
|
+
* are gone, so we rebuild the renderer from scratch via `Scene.webglCreator`
|
|
613
|
+
* on the same canvas, restore DPR/size, and repaint.
|
|
614
|
+
*/
|
|
615
|
+
private setupGLContextRecovery;
|
|
299
616
|
private endContentSelectionDrag;
|
|
300
617
|
private releaseContentSelectionForRebuild;
|
|
618
|
+
/**
|
|
619
|
+
* Rebuild a content-projection element's DOM (`rebuild`) while preserving a
|
|
620
|
+
* text selection the user made inside it. A streaming message replaces its
|
|
621
|
+
* projection children on every appended chunk; without this, a selection in
|
|
622
|
+
* the UNCHANGED prefix is wiped on each frame ("can't select text in a
|
|
623
|
+
* message still receiving tokens"). We snapshot the selection's anchor/focus
|
|
624
|
+
* as linear character offsets within `el` before the rebuild and re-resolve
|
|
625
|
+
* them against the new DOM after, clamped to the new text length.
|
|
626
|
+
*
|
|
627
|
+
* Only fires when `el` owns the current selection and there is no active drag
|
|
628
|
+
* (mid-drag the browser is authoritative). The virtualization case — where
|
|
629
|
+
* `el` itself is removed from the DOM — is out of scope here (the node is
|
|
630
|
+
* genuinely freed; the browser clears the selection and there is nothing to
|
|
631
|
+
* restore against).
|
|
632
|
+
*/
|
|
633
|
+
private preserveContentSelectionAcrossRebuild;
|
|
301
634
|
/**
|
|
302
635
|
* Expose the underlying {@link IRenderer} for advanced direct-draw operations.
|
|
303
636
|
*
|
|
@@ -319,6 +652,14 @@ export declare class Scene {
|
|
|
319
652
|
add(entity: Entity): this;
|
|
320
653
|
private clearContentGridState;
|
|
321
654
|
private removeA11yRecursively;
|
|
655
|
+
/**
|
|
656
|
+
* If `el` is about to be removed from the DOM while it holds browser focus,
|
|
657
|
+
* move focus to the a11y focus sentinel first. Removing the active element
|
|
658
|
+
* otherwise drops focus to `<body>`, which pulls a screen reader out of the
|
|
659
|
+
* scene's a11y region and back to the top of the page — the classic
|
|
660
|
+
* "lost my place on scroll/stream" bug for virtualized/recycled controls.
|
|
661
|
+
*/
|
|
662
|
+
private preserveFocusOnRemoval;
|
|
322
663
|
/**
|
|
323
664
|
* Remove a top-level entity from the scene graph and clean up its
|
|
324
665
|
* accessibility shadow elements recursively.
|
|
@@ -359,6 +700,14 @@ export declare class Scene {
|
|
|
359
700
|
start(): void;
|
|
360
701
|
/** Schedule the next frame, or no-op where `requestAnimationFrame` is absent (SSR). */
|
|
361
702
|
private scheduleFrame;
|
|
703
|
+
/**
|
|
704
|
+
* Observe whether the canvas is on-screen so the rAF loop can pause when it
|
|
705
|
+
* scrolls fully out of view (a dashboard tab, a chart below the fold) and
|
|
706
|
+
* resume when it returns — instead of running the full update/render every
|
|
707
|
+
* frame for a scene nobody can see. No-op (stays "on screen") where
|
|
708
|
+
* `IntersectionObserver` is unavailable, so SSR/jsdom behavior is unchanged.
|
|
709
|
+
*/
|
|
710
|
+
private watchCanvasVisibility;
|
|
362
711
|
/**
|
|
363
712
|
* Halt the render loop after the current frame completes.
|
|
364
713
|
*
|
|
@@ -434,6 +783,16 @@ export declare class Scene {
|
|
|
434
783
|
private getContentMetricScaleX;
|
|
435
784
|
private scheduleContentGridCalibration;
|
|
436
785
|
private enforceA11yDomOrder;
|
|
786
|
+
/**
|
|
787
|
+
* Reorder `normalElements` (in place) into visual reading order using the
|
|
788
|
+
* world positions `syncA11y` already wrote to each element's inline style
|
|
789
|
+
* (`top`/`left`/`height`). Elements are grouped into rows top-to-bottom (an
|
|
790
|
+
* element belongs to the current row while its top is above the row's
|
|
791
|
+
* running bottom edge), then sorted within a row by `left` — ascending for
|
|
792
|
+
* `'ltr'`, descending for `'rtl'`. The sort is stable, so entities at the
|
|
793
|
+
* same position keep their scene-graph (collection) order as a tiebreak.
|
|
794
|
+
*/
|
|
795
|
+
private sortNormalElementsVisually;
|
|
437
796
|
/** Keep DOM/WebGL overlay layers aligned with the canvas's CSS box. */
|
|
438
797
|
private syncOverlayGeometry;
|
|
439
798
|
getA11yTree(): A11yTreeNode[];
|
|
@@ -462,6 +821,13 @@ export declare class Scene {
|
|
|
462
821
|
* Manually resize the Scene's viewport.
|
|
463
822
|
*/
|
|
464
823
|
resize(width: number, height: number): void;
|
|
824
|
+
/** Effective device pixel ratio, matching CanvasRenderer: real DPR clamped to
|
|
825
|
+
* `maxDPR` when set. */
|
|
826
|
+
private effectiveDPR;
|
|
827
|
+
/** Size the WebGPU particle canvas: backing store at logical × DPR, CSS box at
|
|
828
|
+
* the logical size. Sizing the backing store in logical px (the old
|
|
829
|
+
* behavior) left it rasterized at 1× and CSS-stretched — blurry on HiDPI. */
|
|
830
|
+
private sizeGpuCanvas;
|
|
465
831
|
/**
|
|
466
832
|
* Gets the accessibility DOM element projected for the given entity ID.
|
|
467
833
|
*/
|
|
@@ -483,4 +849,18 @@ export declare class Scene {
|
|
|
483
849
|
private recreateWebGPUDeviceWithRetry;
|
|
484
850
|
private renderCPUParticles;
|
|
485
851
|
private findHitRecursively;
|
|
852
|
+
/** Whether `node` opts out of being a pointer hit target: a disabled control
|
|
853
|
+
* or an explicit `pointerEvents: 'none'` in its a11y attributes. Its children
|
|
854
|
+
* are still walked (a transparent container can hold hittable descendants). */
|
|
855
|
+
private isPointerTransparent;
|
|
856
|
+
/**
|
|
857
|
+
* Whether a confirmed geometric hit on `node` at world `(x, y)` is a REAL hit,
|
|
858
|
+
* applying the same visibility/input gating as {@link findHitRecursively} but
|
|
859
|
+
* from a flat candidate (the WASM grid has no recursion clip-stack): the node
|
|
860
|
+
* and all ancestors are visible (`opacity > 0`), the point lies inside every
|
|
861
|
+
* `clipChildren` ancestor's world box, and the node isn't pointer-transparent
|
|
862
|
+
* (disabled / `pointerEvents: 'none'`). Keeps the WASM and JS hit paths in
|
|
863
|
+
* lockstep so they return the same entity.
|
|
864
|
+
*/
|
|
865
|
+
private isHitEligible;
|
|
486
866
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WASM batched-animation backend: advances every currently-active `SpringDriver`/
|
|
3
|
+
* `TweenDriver` in one call each (`spring_step`/`tween_step`), instead of the JS
|
|
4
|
+
* per-driver `driver.tick()` loop. This is an invisible accelerator — the JS tick
|
|
5
|
+
* loop ({@link Entity.tickDrivers}) is the permanent fallback, so a caller that
|
|
6
|
+
* cannot instantiate WASM, or whose active-driver count never crosses the gate
|
|
7
|
+
* (see `Scene._tickBatchedDrivers`), simply keeps using it.
|
|
8
|
+
*
|
|
9
|
+
* The kernel (`crates/vectojs-core-rs/src/anim.rs`) is bit-identical to
|
|
10
|
+
* `SpringPhysics.update` for springs; tweens match to ~1e-9 (not bit-exact — a
|
|
11
|
+
* `Math.pow`-vs-`powi` ULP difference recorded as a spike finding), which is why
|
|
12
|
+
* a WASM-batched tween needs a `retarget`-style resync, not raw byte reuse.
|
|
13
|
+
*
|
|
14
|
+
* Unlike the transform/hit-test stores, this backend holds no cross-frame
|
|
15
|
+
* residency: every qualifying frame re-gathers ALL currently-active batchable
|
|
16
|
+
* drivers into a fresh dense pack (see {@link ensure} + the spring/tween input
|
|
17
|
+
* views), runs the kernel once, and scatters results straight back out. This
|
|
18
|
+
* keeps the design robust to drivers joining/leaving between frames and to the
|
|
19
|
+
* gate itself flipping the JS/WASM path frame-to-frame — there is no persistent
|
|
20
|
+
* wasm-side state to invalidate.
|
|
21
|
+
*/
|
|
22
|
+
export interface SpringView {
|
|
23
|
+
val: Float64Array;
|
|
24
|
+
target: Float64Array;
|
|
25
|
+
vel: Float64Array;
|
|
26
|
+
stiff: Float64Array;
|
|
27
|
+
damp: Float64Array;
|
|
28
|
+
mass: Float64Array;
|
|
29
|
+
}
|
|
30
|
+
export interface TweenView {
|
|
31
|
+
from: Float64Array;
|
|
32
|
+
to: Float64Array;
|
|
33
|
+
elapsed: Float64Array;
|
|
34
|
+
dur: Float64Array;
|
|
35
|
+
delay: Float64Array;
|
|
36
|
+
ease: Float64Array;
|
|
37
|
+
val: Float64Array;
|
|
38
|
+
}
|
|
39
|
+
export declare class AnimBackend {
|
|
40
|
+
private readonly ex;
|
|
41
|
+
private springCap;
|
|
42
|
+
private tweenCap;
|
|
43
|
+
private sv;
|
|
44
|
+
private tv;
|
|
45
|
+
constructor(instance: WebAssembly.Instance);
|
|
46
|
+
/** The resident spring SoA input/output views, valid until the next capacity
|
|
47
|
+
* growth. Write gathered driver state here before calling {@link stepSprings}. */
|
|
48
|
+
springView(): SpringView;
|
|
49
|
+
/** The resident tween SoA input/output views, valid until the next capacity
|
|
50
|
+
* growth. Write gathered driver state here before calling {@link stepTweens}. */
|
|
51
|
+
tweenView(): TweenView;
|
|
52
|
+
/**
|
|
53
|
+
* Size (and grow, if needed) capacity for `springCount` springs and
|
|
54
|
+
* `tweenCount` tweens. Call this BEFORE writing into {@link springView}/
|
|
55
|
+
* {@link tweenView} — a capacity growth detaches the previous views, so
|
|
56
|
+
* writing first and sizing after would write into a stale buffer.
|
|
57
|
+
*/
|
|
58
|
+
ensure(springCount: number, tweenCount: number): void;
|
|
59
|
+
/** Advance `count` springs (from index 0) by `dtMs` milliseconds, in place. */
|
|
60
|
+
stepSprings(dtMs: number, count: number): void;
|
|
61
|
+
/** Advance `count` tweens (from index 0) by `dtMs` milliseconds, writing `val`. */
|
|
62
|
+
stepTweens(dtMs: number, count: number): void;
|
|
63
|
+
private refreshViews;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Instantiate synchronously (Node/tests, or a worker). Rejected on the browser
|
|
67
|
+
* main thread for modules >4 KB — use {@link instantiateAsync} there. Returns
|
|
68
|
+
* `null` if compilation/instantiation throws, so callers fall back to JS.
|
|
69
|
+
*/
|
|
70
|
+
export declare function instantiateSync(bytes: BufferSource): AnimBackend | null;
|
|
71
|
+
/**
|
|
72
|
+
* Instantiate asynchronously (browser main thread). Returns `null` on any
|
|
73
|
+
* failure — CSP `wasm-unsafe-eval`, unsupported, corrupt/missing bytes — so the
|
|
74
|
+
* caller keeps using the JS path.
|
|
75
|
+
*/
|
|
76
|
+
export declare function instantiateAsync(bytes: BufferSource): Promise<AnimBackend | null>;
|
|
77
|
+
/** Anything the anim core can be loaded from, matching the transform/hit-test
|
|
78
|
+
* cores' loading ergonomics. */
|
|
79
|
+
export type AnimModuleSource = BufferSource | string | URL | Response | Promise<Response>;
|
|
80
|
+
/**
|
|
81
|
+
* Instantiate from a URL/Response using streaming compilation when the
|
|
82
|
+
* platform supports it, falling back to fetch → arrayBuffer → instantiate when
|
|
83
|
+
* unavailable or the response's MIME type is rejected. Returns `null` on any
|
|
84
|
+
* failure so the caller keeps the JS path.
|
|
85
|
+
*/
|
|
86
|
+
export declare function instantiateStreaming(source: string | URL | Response | Promise<Response>): Promise<AnimBackend | null>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolved URL of the prebuilt WebAssembly core, co-located with this module in
|
|
3
|
+
* the published package (`dist/wasm/vectojs_core.wasm`). Import it and hand it
|
|
4
|
+
* straight to any of the Scene WASM opt-ins:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { coreWasmUrl } from '@vectojs/core/wasm';
|
|
8
|
+
* await scene.enableWasmTransforms(coreWasmUrl);
|
|
9
|
+
* await scene.enableWasmAnimBatching(coreWasmUrl);
|
|
10
|
+
* await scene.enableWasmHitTest(coreWasmUrl);
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Why a helper module rather than a bare specifier: `new URL('@vectojs/core/…',
|
|
14
|
+
* import.meta.url)` does NOT work — `new URL` only resolves *relative* refs
|
|
15
|
+
* against a base, and a bare package specifier is not relative, so it never
|
|
16
|
+
* goes through package `exports`. Resolving `./vectojs_core.wasm` from *inside*
|
|
17
|
+
* the package (where this file sits next to the binary) is the only form that
|
|
18
|
+
* both native ESM and bundlers resolve correctly.
|
|
19
|
+
*
|
|
20
|
+
* The WASM is an optional accelerator: if a bundler tree-shakes this URL away or
|
|
21
|
+
* a runtime can't fetch it, every `enableWasm*` call simply returns `false` and
|
|
22
|
+
* the scene stays on the identical-output JS path. Nothing here is required for
|
|
23
|
+
* `@vectojs/core` to work.
|
|
24
|
+
*/
|
|
25
|
+
export declare const coreWasmUrl: URL;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});const __vecto_cjs_url=require("url").pathToFileURL(__filename).href;
|
|
2
|
+
|
|
3
|
+
// src/wasm/asset.ts
|
|
4
|
+
var coreWasmUrl = new URL("./vectojs_core.wasm", __vecto_cjs_url);
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
exports.coreWasmUrl = coreWasmUrl;
|