@vectojs/core 1.25.0 → 1.27.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.
@@ -1,5 +1,52 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5;const __vecto_cjs_url=require("url").pathToFileURL(__filename).href;
2
2
 
3
+ // src/renderer/IRenderer.ts
4
+ var RENDERER_STYLE_PROPERTY_HINTS = {
5
+ globalAlpha: "setGlobalAlpha(alpha)",
6
+ strokeStyle: "stroke(color, lineWidth)",
7
+ lineWidth: "stroke(color, lineWidth)",
8
+ fillStyle: "fill(color) \u2014 or fillText(text, x, y, font, color)",
9
+ font: "fillText(text, x, y, font, color)",
10
+ lineCap: "stroke(color, lineWidth) (cap is backend-chosen)",
11
+ lineJoin: "stroke(color, lineWidth) (join is backend-chosen)",
12
+ globalCompositeOperation: "no equivalent \u2014 composite via a separate canvas",
13
+ shadowBlur: "no equivalent \u2014 draw the shadow explicitly",
14
+ shadowColor: "no equivalent \u2014 draw the shadow explicitly",
15
+ textAlign: "no equivalent \u2014 measure and offset x yourself",
16
+ textBaseline: "no equivalent \u2014 measure and offset y yourself"
17
+ };
18
+ var rendererDevMode = false;
19
+ function setRendererDevMode(active) {
20
+ rendererDevMode = active;
21
+ }
22
+ function isRendererDevMode() {
23
+ if (rendererDevMode) return true;
24
+ const g = typeof globalThis !== "undefined" ? globalThis : void 0;
25
+ if (_optionalChain([g, 'optionalAccess', _ => _.__DEV__])) return true;
26
+ return _optionalChain([g, 'optionalAccess', _2 => _2.process, 'optionalAccess', _3 => _3.env, 'optionalAccess', _4 => _4.NODE_ENV]) === "development";
27
+ }
28
+ function installRendererDevTraps(renderer, label) {
29
+ if (!isRendererDevMode()) return;
30
+ for (const [prop, hint] of Object.entries(RENDERER_STYLE_PROPERTY_HINTS)) {
31
+ if (prop in renderer) continue;
32
+ let stored;
33
+ let warned = false;
34
+ Object.defineProperty(renderer, prop, {
35
+ configurable: true,
36
+ enumerable: false,
37
+ get: () => stored,
38
+ set: (value) => {
39
+ stored = value;
40
+ if (warned) return;
41
+ warned = true;
42
+ console.warn(
43
+ `[vectojs/dev] \`${prop}\` is not a renderer property \u2014 assigning it on ${label} has no effect on what is drawn. Call \`${hint}\` instead.`
44
+ );
45
+ }
46
+ });
47
+ }
48
+ }
49
+
3
50
  // src/renderer/CanvasRenderer.ts
4
51
  function emptyDrawCounters() {
5
52
  return {
@@ -79,8 +126,8 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
79
126
  constructor(canvas, size, maxDPR) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);_class.prototype.__init11.call(this);
80
127
  this.maxDPR = maxDPR;
81
128
  const dpr = this.effectiveDPR();
82
- this.width = _nullishCoalesce(_optionalChain([size, 'optionalAccess', _ => _.width]), () => ( (typeof window !== "undefined" ? window.innerWidth : canvas.width || 0)));
83
- this.height = _nullishCoalesce(_optionalChain([size, 'optionalAccess', _2 => _2.height]), () => ( (typeof window !== "undefined" ? window.innerHeight : canvas.height || 0)));
129
+ this.width = _nullishCoalesce(_optionalChain([size, 'optionalAccess', _5 => _5.width]), () => ( (typeof window !== "undefined" ? window.innerWidth : canvas.width || 0)));
130
+ this.height = _nullishCoalesce(_optionalChain([size, 'optionalAccess', _6 => _6.height]), () => ( (typeof window !== "undefined" ? window.innerHeight : canvas.height || 0)));
84
131
  canvas.width = this.width * dpr;
85
132
  canvas.height = this.height * dpr;
86
133
  if (canvas.style) {
@@ -92,6 +139,7 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
92
139
  if (ctx) ctx.scale(dpr, dpr);
93
140
  this.canvas = canvas;
94
141
  this.setupContextLossRecovery();
142
+ installRendererDevTraps(this, "CanvasRenderer");
95
143
  }
96
144
  /** Register a callback fired after a lost 2D context is restored + re-scaled,
97
145
  * so the owner can repaint (the restored canvas comes back cleared). */
@@ -122,7 +170,7 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
122
170
  this.batchActive = false;
123
171
  this.batchCount = 0;
124
172
  this.contextLost = false;
125
- _optionalChain([this, 'access', _3 => _3.contextRestoredCb, 'optionalCall', _4 => _4()]);
173
+ _optionalChain([this, 'access', _7 => _7.contextRestoredCb, 'optionalCall', _8 => _8()]);
126
174
  });
127
175
  }
128
176
  /**
@@ -491,6 +539,7 @@ var SVGRenderer = (_class2 = class {
491
539
  constructor(width, height) {;_class2.prototype.__init12.call(this);_class2.prototype.__init13.call(this);_class2.prototype.__init14.call(this);_class2.prototype.__init15.call(this);_class2.prototype.__init16.call(this);_class2.prototype.__init17.call(this);_class2.prototype.__init18.call(this);_class2.prototype.__init19.call(this);_class2.prototype.__init20.call(this);_class2.prototype.__init21.call(this);_class2.prototype.__init22.call(this);_class2.prototype.__init23.call(this);_class2.prototype.__init24.call(this);_class2.prototype.__init25.call(this);_class2.prototype.__init26.call(this);_class2.prototype.__init27.call(this);_class2.prototype.__init28.call(this);_class2.prototype.__init29.call(this);_class2.prototype.__init30.call(this);_class2.prototype.__init31.call(this);_class2.prototype.__init32.call(this);_class2.prototype.__init33.call(this);_class2.prototype.__init34.call(this);
492
540
  this.width = width;
493
541
  this.height = height;
542
+ installRendererDevTraps(this, "SVGRenderer");
494
543
  }
495
544
  clear() {
496
545
  this.buffer = [];
@@ -712,8 +761,8 @@ var SVGRenderer = (_class2 = class {
712
761
  }
713
762
  drawImage(source, dx, dy, dw, dh) {
714
763
  this.flush();
715
- const fromCanvas2 = typeof _optionalChain([source, 'optionalAccess', _5 => _5.toDataURL]) === "function";
716
- const rawHref = fromCanvas2 ? source.toDataURL() : _optionalChain([source, 'optionalAccess', _6 => _6.src]) || "";
764
+ const fromCanvas2 = typeof _optionalChain([source, 'optionalAccess', _9 => _9.toDataURL]) === "function";
765
+ const rawHref = fromCanvas2 ? source.toDataURL() : _optionalChain([source, 'optionalAccess', _10 => _10.src]) || "";
717
766
  const href = fromCanvas2 && this.isSafeRasterDataUrl(rawHref) ? rawHref : sanitizeUrl(String(rawHref));
718
767
  const transformStr = `matrix(${this.ma},${this.mb},${this.mc},${this.md},${this.me},${this.mf})`;
719
768
  if (href) {
@@ -2285,4 +2334,7 @@ var TextRasterCache = (_class5 = class {
2285
2334
 
2286
2335
 
2287
2336
 
2288
- exports.CanvasRenderer = CanvasRenderer; exports.sanitizeUrl = sanitizeUrl; exports.isSafeUrl = isSafeUrl; exports.SVGRenderer = SVGRenderer; exports.parseColorToRGBA = parseColorToRGBA; exports.createWebGLPointRenderer = createWebGLPointRenderer; exports.WebGPUParticleSystemManager = WebGPUParticleSystemManager; exports.GlyphRasterAtlas = GlyphRasterAtlas; exports.TextRasterCache = TextRasterCache;
2337
+
2338
+
2339
+
2340
+ exports.setRendererDevMode = setRendererDevMode; exports.isRendererDevMode = isRendererDevMode; exports.installRendererDevTraps = installRendererDevTraps; exports.CanvasRenderer = CanvasRenderer; exports.sanitizeUrl = sanitizeUrl; exports.isSafeUrl = isSafeUrl; exports.SVGRenderer = SVGRenderer; exports.parseColorToRGBA = parseColorToRGBA; exports.createWebGLPointRenderer = createWebGLPointRenderer; exports.WebGPUParticleSystemManager = WebGPUParticleSystemManager; exports.GlyphRasterAtlas = GlyphRasterAtlas; exports.TextRasterCache = TextRasterCache;
@@ -1,3 +1,50 @@
1
+ // src/renderer/IRenderer.ts
2
+ var RENDERER_STYLE_PROPERTY_HINTS = {
3
+ globalAlpha: "setGlobalAlpha(alpha)",
4
+ strokeStyle: "stroke(color, lineWidth)",
5
+ lineWidth: "stroke(color, lineWidth)",
6
+ fillStyle: "fill(color) \u2014 or fillText(text, x, y, font, color)",
7
+ font: "fillText(text, x, y, font, color)",
8
+ lineCap: "stroke(color, lineWidth) (cap is backend-chosen)",
9
+ lineJoin: "stroke(color, lineWidth) (join is backend-chosen)",
10
+ globalCompositeOperation: "no equivalent \u2014 composite via a separate canvas",
11
+ shadowBlur: "no equivalent \u2014 draw the shadow explicitly",
12
+ shadowColor: "no equivalent \u2014 draw the shadow explicitly",
13
+ textAlign: "no equivalent \u2014 measure and offset x yourself",
14
+ textBaseline: "no equivalent \u2014 measure and offset y yourself"
15
+ };
16
+ var rendererDevMode = false;
17
+ function setRendererDevMode(active) {
18
+ rendererDevMode = active;
19
+ }
20
+ function isRendererDevMode() {
21
+ if (rendererDevMode) return true;
22
+ const g = typeof globalThis !== "undefined" ? globalThis : void 0;
23
+ if (g?.__DEV__) return true;
24
+ return g?.process?.env?.NODE_ENV === "development";
25
+ }
26
+ function installRendererDevTraps(renderer, label) {
27
+ if (!isRendererDevMode()) return;
28
+ for (const [prop, hint] of Object.entries(RENDERER_STYLE_PROPERTY_HINTS)) {
29
+ if (prop in renderer) continue;
30
+ let stored;
31
+ let warned = false;
32
+ Object.defineProperty(renderer, prop, {
33
+ configurable: true,
34
+ enumerable: false,
35
+ get: () => stored,
36
+ set: (value) => {
37
+ stored = value;
38
+ if (warned) return;
39
+ warned = true;
40
+ console.warn(
41
+ `[vectojs/dev] \`${prop}\` is not a renderer property \u2014 assigning it on ${label} has no effect on what is drawn. Call \`${hint}\` instead.`
42
+ );
43
+ }
44
+ });
45
+ }
46
+ }
47
+
1
48
  // src/renderer/CanvasRenderer.ts
2
49
  function emptyDrawCounters() {
3
50
  return {
@@ -90,6 +137,7 @@ var CanvasRenderer = class _CanvasRenderer {
90
137
  if (ctx) ctx.scale(dpr, dpr);
91
138
  this.canvas = canvas;
92
139
  this.setupContextLossRecovery();
140
+ installRendererDevTraps(this, "CanvasRenderer");
93
141
  }
94
142
  /** Register a callback fired after a lost 2D context is restored + re-scaled,
95
143
  * so the owner can repaint (the restored canvas comes back cleared). */
@@ -489,6 +537,7 @@ var SVGRenderer = class {
489
537
  constructor(width, height) {
490
538
  this.width = width;
491
539
  this.height = height;
540
+ installRendererDevTraps(this, "SVGRenderer");
492
541
  }
493
542
  clear() {
494
543
  this.buffer = [];
@@ -2274,6 +2323,9 @@ var TextRasterCache = class {
2274
2323
  };
2275
2324
 
2276
2325
  export {
2326
+ setRendererDevMode,
2327
+ isRendererDevMode,
2328
+ installRendererDevTraps,
2277
2329
  CanvasRenderer,
2278
2330
  sanitizeUrl,
2279
2331
  isSafeUrl,
package/dist/index.js CHANGED
@@ -9,7 +9,10 @@
9
9
 
10
10
 
11
11
 
12
- var _chunkKHGHP2J3js = require('./chunk-KHGHP2J3.js');
12
+
13
+
14
+
15
+ var _chunkK2UGEIT6js = require('./chunk-K2UGEIT6.js');
13
16
 
14
17
 
15
18
 
@@ -94,7 +97,7 @@ var ComputeParticleEntity = (_class = class extends _chunkZ3HFY75Rjs.Entity {
94
97
  this.particleData[idx + PARTICLE_OFFSET_LIFE] = -1;
95
98
  }
96
99
  this.needsInit = true;
97
- _optionalChain([this, 'access', _ => _.scene, 'optionalAccess', _2 => _2.markDirty, 'call', _3 => _3()]);
100
+ _optionalChain([this, 'access', _2 => _2.scene, 'optionalAccess', _3 => _3.markDirty, 'call', _4 => _4()]);
98
101
  }
99
102
  /**
100
103
  * Sets the origins (ox, oy) for a subset or all particles.
@@ -120,7 +123,7 @@ var ComputeParticleEntity = (_class = class extends _chunkZ3HFY75Rjs.Entity {
120
123
  }
121
124
  }
122
125
  this.needsInit = true;
123
- _optionalChain([this, 'access', _4 => _4.scene, 'optionalAccess', _5 => _5.markDirty, 'call', _6 => _6()]);
126
+ _optionalChain([this, 'access', _5 => _5.scene, 'optionalAccess', _6 => _6.markDirty, 'call', _7 => _7()]);
124
127
  }
125
128
  /**
126
129
  * Sets the current positions (x, y) for a subset or all particles.
@@ -136,7 +139,7 @@ var ComputeParticleEntity = (_class = class extends _chunkZ3HFY75Rjs.Entity {
136
139
  this.particleData[idx + PARTICLE_OFFSET_POSITION_Y] = positions[ptIdx + 1];
137
140
  }
138
141
  this.needsInit = true;
139
- _optionalChain([this, 'access', _7 => _7.scene, 'optionalAccess', _8 => _8.markDirty, 'call', _9 => _9()]);
142
+ _optionalChain([this, 'access', _8 => _8.scene, 'optionalAccess', _9 => _9.markDirty, 'call', _10 => _10()]);
140
143
  }
141
144
  /**
142
145
  * Sets the current velocities (vx, vy) for a subset or all particles.
@@ -152,7 +155,7 @@ var ComputeParticleEntity = (_class = class extends _chunkZ3HFY75Rjs.Entity {
152
155
  this.particleData[idx + PARTICLE_OFFSET_VELOCITY_Y] = velocities[ptIdx + 1];
153
156
  }
154
157
  this.needsInit = true;
155
- _optionalChain([this, 'access', _10 => _10.scene, 'optionalAccess', _11 => _11.markDirty, 'call', _12 => _12()]);
158
+ _optionalChain([this, 'access', _11 => _11.scene, 'optionalAccess', _12 => _12.markDirty, 'call', _13 => _13()]);
156
159
  }
157
160
  /**
158
161
  * Triggers an explosion force center.
@@ -1259,7 +1262,7 @@ var VECTO_USER_TIMING = {
1259
1262
  var nextSpanId = 0;
1260
1263
  function beginVectoUserTiming(name) {
1261
1264
  const candidate = globalThis.performance;
1262
- if (typeof _optionalChain([candidate, 'optionalAccess', _13 => _13.mark]) !== "function" || typeof candidate.measure !== "function") {
1265
+ if (typeof _optionalChain([candidate, 'optionalAccess', _14 => _14.mark]) !== "function" || typeof candidate.measure !== "function") {
1263
1266
  return null;
1264
1267
  }
1265
1268
  const id = nextSpanId++;
@@ -1286,15 +1289,15 @@ function endVectoUserTiming(span) {
1286
1289
  } catch (e8) {
1287
1290
  } finally {
1288
1291
  try {
1289
- _optionalChain([timing, 'access', _14 => _14.clearMarks, 'optionalCall', _15 => _15(span.startMark)]);
1290
- _optionalChain([timing, 'access', _16 => _16.clearMarks, 'optionalCall', _17 => _17(span.endMark)]);
1292
+ _optionalChain([timing, 'access', _15 => _15.clearMarks, 'optionalCall', _16 => _16(span.startMark)]);
1293
+ _optionalChain([timing, 'access', _17 => _17.clearMarks, 'optionalCall', _18 => _18(span.endMark)]);
1291
1294
  } catch (e9) {
1292
1295
  }
1293
1296
  }
1294
1297
  }
1295
1298
  function measureVectoUserTiming(name, durationMs) {
1296
1299
  const candidate = globalThis.performance;
1297
- if (typeof _optionalChain([candidate, 'optionalAccess', _18 => _18.now]) !== "function" || typeof candidate.mark !== "function" || typeof candidate.measure !== "function") {
1300
+ if (typeof _optionalChain([candidate, 'optionalAccess', _19 => _19.now]) !== "function" || typeof candidate.mark !== "function" || typeof candidate.measure !== "function") {
1298
1301
  return;
1299
1302
  }
1300
1303
  const id = nextSpanId++;
@@ -1309,8 +1312,8 @@ function measureVectoUserTiming(name, durationMs) {
1309
1312
  } catch (e10) {
1310
1313
  } finally {
1311
1314
  try {
1312
- _optionalChain([candidate, 'access', _19 => _19.clearMarks, 'optionalCall', _20 => _20(startMark)]);
1313
- _optionalChain([candidate, 'access', _21 => _21.clearMarks, 'optionalCall', _22 => _22(endMark)]);
1315
+ _optionalChain([candidate, 'access', _20 => _20.clearMarks, 'optionalCall', _21 => _21(startMark)]);
1316
+ _optionalChain([candidate, 'access', _22 => _22.clearMarks, 'optionalCall', _23 => _23(endMark)]);
1314
1317
  } catch (e11) {
1315
1318
  }
1316
1319
  }
@@ -1371,6 +1374,57 @@ function rebaseChildBox(parent, parentOriginX, parentOriginY, child, childOrigin
1371
1374
  REBASED_BOX.matrix = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0)`;
1372
1375
  return REBASED_BOX;
1373
1376
  }
1377
+ var SCENE_OPTION_KEYS = [
1378
+ "a11ySyncInterval",
1379
+ "autoThrottle",
1380
+ "contentProjection",
1381
+ "contentProjectionMargin",
1382
+ "debugA11y",
1383
+ "disableWindowResize",
1384
+ "maxDPR",
1385
+ "maxFPS",
1386
+ "particleBackend",
1387
+ "pointBackend",
1388
+ "readingDirection",
1389
+ "renderer",
1390
+ "renderMode",
1391
+ "respectReducedMotion",
1392
+ "userTiming"
1393
+ ];
1394
+ var SCENE_FIELD_NOT_OPTION = {
1395
+ devMode: "set the static `Scene.devMode = true` before constructing"
1396
+ };
1397
+ function editDistance(a, b) {
1398
+ const m = a.length;
1399
+ const n = b.length;
1400
+ if (m === 0 || n === 0) return Math.max(m, n);
1401
+ let prev = Array.from({ length: n + 1 }, (_, i) => i);
1402
+ const curr = Array.from({ length: n + 1 });
1403
+ for (let i = 1; i <= m; i++) {
1404
+ curr[0] = i;
1405
+ for (let j = 1; j <= n; j++) {
1406
+ const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
1407
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
1408
+ }
1409
+ prev = curr.slice();
1410
+ }
1411
+ return prev[n];
1412
+ }
1413
+ function closestOptionKey(key) {
1414
+ const lower = key.toLowerCase();
1415
+ let best;
1416
+ let bestScore = Infinity;
1417
+ for (const candidate of SCENE_OPTION_KEYS) {
1418
+ if (candidate.toLowerCase() === lower) return candidate;
1419
+ const d = editDistance(lower, candidate.toLowerCase());
1420
+ if (d < bestScore) {
1421
+ bestScore = d;
1422
+ best = candidate;
1423
+ }
1424
+ }
1425
+ const limit = Math.max(2, Math.floor(key.length / 3));
1426
+ return bestScore <= limit ? best : void 0;
1427
+ }
1374
1428
  var REDUCED_MOTION_FPS = 30;
1375
1429
  function parseInlinePx(value) {
1376
1430
  if (!value || !value.endsWith("px")) return null;
@@ -1456,9 +1510,9 @@ function parseCssMatrix(transform) {
1456
1510
  }
1457
1511
  function clientToGridLocal(contentEl, canvas, clientX, clientY) {
1458
1512
  const line = contentEl.querySelector("[data-vecto-grid-line]");
1459
- const originMarker = _optionalChain([line, 'optionalAccess', _23 => _23.querySelector, 'call', _24 => _24('[data-vecto-grid-basis="origin"]')]);
1460
- const xMarker = _optionalChain([line, 'optionalAccess', _25 => _25.querySelector, 'call', _26 => _26('[data-vecto-grid-basis="x"]')]);
1461
- const yMarker = _optionalChain([line, 'optionalAccess', _27 => _27.querySelector, 'call', _28 => _28('[data-vecto-grid-basis="y"]')]);
1513
+ const originMarker = _optionalChain([line, 'optionalAccess', _24 => _24.querySelector, 'call', _25 => _25('[data-vecto-grid-basis="origin"]')]);
1514
+ const xMarker = _optionalChain([line, 'optionalAccess', _26 => _26.querySelector, 'call', _27 => _27('[data-vecto-grid-basis="x"]')]);
1515
+ const yMarker = _optionalChain([line, 'optionalAccess', _28 => _28.querySelector, 'call', _29 => _29('[data-vecto-grid-basis="y"]')]);
1462
1516
  if (line && originMarker && xMarker && yMarker) {
1463
1517
  const origin = originMarker.getBoundingClientRect();
1464
1518
  const xPoint = xMarker.getBoundingClientRect();
@@ -1542,7 +1596,7 @@ function nearestTextPositionInLine(line, x, y) {
1542
1596
  nearest = { position: { node, offset }, distance: candidate.distance };
1543
1597
  }
1544
1598
  }
1545
- return _nullishCoalesce(_optionalChain([nearest, 'optionalAccess', _29 => _29.position]), () => ( null));
1599
+ return _nullishCoalesce(_optionalChain([nearest, 'optionalAccess', _30 => _30.position]), () => ( null));
1546
1600
  }
1547
1601
  function nearestTextPositionInProjection(contentEl, canvas, x, y, eventTarget) {
1548
1602
  if (contentEl.dataset.vectoContentGrid !== void 0) {
@@ -1556,7 +1610,7 @@ function nearestTextPositionInProjection(contentEl, canvas, x, y, eventTarget) {
1556
1610
  }
1557
1611
  targetLine = targetLine.parentElement;
1558
1612
  }
1559
- if (_optionalChain([targetLine, 'optionalAccess', _30 => _30.parentElement]) === contentEl) {
1613
+ if (_optionalChain([targetLine, 'optionalAccess', _31 => _31.parentElement]) === contentEl) {
1560
1614
  return nearestTextPositionInLine(targetLine, x, y);
1561
1615
  }
1562
1616
  let bestLine = null;
@@ -1834,7 +1888,7 @@ var Scene = (_class7 = class _Scene {
1834
1888
  __init50() {this.forcedColorsChangeHandler = null}
1835
1889
  /** True when the OS asks for reduced motion and we respect it. Read by the animation drivers. */
1836
1890
  get prefersReducedMotion() {
1837
- return this.respectReducedMotion && !!_optionalChain([this, 'access', _31 => _31.reducedMotionQuery, 'optionalAccess', _32 => _32.matches]);
1891
+ return this.respectReducedMotion && !!_optionalChain([this, 'access', _32 => _32.reducedMotionQuery, 'optionalAccess', _33 => _33.matches]);
1838
1892
  }
1839
1893
  /**
1840
1894
  * True when the OS is in a forced-colors mode (Windows High Contrast, and the
@@ -1845,7 +1899,7 @@ var Scene = (_class7 = class _Scene {
1845
1899
  * when the setting toggles.
1846
1900
  */
1847
1901
  get forcedColors() {
1848
- return !!_optionalChain([this, 'access', _33 => _33.forcedColorsQuery, 'optionalAccess', _34 => _34.matches]);
1902
+ return !!_optionalChain([this, 'access', _34 => _34.forcedColorsQuery, 'optionalAccess', _35 => _35.matches]);
1849
1903
  }
1850
1904
  /**
1851
1905
  * Throttle interval (ms) for the a11y/automation shadow sync. `0` = every
@@ -2281,7 +2335,7 @@ var Scene = (_class7 = class _Scene {
2281
2335
  const idx = cell[k];
2282
2336
  if (x < minx[idx] || x > maxx[idx] || y < miny[idx] || y > maxy[idx]) continue;
2283
2337
  const entity = this._hitSlotEntity[idx];
2284
- if (_optionalChain([entity, 'optionalAccess', _35 => _35.isPointInside, 'call', _36 => _36(x, y)]) && this.isHitEligible(entity, x, y)) {
2338
+ if (_optionalChain([entity, 'optionalAccess', _36 => _36.isPointInside, 'call', _37 => _37(x, y)]) && this.isHitEligible(entity, x, y)) {
2285
2339
  bestIndex = idx;
2286
2340
  bestEntity = entity;
2287
2341
  break;
@@ -2752,7 +2806,7 @@ var Scene = (_class7 = class _Scene {
2752
2806
  * active.
2753
2807
  */
2754
2808
  get webglDrawStats() {
2755
- return _nullishCoalesce(_optionalChain([this, 'access', _37 => _37.pointRenderer, 'optionalAccess', _38 => _38.stats, 'optionalCall', _39 => _39()]), () => ( null));
2809
+ return _nullishCoalesce(_optionalChain([this, 'access', _38 => _38.pointRenderer, 'optionalAccess', _39 => _39.stats, 'optionalCall', _40 => _40()]), () => ( null));
2756
2810
  }
2757
2811
  /**
2758
2812
  * Whether a WebGPU device is currently live for particle compute.
@@ -2789,13 +2843,29 @@ var Scene = (_class7 = class _Scene {
2789
2843
  //
2790
2844
  // Checks run once every ~120 frames (~2s at 60fps) to keep overhead
2791
2845
  // negligible even when dev mode is on.
2792
- /** Toggle development-mode runtime warnings globally. */
2793
- static __initStatic5() {this.devMode = false}
2846
+ static __initStatic5() {this._devMode = false}
2847
+ /**
2848
+ * Toggle development-mode runtime warnings globally.
2849
+ *
2850
+ * An accessor rather than a plain field so the renderer layer learns about it
2851
+ * immediately: renderers cannot import `Scene` (the dependency runs
2852
+ * `Scene → renderer`), and their diagnostics are installed per instance at
2853
+ * construction. A plain field would only reach them the next time a `Scene`
2854
+ * happened to be built, which made a directly-constructed `CanvasRenderer`
2855
+ * silently untrapped.
2856
+ */
2857
+ static get devMode() {
2858
+ return _Scene._devMode;
2859
+ }
2860
+ static set devMode(active) {
2861
+ _Scene._devMode = active;
2862
+ _chunkK2UGEIT6js.setRendererDevMode.call(void 0, active);
2863
+ }
2794
2864
  static _devModeDetected() {
2795
- if (_Scene.devMode) return true;
2865
+ if (_Scene._devMode) return true;
2796
2866
  const gp = typeof globalThis !== "undefined" ? globalThis : void 0;
2797
- if (_optionalChain([gp, 'optionalAccess', _40 => _40.__DEV__])) return true;
2798
- if (_optionalChain([gp, 'optionalAccess', _41 => _41.process, 'optionalAccess', _42 => _42.env, 'optionalAccess', _43 => _43.NODE_ENV]) === "development") return true;
2867
+ if (_optionalChain([gp, 'optionalAccess', _41 => _41.__DEV__])) return true;
2868
+ if (_optionalChain([gp, 'optionalAccess', _42 => _42.process, 'optionalAccess', _43 => _43.env, 'optionalAccess', _44 => _44.NODE_ENV]) === "development") return true;
2799
2869
  return false;
2800
2870
  }
2801
2871
 
@@ -2804,6 +2874,37 @@ var Scene = (_class7 = class _Scene {
2804
2874
  if (!this._devActive) return;
2805
2875
  console.warn(`[vectojs/dev] ${message}`);
2806
2876
  }
2877
+ /**
2878
+ * Warn (dev mode only) about `SceneOptions` keys this version does not read.
2879
+ *
2880
+ * A structural type makes an unrecognized key a silent no-op, and TypeScript
2881
+ * only rejects one when the object literal sits inline at the call site — not
2882
+ * when options are built dynamically, and never in plain JS. Since the
2883
+ * failure mode is "the option appears to work", a runtime check is the only
2884
+ * thing that surfaces it.
2885
+ *
2886
+ * Dev-mode only on purpose: the loop is O(keys × known keys) with an edit
2887
+ * distance per pair, which is nothing at construction but is still pure
2888
+ * overhead in production, where the value has already been shipped.
2889
+ */
2890
+ _warnUnknownOptions(options) {
2891
+ if (!this._devActive) return;
2892
+ const known = new Set(SCENE_OPTION_KEYS);
2893
+ for (const key of Object.keys(options)) {
2894
+ if (known.has(key)) continue;
2895
+ const fieldHint = SCENE_FIELD_NOT_OPTION[key];
2896
+ if (fieldHint) {
2897
+ this._devWarn(
2898
+ `SceneOptions: \`${key}\` is not a constructor option \u2014 ${fieldHint}. Passing it here has no effect.`
2899
+ );
2900
+ continue;
2901
+ }
2902
+ const suggestion = closestOptionKey(key);
2903
+ this._devWarn(
2904
+ `SceneOptions: unknown option \`${key}\` is ignored.` + (suggestion ? ` Did you mean \`${suggestion}\`?` : "")
2905
+ );
2906
+ }
2907
+ }
2807
2908
  /** @internal Periodic dev checks — called once per frame in dev mode. */
2808
2909
  _devRunChecks() {
2809
2910
  this._devFrameCount++;
@@ -2826,9 +2927,9 @@ var Scene = (_class7 = class _Scene {
2826
2927
  let checked = 0;
2827
2928
  const walkProjections = (node) => {
2828
2929
  if (checked > 10) return;
2829
- const proj = _optionalChain([node, 'access', _44 => _44.getContentProjection, 'optionalCall', _45 => _45()]);
2830
- if (_optionalChain([proj, 'optionalAccess', _46 => _46.text]) && proj.selectable !== false) {
2831
- const el = _optionalChain([this, 'access', _47 => _47.contentElements, 'optionalAccess', _48 => _48.get, 'call', _49 => _49(node.id)]);
2930
+ const proj = _optionalChain([node, 'access', _45 => _45.getContentProjection, 'optionalCall', _46 => _46()]);
2931
+ if (_optionalChain([proj, 'optionalAccess', _47 => _47.text]) && proj.selectable !== false) {
2932
+ const el = _optionalChain([this, 'access', _48 => _48.contentElements, 'optionalAccess', _49 => _49.get, 'call', _50 => _50(node.id)]);
2832
2933
  if (el) {
2833
2934
  const projectedText = el.textContent || "";
2834
2935
  if (projectedText !== "" && projectedText !== proj.text) {
@@ -2849,8 +2950,8 @@ var Scene = (_class7 = class _Scene {
2849
2950
  this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
2850
2951
  this.maxDPR = options.maxDPR;
2851
2952
  if (this.disableWindowResize) {
2852
- const styleWidth = parseInlinePx(_optionalChain([canvas, 'access', _50 => _50.style, 'optionalAccess', _51 => _51.width]));
2853
- const styleHeight = parseInlinePx(_optionalChain([canvas, 'access', _52 => _52.style, 'optionalAccess', _53 => _53.height]));
2953
+ const styleWidth = parseInlinePx(_optionalChain([canvas, 'access', _51 => _51.style, 'optionalAccess', _52 => _52.width]));
2954
+ const styleHeight = parseInlinePx(_optionalChain([canvas, 'access', _53 => _53.style, 'optionalAccess', _54 => _54.height]));
2854
2955
  this.width = _nullishCoalesce(styleWidth, () => ( (canvas.width || canvas.clientWidth || 0)));
2855
2956
  this.height = _nullishCoalesce(styleHeight, () => ( (canvas.height || canvas.clientHeight || 0)));
2856
2957
  } else {
@@ -2858,7 +2959,7 @@ var Scene = (_class7 = class _Scene {
2858
2959
  this.height = typeof window !== "undefined" ? window.innerHeight : canvas.clientHeight || canvas.height || 600;
2859
2960
  }
2860
2961
  const globalProcess = typeof globalThis !== "undefined" ? globalThis.process : void 0;
2861
- const isTest = globalProcess && (_optionalChain([globalProcess, 'access', _54 => _54.env, 'optionalAccess', _55 => _55.NODE_ENV]) === "test" || _optionalChain([globalProcess, 'access', _56 => _56.env, 'optionalAccess', _57 => _57.VITEST]) === "true");
2962
+ const isTest = globalProcess && (_optionalChain([globalProcess, 'access', _55 => _55.env, 'optionalAccess', _56 => _56.NODE_ENV]) === "test" || _optionalChain([globalProcess, 'access', _57 => _57.env, 'optionalAccess', _58 => _58.VITEST]) === "true");
2862
2963
  this.maxFPS = _nullishCoalesce(options.maxFPS, () => ( (isTest ? 0 : 60)));
2863
2964
  this.respectReducedMotion = _nullishCoalesce(options.respectReducedMotion, () => ( true));
2864
2965
  this.autoThrottle = _nullishCoalesce(options.autoThrottle, () => ( true));
@@ -2868,12 +2969,15 @@ var Scene = (_class7 = class _Scene {
2868
2969
  this.contentProjectionEnabled = _nullishCoalesce(options.contentProjection, () => ( true));
2869
2970
  this.contentProjectionMargin = options.contentProjectionMargin;
2870
2971
  this.readingDirection = _nullishCoalesce(options.readingDirection, () => ( "ltr"));
2972
+ this.renderMode = _nullishCoalesce(options.renderMode, () => ( "always"));
2871
2973
  this._devActive = _Scene._devModeDetected();
2974
+ _chunkK2UGEIT6js.setRendererDevMode.call(void 0, this._devActive);
2975
+ this._warnUnknownOptions(options);
2872
2976
  this.reducedMotionQuery = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
2873
2977
  if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
2874
2978
  this.forcedColorsQuery = window.matchMedia("(forced-colors: active)");
2875
2979
  this.forcedColorsChangeHandler = () => this.markDirty();
2876
- _optionalChain([this, 'access', _58 => _58.forcedColorsQuery, 'access', _59 => _59.addEventListener, 'optionalCall', _60 => _60("change", this.forcedColorsChangeHandler)]);
2980
+ _optionalChain([this, 'access', _59 => _59.forcedColorsQuery, 'access', _60 => _60.addEventListener, 'optionalCall', _61 => _61("change", this.forcedColorsChangeHandler)]);
2877
2981
  }
2878
2982
  this.root = new class RootEntity extends _chunkZ3HFY75Rjs.Entity {
2879
2983
  isPointInside() {
@@ -2895,15 +2999,15 @@ var Scene = (_class7 = class _Scene {
2895
2999
  if (options.renderer) {
2896
3000
  this.renderer = options.renderer;
2897
3001
  } else {
2898
- this.renderer = new (0, _chunkKHGHP2J3js.CanvasRenderer)(
3002
+ this.renderer = new (0, _chunkK2UGEIT6js.CanvasRenderer)(
2899
3003
  canvas,
2900
3004
  this.disableWindowResize ? { width: this.width, height: this.height } : void 0,
2901
3005
  this.maxDPR
2902
3006
  );
2903
3007
  }
2904
- _optionalChain([this, 'access', _61 => _61.renderer, 'access', _62 => _62.onContextRestored, 'optionalCall', _63 => _63(() => {
3008
+ _optionalChain([this, 'access', _62 => _62.renderer, 'access', _63 => _63.onContextRestored, 'optionalCall', _64 => _64(() => {
2905
3009
  this.markDirty();
2906
- if (_optionalChain([this, 'access', _64 => _64.renderer, 'access', _65 => _65.isContextLost, 'optionalCall', _66 => _66()]) !== true) this.render(this.renderer);
3010
+ if (_optionalChain([this, 'access', _65 => _65.renderer, 'access', _66 => _66.isContextLost, 'optionalCall', _67 => _67()]) !== true) this.render(this.renderer);
2907
3011
  })]);
2908
3012
  if (typeof document !== "undefined") {
2909
3013
  this.a11yRoot = document.createElement("div");
@@ -3073,7 +3177,7 @@ var Scene = (_class7 = class _Scene {
3073
3177
  watchDevicePixelRatio() {
3074
3178
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
3075
3179
  if (this.dprMediaQuery && this.dprChangeHandler) {
3076
- _optionalChain([this, 'access', _67 => _67.dprMediaQuery, 'access', _68 => _68.removeEventListener, 'optionalCall', _69 => _69("change", this.dprChangeHandler)]);
3180
+ _optionalChain([this, 'access', _68 => _68.dprMediaQuery, 'access', _69 => _69.removeEventListener, 'optionalCall', _70 => _70("change", this.dprChangeHandler)]);
3077
3181
  }
3078
3182
  const dpr = window.devicePixelRatio || 1;
3079
3183
  const query = window.matchMedia(`(resolution: ${dpr}dppx)`);
@@ -3081,7 +3185,7 @@ var Scene = (_class7 = class _Scene {
3081
3185
  this.resize(this.width, this.height);
3082
3186
  this.watchDevicePixelRatio();
3083
3187
  };
3084
- _optionalChain([query, 'access', _70 => _70.addEventListener, 'optionalCall', _71 => _71("change", handler)]);
3188
+ _optionalChain([query, 'access', _71 => _71.addEventListener, 'optionalCall', _72 => _72("change", handler)]);
3085
3189
  this.dprMediaQuery = query;
3086
3190
  this.dprChangeHandler = handler;
3087
3191
  }
@@ -3101,7 +3205,7 @@ var Scene = (_class7 = class _Scene {
3101
3205
  if (typeof gl.addEventListener !== "function") return;
3102
3206
  this.glContextLostHandler = (e) => {
3103
3207
  e.preventDefault();
3104
- _optionalChain([this, 'access', _72 => _72.pointRenderer, 'optionalAccess', _73 => _73.destroy, 'call', _74 => _74()]);
3208
+ _optionalChain([this, 'access', _73 => _73.pointRenderer, 'optionalAccess', _74 => _74.destroy, 'call', _75 => _75()]);
3105
3209
  this.pointRenderer = null;
3106
3210
  };
3107
3211
  this.glContextRestoredHandler = () => {
@@ -3131,26 +3235,26 @@ var Scene = (_class7 = class _Scene {
3131
3235
  * to the live DOM selection.
3132
3236
  */
3133
3237
  contentGridSelectionLine(el) {
3134
- const candidates = [_optionalChain([this, 'access', _75 => _75.contentSelectionAnchor, 'optionalAccess', _76 => _76.node])];
3238
+ const candidates = [_optionalChain([this, 'access', _76 => _76.contentSelectionAnchor, 'optionalAccess', _77 => _77.node])];
3135
3239
  if (typeof window !== "undefined" && typeof window.getSelection === "function") {
3136
3240
  const selection = window.getSelection();
3137
- candidates.push(_optionalChain([selection, 'optionalAccess', _77 => _77.anchorNode]), _optionalChain([selection, 'optionalAccess', _78 => _78.focusNode]));
3241
+ candidates.push(_optionalChain([selection, 'optionalAccess', _78 => _78.anchorNode]), _optionalChain([selection, 'optionalAccess', _79 => _79.focusNode]));
3138
3242
  }
3139
3243
  for (const candidate of candidates) {
3140
3244
  if (!candidate || !el.contains(candidate)) continue;
3141
3245
  let cursor = candidate;
3142
3246
  while (cursor && cursor.parentNode !== el) cursor = cursor.parentNode;
3143
- const lineIndex = _optionalChain([cursor, 'optionalAccess', _79 => _79.dataset, 'optionalAccess', _80 => _80.vectoGridLine]);
3247
+ const lineIndex = _optionalChain([cursor, 'optionalAccess', _80 => _80.dataset, 'optionalAccess', _81 => _81.vectoGridLine]);
3144
3248
  if (lineIndex !== void 0) return Number(lineIndex);
3145
3249
  }
3146
3250
  return null;
3147
3251
  }
3148
3252
  releaseContentSelectionForRebuild(el) {
3149
3253
  const selection = typeof window !== "undefined" && typeof window.getSelection === "function" ? window.getSelection() : null;
3150
- const ownsSelection = this.contentSelectionAnchor && el.contains(this.contentSelectionAnchor.node) || (_optionalChain([selection, 'optionalAccess', _81 => _81.anchorNode]) ? el.contains(selection.anchorNode) : false) || (_optionalChain([selection, 'optionalAccess', _82 => _82.focusNode]) ? el.contains(selection.focusNode) : false);
3254
+ const ownsSelection = this.contentSelectionAnchor && el.contains(this.contentSelectionAnchor.node) || (_optionalChain([selection, 'optionalAccess', _82 => _82.anchorNode]) ? el.contains(selection.anchorNode) : false) || (_optionalChain([selection, 'optionalAccess', _83 => _83.focusNode]) ? el.contains(selection.focusNode) : false);
3151
3255
  if (!ownsSelection) return;
3152
3256
  this.endContentSelectionDrag();
3153
- _optionalChain([selection, 'optionalAccess', _83 => _83.removeAllRanges, 'call', _84 => _84()]);
3257
+ _optionalChain([selection, 'optionalAccess', _84 => _84.removeAllRanges, 'call', _85 => _85()]);
3154
3258
  }
3155
3259
  /**
3156
3260
  * Rebuild a content-projection element's DOM (`rebuild`) while preserving a
@@ -3209,7 +3313,7 @@ var Scene = (_class7 = class _Scene {
3209
3313
  }
3210
3314
  /** Convert browser viewport coordinates into this Scene's logical coordinates. */
3211
3315
  clientToScene(clientX, clientY) {
3212
- const rect = _optionalChain([this, 'access', _85 => _85.canvas, 'access', _86 => _86.getBoundingClientRect, 'optionalCall', _87 => _87()]);
3316
+ const rect = _optionalChain([this, 'access', _86 => _86.canvas, 'access', _87 => _87.getBoundingClientRect, 'optionalCall', _88 => _88()]);
3213
3317
  if (!rect) return { x: clientX, y: clientY };
3214
3318
  const cssWidth = rect.width || this.canvas.clientWidth || this.width;
3215
3319
  const cssHeight = rect.height || this.canvas.clientHeight || this.height;
@@ -3247,7 +3351,7 @@ var Scene = (_class7 = class _Scene {
3247
3351
  cancelAnimationFrame(calibrationFrame);
3248
3352
  }
3249
3353
  this.contentGridCalibrationFrames.delete(entityId);
3250
- _optionalChain([this, 'access', _88 => _88.contentGridCalibrationProbes, 'access', _89 => _89.get, 'call', _90 => _90(entityId), 'optionalAccess', _91 => _91.remove, 'call', _92 => _92()]);
3354
+ _optionalChain([this, 'access', _89 => _89.contentGridCalibrationProbes, 'access', _90 => _90.get, 'call', _91 => _91(entityId), 'optionalAccess', _92 => _92.remove, 'call', _93 => _93()]);
3251
3355
  this.contentGridCalibrationProbes.delete(entityId);
3252
3356
  delete el.dataset.vectoGridCalibrationPending;
3253
3357
  delete el.dataset.vectoGridCalibration;
@@ -3401,12 +3505,12 @@ var Scene = (_class7 = class _Scene {
3401
3505
  this.canvasResizeObserver = null;
3402
3506
  }
3403
3507
  if (this.dprMediaQuery && this.dprChangeHandler) {
3404
- _optionalChain([this, 'access', _93 => _93.dprMediaQuery, 'access', _94 => _94.removeEventListener, 'optionalCall', _95 => _95("change", this.dprChangeHandler)]);
3508
+ _optionalChain([this, 'access', _94 => _94.dprMediaQuery, 'access', _95 => _95.removeEventListener, 'optionalCall', _96 => _96("change", this.dprChangeHandler)]);
3405
3509
  this.dprMediaQuery = null;
3406
3510
  this.dprChangeHandler = null;
3407
3511
  }
3408
3512
  if (this.forcedColorsQuery && this.forcedColorsChangeHandler) {
3409
- _optionalChain([this, 'access', _96 => _96.forcedColorsQuery, 'access', _97 => _97.removeEventListener, 'optionalCall', _98 => _98("change", this.forcedColorsChangeHandler)]);
3513
+ _optionalChain([this, 'access', _97 => _97.forcedColorsQuery, 'access', _98 => _98.removeEventListener, 'optionalCall', _99 => _99("change", this.forcedColorsChangeHandler)]);
3410
3514
  this.forcedColorsQuery = null;
3411
3515
  this.forcedColorsChangeHandler = null;
3412
3516
  }
@@ -3424,9 +3528,9 @@ var Scene = (_class7 = class _Scene {
3424
3528
  }
3425
3529
  this.pointerEventTarget = null;
3426
3530
  }
3427
- _optionalChain([this, 'access', _99 => _99.a11yRoot, 'optionalAccess', _100 => _100.remove, 'call', _101 => _101()]);
3531
+ _optionalChain([this, 'access', _100 => _100.a11yRoot, 'optionalAccess', _101 => _101.remove, 'call', _102 => _102()]);
3428
3532
  this.focusSentinel = null;
3429
- _optionalChain([this, 'access', _102 => _102.portalRoot, 'optionalAccess', _103 => _103.remove, 'call', _104 => _104()]);
3533
+ _optionalChain([this, 'access', _103 => _103.portalRoot, 'optionalAccess', _104 => _104.remove, 'call', _105 => _105()]);
3430
3534
  this.a11yElements.clear();
3431
3535
  for (const el of this.contentElements.values()) el.remove();
3432
3536
  this.contentElements.clear();
@@ -3449,10 +3553,10 @@ var Scene = (_class7 = class _Scene {
3449
3553
  }
3450
3554
  this.glContextLostHandler = null;
3451
3555
  this.glContextRestoredHandler = null;
3452
- _optionalChain([this, 'access', _105 => _105.pointRenderer, 'optionalAccess', _106 => _106.destroy, 'call', _107 => _107()]);
3453
- _optionalChain([this, 'access', _108 => _108.renderer, 'access', _109 => _109.dispose, 'optionalCall', _110 => _110()]);
3454
- _optionalChain([this, 'access', _111 => _111.glCanvas, 'optionalAccess', _112 => _112.remove, 'call', _113 => _113()]);
3455
- _optionalChain([this, 'access', _114 => _114.gpuCanvas, 'optionalAccess', _115 => _115.remove, 'call', _116 => _116()]);
3556
+ _optionalChain([this, 'access', _106 => _106.pointRenderer, 'optionalAccess', _107 => _107.destroy, 'call', _108 => _108()]);
3557
+ _optionalChain([this, 'access', _109 => _109.renderer, 'access', _110 => _110.dispose, 'optionalCall', _111 => _111()]);
3558
+ _optionalChain([this, 'access', _112 => _112.glCanvas, 'optionalAccess', _113 => _113.remove, 'call', _114 => _114()]);
3559
+ _optionalChain([this, 'access', _115 => _115.gpuCanvas, 'optionalAccess', _116 => _116.remove, 'call', _117 => _117()]);
3456
3560
  this.gpuCanvas = null;
3457
3561
  this.gpuContext = null;
3458
3562
  if (this.recoveryTimerId) {
@@ -3464,7 +3568,7 @@ var Scene = (_class7 = class _Scene {
3464
3568
  this.manager = null;
3465
3569
  }
3466
3570
  if (this.device) {
3467
- _optionalChain([this, 'access', _117 => _117.device, 'access', _118 => _118.destroy, 'optionalCall', _119 => _119()]);
3571
+ _optionalChain([this, 'access', _118 => _118.device, 'access', _119 => _119.destroy, 'optionalCall', _120 => _120()]);
3468
3572
  this.device = null;
3469
3573
  }
3470
3574
  }
@@ -3897,7 +4001,7 @@ var Scene = (_class7 = class _Scene {
3897
4001
  el.addEventListener("compositionupdate", (e) => {
3898
4002
  const data = _nullishCoalesce(e.data, () => ( ""));
3899
4003
  composition = {
3900
- start: _nullishCoalesce(_optionalChain([composition, 'optionalAccess', _120 => _120.start]), () => ( 0)),
4004
+ start: _nullishCoalesce(_optionalChain([composition, 'optionalAccess', _121 => _121.start]), () => ( 0)),
3901
4005
  length: data.length
3902
4006
  };
3903
4007
  forward();
@@ -3970,7 +4074,7 @@ var Scene = (_class7 = class _Scene {
3970
4074
  this.syncOptionalAttribute(
3971
4075
  el,
3972
4076
  "href",
3973
- attrs.href === void 0 ? void 0 : _chunkKHGHP2J3js.sanitizeUrl.call(void 0, attrs.href)
4077
+ attrs.href === void 0 ? void 0 : _chunkK2UGEIT6js.sanitizeUrl.call(void 0, attrs.href)
3974
4078
  );
3975
4079
  this.syncOptionalAttribute(el, "target", attrs.target);
3976
4080
  }
@@ -4376,9 +4480,9 @@ var Scene = (_class7 = class _Scene {
4376
4480
  for (let lineIndex = 0; lineIndex < grid.lines.length; lineIndex++) {
4377
4481
  const gridLine = grid.lines[lineIndex];
4378
4482
  const projectedLine = projectionLines[lineIndex];
4379
- const lineHeight = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _121 => _121.lineHeight]), () => ( grid.lineHeight));
4380
- const baseline = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _122 => _122.baseline]), () => ( grid.baseline));
4381
- const lineFont = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _123 => _123.font]), () => ( grid.font));
4483
+ const lineHeight = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _122 => _122.lineHeight]), () => ( grid.lineHeight));
4484
+ const baseline = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _123 => _123.baseline]), () => ( grid.baseline));
4485
+ const lineFont = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _124 => _124.font]), () => ( grid.font));
4382
4486
  const lineSignature = contentGridLineSignature(
4383
4487
  grid,
4384
4488
  gridLine,
@@ -4398,8 +4502,8 @@ var Scene = (_class7 = class _Scene {
4398
4502
  lineElement.dir = "ltr";
4399
4503
  lineElement.dataset.vectoGridLine = `${lineIndex}`;
4400
4504
  lineElement.style.position = "absolute";
4401
- lineElement.style.left = `${_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _124 => _124.x]), () => ( 0))}px`;
4402
- lineElement.style.top = `${(_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _125 => _125.y]), () => ( lineIndex * grid.lineHeight))) + baseline - _text.cssLineBoxBaseline.call(void 0, lineFont, lineHeight)}px`;
4505
+ lineElement.style.left = `${_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _125 => _125.x]), () => ( 0))}px`;
4506
+ lineElement.style.top = `${(_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _126 => _126.y]), () => ( lineIndex * grid.lineHeight))) + baseline - _text.cssLineBoxBaseline.call(void 0, lineFont, lineHeight)}px`;
4403
4507
  lineElement.style.width = `${gridLine.width}px`;
4404
4508
  lineElement.style.height = `${lineHeight}px`;
4405
4509
  lineElement.style.whiteSpace = "pre";
@@ -4469,7 +4573,7 @@ var Scene = (_class7 = class _Scene {
4469
4573
  if (selectionLine !== null && selectionLine >= grid.lines.length) {
4470
4574
  rebuiltSelectionLine = true;
4471
4575
  }
4472
- _optionalChain([el, 'access', _126 => _126.lastElementChild, 'optionalAccess', _127 => _127.remove, 'call', _128 => _128()]);
4576
+ _optionalChain([el, 'access', _127 => _127.lastElementChild, 'optionalAccess', _128 => _128.remove, 'call', _129 => _129()]);
4473
4577
  }
4474
4578
  if (rebuiltSelectionLine) this.releaseContentSelectionForRebuild(el);
4475
4579
  el.dataset.vectoProjectionLines = signature;
@@ -4533,7 +4637,7 @@ var Scene = (_class7 = class _Scene {
4533
4637
  if (previous !== void 0 && typeof cancelAnimationFrame === "function") {
4534
4638
  cancelAnimationFrame(previous);
4535
4639
  }
4536
- _optionalChain([this, 'access', _129 => _129.contentGridCalibrationProbes, 'access', _130 => _130.get, 'call', _131 => _131(entityId), 'optionalAccess', _132 => _132.remove, 'call', _133 => _133()]);
4640
+ _optionalChain([this, 'access', _130 => _130.contentGridCalibrationProbes, 'access', _131 => _131.get, 'call', _132 => _132(entityId), 'optionalAccess', _133 => _133.remove, 'call', _134 => _134()]);
4537
4641
  this.contentGridCalibrationProbes.delete(entityId);
4538
4642
  const calibrationStart = typeof performance !== "undefined" ? performance.now() : 0;
4539
4643
  const probe = document.createElement("div");
@@ -4567,7 +4671,7 @@ var Scene = (_class7 = class _Scene {
4567
4671
  target.dataset.vectoGridCalib = generation;
4568
4672
  continue;
4569
4673
  }
4570
- const sourceText = _nullishCoalesce(_optionalChain([target, 'access', _134 => _134.textContent, 'optionalAccess', _135 => _135.slice, 'call', _136 => _136(0, sourceLength)]), () => ( ""));
4674
+ const sourceText = _nullishCoalesce(_optionalChain([target, 'access', _135 => _135.textContent, 'optionalAccess', _136 => _136.slice, 'call', _137 => _137(0, sourceLength)]), () => ( ""));
4571
4675
  if (!sourceText) {
4572
4676
  target.dataset.vectoGridCalib = generation;
4573
4677
  continue;
@@ -4794,12 +4898,12 @@ var Scene = (_class7 = class _Scene {
4794
4898
  syncOverlayGeometry() {
4795
4899
  const parent = this.canvas.parentElement;
4796
4900
  if (!parent) return;
4797
- const canvasRect = _optionalChain([this, 'access', _137 => _137.canvas, 'access', _138 => _138.getBoundingClientRect, 'optionalCall', _139 => _139()]);
4798
- const parentRect = _optionalChain([parent, 'access', _140 => _140.getBoundingClientRect, 'optionalCall', _141 => _141()]);
4799
- const cssWidth = _optionalChain([canvasRect, 'optionalAccess', _142 => _142.width]) || this.canvas.clientWidth || this.width;
4800
- const cssHeight = _optionalChain([canvasRect, 'optionalAccess', _143 => _143.height]) || this.canvas.clientHeight || this.height;
4801
- const left = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _144 => _144.left]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _145 => _145.left]), () => ( 0))) - (parent.clientLeft || 0) + parent.scrollLeft;
4802
- const top = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _146 => _146.top]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _147 => _147.top]), () => ( 0))) - (parent.clientTop || 0) + parent.scrollTop;
4901
+ const canvasRect = _optionalChain([this, 'access', _138 => _138.canvas, 'access', _139 => _139.getBoundingClientRect, 'optionalCall', _140 => _140()]);
4902
+ const parentRect = _optionalChain([parent, 'access', _141 => _141.getBoundingClientRect, 'optionalCall', _142 => _142()]);
4903
+ const cssWidth = _optionalChain([canvasRect, 'optionalAccess', _143 => _143.width]) || this.canvas.clientWidth || this.width;
4904
+ const cssHeight = _optionalChain([canvasRect, 'optionalAccess', _144 => _144.height]) || this.canvas.clientHeight || this.height;
4905
+ const left = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _145 => _145.left]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _146 => _146.left]), () => ( 0))) - (parent.clientLeft || 0) + parent.scrollLeft;
4906
+ const top = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _147 => _147.top]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _148 => _148.top]), () => ( 0))) - (parent.clientTop || 0) + parent.scrollTop;
4803
4907
  const scaleX = this.width > 0 ? cssWidth / this.width : 1;
4804
4908
  const scaleY = this.height > 0 ? cssHeight / this.height : 1;
4805
4909
  const prev = this._overlayGeometry;
@@ -4938,7 +5042,7 @@ var Scene = (_class7 = class _Scene {
4938
5042
  * (and {@link respectReducedMotion} is on). `0` means uncapped.
4939
5043
  */
4940
5044
  effectiveMaxFPS() {
4941
- const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access', _148 => _148.reducedMotionQuery, 'optionalAccess', _149 => _149.matches]);
5045
+ const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access', _149 => _149.reducedMotionQuery, 'optionalAccess', _150 => _150.matches]);
4942
5046
  if (reduced)
4943
5047
  return this.maxFPS > 0 ? Math.min(this.maxFPS, REDUCED_MOTION_FPS) : REDUCED_MOTION_FPS;
4944
5048
  return this.maxFPS;
@@ -5033,7 +5137,7 @@ var Scene = (_class7 = class _Scene {
5033
5137
  * @param time - Current absolute time in milliseconds (default 0).
5034
5138
  */
5035
5139
  render(renderer, dt = 0, time = 0) {
5036
- if (_optionalChain([renderer, 'access', _150 => _150.isContextLost, 'optionalCall', _151 => _151()])) return;
5140
+ if (_optionalChain([renderer, 'access', _151 => _151.isContextLost, 'optionalCall', _152 => _152()])) return;
5037
5141
  const isMainRenderer = renderer === this.renderer;
5038
5142
  if (isMainRenderer && this.a11yRoot && this.canvas.parentElement) {
5039
5143
  const parentStyle = this.canvas.parentElement.style;
@@ -5177,7 +5281,7 @@ var Scene = (_class7 = class _Scene {
5177
5281
  }
5178
5282
  renderer.clear();
5179
5283
  if (isMainRenderer) {
5180
- _optionalChain([this, 'access', _152 => _152.pointRenderer, 'optionalAccess', _153 => _153.begin, 'call', _154 => _154()]);
5284
+ _optionalChain([this, 'access', _153 => _153.pointRenderer, 'optionalAccess', _154 => _154.begin, 'call', _155 => _155()]);
5181
5285
  }
5182
5286
  const vw = this.width;
5183
5287
  const vh = this.height;
@@ -5391,9 +5495,9 @@ var Scene = (_class7 = class _Scene {
5391
5495
  const flushT0 = this._phaseTiming ? performance.now() : 0;
5392
5496
  renderer.flush();
5393
5497
  if (isMainRenderer) {
5394
- _optionalChain([this, 'access', _155 => _155.pointRenderer, 'optionalAccess', _156 => _156.flush, 'call', _157 => _157()]);
5498
+ _optionalChain([this, 'access', _156 => _156.pointRenderer, 'optionalAccess', _157 => _157.flush, 'call', _158 => _158()]);
5395
5499
  }
5396
- _optionalChain([renderer, 'access', _158 => _158.present, 'optionalCall', _159 => _159()]);
5500
+ _optionalChain([renderer, 'access', _159 => _159.present, 'optionalCall', _160 => _160()]);
5397
5501
  if (this._phaseTiming) this._recordPhase("flush", performance.now() - flushT0);
5398
5502
  if (flushTiming) endVectoUserTiming(flushTiming);
5399
5503
  if (this._devActive) {
@@ -5405,7 +5509,7 @@ var Scene = (_class7 = class _Scene {
5405
5509
  * Export the current scene state to a lightweight, flat SVG XML string.
5406
5510
  */
5407
5511
  toSVG() {
5408
- const renderer = new (0, _chunkKHGHP2J3js.SVGRenderer)(this.width, this.height);
5512
+ const renderer = new (0, _chunkK2UGEIT6js.SVGRenderer)(this.width, this.height);
5409
5513
  this.render(renderer, 0, 0);
5410
5514
  return renderer.toXMLString();
5411
5515
  }
@@ -5674,8 +5778,8 @@ function pointInBounds(b, x, y) {
5674
5778
  function contentGridLineSignature(grid, line, projected, lineHeight, baseline, font, isFirstLine) {
5675
5779
  const parts = [
5676
5780
  // Line box: position, size, and the font that resolves its baseline.
5677
- `${_nullishCoalesce(_optionalChain([projected, 'optionalAccess', _160 => _160.x]), () => ( 0))}`,
5678
- `${_nullishCoalesce(_optionalChain([projected, 'optionalAccess', _161 => _161.y]), () => ( ""))}`,
5781
+ `${_nullishCoalesce(_optionalChain([projected, 'optionalAccess', _161 => _161.x]), () => ( 0))}`,
5782
+ `${_nullishCoalesce(_optionalChain([projected, 'optionalAccess', _162 => _162.y]), () => ( ""))}`,
5679
5783
  `${lineHeight}`,
5680
5784
  `${baseline}`,
5681
5785
  font,
@@ -5862,7 +5966,7 @@ var GridTextEntity = (_class9 = class extends _chunkZ3HFY75Rjs.Entity {
5862
5966
  updateGrid(ascii) {
5863
5967
  this.grid = ascii;
5864
5968
  this.rows = ascii.length;
5865
- this.cols = _optionalChain([ascii, 'access', _162 => _162[0], 'optionalAccess', _163 => _163.length]) || 0;
5969
+ this.cols = _optionalChain([ascii, 'access', _163 => _163[0], 'optionalAccess', _164 => _164.length]) || 0;
5866
5970
  }
5867
5971
  isPointInside(_globalX, _globalY) {
5868
5972
  return false;
@@ -5972,7 +6076,7 @@ var SplineEntity = (_class10 = class extends _chunkZ3HFY75Rjs.Entity {
5972
6076
  this.width = this.bounds.width;
5973
6077
  this.height = this.bounds.height;
5974
6078
  const isGradient = (c) => c !== null && !Array.isArray(c);
5975
- this.containsGradient = (_nullishCoalesce(_optionalChain([this, 'access', _164 => _164.doc, 'access', _165 => _165.equations, 'optionalAccess', _166 => _166.some, 'call', _167 => _167((eq) => isGradient(eq.color_rgb))]), () => ( false))) || (_nullishCoalesce(_optionalChain([this, 'access', _168 => _168.doc, 'access', _169 => _169.paths, 'optionalAccess', _170 => _170.some, 'call', _171 => _171((p) => isGradient(p.color_rgb))]), () => ( false)));
6079
+ this.containsGradient = (_nullishCoalesce(_optionalChain([this, 'access', _165 => _165.doc, 'access', _166 => _166.equations, 'optionalAccess', _167 => _167.some, 'call', _168 => _168((eq) => isGradient(eq.color_rgb))]), () => ( false))) || (_nullishCoalesce(_optionalChain([this, 'access', _169 => _169.doc, 'access', _170 => _170.paths, 'optionalAccess', _171 => _171.some, 'call', _172 => _172((p) => isGradient(p.color_rgb))]), () => ( false)));
5976
6080
  this.interactive = true;
5977
6081
  }
5978
6082
  computeBounds() {
@@ -6466,8 +6570,12 @@ var DOMPortalEntity = (_class11 = class extends _chunkZ3HFY75Rjs.Entity {
6466
6570
  }, _class11);
6467
6571
 
6468
6572
  // src/index.ts
6469
- Scene.registerWebGLPointRendererCreator(_chunkKHGHP2J3js.createWebGLPointRenderer);
6470
- Scene.registerWebGPUParticleSystemManager(_chunkKHGHP2J3js.WebGPUParticleSystemManager);
6573
+ Scene.registerWebGLPointRendererCreator(_chunkK2UGEIT6js.createWebGLPointRenderer);
6574
+ Scene.registerWebGPUParticleSystemManager(_chunkK2UGEIT6js.WebGPUParticleSystemManager);
6575
+
6576
+
6577
+
6578
+
6471
6579
 
6472
6580
 
6473
6581
 
@@ -6507,4 +6615,4 @@ Scene.registerWebGPUParticleSystemManager(_chunkKHGHP2J3js.WebGPUParticleSystemM
6507
6615
 
6508
6616
 
6509
6617
 
6510
- exports.CanvasRenderer = _chunkKHGHP2J3js.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Entity = _chunkZ3HFY75Rjs.Entity; exports.GlyphRasterAtlas = _chunkKHGHP2J3js.GlyphRasterAtlas; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.MSDFTextEntity = _chunkZ3HFY75Rjs.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.Rect = Rect; exports.SVGEntity = _chunkZ3HFY75Rjs.SVGEntity; exports.SVGRenderer = _chunkKHGHP2J3js.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkKHGHP2J3js.TextRasterCache; exports.VECTO_USER_TIMING = VECTO_USER_TIMING; exports.VectoJSEvent = _chunkZ3HFY75Rjs.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkKHGHP2J3js.WebGPUParticleSystemManager; exports.beginVectoUserTiming = beginVectoUserTiming; exports.createWebGLPointRenderer = _chunkKHGHP2J3js.createWebGLPointRenderer; exports.endVectoUserTiming = endVectoUserTiming; exports.isSafeUrl = _chunkKHGHP2J3js.isSafeUrl; exports.loadSpline = loadSpline; exports.measureVectoUserTiming = measureVectoUserTiming; exports.parseColorToRGBA = _chunkKHGHP2J3js.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkKHGHP2J3js.sanitizeUrl;
6618
+ exports.CanvasRenderer = _chunkK2UGEIT6js.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Entity = _chunkZ3HFY75Rjs.Entity; exports.GlyphRasterAtlas = _chunkK2UGEIT6js.GlyphRasterAtlas; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.MSDFTextEntity = _chunkZ3HFY75Rjs.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.Rect = Rect; exports.SCENE_OPTION_KEYS = SCENE_OPTION_KEYS; exports.SVGEntity = _chunkZ3HFY75Rjs.SVGEntity; exports.SVGRenderer = _chunkK2UGEIT6js.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkK2UGEIT6js.TextRasterCache; exports.VECTO_USER_TIMING = VECTO_USER_TIMING; exports.VectoJSEvent = _chunkZ3HFY75Rjs.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkK2UGEIT6js.WebGPUParticleSystemManager; exports.beginVectoUserTiming = beginVectoUserTiming; exports.createWebGLPointRenderer = _chunkK2UGEIT6js.createWebGLPointRenderer; exports.endVectoUserTiming = endVectoUserTiming; exports.installRendererDevTraps = _chunkK2UGEIT6js.installRendererDevTraps; exports.isRendererDevMode = _chunkK2UGEIT6js.isRendererDevMode; exports.isSafeUrl = _chunkK2UGEIT6js.isSafeUrl; exports.loadSpline = loadSpline; exports.measureVectoUserTiming = measureVectoUserTiming; exports.parseColorToRGBA = _chunkK2UGEIT6js.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkK2UGEIT6js.sanitizeUrl; exports.setRendererDevMode = _chunkK2UGEIT6js.setRendererDevMode;
package/dist/index.mjs CHANGED
@@ -5,10 +5,13 @@ import {
5
5
  TextRasterCache,
6
6
  WebGPUParticleSystemManager,
7
7
  createWebGLPointRenderer,
8
+ installRendererDevTraps,
9
+ isRendererDevMode,
8
10
  isSafeUrl,
9
11
  parseColorToRGBA,
10
- sanitizeUrl
11
- } from "./chunk-I4WQY7CJ.mjs";
12
+ sanitizeUrl,
13
+ setRendererDevMode
14
+ } from "./chunk-TI5EMJGL.mjs";
12
15
  import {
13
16
  Entity,
14
17
  MSDFTextEntity,
@@ -1370,6 +1373,57 @@ function rebaseChildBox(parent, parentOriginX, parentOriginY, child, childOrigin
1370
1373
  REBASED_BOX.matrix = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0)`;
1371
1374
  return REBASED_BOX;
1372
1375
  }
1376
+ var SCENE_OPTION_KEYS = [
1377
+ "a11ySyncInterval",
1378
+ "autoThrottle",
1379
+ "contentProjection",
1380
+ "contentProjectionMargin",
1381
+ "debugA11y",
1382
+ "disableWindowResize",
1383
+ "maxDPR",
1384
+ "maxFPS",
1385
+ "particleBackend",
1386
+ "pointBackend",
1387
+ "readingDirection",
1388
+ "renderer",
1389
+ "renderMode",
1390
+ "respectReducedMotion",
1391
+ "userTiming"
1392
+ ];
1393
+ var SCENE_FIELD_NOT_OPTION = {
1394
+ devMode: "set the static `Scene.devMode = true` before constructing"
1395
+ };
1396
+ function editDistance(a, b) {
1397
+ const m = a.length;
1398
+ const n = b.length;
1399
+ if (m === 0 || n === 0) return Math.max(m, n);
1400
+ let prev = Array.from({ length: n + 1 }, (_, i) => i);
1401
+ const curr = Array.from({ length: n + 1 });
1402
+ for (let i = 1; i <= m; i++) {
1403
+ curr[0] = i;
1404
+ for (let j = 1; j <= n; j++) {
1405
+ const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
1406
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
1407
+ }
1408
+ prev = curr.slice();
1409
+ }
1410
+ return prev[n];
1411
+ }
1412
+ function closestOptionKey(key) {
1413
+ const lower = key.toLowerCase();
1414
+ let best;
1415
+ let bestScore = Infinity;
1416
+ for (const candidate of SCENE_OPTION_KEYS) {
1417
+ if (candidate.toLowerCase() === lower) return candidate;
1418
+ const d = editDistance(lower, candidate.toLowerCase());
1419
+ if (d < bestScore) {
1420
+ bestScore = d;
1421
+ best = candidate;
1422
+ }
1423
+ }
1424
+ const limit = Math.max(2, Math.floor(key.length / 3));
1425
+ return bestScore <= limit ? best : void 0;
1426
+ }
1373
1427
  var REDUCED_MOTION_FPS = 30;
1374
1428
  function parseInlinePx(value) {
1375
1429
  if (!value || !value.endsWith("px")) return null;
@@ -2788,10 +2842,26 @@ var Scene = class _Scene {
2788
2842
  //
2789
2843
  // Checks run once every ~120 frames (~2s at 60fps) to keep overhead
2790
2844
  // negligible even when dev mode is on.
2791
- /** Toggle development-mode runtime warnings globally. */
2792
- static devMode = false;
2845
+ static _devMode = false;
2846
+ /**
2847
+ * Toggle development-mode runtime warnings globally.
2848
+ *
2849
+ * An accessor rather than a plain field so the renderer layer learns about it
2850
+ * immediately: renderers cannot import `Scene` (the dependency runs
2851
+ * `Scene → renderer`), and their diagnostics are installed per instance at
2852
+ * construction. A plain field would only reach them the next time a `Scene`
2853
+ * happened to be built, which made a directly-constructed `CanvasRenderer`
2854
+ * silently untrapped.
2855
+ */
2856
+ static get devMode() {
2857
+ return _Scene._devMode;
2858
+ }
2859
+ static set devMode(active) {
2860
+ _Scene._devMode = active;
2861
+ setRendererDevMode(active);
2862
+ }
2793
2863
  static _devModeDetected() {
2794
- if (_Scene.devMode) return true;
2864
+ if (_Scene._devMode) return true;
2795
2865
  const gp = typeof globalThis !== "undefined" ? globalThis : void 0;
2796
2866
  if (gp?.__DEV__) return true;
2797
2867
  if (gp?.process?.env?.NODE_ENV === "development") return true;
@@ -2803,6 +2873,37 @@ var Scene = class _Scene {
2803
2873
  if (!this._devActive) return;
2804
2874
  console.warn(`[vectojs/dev] ${message}`);
2805
2875
  }
2876
+ /**
2877
+ * Warn (dev mode only) about `SceneOptions` keys this version does not read.
2878
+ *
2879
+ * A structural type makes an unrecognized key a silent no-op, and TypeScript
2880
+ * only rejects one when the object literal sits inline at the call site — not
2881
+ * when options are built dynamically, and never in plain JS. Since the
2882
+ * failure mode is "the option appears to work", a runtime check is the only
2883
+ * thing that surfaces it.
2884
+ *
2885
+ * Dev-mode only on purpose: the loop is O(keys × known keys) with an edit
2886
+ * distance per pair, which is nothing at construction but is still pure
2887
+ * overhead in production, where the value has already been shipped.
2888
+ */
2889
+ _warnUnknownOptions(options) {
2890
+ if (!this._devActive) return;
2891
+ const known = new Set(SCENE_OPTION_KEYS);
2892
+ for (const key of Object.keys(options)) {
2893
+ if (known.has(key)) continue;
2894
+ const fieldHint = SCENE_FIELD_NOT_OPTION[key];
2895
+ if (fieldHint) {
2896
+ this._devWarn(
2897
+ `SceneOptions: \`${key}\` is not a constructor option \u2014 ${fieldHint}. Passing it here has no effect.`
2898
+ );
2899
+ continue;
2900
+ }
2901
+ const suggestion = closestOptionKey(key);
2902
+ this._devWarn(
2903
+ `SceneOptions: unknown option \`${key}\` is ignored.` + (suggestion ? ` Did you mean \`${suggestion}\`?` : "")
2904
+ );
2905
+ }
2906
+ }
2806
2907
  /** @internal Periodic dev checks — called once per frame in dev mode. */
2807
2908
  _devRunChecks() {
2808
2909
  this._devFrameCount++;
@@ -2867,7 +2968,10 @@ var Scene = class _Scene {
2867
2968
  this.contentProjectionEnabled = options.contentProjection ?? true;
2868
2969
  this.contentProjectionMargin = options.contentProjectionMargin;
2869
2970
  this.readingDirection = options.readingDirection ?? "ltr";
2971
+ this.renderMode = options.renderMode ?? "always";
2870
2972
  this._devActive = _Scene._devModeDetected();
2973
+ setRendererDevMode(this._devActive);
2974
+ this._warnUnknownOptions(options);
2871
2975
  this.reducedMotionQuery = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
2872
2976
  if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
2873
2977
  this.forcedColorsQuery = window.matchMedia("(forced-colors: active)");
@@ -6488,6 +6592,7 @@ export {
6488
6592
  PARTICLE_STRIDE_FLOATS,
6489
6593
  REDUCED_MOTION_FPS,
6490
6594
  Rect,
6595
+ SCENE_OPTION_KEYS,
6491
6596
  SVGEntity,
6492
6597
  SVGRenderer,
6493
6598
  Scene,
@@ -6500,10 +6605,13 @@ export {
6500
6605
  beginVectoUserTiming,
6501
6606
  createWebGLPointRenderer,
6502
6607
  endVectoUserTiming,
6608
+ installRendererDevTraps,
6609
+ isRendererDevMode,
6503
6610
  isSafeUrl,
6504
6611
  loadSpline,
6505
6612
  measureVectoUserTiming,
6506
6613
  parseColorToRGBA,
6507
6614
  polySegmentToBezier,
6508
- sanitizeUrl
6615
+ sanitizeUrl,
6616
+ setRendererDevMode
6509
6617
  };
@@ -299,3 +299,25 @@ export interface IRenderer {
299
299
  */
300
300
  onContextRestored?(cb: () => void): void;
301
301
  }
302
+ /**
303
+ * Publish dev-mode state to the renderer layer. Called by `Scene`; also usable
304
+ * directly when a renderer is constructed without one.
305
+ */
306
+ export declare function setRendererDevMode(active: boolean): void;
307
+ /** Whether renderer dev diagnostics are currently enabled. */
308
+ export declare function isRendererDevMode(): boolean;
309
+ /**
310
+ * Install dev-mode-only accessors that warn when Canvas2D style *properties* are
311
+ * assigned on a renderer instead of calling the corresponding method.
312
+ *
313
+ * Call once per renderer instance, from its constructor. No-ops outside dev
314
+ * mode, so the accessors never exist in production.
315
+ * Each trapped property warns on its **first** write only — a per-frame
316
+ * assignment would otherwise flood the console at 240fps — and then stores and
317
+ * returns the value like a plain field, so a warned write is never a hard break
318
+ * for code that assigns and reads back.
319
+ *
320
+ * Skips any property the instance genuinely defines, so a backend that really
321
+ * does expose one keeps its own behavior.
322
+ */
323
+ export declare function installRendererDevTraps(renderer: object, label: string): void;
package/dist/renderer.js CHANGED
@@ -7,13 +7,19 @@
7
7
 
8
8
 
9
9
 
10
- var _chunkKHGHP2J3js = require('./chunk-KHGHP2J3.js');
11
10
 
12
11
 
13
12
 
13
+ var _chunkK2UGEIT6js = require('./chunk-K2UGEIT6.js');
14
14
 
15
15
 
16
16
 
17
17
 
18
18
 
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;
19
+
20
+
21
+
22
+
23
+
24
+
25
+ exports.CanvasRenderer = _chunkK2UGEIT6js.CanvasRenderer; exports.GlyphRasterAtlas = _chunkK2UGEIT6js.GlyphRasterAtlas; exports.SVGRenderer = _chunkK2UGEIT6js.SVGRenderer; exports.TextRasterCache = _chunkK2UGEIT6js.TextRasterCache; exports.WebGPUParticleSystemManager = _chunkK2UGEIT6js.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkK2UGEIT6js.createWebGLPointRenderer; exports.installRendererDevTraps = _chunkK2UGEIT6js.installRendererDevTraps; exports.isRendererDevMode = _chunkK2UGEIT6js.isRendererDevMode; exports.parseColorToRGBA = _chunkK2UGEIT6js.parseColorToRGBA; exports.setRendererDevMode = _chunkK2UGEIT6js.setRendererDevMode;
package/dist/renderer.mjs CHANGED
@@ -5,8 +5,11 @@ import {
5
5
  TextRasterCache,
6
6
  WebGPUParticleSystemManager,
7
7
  createWebGLPointRenderer,
8
- parseColorToRGBA
9
- } from "./chunk-I4WQY7CJ.mjs";
8
+ installRendererDevTraps,
9
+ isRendererDevMode,
10
+ parseColorToRGBA,
11
+ setRendererDevMode
12
+ } from "./chunk-TI5EMJGL.mjs";
10
13
  export {
11
14
  CanvasRenderer,
12
15
  GlyphRasterAtlas,
@@ -14,5 +17,8 @@ export {
14
17
  TextRasterCache,
15
18
  WebGPUParticleSystemManager,
16
19
  createWebGLPointRenderer,
17
- parseColorToRGBA
20
+ installRendererDevTraps,
21
+ isRendererDevMode,
22
+ parseColorToRGBA,
23
+ setRendererDevMode
18
24
  };
@@ -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
  /**
@@ -949,12 +979,37 @@ export declare class Scene {
949
979
  private pointerEventTarget;
950
980
  private hasWarnedZeroSize;
951
981
  private fontLoadHandler;
952
- /** Toggle development-mode runtime warnings globally. */
953
- static devMode: boolean;
982
+ private static _devMode;
983
+ /**
984
+ * Toggle development-mode runtime warnings globally.
985
+ *
986
+ * An accessor rather than a plain field so the renderer layer learns about it
987
+ * immediately: renderers cannot import `Scene` (the dependency runs
988
+ * `Scene → renderer`), and their diagnostics are installed per instance at
989
+ * construction. A plain field would only reach them the next time a `Scene`
990
+ * happened to be built, which made a directly-constructed `CanvasRenderer`
991
+ * silently untrapped.
992
+ */
993
+ static get devMode(): boolean;
994
+ static set devMode(active: boolean);
954
995
  private static _devModeDetected;
955
996
  private _devActive;
956
997
  private _devFrameCount;
957
998
  private _devWarn;
999
+ /**
1000
+ * Warn (dev mode only) about `SceneOptions` keys this version does not read.
1001
+ *
1002
+ * A structural type makes an unrecognized key a silent no-op, and TypeScript
1003
+ * only rejects one when the object literal sits inline at the call site — not
1004
+ * when options are built dynamically, and never in plain JS. Since the
1005
+ * failure mode is "the option appears to work", a runtime check is the only
1006
+ * thing that surfaces it.
1007
+ *
1008
+ * Dev-mode only on purpose: the loop is O(keys × known keys) with an edit
1009
+ * distance per pair, which is nothing at construction but is still pure
1010
+ * overhead in production, where the value has already been shipped.
1011
+ */
1012
+ private _warnUnknownOptions;
958
1013
  /** @internal Periodic dev checks — called once per frame in dev mode. */
959
1014
  private _devRunChecks;
960
1015
  constructor(canvas: HTMLCanvasElement, options?: SceneOptions);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "1.25.0",
3
+ "version": "1.27.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },