@vectojs/core 0.2.7 → 0.2.8

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/index.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  LayoutResultBuffer,
4
4
  computeLineSegments,
5
5
  createCanvasMeasurer
6
- } from "./chunk-SSOP3F5F.mjs";
6
+ } from "./chunk-FNXOS7IA.mjs";
7
7
  import {
8
8
  CanvasRenderer,
9
9
  SVGRenderer,
@@ -12,7 +12,7 @@ import {
12
12
  isSafeUrl,
13
13
  parseColorToRGBA,
14
14
  sanitizeUrl
15
- } from "./chunk-HVJHXIKW.mjs";
15
+ } from "./chunk-YBW7ECZP.mjs";
16
16
  import {
17
17
  Easing,
18
18
  Entity,
@@ -24,7 +24,7 @@ import {
24
24
  TweenDriver,
25
25
  VectoJSEvent,
26
26
  isTweenConfig
27
- } from "./chunk-YVN3PJRC.mjs";
27
+ } from "./chunk-G6GQO7W2.mjs";
28
28
  import {
29
29
  ArabicShaper,
30
30
  BidiResolver,
@@ -358,6 +358,11 @@ var Scene = class _Scene {
358
358
  /** DOM nodes mirroring static text content, keyed by entity id. */
359
359
  contentElements = /* @__PURE__ */ new Map();
360
360
  contentProjectionEnabled = true;
361
+ // Animation/interactive flags collected during the render walk (tree-walk
362
+ // fusion): the loop reads last frame's answers instead of re-walking the
363
+ // tree up to 4× per tick. Start true so the first tick stays conservative.
364
+ frameHadAnimation = true;
365
+ frameHadInteractive = true;
361
366
  resizeHandler;
362
367
  focusedA11yElement = null;
363
368
  caretBlinkTimer = null;
@@ -711,6 +716,18 @@ var Scene = class _Scene {
711
716
  * Essential for deterministic rendering (e.g. video export).
712
717
  * Note: You should call `scene.stop()` before using this to avoid conflict with the rAF loop.
713
718
  */
719
+ /**
720
+ * The scene-graph root entity. Exposed read-only for tooling — the devtools
721
+ * inspector walks it to build the Virtual Math Tree view. Mutate the graph
722
+ * through {@link add}/{@link remove}, not by editing this node directly.
723
+ */
724
+ get rootEntity() {
725
+ return this.root;
726
+ }
727
+ /** The overlay layer root (see {@link showOverlay}), read-only for tooling. */
728
+ get overlayRootEntity() {
729
+ return this.overlayRoot;
730
+ }
714
731
  step(dt) {
715
732
  const time = this.lastTime + dt;
716
733
  this.lastTime = time;
@@ -727,21 +744,7 @@ var Scene = class _Scene {
727
744
  this.dirty = true;
728
745
  }
729
746
  /** True when any node in the subtree has a pending animation. */
730
- hasAnyPendingAnimation(node) {
731
- if (node.hasPendingAnimations()) return true;
732
- for (const child of node.children) {
733
- if (this.hasAnyPendingAnimation(child)) return true;
734
- }
735
- return false;
736
- }
737
747
  /** True when any node in the subtree is interactive (drives a11y sync). */
738
- hasAnyInteractive(node) {
739
- if (node.interactive) return true;
740
- for (const child of node.children) {
741
- if (this.hasAnyInteractive(child)) return true;
742
- }
743
- return false;
744
- }
745
748
  syncA11y(node) {
746
749
  if (!this.a11yRoot) return;
747
750
  if (node.isDOMPortal) {
@@ -1276,7 +1279,7 @@ var Scene = class _Scene {
1276
1279
  loop(time) {
1277
1280
  if (!this.isRunning) return;
1278
1281
  let cap = this.effectiveMaxFPS();
1279
- const isIdle = !this.dirty && !this.hasAnyPendingAnimation(this.root) && !this.hasAnyPendingAnimation(this.overlayRoot);
1282
+ const isIdle = !this.dirty && !this.frameHadAnimation;
1280
1283
  if (isIdle && this.autoThrottle && this.renderMode === "always" && this.maxFPS > 0) {
1281
1284
  cap = Math.min(cap, 2);
1282
1285
  }
@@ -1292,8 +1295,8 @@ var Scene = class _Scene {
1292
1295
  }
1293
1296
  this.dirty = false;
1294
1297
  this.render(this.renderer, dt, time);
1295
- const hasActiveAnimation = this.hasAnyPendingAnimation(this.root) || this.hasAnyPendingAnimation(this.overlayRoot);
1296
- const hasInteractive = this.hasAnyInteractive(this.root) || this.hasAnyInteractive(this.overlayRoot);
1298
+ const hasActiveAnimation = this.frameHadAnimation;
1299
+ const hasInteractive = this.frameHadInteractive;
1297
1300
  const wantsContentSync = this.contentProjectionEnabled;
1298
1301
  const shouldSyncInterval = this.a11ySyncInterval <= 0 || time - this.lastA11ySync >= this.a11ySyncInterval;
1299
1302
  if ((hasInteractive || this.a11yElements.size > 0 || wantsContentSync) && (shouldSyncInterval || this.a11yPendingSyncAfterAnimation)) {
@@ -1450,8 +1453,14 @@ var Scene = class _Scene {
1450
1453
  }
1451
1454
  const vw = this.width;
1452
1455
  const vh = this.height;
1456
+ let walkHadAnimation = false;
1457
+ let walkHadInteractive = false;
1453
1458
  const renderNode = (node, pa, pb, pc, pd, pe, pf, parentOpacity) => {
1454
- if (isMainRenderer) node.update(dt, time);
1459
+ if (isMainRenderer) {
1460
+ node.update(dt, time);
1461
+ if (!walkHadAnimation && node.hasPendingAnimations()) walkHadAnimation = true;
1462
+ if (!walkHadInteractive && node.interactive) walkHadInteractive = true;
1463
+ }
1455
1464
  const cos = Math.cos(node.rotation);
1456
1465
  const sin = Math.sin(node.rotation);
1457
1466
  const te = pa * node.x + pc * node.y + pe;
@@ -1571,7 +1580,11 @@ var Scene = class _Scene {
1571
1580
  for (const overlay of this.overlayRoot.children) {
1572
1581
  renderNode(overlay, 1, 0, 0, 1, 0, 0, 1);
1573
1582
  }
1574
- if (isMainRenderer) this.reconcilePortals();
1583
+ if (isMainRenderer) {
1584
+ this.frameHadAnimation = walkHadAnimation;
1585
+ this.frameHadInteractive = walkHadInteractive;
1586
+ this.reconcilePortals();
1587
+ }
1575
1588
  renderer.flush();
1576
1589
  if (isMainRenderer) {
1577
1590
  this.pointRenderer?.flush();
@@ -1835,6 +1848,26 @@ var TextEntity = class extends Entity {
1835
1848
  this.applyLayout();
1836
1849
  return this;
1837
1850
  }
1851
+ /**
1852
+ * Set horizontal alignment (`'justify'` stretches wrapped lines flush to
1853
+ * the wrap width; the last line stays ragged) and reflow.
1854
+ */
1855
+ setTextAlign(align) {
1856
+ this.layout.textAlign = align;
1857
+ this.applyLayout();
1858
+ return this;
1859
+ }
1860
+ /**
1861
+ * Plug a hyphenator (word → parts). Break opportunities are baked in during
1862
+ * the cold pass, so this re-prepares the current text. Soft hyphens
1863
+ * (U+00AD) in the text work without one.
1864
+ */
1865
+ setHyphenator(fn) {
1866
+ this.layout.hyphenate = fn;
1867
+ this.prepared = this.layout.prepare(this.text, this.atlas, this.fontSize);
1868
+ this.applyLayout();
1869
+ return this;
1870
+ }
1838
1871
  /** Hot pass: place the cached {@link PreparedText} and refresh the a11y box. */
1839
1872
  applyLayout() {
1840
1873
  const result = this.layout.layoutPrepared(this.prepared);
@@ -88,6 +88,11 @@ export interface PreparedWord {
88
88
  isWordLike: boolean | undefined;
89
89
  /** Pre-computed `word.trim().length === 0`. */
90
90
  isWhitespace: boolean;
91
+ /**
92
+ * Glyph indices where the word may break with a visible hyphen — from
93
+ * soft hyphens (U+00AD) in the source or the engine's `hyphenate` hook.
94
+ */
95
+ breakPoints?: number[];
91
96
  }
92
97
  /** A measured paragraph; `isEmpty` marks a blank line (forced newline). */
93
98
  export interface PreparedParagraph {
@@ -107,6 +112,8 @@ export interface PreparedText {
107
112
  paragraphs: PreparedParagraph[];
108
113
  fontSize: number;
109
114
  fallbackToCanvas?: boolean;
115
+ /** Advance width of '-' at `fontSize`, for wrap-time hyphen insertion. */
116
+ hyphenWidth?: number;
110
117
  }
111
118
  /**
112
119
  * A rectangular region (in the text's local coordinate space) that text must
@@ -140,6 +147,13 @@ export declare function computeLineSegments(top: number, bottom: number, maxWidt
140
147
  */
141
148
  export declare class LayoutEngine {
142
149
  maxWidth: number;
150
+ /**
151
+ * Horizontal alignment. `'justify'` stretches inter-word spaces (or, for
152
+ * space-less CJK lines, inter-character gaps) so wrapped lines end flush at
153
+ * `maxWidth`; the last line of each paragraph stays ragged. Only applies to
154
+ * the object layout path without exclusion shapes.
155
+ */
156
+ textAlign: 'left' | 'justify';
143
157
  maxHeight: number;
144
158
  preserveLeadingSpaces: boolean;
145
159
  private wordSegmenter;
@@ -150,6 +164,16 @@ export declare class LayoutEngine {
150
164
  private richParagraphCache;
151
165
  private lastAtlas;
152
166
  private measurer;
167
+ private _hyphenate;
168
+ /**
169
+ * Optional hyphenator: given a word, return its break parts (e.g.
170
+ * `['hyphen', 'ation']`). Used at wrap time when a word doesn't fit; a
171
+ * visible '-' is drawn at the chosen break. Soft hyphens (U+00AD) in the
172
+ * source work without any hyphenator. Setting this clears the prepared
173
+ * caches (break opportunities are baked in during prepare()).
174
+ */
175
+ get hyphenate(): ((word: string) => string[]) | null;
176
+ set hyphenate(fn: ((word: string) => string[]) | null);
153
177
  constructor(maxWidth: number, maxHeight: number, measurer?: GlyphMeasurer | null);
154
178
  private getWordSegments;
155
179
  /**
package/dist/layout.js CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
 
5
5
 
6
- var _chunkBWLNEJJWjs = require('./chunk-BWLNEJJW.js');
6
+ var _chunkXLZDOFSBjs = require('./chunk-XLZDOFSB.js');
7
7
 
8
8
 
9
9
  var _chunkCTZQOM5Zjs = require('./chunk-CTZQOM5Z.js');
@@ -13,4 +13,4 @@ var _chunkCTZQOM5Zjs = require('./chunk-CTZQOM5Z.js');
13
13
 
14
14
 
15
15
 
16
- exports.LayoutEngine = _chunkBWLNEJJWjs.LayoutEngine; exports.LayoutResultBuffer = _chunkBWLNEJJWjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunkCTZQOM5Zjs.LayoutWorkerManager; exports.computeLineSegments = _chunkBWLNEJJWjs.computeLineSegments; exports.createCanvasMeasurer = _chunkBWLNEJJWjs.createCanvasMeasurer;
16
+ exports.LayoutEngine = _chunkXLZDOFSBjs.LayoutEngine; exports.LayoutResultBuffer = _chunkXLZDOFSBjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunkCTZQOM5Zjs.LayoutWorkerManager; exports.computeLineSegments = _chunkXLZDOFSBjs.computeLineSegments; exports.createCanvasMeasurer = _chunkXLZDOFSBjs.createCanvasMeasurer;
package/dist/layout.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  LayoutResultBuffer,
4
4
  computeLineSegments,
5
5
  createCanvasMeasurer
6
- } from "./chunk-SSOP3F5F.mjs";
6
+ } from "./chunk-FNXOS7IA.mjs";
7
7
  import {
8
8
  LayoutWorkerManager
9
9
  } from "./chunk-STWPTWO4.mjs";
package/dist/renderer.js CHANGED
@@ -4,11 +4,11 @@
4
4
 
5
5
 
6
6
 
7
- var _chunkGTQRCY6Wjs = require('./chunk-GTQRCY6W.js');
7
+ var _chunkXB3ZY5MFjs = require('./chunk-XB3ZY5MF.js');
8
8
 
9
9
 
10
10
 
11
11
 
12
12
 
13
13
 
14
- exports.CanvasRenderer = _chunkGTQRCY6Wjs.CanvasRenderer; exports.SVGRenderer = _chunkGTQRCY6Wjs.SVGRenderer; exports.WebGPUParticleSystemManager = _chunkGTQRCY6Wjs.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkGTQRCY6Wjs.createWebGLPointRenderer; exports.parseColorToRGBA = _chunkGTQRCY6Wjs.parseColorToRGBA;
14
+ exports.CanvasRenderer = _chunkXB3ZY5MFjs.CanvasRenderer; exports.SVGRenderer = _chunkXB3ZY5MFjs.SVGRenderer; exports.WebGPUParticleSystemManager = _chunkXB3ZY5MFjs.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkXB3ZY5MFjs.createWebGLPointRenderer; exports.parseColorToRGBA = _chunkXB3ZY5MFjs.parseColorToRGBA;
package/dist/renderer.mjs CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  WebGPUParticleSystemManager,
5
5
  createWebGLPointRenderer,
6
6
  parseColorToRGBA
7
- } from "./chunk-HVJHXIKW.mjs";
7
+ } from "./chunk-YBW7ECZP.mjs";
8
8
  export {
9
9
  CanvasRenderer,
10
10
  SVGRenderer,
package/dist/text.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkDWMQYHNCjs = require('./chunk-DWMQYHNC.js');
5
+ var _chunkXRWZS2DMjs = require('./chunk-XRWZS2DM.js');
6
6
 
7
7
 
8
8
 
@@ -13,4 +13,4 @@ var _chunkCTZQOM5Zjs = require('./chunk-CTZQOM5Z.js');
13
13
 
14
14
 
15
15
 
16
- exports.ArabicShaper = _chunkCTZQOM5Zjs.ArabicShaper; exports.BidiResolver = _chunkCTZQOM5Zjs.BidiResolver; exports.MSDFFont = _chunkDWMQYHNCjs.MSDFFont; exports.MSDFTextEntity = _chunkDWMQYHNCjs.MSDFTextEntity; exports.SVGEntity = _chunkDWMQYHNCjs.SVGEntity;
16
+ exports.ArabicShaper = _chunkCTZQOM5Zjs.ArabicShaper; exports.BidiResolver = _chunkCTZQOM5Zjs.BidiResolver; exports.MSDFFont = _chunkXRWZS2DMjs.MSDFFont; exports.MSDFTextEntity = _chunkXRWZS2DMjs.MSDFTextEntity; exports.SVGEntity = _chunkXRWZS2DMjs.SVGEntity;
package/dist/text.mjs CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  MSDFFont,
3
3
  MSDFTextEntity,
4
4
  SVGEntity
5
- } from "./chunk-YVN3PJRC.mjs";
5
+ } from "./chunk-G6GQO7W2.mjs";
6
6
  import {
7
7
  ArabicShaper,
8
8
  BidiResolver
@@ -158,6 +158,8 @@ export declare class Scene {
158
158
  /** DOM nodes mirroring static text content, keyed by entity id. */
159
159
  private contentElements;
160
160
  private contentProjectionEnabled;
161
+ private frameHadAnimation;
162
+ private frameHadInteractive;
161
163
  private resizeHandler;
162
164
  private focusedA11yElement;
163
165
  private caretBlinkTimer;
@@ -267,6 +269,14 @@ export declare class Scene {
267
269
  * Essential for deterministic rendering (e.g. video export).
268
270
  * Note: You should call `scene.stop()` before using this to avoid conflict with the rAF loop.
269
271
  */
272
+ /**
273
+ * The scene-graph root entity. Exposed read-only for tooling — the devtools
274
+ * inspector walks it to build the Virtual Math Tree view. Mutate the graph
275
+ * through {@link add}/{@link remove}, not by editing this node directly.
276
+ */
277
+ get rootEntity(): Entity;
278
+ /** The overlay layer root (see {@link showOverlay}), read-only for tooling. */
279
+ get overlayRootEntity(): Entity;
270
280
  step(dt: number): void;
271
281
  /**
272
282
  * Mark the scene as needing a redraw on the next frame.
@@ -276,9 +286,7 @@ export declare class Scene {
276
286
  */
277
287
  markDirty(): void;
278
288
  /** True when any node in the subtree has a pending animation. */
279
- private hasAnyPendingAnimation;
280
289
  /** True when any node in the subtree is interactive (drives a11y sync). */
281
- private hasAnyInteractive;
282
290
  private syncA11y;
283
291
  /**
284
292
  * Mirror one entity's static text ({@link Entity.getContentProjection}) as a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },