@vectojs/core 1.24.0 → 1.26.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-ME4LB2HB.mjs → chunk-I4WQY7CJ.mjs} +15 -0
- package/dist/{chunk-KEBYJVD6.js → chunk-KHGHP2J3.js} +15 -0
- package/dist/{chunk-FRMLD4PP.mjs → chunk-MVFPN4Y5.mjs} +114 -1
- package/dist/{chunk-AGP4VLF4.js → chunk-Z3HFY75R.js} +131 -18
- package/dist/index.js +197 -112
- package/dist/index.mjs +90 -5
- package/dist/renderer.js +2 -2
- package/dist/renderer.mjs +1 -1
- package/dist/text/MSDFTextEntity.d.ts +24 -0
- package/dist/text/SVGEntity.d.ts +24 -0
- package/dist/text.js +2 -2
- package/dist/text.mjs +1 -1
- package/dist/tree/Scene.d.ts +44 -0
- package/package.json +6 -6
package/dist/index.mjs
CHANGED
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
isSafeUrl,
|
|
9
9
|
parseColorToRGBA,
|
|
10
10
|
sanitizeUrl
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-I4WQY7CJ.mjs";
|
|
12
12
|
import {
|
|
13
13
|
Entity,
|
|
14
14
|
MSDFTextEntity,
|
|
15
15
|
SVGEntity,
|
|
16
16
|
VectoJSEvent
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-MVFPN4Y5.mjs";
|
|
18
18
|
|
|
19
19
|
// src/tree/Scene.ts
|
|
20
20
|
import { SpringDriver, TweenDriver } from "@vectojs/animation";
|
|
@@ -1370,6 +1370,57 @@ function rebaseChildBox(parent, parentOriginX, parentOriginY, child, childOrigin
|
|
|
1370
1370
|
REBASED_BOX.matrix = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0)`;
|
|
1371
1371
|
return REBASED_BOX;
|
|
1372
1372
|
}
|
|
1373
|
+
var SCENE_OPTION_KEYS = [
|
|
1374
|
+
"a11ySyncInterval",
|
|
1375
|
+
"autoThrottle",
|
|
1376
|
+
"contentProjection",
|
|
1377
|
+
"contentProjectionMargin",
|
|
1378
|
+
"debugA11y",
|
|
1379
|
+
"disableWindowResize",
|
|
1380
|
+
"maxDPR",
|
|
1381
|
+
"maxFPS",
|
|
1382
|
+
"particleBackend",
|
|
1383
|
+
"pointBackend",
|
|
1384
|
+
"readingDirection",
|
|
1385
|
+
"renderer",
|
|
1386
|
+
"renderMode",
|
|
1387
|
+
"respectReducedMotion",
|
|
1388
|
+
"userTiming"
|
|
1389
|
+
];
|
|
1390
|
+
var SCENE_FIELD_NOT_OPTION = {
|
|
1391
|
+
devMode: "set the static `Scene.devMode = true` before constructing"
|
|
1392
|
+
};
|
|
1393
|
+
function editDistance(a, b) {
|
|
1394
|
+
const m = a.length;
|
|
1395
|
+
const n = b.length;
|
|
1396
|
+
if (m === 0 || n === 0) return Math.max(m, n);
|
|
1397
|
+
let prev = Array.from({ length: n + 1 }, (_, i) => i);
|
|
1398
|
+
const curr = Array.from({ length: n + 1 });
|
|
1399
|
+
for (let i = 1; i <= m; i++) {
|
|
1400
|
+
curr[0] = i;
|
|
1401
|
+
for (let j = 1; j <= n; j++) {
|
|
1402
|
+
const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
|
|
1403
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
1404
|
+
}
|
|
1405
|
+
prev = curr.slice();
|
|
1406
|
+
}
|
|
1407
|
+
return prev[n];
|
|
1408
|
+
}
|
|
1409
|
+
function closestOptionKey(key) {
|
|
1410
|
+
const lower = key.toLowerCase();
|
|
1411
|
+
let best;
|
|
1412
|
+
let bestScore = Infinity;
|
|
1413
|
+
for (const candidate of SCENE_OPTION_KEYS) {
|
|
1414
|
+
if (candidate.toLowerCase() === lower) return candidate;
|
|
1415
|
+
const d = editDistance(lower, candidate.toLowerCase());
|
|
1416
|
+
if (d < bestScore) {
|
|
1417
|
+
bestScore = d;
|
|
1418
|
+
best = candidate;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
const limit = Math.max(2, Math.floor(key.length / 3));
|
|
1422
|
+
return bestScore <= limit ? best : void 0;
|
|
1423
|
+
}
|
|
1373
1424
|
var REDUCED_MOTION_FPS = 30;
|
|
1374
1425
|
function parseInlinePx(value) {
|
|
1375
1426
|
if (!value || !value.endsWith("px")) return null;
|
|
@@ -2803,6 +2854,37 @@ var Scene = class _Scene {
|
|
|
2803
2854
|
if (!this._devActive) return;
|
|
2804
2855
|
console.warn(`[vectojs/dev] ${message}`);
|
|
2805
2856
|
}
|
|
2857
|
+
/**
|
|
2858
|
+
* Warn (dev mode only) about `SceneOptions` keys this version does not read.
|
|
2859
|
+
*
|
|
2860
|
+
* A structural type makes an unrecognized key a silent no-op, and TypeScript
|
|
2861
|
+
* only rejects one when the object literal sits inline at the call site — not
|
|
2862
|
+
* when options are built dynamically, and never in plain JS. Since the
|
|
2863
|
+
* failure mode is "the option appears to work", a runtime check is the only
|
|
2864
|
+
* thing that surfaces it.
|
|
2865
|
+
*
|
|
2866
|
+
* Dev-mode only on purpose: the loop is O(keys × known keys) with an edit
|
|
2867
|
+
* distance per pair, which is nothing at construction but is still pure
|
|
2868
|
+
* overhead in production, where the value has already been shipped.
|
|
2869
|
+
*/
|
|
2870
|
+
_warnUnknownOptions(options) {
|
|
2871
|
+
if (!this._devActive) return;
|
|
2872
|
+
const known = new Set(SCENE_OPTION_KEYS);
|
|
2873
|
+
for (const key of Object.keys(options)) {
|
|
2874
|
+
if (known.has(key)) continue;
|
|
2875
|
+
const fieldHint = SCENE_FIELD_NOT_OPTION[key];
|
|
2876
|
+
if (fieldHint) {
|
|
2877
|
+
this._devWarn(
|
|
2878
|
+
`SceneOptions: \`${key}\` is not a constructor option \u2014 ${fieldHint}. Passing it here has no effect.`
|
|
2879
|
+
);
|
|
2880
|
+
continue;
|
|
2881
|
+
}
|
|
2882
|
+
const suggestion = closestOptionKey(key);
|
|
2883
|
+
this._devWarn(
|
|
2884
|
+
`SceneOptions: unknown option \`${key}\` is ignored.` + (suggestion ? ` Did you mean \`${suggestion}\`?` : "")
|
|
2885
|
+
);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2806
2888
|
/** @internal Periodic dev checks — called once per frame in dev mode. */
|
|
2807
2889
|
_devRunChecks() {
|
|
2808
2890
|
this._devFrameCount++;
|
|
@@ -2867,7 +2949,9 @@ var Scene = class _Scene {
|
|
|
2867
2949
|
this.contentProjectionEnabled = options.contentProjection ?? true;
|
|
2868
2950
|
this.contentProjectionMargin = options.contentProjectionMargin;
|
|
2869
2951
|
this.readingDirection = options.readingDirection ?? "ltr";
|
|
2952
|
+
this.renderMode = options.renderMode ?? "always";
|
|
2870
2953
|
this._devActive = _Scene._devModeDetected();
|
|
2954
|
+
this._warnUnknownOptions(options);
|
|
2871
2955
|
this.reducedMotionQuery = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
|
|
2872
2956
|
if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
|
|
2873
2957
|
this.forcedColorsQuery = window.matchMedia("(forced-colors: active)");
|
|
@@ -5709,11 +5793,11 @@ function contentGridLineSignature(grid, line, projected, lineHeight, baseline, f
|
|
|
5709
5793
|
// src/components/TextEntity.ts
|
|
5710
5794
|
import {
|
|
5711
5795
|
LayoutEngine,
|
|
5712
|
-
|
|
5796
|
+
resolveGlyphMeasurer
|
|
5713
5797
|
} from "@vectojs/layout";
|
|
5714
|
-
var sharedMeasurer;
|
|
5798
|
+
var sharedMeasurer = null;
|
|
5715
5799
|
function defaultMeasurer() {
|
|
5716
|
-
|
|
5800
|
+
sharedMeasurer ??= resolveGlyphMeasurer("sans-serif");
|
|
5717
5801
|
return sharedMeasurer;
|
|
5718
5802
|
}
|
|
5719
5803
|
var TextEntity = class extends Entity {
|
|
@@ -6488,6 +6572,7 @@ export {
|
|
|
6488
6572
|
PARTICLE_STRIDE_FLOATS,
|
|
6489
6573
|
REDUCED_MOTION_FPS,
|
|
6490
6574
|
Rect,
|
|
6575
|
+
SCENE_OPTION_KEYS,
|
|
6491
6576
|
SVGEntity,
|
|
6492
6577
|
SVGRenderer,
|
|
6493
6578
|
Scene,
|
package/dist/renderer.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
var
|
|
10
|
+
var _chunkKHGHP2J3js = require('./chunk-KHGHP2J3.js');
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
@@ -16,4 +16,4 @@ var _chunkKEBYJVD6js = require('./chunk-KEBYJVD6.js');
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
exports.CanvasRenderer =
|
|
19
|
+
exports.CanvasRenderer = _chunkKHGHP2J3js.CanvasRenderer; exports.GlyphRasterAtlas = _chunkKHGHP2J3js.GlyphRasterAtlas; exports.SVGRenderer = _chunkKHGHP2J3js.SVGRenderer; exports.TextRasterCache = _chunkKHGHP2J3js.TextRasterCache; exports.WebGPUParticleSystemManager = _chunkKHGHP2J3js.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkKHGHP2J3js.createWebGLPointRenderer; exports.parseColorToRGBA = _chunkKHGHP2J3js.parseColorToRGBA;
|
package/dist/renderer.mjs
CHANGED
|
@@ -34,10 +34,34 @@ export declare class MSDFTextEntity extends Entity {
|
|
|
34
34
|
private layoutText;
|
|
35
35
|
private text;
|
|
36
36
|
private lastRenderedSeqId;
|
|
37
|
+
private atlasDecodeTarget;
|
|
38
|
+
private atlasDecodeHandler;
|
|
37
39
|
private rgbColorCache;
|
|
38
40
|
private fontStringCache;
|
|
39
41
|
private layoutResult;
|
|
40
42
|
constructor(text: string, options: MSDFTextEntityOptions);
|
|
43
|
+
/**
|
|
44
|
+
* Repaint once the atlas raster decodes.
|
|
45
|
+
*
|
|
46
|
+
* The WebGL backend refuses to upload a not-yet-decoded atlas (it would pin an
|
|
47
|
+
* empty texture in its identity cache forever), so the upload has to happen on
|
|
48
|
+
* a LATER frame — and nothing else schedules one. Layout marks the scene dirty
|
|
49
|
+
* when the worker replies, which for a network-served atlas is long before the
|
|
50
|
+
* image lands, so the scene is already idle by then.
|
|
51
|
+
*
|
|
52
|
+
* Measured on Chromium and Firefox (2026-07-31) with a 600 ms atlas: the
|
|
53
|
+
* scene's own rAF loop never uploaded a decoded atlas in EITHER render mode.
|
|
54
|
+
* `onDemand` skips idle frames outright; `always` throttles to 2 FPS when
|
|
55
|
+
* idle, so whether it recovers is down to whether a throttled tick happens to
|
|
56
|
+
* land after the decode — Chromium got one, Firefox did not. Neither is a
|
|
57
|
+
* mechanism, which is why this listener exists rather than relying on the
|
|
58
|
+
* frame loop to come back around.
|
|
59
|
+
*
|
|
60
|
+
* Only `HTMLImageElement`-shaped sources have a decode to wait for; a canvas,
|
|
61
|
+
* `ImageBitmap`, or `VideoFrame` atlas is ready on arrival.
|
|
62
|
+
*/
|
|
63
|
+
private watchAtlasDecode;
|
|
64
|
+
private detachAtlasDecodeListener;
|
|
41
65
|
/** Change the wrap boundary and re-run layout for the current text. */
|
|
42
66
|
setMaxWidth(maxWidth: number): void;
|
|
43
67
|
/**
|
package/dist/text/SVGEntity.d.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { Entity } from '../tree/Entity';
|
|
2
2
|
import { IRenderer } from '../renderer/IRenderer';
|
|
3
3
|
export declare class SVGEntity extends Entity {
|
|
4
|
+
/**
|
|
5
|
+
* Stroke colour of the fallback marker drawn when the source cannot be
|
|
6
|
+
* rasterized. Set to `'transparent'` to opt out and keep the box empty.
|
|
7
|
+
* Default `'rgba(248,113,113,0.9)'`.
|
|
8
|
+
*/
|
|
9
|
+
fallbackStroke: string;
|
|
10
|
+
/** Fill behind the fallback marker. Default `'rgba(248,113,113,0.12)'`. */
|
|
11
|
+
fallbackFill: string;
|
|
4
12
|
private svgSource;
|
|
5
13
|
private imageBitmap;
|
|
6
14
|
private imageElement;
|
|
7
15
|
private blobURL;
|
|
8
16
|
private currentImg;
|
|
9
17
|
private lodTimeout;
|
|
18
|
+
private rasterFailed;
|
|
10
19
|
private cachedDoc;
|
|
11
20
|
private baseWidth;
|
|
12
21
|
private baseHeight;
|
|
@@ -16,7 +25,22 @@ export declare class SVGEntity extends Entity {
|
|
|
16
25
|
setSVGSource(svgSource: string): void;
|
|
17
26
|
private parseSVGDimensions;
|
|
18
27
|
private triggerRasterization;
|
|
28
|
+
/**
|
|
29
|
+
* Whether the source genuinely rasterized to a bitmap.
|
|
30
|
+
*
|
|
31
|
+
* Distinguishes "drew the real artwork" from "drew the fallback marker",
|
|
32
|
+
* which pixel counts alone cannot tell apart — both are non-blank.
|
|
33
|
+
*/
|
|
34
|
+
hasRasterBitmap(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Whether rasterization failed, so {@link render} draws the fallback marker.
|
|
37
|
+
*
|
|
38
|
+
* `false` while a raster is still in flight; only a settled failure sets it.
|
|
39
|
+
*/
|
|
40
|
+
hasRasterFailed(): boolean;
|
|
19
41
|
isPointInside(globalX: number, globalY: number): boolean;
|
|
20
42
|
render(r: IRenderer): void;
|
|
43
|
+
/** Box outline plus a diagonal cross — the conventional "broken image" mark. */
|
|
44
|
+
private drawFallback;
|
|
21
45
|
destroy(): void;
|
|
22
46
|
}
|
package/dist/text.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var _chunkZ3HFY75Rjs = require('./chunk-Z3HFY75R.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 = _chunkZ3HFY75Rjs.MSDFTextEntity; exports.SVGEntity = _chunkZ3HFY75Rjs.SVGEntity;
|
package/dist/text.mjs
CHANGED
package/dist/tree/Scene.d.ts
CHANGED
|
@@ -210,7 +210,37 @@ export interface SceneOptions {
|
|
|
210
210
|
* (`'rtl'`). Also settable later via {@link Scene.readingDirection}.
|
|
211
211
|
*/
|
|
212
212
|
readingDirection?: 'ltr' | 'rtl';
|
|
213
|
+
/**
|
|
214
|
+
* When to repaint:
|
|
215
|
+
* - `'always'` (default): drive a continuous rAF loop, throttling to 2 FPS
|
|
216
|
+
* while the scene is idle if {@link SceneOptions.autoThrottle} is on.
|
|
217
|
+
* - `'onDemand'`: paint only after {@link Scene.markDirty} (or an active
|
|
218
|
+
* transition), so a genuinely static scene costs zero frames.
|
|
219
|
+
*
|
|
220
|
+
* Also settable later via {@link Scene.renderMode}. Prefer this option when
|
|
221
|
+
* the mode is known at construction: it applies before the first frame, so an
|
|
222
|
+
* `onDemand` scene never pays for the initial always-on frames.
|
|
223
|
+
*/
|
|
224
|
+
renderMode?: 'always' | 'onDemand';
|
|
213
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Every recognized {@link SceneOptions} key, used only to warn about unknown
|
|
228
|
+
* ones in dev mode.
|
|
229
|
+
*
|
|
230
|
+
* This exists because `SceneOptions` is structural: passing a key it does not
|
|
231
|
+
* declare is a **silent** no-op, and TypeScript only catches it when the object
|
|
232
|
+
* is written inline at the call site. Code that builds options dynamically, or
|
|
233
|
+
* plain untranspiled JS, gets no diagnostic at all. `renderMode` was a public
|
|
234
|
+
* field with no matching option for several releases, and four `@vectojs` demos
|
|
235
|
+
* shipped `new Scene(canvas, { renderMode: 'onDemand' })` — reading correctly,
|
|
236
|
+
* doing nothing, and sitting on the 2 FPS idle floor.
|
|
237
|
+
*
|
|
238
|
+
* Kept as a literal rather than derived from a type: `keyof SceneOptions` does
|
|
239
|
+
* not survive to runtime, so this list is the only form a constructor can check
|
|
240
|
+
* against. A new option must be added here too — the test suite asserts the two
|
|
241
|
+
* stay in sync.
|
|
242
|
+
*/
|
|
243
|
+
export declare const SCENE_OPTION_KEYS: readonly ['a11ySyncInterval', 'autoThrottle', 'contentProjection', 'contentProjectionMargin', 'debugA11y', 'disableWindowResize', 'maxDPR', 'maxFPS', 'particleBackend', 'pointBackend', 'readingDirection', 'renderer', 'renderMode', 'respectReducedMotion', 'userTiming'];
|
|
214
244
|
/** Frame-rate the loop is capped to when the OS requests reduced motion. */
|
|
215
245
|
export declare const REDUCED_MOTION_FPS = 30;
|
|
216
246
|
/**
|
|
@@ -955,6 +985,20 @@ export declare class Scene {
|
|
|
955
985
|
private _devActive;
|
|
956
986
|
private _devFrameCount;
|
|
957
987
|
private _devWarn;
|
|
988
|
+
/**
|
|
989
|
+
* Warn (dev mode only) about `SceneOptions` keys this version does not read.
|
|
990
|
+
*
|
|
991
|
+
* A structural type makes an unrecognized key a silent no-op, and TypeScript
|
|
992
|
+
* only rejects one when the object literal sits inline at the call site — not
|
|
993
|
+
* when options are built dynamically, and never in plain JS. Since the
|
|
994
|
+
* failure mode is "the option appears to work", a runtime check is the only
|
|
995
|
+
* thing that surfaces it.
|
|
996
|
+
*
|
|
997
|
+
* Dev-mode only on purpose: the loop is O(keys × known keys) with an edit
|
|
998
|
+
* distance per pair, which is nothing at construction but is still pure
|
|
999
|
+
* overhead in production, where the value has already been shipped.
|
|
1000
|
+
*/
|
|
1001
|
+
private _warnUnknownOptions;
|
|
958
1002
|
/** @internal Periodic dev checks — called once per frame in dev mode. */
|
|
959
1003
|
private _devRunChecks;
|
|
960
1004
|
constructor(canvas: HTMLCanvasElement, options?: SceneOptions);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vectojs/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -61,20 +61,20 @@
|
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "(cd ../math && bun run build) && (cd ../text && bun run build) && (cd ../layout && bun run build) && (cd ../animation && bun run build) && tsup && tsc -p tsconfig.build.json",
|
|
63
63
|
"test": "vitest run",
|
|
64
|
-
"test:e2e": "bun e2e/hidpi.e2e.ts && bun e2e/text-projection.e2e.ts"
|
|
64
|
+
"test:e2e": "bun e2e/hidpi.e2e.ts && bun e2e/text-projection.e2e.ts && bun e2e/svg-fallback.e2e.ts && bun e2e/msdf-atlas-decode.e2e.ts && bun e2e/layout-worker-fallback.e2e.ts"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@vectojs/animation": "^0.1.1",
|
|
68
|
-
"@vectojs/layout": "^0.
|
|
68
|
+
"@vectojs/layout": "^0.5.0",
|
|
69
69
|
"@vectojs/math": "^0.1.1",
|
|
70
|
-
"@vectojs/text": "^0.
|
|
70
|
+
"@vectojs/text": "^0.3.0"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@guidepup/virtual-screen-reader": "0.32.1",
|
|
74
74
|
"@vitest/coverage-v8": "^4.1.10",
|
|
75
75
|
"esbuild": "^0.28.1",
|
|
76
|
-
"jsdom": "^
|
|
77
|
-
"puppeteer-core": "^25.
|
|
76
|
+
"jsdom": "^30.0.1",
|
|
77
|
+
"puppeteer-core": "^25.4.0",
|
|
78
78
|
"tsup": "^8.3.5",
|
|
79
79
|
"vitest": "^4.1.10"
|
|
80
80
|
}
|