@vectojs/core 1.9.2 → 1.11.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-GKLTTGBR.js → chunk-2Z23LTH3.js} +20 -1
- package/dist/{chunk-BPMNCGU7.js → chunk-ORCPCIJ7.js} +24 -5
- package/dist/{chunk-J2P6QZJV.mjs → chunk-XIEQHSBB.mjs} +19 -0
- package/dist/{chunk-WT2XZHJQ.mjs → chunk-XZEPHCDZ.mjs} +24 -5
- package/dist/index.js +193 -108
- package/dist/index.mjs +89 -4
- package/dist/renderer/CanvasRenderer.d.ts +13 -1
- package/dist/renderer/WebGLPointRenderer.d.ts +12 -0
- package/dist/renderer.js +2 -2
- package/dist/renderer.mjs +1 -1
- package/dist/text.js +2 -2
- package/dist/text.mjs +1 -1
- package/dist/tree/Entity.d.ts +10 -1
- package/dist/tree/Scene.d.ts +25 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
isSafeUrl,
|
|
13
13
|
parseColorToRGBA,
|
|
14
14
|
sanitizeUrl
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-XZEPHCDZ.mjs";
|
|
16
16
|
import {
|
|
17
17
|
Easing,
|
|
18
18
|
Entity,
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
cssLineBoxBaseline,
|
|
28
28
|
isTweenConfig,
|
|
29
29
|
prepareContentGrid
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-XIEQHSBB.mjs";
|
|
31
31
|
import {
|
|
32
32
|
ArabicShaper,
|
|
33
33
|
BidiResolver,
|
|
@@ -708,6 +708,8 @@ var Scene = class _Scene {
|
|
|
708
708
|
width;
|
|
709
709
|
height;
|
|
710
710
|
disableWindowResize = false;
|
|
711
|
+
/** See {@link SceneOptions.maxDPR}. `undefined` = uncapped (real DPR). */
|
|
712
|
+
maxDPR;
|
|
711
713
|
// WebGPU properties
|
|
712
714
|
destroyed = false;
|
|
713
715
|
device = null;
|
|
@@ -733,10 +735,72 @@ var Scene = class _Scene {
|
|
|
733
735
|
pointerLeaveListener = null;
|
|
734
736
|
hasWarnedZeroSize = false;
|
|
735
737
|
fontLoadHandler = null;
|
|
738
|
+
// ── Dev-mode warning infrastructure ──────────────────────────────
|
|
739
|
+
//
|
|
740
|
+
// Enable with `Scene.devMode = true` or by setting `globalThis.__DEV__`.
|
|
741
|
+
// Auto-detected when `NODE_ENV === 'development'`.
|
|
742
|
+
//
|
|
743
|
+
// Checks run once every ~120 frames (~2s at 60fps) to keep overhead
|
|
744
|
+
// negligible even when dev mode is on.
|
|
745
|
+
/** Toggle development-mode runtime warnings globally. */
|
|
746
|
+
static devMode = false;
|
|
747
|
+
static _devModeDetected() {
|
|
748
|
+
if (_Scene.devMode) return true;
|
|
749
|
+
const gp = typeof globalThis !== "undefined" ? globalThis : void 0;
|
|
750
|
+
if (gp?.__DEV__) return true;
|
|
751
|
+
if (gp?.process?.env?.NODE_ENV === "development") return true;
|
|
752
|
+
return false;
|
|
753
|
+
}
|
|
754
|
+
_devActive;
|
|
755
|
+
_devFrameCount = 0;
|
|
756
|
+
_devWarn(message) {
|
|
757
|
+
if (!this._devActive) return;
|
|
758
|
+
console.warn(`[vectojs/dev] ${message}`);
|
|
759
|
+
}
|
|
760
|
+
/** @internal Periodic dev checks — called once per frame in dev mode. */
|
|
761
|
+
_devRunChecks() {
|
|
762
|
+
this._devFrameCount++;
|
|
763
|
+
if (this._devFrameCount % 120 !== 0) return;
|
|
764
|
+
if (this.a11yElements) {
|
|
765
|
+
let interactiveCount = 0;
|
|
766
|
+
const walk = (node) => {
|
|
767
|
+
if (node.interactive && node.width > 0) interactiveCount++;
|
|
768
|
+
for (const c of node.children) walk(c);
|
|
769
|
+
};
|
|
770
|
+
walk(this.root);
|
|
771
|
+
for (const c of this.overlayRoot.children) walk(c);
|
|
772
|
+
const shadowCount = this.a11yElements.size;
|
|
773
|
+
if (shadowCount > interactiveCount + 2) {
|
|
774
|
+
this._devWarn(
|
|
775
|
+
`a11yElements (${shadowCount}) exceeds interactive entities (${interactiveCount}). Call scene.detachA11y(entity) before removing interactive children from the tree, or their shadow nodes leak.`
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
let checked = 0;
|
|
780
|
+
const walkProjections = (node) => {
|
|
781
|
+
if (checked > 10) return;
|
|
782
|
+
const proj = node.getContentProjection?.();
|
|
783
|
+
if (proj?.text && proj.selectable !== false) {
|
|
784
|
+
const el = this.contentElements?.get(node.id);
|
|
785
|
+
if (el) {
|
|
786
|
+
const projectedText = el.textContent || "";
|
|
787
|
+
if (projectedText !== "" && projectedText !== proj.text) {
|
|
788
|
+
this._devWarn(
|
|
789
|
+
`Content projection mismatch for entity "${node.id}": projection says "${proj.text.slice(0, 60)}" but DOM shows "${projectedText.slice(0, 60)}". Ensure getContentProjection() output matches what drawSelf renders.`
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
checked++;
|
|
795
|
+
for (const c of node.children) walkProjections(c);
|
|
796
|
+
};
|
|
797
|
+
walkProjections(this.root);
|
|
798
|
+
}
|
|
736
799
|
constructor(canvas, options = {}) {
|
|
737
800
|
this.canvas = canvas;
|
|
738
801
|
this.debugA11y = options.debugA11y ?? false;
|
|
739
802
|
this.disableWindowResize = options.disableWindowResize ?? false;
|
|
803
|
+
this.maxDPR = options.maxDPR;
|
|
740
804
|
if (this.disableWindowResize) {
|
|
741
805
|
const styleWidth = parseInlinePx(canvas.style?.width);
|
|
742
806
|
const styleHeight = parseInlinePx(canvas.style?.height);
|
|
@@ -754,6 +818,7 @@ var Scene = class _Scene {
|
|
|
754
818
|
this.particleBackend = options.particleBackend ?? "auto";
|
|
755
819
|
this.a11ySyncInterval = options.a11ySyncInterval ?? 0;
|
|
756
820
|
this.contentProjectionEnabled = options.contentProjection ?? true;
|
|
821
|
+
this._devActive = _Scene._devModeDetected();
|
|
757
822
|
this.reducedMotionQuery = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
|
|
758
823
|
this.root = new class RootEntity extends Entity {
|
|
759
824
|
isPointInside() {
|
|
@@ -777,7 +842,8 @@ var Scene = class _Scene {
|
|
|
777
842
|
} else {
|
|
778
843
|
this.renderer = new CanvasRenderer(
|
|
779
844
|
canvas,
|
|
780
|
-
this.disableWindowResize ? { width: this.width, height: this.height } : void 0
|
|
845
|
+
this.disableWindowResize ? { width: this.width, height: this.height } : void 0,
|
|
846
|
+
this.maxDPR
|
|
781
847
|
);
|
|
782
848
|
}
|
|
783
849
|
if (typeof document !== "undefined") {
|
|
@@ -903,6 +969,7 @@ var Scene = class _Scene {
|
|
|
903
969
|
if (canvas.parentElement) canvas.parentElement.appendChild(gl);
|
|
904
970
|
const pr = _Scene.webglCreator ? _Scene.webglCreator(gl) : null;
|
|
905
971
|
if (pr) {
|
|
972
|
+
pr.maxDPR = this.maxDPR;
|
|
906
973
|
pr.resize(this.width, this.height);
|
|
907
974
|
this.glCanvas = gl;
|
|
908
975
|
this.pointRenderer = pr;
|
|
@@ -1268,6 +1335,9 @@ var Scene = class _Scene {
|
|
|
1268
1335
|
el.addEventListener("click", (e) => {
|
|
1269
1336
|
node.dispatchEvent(new VectoJSEvent("click", node, e));
|
|
1270
1337
|
});
|
|
1338
|
+
el.addEventListener("dblclick", (e) => {
|
|
1339
|
+
node.dispatchEvent(new VectoJSEvent("dblclick", node, e));
|
|
1340
|
+
});
|
|
1271
1341
|
el.addEventListener("mouseenter", (e) => {
|
|
1272
1342
|
if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.2)";
|
|
1273
1343
|
node.dispatchEvent(new VectoJSEvent("hover", node, e, false));
|
|
@@ -2284,6 +2354,13 @@ var Scene = class _Scene {
|
|
|
2284
2354
|
node.update(dt, time);
|
|
2285
2355
|
if (!walkHadAnimation && node.hasPendingAnimations()) walkHadAnimation = true;
|
|
2286
2356
|
if (!walkHadInteractive && node.interactive) walkHadInteractive = true;
|
|
2357
|
+
if (this._devActive && this._devFrameCount % 120 === 0) {
|
|
2358
|
+
if (node.update !== Entity.prototype.update && node.hasPendingAnimations === Entity.prototype.hasPendingAnimations) {
|
|
2359
|
+
this._devWarn(
|
|
2360
|
+
`Entity "${node.id}" overrides update() but not hasPendingAnimations(). Custom motion in update() without overriding hasPendingAnimations() causes the idle throttle to drop the animation to ~2fps. Override hasPendingAnimations() to return true while motion is in flight.`
|
|
2361
|
+
);
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2287
2364
|
}
|
|
2288
2365
|
const cos = Math.cos(node.rotation);
|
|
2289
2366
|
const sin = Math.sin(node.rotation);
|
|
@@ -2414,6 +2491,10 @@ var Scene = class _Scene {
|
|
|
2414
2491
|
this.pointRenderer?.flush();
|
|
2415
2492
|
}
|
|
2416
2493
|
renderer.present?.();
|
|
2494
|
+
if (this._devActive) {
|
|
2495
|
+
this._devFrameCount++;
|
|
2496
|
+
this._devRunChecks();
|
|
2497
|
+
}
|
|
2417
2498
|
}
|
|
2418
2499
|
/**
|
|
2419
2500
|
* Export the current scene state to a lightweight, flat SVG XML string.
|
|
@@ -2431,9 +2512,13 @@ var Scene = class _Scene {
|
|
|
2431
2512
|
this.height = height;
|
|
2432
2513
|
this.contentFontEpoch++;
|
|
2433
2514
|
if (typeof this.renderer.resize === "function") {
|
|
2515
|
+
if ("maxDPR" in this.renderer) this.renderer.maxDPR = this.maxDPR;
|
|
2434
2516
|
this.renderer.resize(width, height);
|
|
2435
2517
|
}
|
|
2436
|
-
this.pointRenderer
|
|
2518
|
+
if (this.pointRenderer) {
|
|
2519
|
+
this.pointRenderer.maxDPR = this.maxDPR;
|
|
2520
|
+
this.pointRenderer.resize(width, height);
|
|
2521
|
+
}
|
|
2437
2522
|
if (this.gpuCanvas) {
|
|
2438
2523
|
this.gpuCanvas.width = width;
|
|
2439
2524
|
this.gpuCanvas.height = height;
|
|
@@ -3,6 +3,15 @@ export declare class CanvasRenderer implements IRenderer {
|
|
|
3
3
|
private ctx;
|
|
4
4
|
private width;
|
|
5
5
|
private height;
|
|
6
|
+
/**
|
|
7
|
+
* Cap on the effective device pixel ratio applied by the constructor and
|
|
8
|
+
* {@link resize}. `undefined` (default) uses the real, uncapped
|
|
9
|
+
* `devicePixelRatio` — unchanged from prior versions. Set directly, or via
|
|
10
|
+
* the constructor's third argument; `Scene` (see `SceneOptions.maxDPR`)
|
|
11
|
+
* keeps this in sync on every {@link resize} call, since the real DPR can
|
|
12
|
+
* change at runtime (e.g. a window dragged between displays).
|
|
13
|
+
*/
|
|
14
|
+
maxDPR?: number;
|
|
6
15
|
/**
|
|
7
16
|
* Max circles per batched `fill()`. A single Canvas 2D `fill()` over a path is
|
|
8
17
|
* superlinear in sub-path count, so an unbounded batch is *slower* than many
|
|
@@ -21,11 +30,12 @@ export declare class CanvasRenderer implements IRenderer {
|
|
|
21
30
|
* fullscreen canvas and sizes to the window — pass this for embedded /
|
|
22
31
|
* custom-container canvases (the Scene does when `disableWindowResize` is
|
|
23
32
|
* set) so the canvas's own dimensions aren't clobbered by the window's.
|
|
33
|
+
* @param maxDPR - See {@link maxDPR}.
|
|
24
34
|
*/
|
|
25
35
|
constructor(canvas: HTMLCanvasElement, size?: {
|
|
26
36
|
width: number;
|
|
27
37
|
height: number;
|
|
28
|
-
});
|
|
38
|
+
}, maxDPR?: number);
|
|
29
39
|
/**
|
|
30
40
|
* Expose the underlying `CanvasRenderingContext2D` for operations not
|
|
31
41
|
* covered by the {@link IRenderer} interface.
|
|
@@ -33,6 +43,8 @@ export declare class CanvasRenderer implements IRenderer {
|
|
|
33
43
|
* @returns The raw 2D rendering context.
|
|
34
44
|
*/
|
|
35
45
|
getContext(): CanvasRenderingContext2D;
|
|
46
|
+
/** Real `devicePixelRatio`, clamped to {@link maxDPR} when set. */
|
|
47
|
+
private effectiveDPR;
|
|
36
48
|
/**
|
|
37
49
|
* Resize the backing canvas buffer and re-apply DPR scaling.
|
|
38
50
|
*
|
|
@@ -7,6 +7,18 @@
|
|
|
7
7
|
export interface PointRenderer {
|
|
8
8
|
/** Resize the backing buffer + GL viewport to a logical `w × h` (DPR applied). */
|
|
9
9
|
resize(width: number, height: number): void;
|
|
10
|
+
/**
|
|
11
|
+
* Cap on the effective device pixel ratio applied by {@link resize}.
|
|
12
|
+
* `undefined` (default) uses the real, uncapped `devicePixelRatio`. Set
|
|
13
|
+
* before calling `resize()` for it to take effect on that call (matches
|
|
14
|
+
* {@link import('../tree/Scene').SceneOptions.maxDPR} — `Scene` sets this
|
|
15
|
+
* once at construction and again before every `resize()` call, since a
|
|
16
|
+
* factory function has no other way to receive the option: the WebGL point
|
|
17
|
+
* layer's creator is a plain `(canvas) => PointRenderer` registered once by
|
|
18
|
+
* `@vectojs/core`'s module init, with no room for a per-Scene constructor
|
|
19
|
+
* argument).
|
|
20
|
+
*/
|
|
21
|
+
maxDPR?: number;
|
|
10
22
|
/** Begin a frame: reset the accumulated primitive buffers. */
|
|
11
23
|
begin(): void;
|
|
12
24
|
/** Add one circle in world (CSS-pixel) coordinates; `alpha` multiplies the color's. */
|
package/dist/renderer.js
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
var
|
|
7
|
+
var _chunkORCPCIJ7js = require('./chunk-ORCPCIJ7.js');
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
exports.CanvasRenderer =
|
|
14
|
+
exports.CanvasRenderer = _chunkORCPCIJ7js.CanvasRenderer; exports.SVGRenderer = _chunkORCPCIJ7js.SVGRenderer; exports.WebGPUParticleSystemManager = _chunkORCPCIJ7js.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkORCPCIJ7js.createWebGLPointRenderer; exports.parseColorToRGBA = _chunkORCPCIJ7js.parseColorToRGBA;
|
package/dist/renderer.mjs
CHANGED
package/dist/text.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var _chunk2Z23LTH3js = require('./chunk-2Z23LTH3.js');
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
@@ -19,4 +19,4 @@ var _chunk4AR425ARjs = require('./chunk-4AR425AR.js');
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
exports.ArabicShaper = _chunk4AR425ARjs.ArabicShaper; exports.BidiResolver = _chunk4AR425ARjs.BidiResolver; exports.MSDFFont =
|
|
22
|
+
exports.ArabicShaper = _chunk4AR425ARjs.ArabicShaper; exports.BidiResolver = _chunk4AR425ARjs.BidiResolver; exports.MSDFFont = _chunk2Z23LTH3js.MSDFFont; exports.MSDFTextEntity = _chunk2Z23LTH3js.MSDFTextEntity; exports.SVGEntity = _chunk2Z23LTH3js.SVGEntity; exports.clearCssLineBoxMetrics = _chunk2Z23LTH3js.clearCssLineBoxMetrics; exports.cssLineBoxBaseline = _chunk2Z23LTH3js.cssLineBoxBaseline; exports.prepareContentGrid = _chunk2Z23LTH3js.prepareContentGrid;
|
package/dist/text.mjs
CHANGED
package/dist/tree/Entity.d.ts
CHANGED
|
@@ -192,7 +192,7 @@ export interface A11yAttributes {
|
|
|
192
192
|
/**
|
|
193
193
|
* Union of all pointer/interaction events that can be emitted by an {@link Entity}.
|
|
194
194
|
*/
|
|
195
|
-
export type VectoEvent = 'click' | 'hover' | 'pointerdown' | 'pointerup' | 'pointercancel' | 'pointermove' | 'pointerleave' | 'change' | 'focus' | 'blur' | 'wheel' | 'keydown' | 'keyup';
|
|
195
|
+
export type VectoEvent = 'click' | 'dblclick' | 'hover' | 'pointerdown' | 'pointerup' | 'pointercancel' | 'pointermove' | 'pointerleave' | 'change' | 'focus' | 'blur' | 'wheel' | 'keydown' | 'keyup';
|
|
196
196
|
/** Options for {@link Entity.on} / {@link Entity.off}. */
|
|
197
197
|
export interface ListenerOptions {
|
|
198
198
|
/** Register the listener for the capture phase (root→target) instead of bubble. */
|
|
@@ -470,6 +470,15 @@ export declare abstract class Entity {
|
|
|
470
470
|
* @param payload - Arbitrary data forwarded to each listener.
|
|
471
471
|
*/
|
|
472
472
|
emit(event: VectoEvent, payload: any): void;
|
|
473
|
+
/**
|
|
474
|
+
* Programmatically focus the entity's projected a11y shadow element, if one
|
|
475
|
+
* exists. After `scene.add(entity)` the shadow element is typically created
|
|
476
|
+
* on the next a11y sync (within one animation frame), so this method retries
|
|
477
|
+
* once on the next rAF if the element isn't immediately available — matching
|
|
478
|
+
* the timing pattern the prior workaround described in findings.md achieved
|
|
479
|
+
* with `requestAnimationFrame(() => document.getElementById(id)?.focus())`.
|
|
480
|
+
*/
|
|
481
|
+
focus(): void;
|
|
473
482
|
/** Run one node's listeners for the event, honoring stopImmediatePropagation. */
|
|
474
483
|
private fireListeners;
|
|
475
484
|
/**
|
package/dist/tree/Scene.d.ts
CHANGED
|
@@ -70,6 +70,21 @@ export interface SceneOptions {
|
|
|
70
70
|
* Useful when Vecto is running inside a custom layout container or offscreen canvas.
|
|
71
71
|
*/
|
|
72
72
|
disableWindowResize?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Cap the effective device pixel ratio used to size the Canvas2D and WebGL
|
|
75
|
+
* point-layer backing stores. `undefined` (default) reads the real,
|
|
76
|
+
* uncapped `window.devicePixelRatio` — unchanged from prior versions.
|
|
77
|
+
* Backing-store render cost scales with `logical size × dpr²`, so a
|
|
78
|
+
* full-screen HiDPI scene (`pointBackend: 'webgl'` in particular) can
|
|
79
|
+
* overrun its frame budget on a DPR-3 display while running fine on the
|
|
80
|
+
* DPR-1 dev machine it was tuned on (findings.md, 2026-07-16). `maxDPR: 2`
|
|
81
|
+
* keeps the display retina-crisp (2x already exceeds what most eyes
|
|
82
|
+
* resolve) while roughly halving the backing-store pixel count at DPR 3.
|
|
83
|
+
* Applied at construction and re-applied on every {@link resize} call
|
|
84
|
+
* (including the automatic window-resize listener), since the real DPR
|
|
85
|
+
* can change at runtime (a window dragged between displays).
|
|
86
|
+
*/
|
|
87
|
+
maxDPR?: number;
|
|
73
88
|
/**
|
|
74
89
|
* Enable automatic throttling to 2 FPS when the scene is static (no active transitions
|
|
75
90
|
* and not marked dirty) to save power/CPU. Default is `true`.
|
|
@@ -196,6 +211,8 @@ export declare class Scene {
|
|
|
196
211
|
width: number;
|
|
197
212
|
height: number;
|
|
198
213
|
private disableWindowResize;
|
|
214
|
+
/** See {@link SceneOptions.maxDPR}. `undefined` = uncapped (real DPR). */
|
|
215
|
+
maxDPR?: number;
|
|
199
216
|
private destroyed;
|
|
200
217
|
private device;
|
|
201
218
|
private deviceLost;
|
|
@@ -216,6 +233,14 @@ export declare class Scene {
|
|
|
216
233
|
private pointerLeaveListener;
|
|
217
234
|
private hasWarnedZeroSize;
|
|
218
235
|
private fontLoadHandler;
|
|
236
|
+
/** Toggle development-mode runtime warnings globally. */
|
|
237
|
+
static devMode: boolean;
|
|
238
|
+
private static _devModeDetected;
|
|
239
|
+
private _devActive;
|
|
240
|
+
private _devFrameCount;
|
|
241
|
+
private _devWarn;
|
|
242
|
+
/** @internal Periodic dev checks — called once per frame in dev mode. */
|
|
243
|
+
private _devRunChecks;
|
|
219
244
|
constructor(canvas: HTMLCanvasElement, options?: SceneOptions);
|
|
220
245
|
private endContentSelectionDrag;
|
|
221
246
|
private releaseContentSelectionForRebuild;
|