@vectojs/core 1.30.0 → 1.31.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-2WXZCPYQ.mjs → chunk-RWN3ITBD.mjs} +34 -0
- package/dist/{chunk-7PI7LVWW.js → chunk-TRT2TRCW.js} +55 -21
- package/dist/components/TextEntity.d.ts +3 -0
- package/dist/index.js +252 -192
- package/dist/index.mjs +71 -11
- package/dist/text/MSDFTextEntity.d.ts +3 -0
- package/dist/text.js +2 -2
- package/dist/text.mjs +1 -1
- package/dist/tree/Entity.d.ts +26 -0
- package/dist/tree/Scene.d.ts +39 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
SVGEntity,
|
|
19
19
|
VectoJSEvent,
|
|
20
20
|
contentLineInHint
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-RWN3ITBD.mjs";
|
|
22
22
|
|
|
23
23
|
// src/tree/Scene.ts
|
|
24
24
|
import { SpringDriver, TweenDriver } from "@vectojs/animation";
|
|
@@ -1379,6 +1379,7 @@ var SCENE_OPTION_KEYS = [
|
|
|
1379
1379
|
"autoThrottle",
|
|
1380
1380
|
"contentProjection",
|
|
1381
1381
|
"contentProjectionMargin",
|
|
1382
|
+
"contentSemanticMargin",
|
|
1382
1383
|
"debugA11y",
|
|
1383
1384
|
"disableWindowResize",
|
|
1384
1385
|
"maxDPR",
|
|
@@ -1956,6 +1957,15 @@ var Scene = class _Scene {
|
|
|
1956
1957
|
a11yElements = /* @__PURE__ */ new Map();
|
|
1957
1958
|
/** DOM nodes mirroring static text content, keyed by entity id. */
|
|
1958
1959
|
contentElements = /* @__PURE__ */ new Map();
|
|
1960
|
+
/**
|
|
1961
|
+
* What the last completed content-projection sync was built from, per entity.
|
|
1962
|
+
*
|
|
1963
|
+
* Compared at the top of {@link syncContentProjection} to skip a block whose
|
|
1964
|
+
* content AND geometry are both unchanged, before the O(glyphs) projection
|
|
1965
|
+
* build. Only populated for entities that opt in via
|
|
1966
|
+
* {@link Entity.getContentEpoch}. (carryctx CTX-0199)
|
|
1967
|
+
*/
|
|
1968
|
+
contentSyncState = /* @__PURE__ */ new Map();
|
|
1959
1969
|
/** Pending cold font-calibration frame per projected grid entity. */
|
|
1960
1970
|
contentGridCalibrationFrames = /* @__PURE__ */ new Map();
|
|
1961
1971
|
/** Detached, untransformed font probes used by the cold calibration pass. */
|
|
@@ -1989,9 +1999,14 @@ var Scene = class _Scene {
|
|
|
1989
1999
|
contentMetricScaleEpoch = -1;
|
|
1990
2000
|
contentMetricScaleX = 1;
|
|
1991
2001
|
contentProjectionEnabled = true;
|
|
1992
|
-
// Virtualization margin (px) for content
|
|
1993
|
-
// viewport height, resolved at sync time. `Infinity`
|
|
2002
|
+
// Virtualization margin (px) for the content-projection CARRIER band;
|
|
2003
|
+
// `undefined` → one viewport height, resolved at sync time. `Infinity` is
|
|
2004
|
+
// unsupported here: it unwindows every carrier, which is O(total glyphs).
|
|
1994
2005
|
contentProjectionMargin = void 0;
|
|
2006
|
+
// Virtualization margin (px) for the SEMANTIC tier — whether a block has
|
|
2007
|
+
// any projected DOM. `undefined` → falls back to contentProjectionMargin, so
|
|
2008
|
+
// the default keeps one gate. `Infinity` = every block keeps resident text.
|
|
2009
|
+
contentSemanticMargin = void 0;
|
|
1995
2010
|
/**
|
|
1996
2011
|
* True while a text-selection drag that started on a projection's blank
|
|
1997
2012
|
* region (no text node under the press) is being driven manually — the
|
|
@@ -3027,6 +3042,7 @@ var Scene = class _Scene {
|
|
|
3027
3042
|
this.a11ySyncInterval = options.a11ySyncInterval ?? 0;
|
|
3028
3043
|
this.contentProjectionEnabled = options.contentProjection ?? true;
|
|
3029
3044
|
this.contentProjectionMargin = options.contentProjectionMargin;
|
|
3045
|
+
this.contentSemanticMargin = options.contentSemanticMargin;
|
|
3030
3046
|
this.readingDirection = options.readingDirection ?? "ltr";
|
|
3031
3047
|
this.renderMode = options.renderMode ?? "always";
|
|
3032
3048
|
this._devActive = _Scene._devModeDetected();
|
|
@@ -3449,6 +3465,7 @@ var Scene = class _Scene {
|
|
|
3449
3465
|
this.clearContentGridState(node.id, contentEl);
|
|
3450
3466
|
contentEl.remove();
|
|
3451
3467
|
this.contentElements.delete(node.id);
|
|
3468
|
+
this.contentSyncState.delete(node.id);
|
|
3452
3469
|
this.a11yNeedsReorder = true;
|
|
3453
3470
|
}
|
|
3454
3471
|
const el = this.a11yElements.get(node.id);
|
|
@@ -3593,6 +3610,7 @@ var Scene = class _Scene {
|
|
|
3593
3610
|
this.a11yElements.clear();
|
|
3594
3611
|
for (const el of this.contentElements.values()) el.remove();
|
|
3595
3612
|
this.contentElements.clear();
|
|
3613
|
+
this.contentSyncState.clear();
|
|
3596
3614
|
if (typeof cancelAnimationFrame === "function") {
|
|
3597
3615
|
for (const frame of this.contentGridCalibrationFrames.values()) {
|
|
3598
3616
|
cancelAnimationFrame(frame);
|
|
@@ -4555,14 +4573,27 @@ var Scene = class _Scene {
|
|
|
4555
4573
|
this.contentElements.delete(node.id);
|
|
4556
4574
|
this.a11yNeedsReorder = true;
|
|
4557
4575
|
}
|
|
4576
|
+
this.contentSyncState.delete(node.id);
|
|
4558
4577
|
};
|
|
4559
4578
|
const worldTf = node.getWorldTransform();
|
|
4560
|
-
const
|
|
4561
|
-
|
|
4579
|
+
const interactionMargin = this.contentProjectionMargin ?? this.height;
|
|
4580
|
+
const semanticMargin = this.contentSemanticMargin ?? interactionMargin;
|
|
4581
|
+
if (Number.isFinite(semanticMargin) && !this.projectionBoxVisible(node, worldTf, semanticMargin)) {
|
|
4562
4582
|
releaseProjectionEl();
|
|
4563
4583
|
return;
|
|
4564
4584
|
}
|
|
4565
|
-
const
|
|
4585
|
+
const inInteractionBand = !Number.isFinite(interactionMargin) || this.projectionBoxVisible(node, worldTf, interactionMargin);
|
|
4586
|
+
const tier = inInteractionBand ? "fine" : "coarse";
|
|
4587
|
+
const lineBand = tier === "coarse" ? null : Number.isFinite(interactionMargin) ? this.projectionVisibleLocalYBand(node, worldTf, interactionMargin) : null;
|
|
4588
|
+
const visible = this.projectionBoxVisible(node, worldTf, 0);
|
|
4589
|
+
const epoch = node.getContentEpoch();
|
|
4590
|
+
const prior = this.contentSyncState.get(node.id);
|
|
4591
|
+
if (epoch !== null && el && prior !== void 0) {
|
|
4592
|
+
const { a: a2, b: b2, c: c2, d: d2, e: e2, f: f2 } = worldTf;
|
|
4593
|
+
if (prior.epoch === epoch && prior.fontEpoch === this.contentFontEpoch && prior.a === a2 && prior.b === b2 && prior.c === c2 && prior.d === d2 && prior.e === e2 && prior.f === f2 && prior.tier === tier && prior.hasBand === (lineBand !== null) && (lineBand === null || prior.bandMin === lineBand.minY && prior.bandMax === lineBand.maxY) && prior.visible === visible && prior.width === node.width && prior.height === node.height && prior.interactive === node.interactive) {
|
|
4594
|
+
return;
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4566
4597
|
const projection = node.getContentProjection(
|
|
4567
4598
|
lineBand ? { minY: lineBand.minY, maxY: lineBand.maxY } : void 0
|
|
4568
4599
|
);
|
|
@@ -4595,14 +4626,15 @@ var Scene = class _Scene {
|
|
|
4595
4626
|
this.a11yNeedsReorder = true;
|
|
4596
4627
|
}
|
|
4597
4628
|
const lines = projection.lines;
|
|
4598
|
-
|
|
4629
|
+
const useCarriers = tier === "fine";
|
|
4630
|
+
if ((!projection.grid || !useCarriers) && el.dataset.vectoContentGrid !== void 0) {
|
|
4599
4631
|
this.clearContentGridState(node.id, el);
|
|
4600
4632
|
}
|
|
4601
|
-
if (projection.grid) {
|
|
4633
|
+
if (projection.grid && useCarriers) {
|
|
4602
4634
|
const gridSyncStart = this._phaseTiming ? performance.now() : 0;
|
|
4603
4635
|
this.syncContentGridProjection(node, el, projection, projection.grid, lineBand);
|
|
4604
4636
|
if (this._phaseTiming) this._recordPhase("gridSync", performance.now() - gridSyncStart);
|
|
4605
|
-
} else if (lines && lines.length > 0) {
|
|
4637
|
+
} else if (useCarriers && lines && lines.length > 0) {
|
|
4606
4638
|
const lineWindow = projectionLineWindow(lines, lineBand, projection.lineHeight ?? 16);
|
|
4607
4639
|
const signature = JSON.stringify({
|
|
4608
4640
|
lines,
|
|
@@ -4665,11 +4697,13 @@ var Scene = class _Scene {
|
|
|
4665
4697
|
}
|
|
4666
4698
|
}
|
|
4667
4699
|
} else {
|
|
4668
|
-
|
|
4700
|
+
const demoted = tier === "coarse" && el.children.length > 0;
|
|
4701
|
+
if (el.textContent !== projection.text || demoted) {
|
|
4669
4702
|
this.releaseContentSelectionForRebuild(el);
|
|
4670
4703
|
el.textContent = projection.text;
|
|
4671
4704
|
}
|
|
4672
4705
|
delete el.dataset.vectoProjectionLines;
|
|
4706
|
+
if (tier === "coarse") delete el.dataset.vectoProjectionWindow;
|
|
4673
4707
|
}
|
|
4674
4708
|
const font = projection.font ?? "";
|
|
4675
4709
|
if (el.style.font !== font) el.style.font = font;
|
|
@@ -4702,9 +4736,29 @@ var Scene = class _Scene {
|
|
|
4702
4736
|
if (node.width > 0) el.style.width = `${node.width}px`;
|
|
4703
4737
|
if (node.height > 0) el.style.height = `${node.height}px`;
|
|
4704
4738
|
el.style.transform = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0)`;
|
|
4705
|
-
const visible = this.projectionBoxVisible(node, worldTf, 0);
|
|
4706
4739
|
const display = visible ? "" : "none";
|
|
4707
4740
|
if (el.style.display !== display) el.style.display = display;
|
|
4741
|
+
if (epoch !== null) {
|
|
4742
|
+
const { a: a2, b: b2, c: c2, d: d2, e: e2, f: f2 } = worldTf;
|
|
4743
|
+
const next = prior ?? {};
|
|
4744
|
+
next.epoch = epoch;
|
|
4745
|
+
next.fontEpoch = this.contentFontEpoch;
|
|
4746
|
+
next.a = a2;
|
|
4747
|
+
next.b = b2;
|
|
4748
|
+
next.c = c2;
|
|
4749
|
+
next.d = d2;
|
|
4750
|
+
next.e = e2;
|
|
4751
|
+
next.f = f2;
|
|
4752
|
+
next.tier = tier;
|
|
4753
|
+
next.hasBand = lineBand !== null;
|
|
4754
|
+
next.bandMin = lineBand?.minY ?? 0;
|
|
4755
|
+
next.bandMax = lineBand?.maxY ?? 0;
|
|
4756
|
+
next.visible = visible;
|
|
4757
|
+
next.width = node.width;
|
|
4758
|
+
next.height = node.height;
|
|
4759
|
+
next.interactive = node.interactive;
|
|
4760
|
+
if (prior === void 0) this.contentSyncState.set(node.id, next);
|
|
4761
|
+
}
|
|
4708
4762
|
}
|
|
4709
4763
|
/**
|
|
4710
4764
|
* Materialize a prepared grid in logical source order while positioning each
|
|
@@ -6107,6 +6161,8 @@ var TextEntity = class extends Entity {
|
|
|
6107
6161
|
hoveredFillStyle = "#ffffff";
|
|
6108
6162
|
lineWidth = 1;
|
|
6109
6163
|
isHovered = false;
|
|
6164
|
+
/** Bumped by {@link applyLayout}; read by `Scene` to skip an unchanged sync. */
|
|
6165
|
+
contentEpoch = 0;
|
|
6110
6166
|
constructor(text, atlas, maxWidth, fontSize = 24) {
|
|
6111
6167
|
super();
|
|
6112
6168
|
this.text = text;
|
|
@@ -6127,6 +6183,9 @@ var TextEntity = class extends Entity {
|
|
|
6127
6183
|
if (!this.text) return null;
|
|
6128
6184
|
return { text: this.text, font: `${this.fontSize}px sans-serif` };
|
|
6129
6185
|
}
|
|
6186
|
+
getContentEpoch() {
|
|
6187
|
+
return this.contentEpoch;
|
|
6188
|
+
}
|
|
6130
6189
|
/**
|
|
6131
6190
|
* Replace the text content. Runs the **cold** measurement pass (re-segment +
|
|
6132
6191
|
* re-measure) since the glyphs changed, then re-lays out.
|
|
@@ -6173,6 +6232,7 @@ var TextEntity = class extends Entity {
|
|
|
6173
6232
|
}
|
|
6174
6233
|
/** Hot pass: place the cached {@link PreparedText} and refresh the a11y box. */
|
|
6175
6234
|
applyLayout() {
|
|
6235
|
+
this.contentEpoch++;
|
|
6176
6236
|
const result = this.layout.layoutPrepared(this.prepared);
|
|
6177
6237
|
this.nodes = result.nodes;
|
|
6178
6238
|
this.width = result.totalWidth;
|
|
@@ -34,6 +34,8 @@ export declare class MSDFTextEntity extends Entity {
|
|
|
34
34
|
private layoutText;
|
|
35
35
|
private text;
|
|
36
36
|
private lastRenderedSeqId;
|
|
37
|
+
/** Bumped by {@link queueLayout}; read by `Scene` to skip an unchanged sync. */
|
|
38
|
+
private contentEpoch;
|
|
37
39
|
private atlasDecodeTarget;
|
|
38
40
|
private atlasDecodeHandler;
|
|
39
41
|
private rgbColorCache;
|
|
@@ -91,6 +93,7 @@ export declare class MSDFTextEntity extends Entity {
|
|
|
91
93
|
* readers, crawlers, and translation see the same string the canvas draws.
|
|
92
94
|
*/
|
|
93
95
|
getContentProjection(): ContentProjection | null;
|
|
96
|
+
getContentEpoch(): number;
|
|
94
97
|
isPointInside(globalX: number, globalY: number): boolean;
|
|
95
98
|
render(renderer: any): void;
|
|
96
99
|
destroy(): void;
|
package/dist/text.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var _chunkTRT2TRCWjs = require('./chunk-TRT2TRCW.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 = _chunkTRT2TRCWjs.MSDFTextEntity; exports.SVGEntity = _chunkTRT2TRCWjs.SVGEntity;
|
package/dist/text.mjs
CHANGED
package/dist/tree/Entity.d.ts
CHANGED
|
@@ -1000,6 +1000,32 @@ export declare abstract class Entity {
|
|
|
1000
1000
|
* @returns The projection descriptor, or `null` to project nothing.
|
|
1001
1001
|
*/
|
|
1002
1002
|
getContentProjection(hint?: ContentProjectionHint): ContentProjection | null;
|
|
1003
|
+
/**
|
|
1004
|
+
* A cheap, monotonically-increasing stamp of this entity's projected content.
|
|
1005
|
+
*
|
|
1006
|
+
* Purely an optimization, and opt-in: returning `null` (the default) means
|
|
1007
|
+
* "I cannot cheaply tell whether my content changed", and the {@link Scene}
|
|
1008
|
+
* then rebuilds the projection every synced frame exactly as before. An
|
|
1009
|
+
* implementation must bump the value whenever anything
|
|
1010
|
+
* {@link getContentProjection} would report changes — text, fonts, line
|
|
1011
|
+
* geometry, `selectable`, grid revision.
|
|
1012
|
+
*
|
|
1013
|
+
* When two consecutive syncs report the same epoch AND the entity's geometry
|
|
1014
|
+
* is unchanged, `Scene` skips the block *before* calling
|
|
1015
|
+
* {@link getContentProjection}. That matters because the projection call is
|
|
1016
|
+
* O(glyphs-in-block) and the DOM diff around it costs about the same again:
|
|
1017
|
+
* measured on a 1500-resident-block document, a sync in which the projected
|
|
1018
|
+
* text was byte-identical before and after still cost 17.875 ms, and skipping
|
|
1019
|
+
* unchanged blocks took that to 0.475 ms (carryctx CTX-0199, vectojs#343).
|
|
1020
|
+
*
|
|
1021
|
+
* Correctness is entirely on the implementer: a stale epoch means stale DOM,
|
|
1022
|
+
* so bump it in the same place the content is invalidated rather than trying
|
|
1023
|
+
* to enumerate mutation sites afterwards. Any monotonic counter works; the
|
|
1024
|
+
* value is only ever compared for equality with the previous sync's.
|
|
1025
|
+
*
|
|
1026
|
+
* @returns The current content epoch, or `null` to disable skipping.
|
|
1027
|
+
*/
|
|
1028
|
+
getContentEpoch(): number | null;
|
|
1003
1029
|
/**
|
|
1004
1030
|
* Whether this entity still has a queued/running tween animation, or an
|
|
1005
1031
|
* active {@link setTransition}/{@link animateTo}/{@link springTo} property
|
package/dist/tree/Scene.d.ts
CHANGED
|
@@ -201,6 +201,34 @@ export interface SceneOptions {
|
|
|
201
201
|
* height (`undefined` → resolved to `Scene.height` at sync time).
|
|
202
202
|
*/
|
|
203
203
|
contentProjectionMargin?: number;
|
|
204
|
+
/**
|
|
205
|
+
* Virtualization margin (px) for the *semantic* tier of content projection —
|
|
206
|
+
* whether a block has **any** projected DOM at all, as opposed to
|
|
207
|
+
* {@link SceneOptions.contentProjectionMargin}, which decides whether that
|
|
208
|
+
* block's per-line **carriers** are windowed.
|
|
209
|
+
*
|
|
210
|
+
* Splitting the two makes a coarse resident tier expressible: with
|
|
211
|
+
* `contentSemanticMargin: Infinity` and a finite `contentProjectionMargin`,
|
|
212
|
+
* every block in the document keeps an element holding its full text — so
|
|
213
|
+
* find-in-page and screen-reader read-ahead see the whole document — while
|
|
214
|
+
* only blocks near the viewport pay for per-line carriers. One scalar could
|
|
215
|
+
* not express that, because a finite value freed off-band blocks entirely and
|
|
216
|
+
* `Infinity` also unwindowed every carrier, which is O(total document glyphs).
|
|
217
|
+
*
|
|
218
|
+
* `Infinity` is safe **here** and remains unsupported for
|
|
219
|
+
* `contentProjectionMargin`: the cost that made it unsupported comes from an
|
|
220
|
+
* unwindowed carrier band, not from resident text.
|
|
221
|
+
*
|
|
222
|
+
* Note the one-time cost. A resident tier materializes one element per block
|
|
223
|
+
* on the first sync — measured ~13µs per node created, so ~20ms at 1000 blocks
|
|
224
|
+
* and ~146ms at 10000 — as one synchronous block. Steady state is cheap
|
|
225
|
+
* (unchanged blocks skip via {@link Entity.getContentEpoch}), so this is a
|
|
226
|
+
* document-open stall, not a per-frame cost.
|
|
227
|
+
*
|
|
228
|
+
* Default: whatever `contentProjectionMargin` resolves to, so omitting this
|
|
229
|
+
* leaves behaviour unchanged.
|
|
230
|
+
*/
|
|
231
|
+
contentSemanticMargin?: number;
|
|
204
232
|
/**
|
|
205
233
|
* Reading direction used to order the accessibility/automation shadow tree so
|
|
206
234
|
* keyboard **tab order** and screen-reader traversal follow the *visual*
|
|
@@ -240,7 +268,7 @@ export interface SceneOptions {
|
|
|
240
268
|
* against. A new option must be added here too — the test suite asserts the two
|
|
241
269
|
* stay in sync.
|
|
242
270
|
*/
|
|
243
|
-
export declare const SCENE_OPTION_KEYS: readonly ['a11ySyncInterval', 'autoThrottle', 'contentProjection', 'contentProjectionMargin', 'debugA11y', 'disableWindowResize', 'maxDPR', 'maxFPS', 'particleBackend', 'pointBackend', 'readingDirection', 'renderer', 'renderMode', 'respectReducedMotion', 'userTiming'];
|
|
271
|
+
export declare const SCENE_OPTION_KEYS: readonly ['a11ySyncInterval', 'autoThrottle', 'contentProjection', 'contentProjectionMargin', 'contentSemanticMargin', 'debugA11y', 'disableWindowResize', 'maxDPR', 'maxFPS', 'particleBackend', 'pointBackend', 'readingDirection', 'renderer', 'renderMode', 'respectReducedMotion', 'userTiming'];
|
|
244
272
|
/** Frame-rate the loop is capped to when the OS requests reduced motion. */
|
|
245
273
|
export declare const REDUCED_MOTION_FPS = 30;
|
|
246
274
|
/**
|
|
@@ -488,6 +516,15 @@ export declare class Scene {
|
|
|
488
516
|
private a11yElements;
|
|
489
517
|
/** DOM nodes mirroring static text content, keyed by entity id. */
|
|
490
518
|
private contentElements;
|
|
519
|
+
/**
|
|
520
|
+
* What the last completed content-projection sync was built from, per entity.
|
|
521
|
+
*
|
|
522
|
+
* Compared at the top of {@link syncContentProjection} to skip a block whose
|
|
523
|
+
* content AND geometry are both unchanged, before the O(glyphs) projection
|
|
524
|
+
* build. Only populated for entities that opt in via
|
|
525
|
+
* {@link Entity.getContentEpoch}. (carryctx CTX-0199)
|
|
526
|
+
*/
|
|
527
|
+
private contentSyncState;
|
|
491
528
|
/** Pending cold font-calibration frame per projected grid entity. */
|
|
492
529
|
private contentGridCalibrationFrames;
|
|
493
530
|
/** Detached, untransformed font probes used by the cold calibration pass. */
|
|
@@ -522,6 +559,7 @@ export declare class Scene {
|
|
|
522
559
|
private contentMetricScaleX;
|
|
523
560
|
private contentProjectionEnabled;
|
|
524
561
|
private contentProjectionMargin;
|
|
562
|
+
private contentSemanticMargin;
|
|
525
563
|
/**
|
|
526
564
|
* True while a text-selection drag that started on a projection's blank
|
|
527
565
|
* region (no text node under the press) is being driven manually — the
|