@vectojs/core 1.19.0 → 1.20.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-XWBVBXFZ.mjs → chunk-TJCXB2F6.mjs} +18 -0
- package/dist/{chunk-IA3KW4CG.js → chunk-YLH7F4ZV.js} +43 -25
- package/dist/index.js +272 -170
- package/dist/index.mjs +104 -2
- package/dist/text.js +2 -2
- package/dist/text.mjs +1 -1
- package/dist/tree/Entity.d.ts +18 -0
- package/dist/tree/Scene.d.ts +66 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
MSDFTextEntity,
|
|
14
14
|
SVGEntity,
|
|
15
15
|
VectoJSEvent
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-TJCXB2F6.mjs";
|
|
17
17
|
|
|
18
18
|
// src/tree/Scene.ts
|
|
19
19
|
import { SpringDriver, TweenDriver } from "@vectojs/animation";
|
|
@@ -1538,6 +1538,70 @@ var Scene = class _Scene {
|
|
|
1538
1538
|
renderMode = "always";
|
|
1539
1539
|
/** Cap on distinct recorded dirty reasons (see `recordDirtyReason`). */
|
|
1540
1540
|
static MAX_DIRTY_REASONS = 200;
|
|
1541
|
+
_phaseTiming = false;
|
|
1542
|
+
_phaseTotals = /* @__PURE__ */ new Map();
|
|
1543
|
+
/**
|
|
1544
|
+
* Start or stop per-phase render timing.
|
|
1545
|
+
*
|
|
1546
|
+
* Off by default, and the probes compile to a single boolean test when off:
|
|
1547
|
+
* these sit on the frame path, so the disabled cost has to be nothing. Enable,
|
|
1548
|
+
* run the scene, then read {@link renderPhases}.
|
|
1549
|
+
*
|
|
1550
|
+
* Exists because a frame total cannot tell you where the time went. The
|
|
1551
|
+
* markdown streaming benchmark put render at 85-99% of an append's cost, and
|
|
1552
|
+
* there was no way to decompose that number further — which is exactly the
|
|
1553
|
+
* position that led to two wrong optimisation guesses earlier
|
|
1554
|
+
* (`CodeBlock` reuse, hit-grid fusion), both of which measured as no change.
|
|
1555
|
+
*/
|
|
1556
|
+
setPhaseTiming(enabled) {
|
|
1557
|
+
this._phaseTiming = enabled;
|
|
1558
|
+
if (!enabled) this.clearRenderPhases();
|
|
1559
|
+
}
|
|
1560
|
+
/** Whether per-phase render timing is being recorded. */
|
|
1561
|
+
get phaseTiming() {
|
|
1562
|
+
return this._phaseTiming;
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Accumulate one phase sample.
|
|
1566
|
+
*
|
|
1567
|
+
* Totals rather than a per-frame log: the question is always "which phase owns
|
|
1568
|
+
* the frame", and a log of thousands of samples answers it less directly while
|
|
1569
|
+
* costing far more memory. `maxMs` is kept because a phase that is cheap on
|
|
1570
|
+
* average but spikes is a different problem from one that is uniformly slow.
|
|
1571
|
+
*/
|
|
1572
|
+
_recordPhase(phase, ms) {
|
|
1573
|
+
const existing = this._phaseTotals.get(phase);
|
|
1574
|
+
if (existing) {
|
|
1575
|
+
existing.totalMs += ms;
|
|
1576
|
+
existing.calls++;
|
|
1577
|
+
if (ms > existing.maxMs) existing.maxMs = ms;
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
this._phaseTotals.set(phase, { totalMs: ms, calls: 1, maxMs: ms });
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Recorded phase timings, most expensive first, with each phase's share of the
|
|
1584
|
+
* measured total.
|
|
1585
|
+
*
|
|
1586
|
+
* `share` is the number that matters: a phase at 4% cannot be worth optimising
|
|
1587
|
+
* however inefficient it looks in isolation.
|
|
1588
|
+
*/
|
|
1589
|
+
get renderPhases() {
|
|
1590
|
+
const entries = [...this._phaseTotals.entries()];
|
|
1591
|
+
const denominator = entries.filter(([phase]) => phase !== "render").reduce((sum, [, v]) => sum + v.totalMs, 0);
|
|
1592
|
+
return entries.map(([phase, v]) => ({
|
|
1593
|
+
phase,
|
|
1594
|
+
totalMs: +v.totalMs.toFixed(3),
|
|
1595
|
+
calls: v.calls,
|
|
1596
|
+
avgMs: +(v.totalMs / Math.max(1, v.calls)).toFixed(4),
|
|
1597
|
+
maxMs: +v.maxMs.toFixed(3),
|
|
1598
|
+
share: phase === "render" ? null : +(100 * v.totalMs / Math.max(1e-9, denominator)).toFixed(1)
|
|
1599
|
+
})).sort((a, b) => b.totalMs - a.totalMs);
|
|
1600
|
+
}
|
|
1601
|
+
/** Drop recorded phase timings, keeping timing enabled. */
|
|
1602
|
+
clearRenderPhases() {
|
|
1603
|
+
this._phaseTotals.clear();
|
|
1604
|
+
}
|
|
1541
1605
|
_dirtyTracking = false;
|
|
1542
1606
|
_dirtyReasons = /* @__PURE__ */ new Map();
|
|
1543
1607
|
dirty = true;
|
|
@@ -2834,6 +2898,20 @@ var Scene = class _Scene {
|
|
|
2834
2898
|
delete el.dataset.vectoGridCalibrationMs;
|
|
2835
2899
|
this.releaseContentSelectionForRebuild(el);
|
|
2836
2900
|
}
|
|
2901
|
+
/**
|
|
2902
|
+
* Drop any projected elements under `node` without touching the entity tree.
|
|
2903
|
+
*
|
|
2904
|
+
* Used when the walk reaches an invisible subtree: the entities stay put (a
|
|
2905
|
+
* later `show()` re-projects them), but nothing under here may remain
|
|
2906
|
+
* focusable or announced while hidden.
|
|
2907
|
+
*/
|
|
2908
|
+
pruneA11ySubtree(node) {
|
|
2909
|
+
if (this.a11yElements.has(node.id) || this.contentElements.has(node.id)) {
|
|
2910
|
+
this.removeA11yRecursively(node);
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
for (const child of node.children) this.pruneA11ySubtree(child);
|
|
2914
|
+
}
|
|
2837
2915
|
removeA11yRecursively(node) {
|
|
2838
2916
|
if (node.isDOMPortal) {
|
|
2839
2917
|
node.releaseDOMBindings();
|
|
@@ -3151,7 +3229,9 @@ var Scene = class _Scene {
|
|
|
3151
3229
|
step(dt) {
|
|
3152
3230
|
const time = this.lastTime + dt;
|
|
3153
3231
|
this.lastTime = time;
|
|
3232
|
+
const t0 = this._phaseTiming ? performance.now() : 0;
|
|
3154
3233
|
this.render(this.renderer, dt, time);
|
|
3234
|
+
if (this._phaseTiming) this._recordPhase("render", performance.now() - t0);
|
|
3155
3235
|
this.dirty = false;
|
|
3156
3236
|
}
|
|
3157
3237
|
/**
|
|
@@ -3286,6 +3366,10 @@ var Scene = class _Scene {
|
|
|
3286
3366
|
if (node.isDOMPortal) {
|
|
3287
3367
|
return;
|
|
3288
3368
|
}
|
|
3369
|
+
if (node.a11yHidden) {
|
|
3370
|
+
this.pruneA11ySubtree(node);
|
|
3371
|
+
return;
|
|
3372
|
+
}
|
|
3289
3373
|
if (this.shouldProjectA11y(node)) {
|
|
3290
3374
|
let el = this.a11yElements.get(node.id);
|
|
3291
3375
|
const attrs = node.getA11yAttributes();
|
|
@@ -4372,7 +4456,9 @@ var Scene = class _Scene {
|
|
|
4372
4456
|
}
|
|
4373
4457
|
this._lastRenderTick = time;
|
|
4374
4458
|
this._lastDt = dt;
|
|
4459
|
+
const phaseClock = this._phaseTiming ? performance.now() : 0;
|
|
4375
4460
|
this.render(this.renderer, dt, time);
|
|
4461
|
+
if (this._phaseTiming) this._recordPhase("render", performance.now() - phaseClock);
|
|
4376
4462
|
this._lastFrameMs = (typeof performance !== "undefined" ? performance.now() : time) - now;
|
|
4377
4463
|
this._renderedFrames++;
|
|
4378
4464
|
const hasActiveAnimation = this.frameHadAnimation;
|
|
@@ -4382,9 +4468,13 @@ var Scene = class _Scene {
|
|
|
4382
4468
|
if ((hasInteractive || this.a11yElements.size > 0 || wantsContentSync) && (shouldSyncInterval || this.a11yPendingSyncAfterAnimation)) {
|
|
4383
4469
|
this.lastA11ySync = time;
|
|
4384
4470
|
if (hasInteractive || wantsContentSync) {
|
|
4471
|
+
const t0 = this._phaseTiming ? performance.now() : 0;
|
|
4385
4472
|
this.syncA11y(this.root);
|
|
4473
|
+
if (this._phaseTiming) this._recordPhase("a11ySync", performance.now() - t0);
|
|
4386
4474
|
}
|
|
4475
|
+
const t1 = this._phaseTiming ? performance.now() : 0;
|
|
4387
4476
|
this.enforceA11yDomOrder();
|
|
4477
|
+
if (this._phaseTiming) this._recordPhase("a11yOrder", performance.now() - t1);
|
|
4388
4478
|
this.a11yPendingSyncAfterAnimation = hasActiveAnimation;
|
|
4389
4479
|
} else if (hasActiveAnimation) {
|
|
4390
4480
|
this.a11yPendingSyncAfterAnimation = true;
|
|
@@ -4558,7 +4648,9 @@ var Scene = class _Scene {
|
|
|
4558
4648
|
updateWalk(this.root);
|
|
4559
4649
|
for (const overlay of this.overlayRoot.children) updateWalk(overlay);
|
|
4560
4650
|
}
|
|
4651
|
+
const wasmT0 = this._phaseTiming ? performance.now() : 0;
|
|
4561
4652
|
const wasmWorld = wasmMain ? this._syncWasmStore() : null;
|
|
4653
|
+
if (this._phaseTiming) this._recordPhase("transform", performance.now() - wasmT0);
|
|
4562
4654
|
const wasmSlotEntity = this._slotEntity;
|
|
4563
4655
|
const renderNode = (node, pa, pb, pc, pd, pe, pf, parentOpacity) => {
|
|
4564
4656
|
if (isMainRenderer && !wasmMain) {
|
|
@@ -4688,7 +4780,13 @@ var Scene = class _Scene {
|
|
|
4688
4780
|
);
|
|
4689
4781
|
}
|
|
4690
4782
|
} else {
|
|
4691
|
-
|
|
4783
|
+
if (this._phaseTiming) {
|
|
4784
|
+
const t0 = performance.now();
|
|
4785
|
+
node.render(renderer);
|
|
4786
|
+
this._recordPhase("entityPaint", performance.now() - t0);
|
|
4787
|
+
} else {
|
|
4788
|
+
node.render(renderer);
|
|
4789
|
+
}
|
|
4692
4790
|
}
|
|
4693
4791
|
}
|
|
4694
4792
|
if (node.clipChildren) {
|
|
@@ -4700,20 +4798,24 @@ var Scene = class _Scene {
|
|
|
4700
4798
|
renderer.flush();
|
|
4701
4799
|
renderer.restore();
|
|
4702
4800
|
};
|
|
4801
|
+
const drawT0 = this._phaseTiming ? performance.now() : 0;
|
|
4703
4802
|
renderNode(this.root, 1, 0, 0, 1, 0, 0, 1);
|
|
4704
4803
|
for (const overlay of this.overlayRoot.children) {
|
|
4705
4804
|
renderNode(overlay, 1, 0, 0, 1, 0, 0, 1);
|
|
4706
4805
|
}
|
|
4806
|
+
if (this._phaseTiming) this._recordPhase("drawWalk", performance.now() - drawT0);
|
|
4707
4807
|
if (isMainRenderer) {
|
|
4708
4808
|
this.frameHadAnimation = walkHadAnimation;
|
|
4709
4809
|
this.frameHadInteractive = walkHadInteractive;
|
|
4710
4810
|
this.reconcilePortals();
|
|
4711
4811
|
}
|
|
4812
|
+
const flushT0 = this._phaseTiming ? performance.now() : 0;
|
|
4712
4813
|
renderer.flush();
|
|
4713
4814
|
if (isMainRenderer) {
|
|
4714
4815
|
this.pointRenderer?.flush();
|
|
4715
4816
|
}
|
|
4716
4817
|
renderer.present?.();
|
|
4818
|
+
if (this._phaseTiming) this._recordPhase("flush", performance.now() - flushT0);
|
|
4717
4819
|
if (this._devActive) {
|
|
4718
4820
|
this._devFrameCount++;
|
|
4719
4821
|
this._devRunChecks();
|
package/dist/text.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var _chunkYLH7F4ZVjs = require('./chunk-YLH7F4ZV.js');
|
|
6
6
|
|
|
7
7
|
// src/text/index.ts
|
|
8
8
|
var _text = require('@vectojs/text'); _createStarExport(_text);
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
exports.MSDFTextEntity =
|
|
12
|
+
exports.MSDFTextEntity = _chunkYLH7F4ZVjs.MSDFTextEntity; exports.SVGEntity = _chunkYLH7F4ZVjs.SVGEntity;
|
package/dist/text.mjs
CHANGED
package/dist/tree/Entity.d.ts
CHANGED
|
@@ -416,6 +416,24 @@ export declare abstract class Entity {
|
|
|
416
416
|
* nodes, so on-top components stay clickable.
|
|
417
417
|
*/
|
|
418
418
|
a11yFullViewport: boolean;
|
|
419
|
+
/**
|
|
420
|
+
* Hide this entity AND its whole subtree from the accessibility/automation
|
|
421
|
+
* projection, regardless of each node's own `interactive` flag.
|
|
422
|
+
*
|
|
423
|
+
* For a container that is logically closed while still mounted — an `Overlay`
|
|
424
|
+
* after `hide()`, a collapsed panel kept in the tree for its transition. Setting
|
|
425
|
+
* `interactive = false` on the container alone is not enough: the projection walk
|
|
426
|
+
* still descends, and any still-interactive child is re-created on the next
|
|
427
|
+
* frame. Measured before this existed: after `Popover.hide()` the popover's own
|
|
428
|
+
* element was gone while its button stayed projected with `tabIndex: 0` and a
|
|
429
|
+
* live box, so a keyboard user could Tab into a hidden popover.
|
|
430
|
+
*
|
|
431
|
+
* Deliberately NOT inferred from `opacity`: `Overlay.hide()` springs opacity
|
|
432
|
+
* toward 0, so mid-transition it reads nonzero (~0.26 when measured) and an
|
|
433
|
+
* `=== 0` test never fires; a threshold would instead silently un-project a
|
|
434
|
+
* faint-but-live control.
|
|
435
|
+
*/
|
|
436
|
+
a11yHidden: boolean;
|
|
419
437
|
/**
|
|
420
438
|
* Clip this node's children to its local box (`[0,0]–[width,height]`) while
|
|
421
439
|
* rendering. Combined with translating a content child, this is how
|
package/dist/tree/Scene.d.ts
CHANGED
|
@@ -17,6 +17,27 @@ import { type CoreWasmRuntime } from '../wasm/runtime';
|
|
|
17
17
|
import { type HitModuleSource, type HitTestBackend } from '../wasm/hit-backend';
|
|
18
18
|
import { type AnimModuleSource, type AnimBackend } from '../wasm/anim-backend';
|
|
19
19
|
import { type ParticleModuleSource, type ParticleBackend } from '../wasm/particle-backend';
|
|
20
|
+
/**
|
|
21
|
+
* A timed phase of a frame.
|
|
22
|
+
*
|
|
23
|
+
* `render` is the ENCLOSING phase — it contains `transform`, `drawWalk` and
|
|
24
|
+
* `flush` — so it is reported without a share to avoid double-counting.
|
|
25
|
+
* `a11ySync` and `a11yOrder` run after `render` in the frame loop, so they are
|
|
26
|
+
* siblings of it, not children.
|
|
27
|
+
*/
|
|
28
|
+
export type RenderPhase = 'render' | 'transform' | 'drawWalk' | 'flush' | 'a11ySync' | 'a11yOrder'
|
|
29
|
+
/** Sum of every entity's own render(), nested inside drawWalk. */
|
|
30
|
+
| 'entityPaint';
|
|
31
|
+
export interface RenderPhaseEntry {
|
|
32
|
+
phase: RenderPhase;
|
|
33
|
+
totalMs: number;
|
|
34
|
+
calls: number;
|
|
35
|
+
avgMs: number;
|
|
36
|
+
/** Worst single sample — a spiky phase is a different problem from a slow one. */
|
|
37
|
+
maxMs: number;
|
|
38
|
+
/** Percent of the measured total, or `null` for the enclosing `render` phase. */
|
|
39
|
+
share: number | null;
|
|
40
|
+
}
|
|
20
41
|
/**
|
|
21
42
|
* Who marked the scene dirty, and why.
|
|
22
43
|
*
|
|
@@ -230,6 +251,43 @@ export declare class Scene {
|
|
|
230
251
|
renderMode: 'always' | 'onDemand';
|
|
231
252
|
/** Cap on distinct recorded dirty reasons (see `recordDirtyReason`). */
|
|
232
253
|
private static readonly MAX_DIRTY_REASONS;
|
|
254
|
+
private _phaseTiming;
|
|
255
|
+
private _phaseTotals;
|
|
256
|
+
/**
|
|
257
|
+
* Start or stop per-phase render timing.
|
|
258
|
+
*
|
|
259
|
+
* Off by default, and the probes compile to a single boolean test when off:
|
|
260
|
+
* these sit on the frame path, so the disabled cost has to be nothing. Enable,
|
|
261
|
+
* run the scene, then read {@link renderPhases}.
|
|
262
|
+
*
|
|
263
|
+
* Exists because a frame total cannot tell you where the time went. The
|
|
264
|
+
* markdown streaming benchmark put render at 85-99% of an append's cost, and
|
|
265
|
+
* there was no way to decompose that number further — which is exactly the
|
|
266
|
+
* position that led to two wrong optimisation guesses earlier
|
|
267
|
+
* (`CodeBlock` reuse, hit-grid fusion), both of which measured as no change.
|
|
268
|
+
*/
|
|
269
|
+
setPhaseTiming(enabled: boolean): void;
|
|
270
|
+
/** Whether per-phase render timing is being recorded. */
|
|
271
|
+
get phaseTiming(): boolean;
|
|
272
|
+
/**
|
|
273
|
+
* Accumulate one phase sample.
|
|
274
|
+
*
|
|
275
|
+
* Totals rather than a per-frame log: the question is always "which phase owns
|
|
276
|
+
* the frame", and a log of thousands of samples answers it less directly while
|
|
277
|
+
* costing far more memory. `maxMs` is kept because a phase that is cheap on
|
|
278
|
+
* average but spikes is a different problem from one that is uniformly slow.
|
|
279
|
+
*/
|
|
280
|
+
private _recordPhase;
|
|
281
|
+
/**
|
|
282
|
+
* Recorded phase timings, most expensive first, with each phase's share of the
|
|
283
|
+
* measured total.
|
|
284
|
+
*
|
|
285
|
+
* `share` is the number that matters: a phase at 4% cannot be worth optimising
|
|
286
|
+
* however inefficient it looks in isolation.
|
|
287
|
+
*/
|
|
288
|
+
get renderPhases(): RenderPhaseEntry[];
|
|
289
|
+
/** Drop recorded phase timings, keeping timing enabled. */
|
|
290
|
+
clearRenderPhases(): void;
|
|
233
291
|
private _dirtyTracking;
|
|
234
292
|
private _dirtyReasons;
|
|
235
293
|
private dirty;
|
|
@@ -779,6 +837,14 @@ export declare class Scene {
|
|
|
779
837
|
*/
|
|
780
838
|
add(entity: Entity): this;
|
|
781
839
|
private clearContentGridState;
|
|
840
|
+
/**
|
|
841
|
+
* Drop any projected elements under `node` without touching the entity tree.
|
|
842
|
+
*
|
|
843
|
+
* Used when the walk reaches an invisible subtree: the entities stay put (a
|
|
844
|
+
* later `show()` re-projects them), but nothing under here may remain
|
|
845
|
+
* focusable or announced while hidden.
|
|
846
|
+
*/
|
|
847
|
+
private pruneA11ySubtree;
|
|
782
848
|
private removeA11yRecursively;
|
|
783
849
|
/**
|
|
784
850
|
* If `el` is about to be removed from the DOM while it holds browser focus,
|