@vectojs/core 1.15.0 → 1.16.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-64UFEHOJ.mjs → chunk-AQTO7OSU.mjs} +246 -32
- package/dist/{chunk-J6NHSFIE.js → chunk-DUYB4GX4.js} +285 -69
- package/dist/{chunk-BEUIB3U7.js → chunk-IYPLG4Z4.js} +93 -43
- package/dist/{chunk-L5BCKFQE.mjs → chunk-JFU56BX4.mjs} +48 -0
- package/dist/index.js +2144 -325
- package/dist/index.mjs +1966 -148
- package/dist/layout.js +3 -1
- package/dist/renderer/CanvasRenderer.d.ts +22 -0
- package/dist/renderer/IRenderer.d.ts +13 -0
- package/dist/renderer.js +4 -3
- package/dist/renderer.mjs +1 -1
- package/dist/text/MSDFTextEntity.d.ts +29 -0
- package/dist/text.js +4 -3
- package/dist/text.mjs +1 -1
- package/dist/tree/ComputeParticleEntity.d.ts +18 -0
- package/dist/tree/DOMPortalEntity.d.ts +18 -0
- package/dist/tree/Entity.d.ts +121 -5
- package/dist/tree/Scene.d.ts +380 -0
- package/dist/wasm/anim-backend.d.ts +86 -0
- package/dist/wasm/asset.d.ts +25 -0
- package/dist/wasm/asset.js +7 -0
- package/dist/wasm/asset.mjs +5 -0
- package/dist/wasm/backend.d.ts +154 -0
- package/dist/wasm/hit-backend.d.ts +92 -0
- package/dist/wasm/hit-store.d.ts +48 -0
- package/dist/wasm/particle-backend.d.ts +104 -0
- package/dist/wasm/scene-store.d.ts +28 -0
- package/dist/wasm/soa.d.ts +146 -0
- package/dist/wasm/vectojs_core.wasm +0 -0
- package/package.json +15 -8
|
@@ -1,4 +1,6 @@
|
|
|
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
|
|
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;const __vecto_cjs_url=require("url").pathToFileURL(__filename).href;
|
|
2
|
+
|
|
3
|
+
// src/tree/Entity.ts
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
|
|
@@ -146,8 +148,42 @@ var Entity = (_class2 = class {
|
|
|
146
148
|
// Danmaku hot loop), so a bare `entity.x = v` is one boolean check + field write.
|
|
147
149
|
__init11() {this._hasTransitions = false}
|
|
148
150
|
__init12() {this._transitions = null}
|
|
149
|
-
|
|
151
|
+
// Lazily allocated: null until first use. A scene of many passive entities
|
|
152
|
+
// (particles, data points) never touches these, so paying an empty-Map/array
|
|
153
|
+
// allocation per entity in the constructor is pure waste at scale.
|
|
154
|
+
__init13() {this._drivers = null}
|
|
150
155
|
__init14() {this._mounted = false}
|
|
156
|
+
__init15() {this._destroyed = false}
|
|
157
|
+
// Frame this entity's active drivers were last advanced by Scene's batched
|
|
158
|
+
// WASM animation pass (see Scene._tickBatchedDrivers), or -1 if never. When
|
|
159
|
+
// it equals the current frame, tickDrivers() must skip its own tick loop —
|
|
160
|
+
// otherwise a driver already advanced by the batch pass would be ticked a
|
|
161
|
+
// second time by the normal per-entity update() walk in the same frame.
|
|
162
|
+
// Irrelevant (and harmless) for entities never touched by WASM batching.
|
|
163
|
+
__init16() {this._driversTickedFrame = -1}
|
|
164
|
+
// Cached cos/sin, recomputed only when rotation actually changes. renderNode
|
|
165
|
+
// and getWorldTransform() both read this instead of calling Math.cos/sin per
|
|
166
|
+
// entity per frame (V8's are ~2.5x slower than other engines).
|
|
167
|
+
__init17() {this._trig = { cos: 1, sin: 0 }}
|
|
168
|
+
__init18() {this._trigRotation = Number.NaN}
|
|
169
|
+
// NaN !== any rotation -> first read computes
|
|
170
|
+
// Per-frame world-matrix cache. Written by Scene during the render walk;
|
|
171
|
+
// getWorldTransform() returns it only while `_worldFrame === scene.currentFrame`
|
|
172
|
+
// and otherwise falls back to the full ancestor walk, so it can never return a
|
|
173
|
+
// stale/wrong transform — only sometimes miss the fast path.
|
|
174
|
+
__init19() {this._wa = 1}
|
|
175
|
+
__init20() {this._wb = 0}
|
|
176
|
+
__init21() {this._wc = 0}
|
|
177
|
+
__init22() {this._wd = 1}
|
|
178
|
+
__init23() {this._we = 0}
|
|
179
|
+
__init24() {this._wf = 0}
|
|
180
|
+
__init25() {this._worldFrame = -1}
|
|
181
|
+
// Slot in the Scene's resident WASM transform store, or -1 when this entity is
|
|
182
|
+
// not in that store (JS transform path, overlay/detached, or before the first
|
|
183
|
+
// structural rebuild). Assigned by Scene on a structural rebuild; the Scene
|
|
184
|
+
// validates it against its slot table before trusting it, so a stale value can
|
|
185
|
+
// only cost a JS-path fallback, never a wrong read.
|
|
186
|
+
__init26() {this._storeSlot = -1}
|
|
151
187
|
get x() {
|
|
152
188
|
return this._x;
|
|
153
189
|
}
|
|
@@ -190,8 +226,8 @@ var Entity = (_class2 = class {
|
|
|
190
226
|
if (this._hasTransitions) this._animateProp("opacity", v);
|
|
191
227
|
else this._opacity = v;
|
|
192
228
|
}
|
|
193
|
-
|
|
194
|
-
|
|
229
|
+
__init27() {this.isDOMPortal = false}
|
|
230
|
+
__init28() {this._interactive = false}
|
|
195
231
|
get interactive() {
|
|
196
232
|
return this._interactive;
|
|
197
233
|
}
|
|
@@ -205,10 +241,10 @@ var Entity = (_class2 = class {
|
|
|
205
241
|
}
|
|
206
242
|
}
|
|
207
243
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
244
|
+
__init29() {this.width = 0}
|
|
245
|
+
__init30() {this.height = 0}
|
|
246
|
+
__init31() {this.a11yOffsetX = 0}
|
|
247
|
+
__init32() {this.a11yOffsetY = 0}
|
|
212
248
|
/**
|
|
213
249
|
* Opt in to a viewport-filling accessibility/automation shadow node even when
|
|
214
250
|
* this entity has no intrinsic box (`width`/`height` of `0`). Use for
|
|
@@ -216,19 +252,21 @@ var Entity = (_class2 = class {
|
|
|
216
252
|
* that need global pointer events. The node is mounted behind all other shadow
|
|
217
253
|
* nodes, so on-top components stay clickable.
|
|
218
254
|
*/
|
|
219
|
-
|
|
255
|
+
__init33() {this.a11yFullViewport = false}
|
|
220
256
|
/**
|
|
221
257
|
* Clip this node's children to its local box (`[0,0]–[width,height]`) while
|
|
222
258
|
* rendering. Combined with translating a content child, this is how
|
|
223
259
|
* scroll/overflow containers (e.g. `ScrollView`) keep their content inside a
|
|
224
260
|
* fixed viewport. Off by default (children render unclipped). Canvas2D only.
|
|
225
261
|
*/
|
|
226
|
-
|
|
227
|
-
|
|
262
|
+
__init34() {this.clipChildren = false}
|
|
263
|
+
// Lazily allocated (see _drivers above). Most entities never register a
|
|
264
|
+
// listener or an imperative animate() tween.
|
|
265
|
+
__init35() {this.listeners = null}
|
|
228
266
|
/** Capture-phase listeners (fired root→target before bubble). */
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
constructor(id) {;_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this);_class2.prototype.__init6.call(this);_class2.prototype.__init7.call(this);_class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);_class2.prototype.__init10.call(this);_class2.prototype.__init11.call(this);_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);
|
|
267
|
+
__init36() {this.captureListeners = null}
|
|
268
|
+
__init37() {this.animations = null}
|
|
269
|
+
constructor(id) {;_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this);_class2.prototype.__init6.call(this);_class2.prototype.__init7.call(this);_class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);_class2.prototype.__init10.call(this);_class2.prototype.__init11.call(this);_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);_class2.prototype.__init35.call(this);_class2.prototype.__init36.call(this);_class2.prototype.__init37.call(this);
|
|
232
270
|
this.id = id || `entity_${Math.random().toString(36).substring(2, 9)}`;
|
|
233
271
|
}
|
|
234
272
|
/**
|
|
@@ -262,6 +300,7 @@ var Entity = (_class2 = class {
|
|
|
262
300
|
const s = this.scene;
|
|
263
301
|
if (s) {
|
|
264
302
|
s.a11yNeedsReorder = true;
|
|
303
|
+
_optionalChain([s, 'access', _40 => _40.markStructureChanged, 'optionalCall', _41 => _41()]);
|
|
265
304
|
s.markDirty();
|
|
266
305
|
child._notifyMounted();
|
|
267
306
|
}
|
|
@@ -291,6 +330,7 @@ var Entity = (_class2 = class {
|
|
|
291
330
|
if (s) {
|
|
292
331
|
s.detachA11y(child);
|
|
293
332
|
s.a11yNeedsReorder = true;
|
|
333
|
+
_optionalChain([s, 'access', _42 => _42.markStructureChanged, 'optionalCall', _43 => _43()]);
|
|
294
334
|
s.markDirty();
|
|
295
335
|
}
|
|
296
336
|
}
|
|
@@ -339,13 +379,13 @@ var Entity = (_class2 = class {
|
|
|
339
379
|
* @example entity.animate({ x: 400, opacity: 0 }, 500);
|
|
340
380
|
*/
|
|
341
381
|
animate(targetProps, durationMs) {
|
|
342
|
-
this.animations.push({
|
|
382
|
+
(this.animations ??= []).push({
|
|
343
383
|
target: targetProps,
|
|
344
384
|
duration: durationMs,
|
|
345
385
|
startTime: -1,
|
|
346
386
|
startProps: {}
|
|
347
387
|
});
|
|
348
|
-
_optionalChain([this, 'access',
|
|
388
|
+
_optionalChain([this, 'access', _44 => _44.scene, 'optionalAccess', _45 => _45.markDirty, 'call', _46 => _46()]);
|
|
349
389
|
return this;
|
|
350
390
|
}
|
|
351
391
|
/** Write a driver-computed value to a backing field without re-triggering the setter. */
|
|
@@ -392,26 +432,26 @@ var Entity = (_class2 = class {
|
|
|
392
432
|
* that need to seed a starting state (e.g. the presence helper's enter `from`).
|
|
393
433
|
*/
|
|
394
434
|
setImmediate(prop, v) {
|
|
395
|
-
const existing = this._drivers.get(prop);
|
|
435
|
+
const existing = _optionalChain([this, 'access', _47 => _47._drivers, 'optionalAccess', _48 => _48.get, 'call', _49 => _49(prop)]);
|
|
396
436
|
if (existing) this._settleDriver(existing);
|
|
397
|
-
this._drivers.delete(prop);
|
|
437
|
+
_optionalChain([this, 'access', _50 => _50._drivers, 'optionalAccess', _51 => _51.delete, 'call', _52 => _52(prop)]);
|
|
398
438
|
this._applyAnimated(prop, v);
|
|
399
439
|
}
|
|
400
440
|
_settleDriver(driver) {
|
|
401
441
|
const active = driver;
|
|
402
442
|
const onDone = active.onDone;
|
|
403
443
|
active.onDone = void 0;
|
|
404
|
-
_optionalChain([onDone, 'optionalCall',
|
|
444
|
+
_optionalChain([onDone, 'optionalCall', _53 => _53()]);
|
|
405
445
|
}
|
|
406
446
|
_spawnDriver(prop, to, cfg) {
|
|
407
|
-
if (prop !== "opacity" && _optionalChain([this, 'access',
|
|
408
|
-
const existing2 = this._drivers.get(prop);
|
|
447
|
+
if (prop !== "opacity" && _optionalChain([this, 'access', _54 => _54.scene, 'optionalAccess', _55 => _55.prefersReducedMotion])) {
|
|
448
|
+
const existing2 = _optionalChain([this, 'access', _56 => _56._drivers, 'optionalAccess', _57 => _57.get, 'call', _58 => _58(prop)]);
|
|
409
449
|
if (existing2) this._settleDriver(existing2);
|
|
410
|
-
this._drivers.delete(prop);
|
|
450
|
+
_optionalChain([this, 'access', _59 => _59._drivers, 'optionalAccess', _60 => _60.delete, 'call', _61 => _61(prop)]);
|
|
411
451
|
this._applyAnimated(prop, to);
|
|
412
452
|
return;
|
|
413
453
|
}
|
|
414
|
-
const existing = this._drivers.get(prop);
|
|
454
|
+
const existing = _optionalChain([this, 'access', _62 => _62._drivers, 'optionalAccess', _63 => _63.get, 'call', _64 => _64(prop)]);
|
|
415
455
|
if (existing) {
|
|
416
456
|
this._settleDriver(existing);
|
|
417
457
|
existing.retarget(to);
|
|
@@ -419,12 +459,13 @@ var Entity = (_class2 = class {
|
|
|
419
459
|
}
|
|
420
460
|
const from = this._currentOf(prop);
|
|
421
461
|
const driver = _animation.isTweenConfig.call(void 0, cfg) ? new (0, _animation.TweenDriver)(from, to, cfg) : new (0, _animation.SpringDriver)(from, to, cfg === "spring" ? {} : cfg);
|
|
422
|
-
this._drivers.set(prop, driver);
|
|
423
|
-
_optionalChain([this, 'access',
|
|
462
|
+
(this._drivers ??= /* @__PURE__ */ new Map()).set(prop, driver);
|
|
463
|
+
_optionalChain([this, 'access', _65 => _65.scene, 'optionalAccess', _66 => _66.markDirty, 'call', _67 => _67()]);
|
|
464
|
+
_optionalChain([this, 'access', _68 => _68.scene, 'optionalAccess', _69 => _69._registerActiveDriverEntity, 'call', _70 => _70(this)]);
|
|
424
465
|
}
|
|
425
466
|
/** Assignment path when a declarative transition is configured for `prop`. */
|
|
426
467
|
_animateProp(prop, to) {
|
|
427
|
-
const cfg = _optionalChain([this, 'access',
|
|
468
|
+
const cfg = _optionalChain([this, 'access', _71 => _71._transitions, 'optionalAccess', _72 => _72.get, 'call', _73 => _73(prop)]);
|
|
428
469
|
if (!cfg) {
|
|
429
470
|
this._applyAnimated(prop, to);
|
|
430
471
|
return;
|
|
@@ -453,7 +494,7 @@ var Entity = (_class2 = class {
|
|
|
453
494
|
entries.map(
|
|
454
495
|
(e) => new Promise((resolve) => {
|
|
455
496
|
this._spawnDriver(e[0], e[1], cfg);
|
|
456
|
-
const d = this._drivers.get(e[0]);
|
|
497
|
+
const d = _optionalChain([this, 'access', _74 => _74._drivers, 'optionalAccess', _75 => _75.get, 'call', _76 => _76(e[0])]);
|
|
457
498
|
if (!d)
|
|
458
499
|
resolve();
|
|
459
500
|
else d.onDone = resolve;
|
|
@@ -463,7 +504,8 @@ var Entity = (_class2 = class {
|
|
|
463
504
|
}
|
|
464
505
|
/** Advance active property drivers one frame. Call from update(). */
|
|
465
506
|
tickDrivers(dt) {
|
|
466
|
-
if (this._drivers.size === 0) return;
|
|
507
|
+
if (!this._drivers || this._drivers.size === 0) return;
|
|
508
|
+
if (this.scene && this._driversTickedFrame === this.scene.currentFrame) return;
|
|
467
509
|
for (const [prop, driver] of this._drivers) {
|
|
468
510
|
driver.tick(dt);
|
|
469
511
|
if (driver.isDone()) {
|
|
@@ -474,7 +516,39 @@ var Entity = (_class2 = class {
|
|
|
474
516
|
this._applyAnimated(prop, driver.value);
|
|
475
517
|
}
|
|
476
518
|
}
|
|
477
|
-
_optionalChain([this, 'access',
|
|
519
|
+
_optionalChain([this, 'access', _77 => _77.scene, 'optionalAccess', _78 => _78.markDirty, 'call', _79 => _79()]);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Internal: this entity's active-driver map (read-only view), or `null` if
|
|
523
|
+
* it has none. Called only by Scene's batched WASM animation pass, never
|
|
524
|
+
* application code. Returns the Map directly (not a callback iteration) so
|
|
525
|
+
* a caller can `for...of` it with zero per-entity closure allocation — the
|
|
526
|
+
* integrated benchmark (benchmarks/anim-wasm-scene) found a fresh callback
|
|
527
|
+
* per entity per frame was a real cost, not a negligible one.
|
|
528
|
+
*/
|
|
529
|
+
_driverEntries() {
|
|
530
|
+
return this._drivers;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Internal: finalize one driver that was ALREADY advanced externally this
|
|
534
|
+
* frame (e.g. by Scene's batched WASM tick via `driver.syncExternal`, or a
|
|
535
|
+
* direct `driver.tick()` call for a driver the batch can't offload) —
|
|
536
|
+
* exactly mirrors tickDrivers()'s own per-driver completion logic, so a
|
|
537
|
+
* driver behaves identically regardless of which path ticked it. Called
|
|
538
|
+
* only by Scene, never application code.
|
|
539
|
+
*/
|
|
540
|
+
_applyDriverTick(prop, driver) {
|
|
541
|
+
if (driver.isDone()) {
|
|
542
|
+
this._applyAnimated(prop, driver.target);
|
|
543
|
+
this._settleDriver(driver);
|
|
544
|
+
_optionalChain([this, 'access', _80 => _80._drivers, 'optionalAccess', _81 => _81.delete, 'call', _82 => _82(prop)]);
|
|
545
|
+
} else {
|
|
546
|
+
this._applyAnimated(prop, driver.value);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
/** Internal: true if this entity currently has any active property driver. */
|
|
550
|
+
_hasActiveDrivers() {
|
|
551
|
+
return !!this._drivers && this._drivers.size > 0;
|
|
478
552
|
}
|
|
479
553
|
/**
|
|
480
554
|
* Advance the entity's internal state for one frame.
|
|
@@ -487,7 +561,7 @@ var Entity = (_class2 = class {
|
|
|
487
561
|
*/
|
|
488
562
|
update(dt, time) {
|
|
489
563
|
this.tickDrivers(dt);
|
|
490
|
-
if (this.animations.length > 0) {
|
|
564
|
+
if (this.animations && this.animations.length > 0) {
|
|
491
565
|
const anim = this.animations[0];
|
|
492
566
|
if (anim.startTime === -1) {
|
|
493
567
|
anim.startTime = time;
|
|
@@ -528,7 +602,7 @@ var Entity = (_class2 = class {
|
|
|
528
602
|
* @example entity.on('click', (e) => console.log('clicked', e));
|
|
529
603
|
*/
|
|
530
604
|
on(event, callback, options) {
|
|
531
|
-
const map = _optionalChain([options, 'optionalAccess',
|
|
605
|
+
const map = _optionalChain([options, 'optionalAccess', _83 => _83.capture]) ? this.captureListeners ??= /* @__PURE__ */ new Map() : this.listeners ??= /* @__PURE__ */ new Map();
|
|
532
606
|
if (!map.has(event)) {
|
|
533
607
|
map.set(event, []);
|
|
534
608
|
}
|
|
@@ -544,7 +618,7 @@ var Entity = (_class2 = class {
|
|
|
544
618
|
* @returns `this` for method chaining.
|
|
545
619
|
*/
|
|
546
620
|
off(event, callback, options) {
|
|
547
|
-
const handlers = (_optionalChain([options, 'optionalAccess',
|
|
621
|
+
const handlers = _optionalChain([(_optionalChain([options, 'optionalAccess', _84 => _84.capture]) ? this.captureListeners : this.listeners), 'optionalAccess', _85 => _85.get, 'call', _86 => _86(event)]);
|
|
548
622
|
if (handlers) {
|
|
549
623
|
const idx = handlers.indexOf(callback);
|
|
550
624
|
if (idx !== -1) handlers.splice(idx, 1);
|
|
@@ -552,17 +626,37 @@ var Entity = (_class2 = class {
|
|
|
552
626
|
return this;
|
|
553
627
|
}
|
|
554
628
|
/**
|
|
555
|
-
* Tear down this entity
|
|
556
|
-
*
|
|
629
|
+
* Tear down this entity **and its entire subtree**: recursively destroy every
|
|
630
|
+
* descendant (leaf-first), clear all animations, event listeners, and property
|
|
631
|
+
* drivers, then detach from the parent. Call before discarding an entity to
|
|
632
|
+
* prevent memory leaks.
|
|
633
|
+
*
|
|
634
|
+
* Recursing here is what frees a subtree's GPU buffers, layout workers, and DOM
|
|
635
|
+
* observers when an app does `entity.destroy()` or `scene.remove(subtree)` on a
|
|
636
|
+
* route change — without it, only the root's own state was released and every
|
|
637
|
+
* descendant (and its subclass resources) was stranded. Subclasses that own
|
|
638
|
+
* external resources override `destroy()`, free their resource, then call
|
|
639
|
+
* `super.destroy()`; because children don't depend on a parent's resource, the
|
|
640
|
+
* order (parent resource first, then descendants) is safe.
|
|
641
|
+
*
|
|
642
|
+
* Idempotent and re-entrancy safe: a second call is a no-op, and because each
|
|
643
|
+
* child is detached as it is destroyed, destroying a subtree never double-frees.
|
|
557
644
|
*/
|
|
558
645
|
destroy() {
|
|
559
|
-
this.
|
|
560
|
-
|
|
561
|
-
|
|
646
|
+
if (this._destroyed) return;
|
|
647
|
+
this._destroyed = true;
|
|
648
|
+
while (this.children.length > 0) {
|
|
649
|
+
this.children.at(-1).destroy();
|
|
650
|
+
}
|
|
651
|
+
this.animations = null;
|
|
652
|
+
if (this._drivers) {
|
|
653
|
+
for (const driver of this._drivers.values()) {
|
|
654
|
+
this._settleDriver(driver);
|
|
655
|
+
}
|
|
656
|
+
this._drivers.clear();
|
|
562
657
|
}
|
|
563
|
-
this.
|
|
564
|
-
this.
|
|
565
|
-
this.captureListeners.clear();
|
|
658
|
+
_optionalChain([this, 'access', _87 => _87.listeners, 'optionalAccess', _88 => _88.clear, 'call', _89 => _89()]);
|
|
659
|
+
_optionalChain([this, 'access', _90 => _90.captureListeners, 'optionalAccess', _91 => _91.clear, 'call', _92 => _92()]);
|
|
566
660
|
if (this.parent) {
|
|
567
661
|
this.parent.remove(this);
|
|
568
662
|
}
|
|
@@ -577,7 +671,7 @@ var Entity = (_class2 = class {
|
|
|
577
671
|
* @param payload - Arbitrary data forwarded to each listener.
|
|
578
672
|
*/
|
|
579
673
|
emit(event, payload) {
|
|
580
|
-
const handlers = this.listeners.get(event);
|
|
674
|
+
const handlers = _optionalChain([this, 'access', _93 => _93.listeners, 'optionalAccess', _94 => _94.get, 'call', _95 => _95(event)]);
|
|
581
675
|
if (handlers) {
|
|
582
676
|
handlers.forEach((h) => h(payload));
|
|
583
677
|
}
|
|
@@ -591,19 +685,19 @@ var Entity = (_class2 = class {
|
|
|
591
685
|
* with `requestAnimationFrame(() => document.getElementById(id)?.focus())`.
|
|
592
686
|
*/
|
|
593
687
|
focus() {
|
|
594
|
-
const el = _optionalChain([this, 'access',
|
|
688
|
+
const el = _optionalChain([this, 'access', _96 => _96.scene, 'optionalAccess', _97 => _97.getA11yElement, 'call', _98 => _98(this.id)]);
|
|
595
689
|
if (el) {
|
|
596
690
|
el.focus();
|
|
597
691
|
return;
|
|
598
692
|
}
|
|
599
693
|
requestAnimationFrame(() => {
|
|
600
|
-
const retry = _optionalChain([this, 'access',
|
|
694
|
+
const retry = _optionalChain([this, 'access', _99 => _99.scene, 'optionalAccess', _100 => _100.getA11yElement, 'call', _101 => _101(this.id)]);
|
|
601
695
|
if (retry) retry.focus();
|
|
602
696
|
});
|
|
603
697
|
}
|
|
604
698
|
/** Run one node's listeners for the event, honoring stopImmediatePropagation. */
|
|
605
699
|
fireListeners(node, map, event) {
|
|
606
|
-
const handlers = map.get(event.type);
|
|
700
|
+
const handlers = _optionalChain([map, 'optionalAccess', _102 => _102.get, 'call', _103 => _103(event.type)]);
|
|
607
701
|
if (!handlers) return;
|
|
608
702
|
event.currentTarget = node;
|
|
609
703
|
for (const h of handlers.slice()) {
|
|
@@ -642,10 +736,45 @@ var Entity = (_class2 = class {
|
|
|
642
736
|
getGlobalPosition() {
|
|
643
737
|
return this.localToWorld(0, 0);
|
|
644
738
|
}
|
|
739
|
+
/**
|
|
740
|
+
* Internal: read this entity's cached world matrix into `out` — the SAME
|
|
741
|
+
* cache {@link getWorldTransform} reads — without allocating a wrapper
|
|
742
|
+
* object. Returns `false` (leaving `out` untouched) when the cache isn't
|
|
743
|
+
* valid for `frame` (typically the caller's `scene.currentFrame`), exactly
|
|
744
|
+
* mirroring {@link getWorldTransform}'s own validity check; the caller
|
|
745
|
+
* falls back to {@link getWorldTransform}'s full walk in that case. Exists
|
|
746
|
+
* so a per-entity gather that runs every entity through this (e.g. G3's
|
|
747
|
+
* `gatherHitAABBs`) pays for six scalar reads instead of one object
|
|
748
|
+
* allocation per entity per call — the exact class of per-frame garbage
|
|
749
|
+
* the G2 integrated benchmark found dominating its own gather cost.
|
|
750
|
+
*/
|
|
751
|
+
_readWorldCache(frame, out) {
|
|
752
|
+
if (this._worldFrame < 0 || this._worldFrame !== frame) return false;
|
|
753
|
+
out.a = this._wa;
|
|
754
|
+
out.b = this._wb;
|
|
755
|
+
out.c = this._wc;
|
|
756
|
+
out.d = this._wd;
|
|
757
|
+
out.e = this._we;
|
|
758
|
+
out.f = this._wf;
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
645
761
|
/**
|
|
646
762
|
* Return the exact accumulated Canvas `T * S * R` transform for this entity.
|
|
647
763
|
*/
|
|
648
764
|
getWorldTransform() {
|
|
765
|
+
if (this._worldFrame >= 0) {
|
|
766
|
+
const s = this.scene;
|
|
767
|
+
if (s && this._worldFrame === s.currentFrame) {
|
|
768
|
+
return {
|
|
769
|
+
a: this._wa,
|
|
770
|
+
b: this._wb,
|
|
771
|
+
c: this._wc,
|
|
772
|
+
d: this._wd,
|
|
773
|
+
e: this._we,
|
|
774
|
+
f: this._wf
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
}
|
|
649
778
|
const path = [this];
|
|
650
779
|
let ancestor = this.parent;
|
|
651
780
|
while (ancestor) {
|
|
@@ -660,8 +789,9 @@ var Entity = (_class2 = class {
|
|
|
660
789
|
let f = 0;
|
|
661
790
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
662
791
|
const node = path[i];
|
|
663
|
-
const
|
|
664
|
-
const
|
|
792
|
+
const trig = node._getTrig();
|
|
793
|
+
const cos = trig.cos;
|
|
794
|
+
const sin = trig.sin;
|
|
665
795
|
const la = node.scaleX * cos;
|
|
666
796
|
const lb = node.scaleY * sin;
|
|
667
797
|
const lc = -node.scaleX * sin;
|
|
@@ -683,6 +813,37 @@ var Entity = (_class2 = class {
|
|
|
683
813
|
}
|
|
684
814
|
return { a, b, c, d, e, f };
|
|
685
815
|
}
|
|
816
|
+
/**
|
|
817
|
+
* Return this entity's cached `{ cos, sin }` of its current rotation,
|
|
818
|
+
* recomputing only when `rotation` has actually changed since the last call.
|
|
819
|
+
* The same object identity is returned across calls with an unchanged
|
|
820
|
+
* rotation, so callers must treat it as read-only. Used by the render walk
|
|
821
|
+
* and {@link getWorldTransform} to avoid V8's comparatively slow Math.cos/sin
|
|
822
|
+
* (a software libm call, ~2.5x slower than other engines) per entity/frame.
|
|
823
|
+
*/
|
|
824
|
+
_getTrig() {
|
|
825
|
+
if (this._trigRotation !== this._rotation) {
|
|
826
|
+
this._trig.cos = Math.cos(this._rotation);
|
|
827
|
+
this._trig.sin = Math.sin(this._rotation);
|
|
828
|
+
this._trigRotation = this._rotation;
|
|
829
|
+
}
|
|
830
|
+
return this._trig;
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Store the world matrix Scene computed for this entity during the render
|
|
834
|
+
* walk, stamped with the frame it belongs to. {@link getWorldTransform}
|
|
835
|
+
* returns it verbatim while `frame === scene.currentFrame`. Internal: called
|
|
836
|
+
* only by Scene's renderer, never by application code.
|
|
837
|
+
*/
|
|
838
|
+
_setWorldCache(a, b, c, d, e, f, frame) {
|
|
839
|
+
this._wa = a;
|
|
840
|
+
this._wb = b;
|
|
841
|
+
this._wc = c;
|
|
842
|
+
this._wd = d;
|
|
843
|
+
this._we = e;
|
|
844
|
+
this._wf = f;
|
|
845
|
+
this._worldFrame = frame;
|
|
846
|
+
}
|
|
686
847
|
/** Convert a point from this entity's local space to Scene/world space. */
|
|
687
848
|
localToWorld(localX, localY) {
|
|
688
849
|
const { a, b, c, d, e, f } = this.getWorldTransform();
|
|
@@ -712,7 +873,12 @@ var Entity = (_class2 = class {
|
|
|
712
873
|
* does not provide a render-specific box.
|
|
713
874
|
*/
|
|
714
875
|
getWorldBounds() {
|
|
715
|
-
const bounds = _nullishCoalesce(this.getBounds(), () => ( {
|
|
876
|
+
const bounds = _nullishCoalesce(this.getBounds(), () => ( {
|
|
877
|
+
x: 0,
|
|
878
|
+
y: 0,
|
|
879
|
+
width: this.width,
|
|
880
|
+
height: this.height
|
|
881
|
+
}));
|
|
716
882
|
const { a, b, c, d, e, f } = this.getWorldTransform();
|
|
717
883
|
let minX = Infinity;
|
|
718
884
|
let minY = Infinity;
|
|
@@ -853,7 +1019,7 @@ var Entity = (_class2 = class {
|
|
|
853
1019
|
* @returns `true` if at least one animation or property driver remains.
|
|
854
1020
|
*/
|
|
855
1021
|
hasPendingAnimations() {
|
|
856
|
-
return this.animations.length > 0 || this._drivers.size > 0;
|
|
1022
|
+
return (_nullishCoalesce(_optionalChain([this, 'access', _104 => _104.animations, 'optionalAccess', _105 => _105.length]), () => ( 0))) > 0 || (_nullishCoalesce(_optionalChain([this, 'access', _106 => _106._drivers, 'optionalAccess', _107 => _107.size]), () => ( 0))) > 0;
|
|
857
1023
|
}
|
|
858
1024
|
}, _class2);
|
|
859
1025
|
|
|
@@ -869,13 +1035,21 @@ var MSDFTextEntity = (_class3 = class extends Entity {
|
|
|
869
1035
|
|
|
870
1036
|
|
|
871
1037
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1038
|
+
|
|
1039
|
+
// Optional hyphenator. Runs on the MAIN thread (a function can't be
|
|
1040
|
+
// structure-cloned into the layout worker), turning each word into parts
|
|
1041
|
+
// joined by soft hyphens (U+00AD); the worker then treats those as break
|
|
1042
|
+
// opportunities. `text` keeps the original string for a11y/content
|
|
1043
|
+
// projection; `layoutText` is the soft-hyphen-annotated string sent to layout.
|
|
1044
|
+
__init38() {this.hyphenator = null}
|
|
1045
|
+
__init39() {this.layoutText = ""}
|
|
1046
|
+
__init40() {this.text = ""}
|
|
1047
|
+
__init41() {this.lastRenderedSeqId = 0}
|
|
1048
|
+
__init42() {this.rgbColorCache = /* @__PURE__ */ new Map()}
|
|
1049
|
+
__init43() {this.fontStringCache = []}
|
|
1050
|
+
__init44() {this.layoutResult = null}
|
|
877
1051
|
constructor(text, options) {
|
|
878
|
-
super();_class3.prototype.
|
|
1052
|
+
super();_class3.prototype.__init38.call(this);_class3.prototype.__init39.call(this);_class3.prototype.__init40.call(this);_class3.prototype.__init41.call(this);_class3.prototype.__init42.call(this);_class3.prototype.__init43.call(this);_class3.prototype.__init44.call(this);;
|
|
879
1053
|
this.font = options.font;
|
|
880
1054
|
this.texture = options.texture;
|
|
881
1055
|
this.fallbackFont = _nullishCoalesce(options.fallbackFont, () => ( "sans-serif"));
|
|
@@ -885,6 +1059,7 @@ var MSDFTextEntity = (_class3 = class extends Entity {
|
|
|
885
1059
|
this.lineHeight = options.lineHeight;
|
|
886
1060
|
this.maxWidth = _nullishCoalesce(options.maxWidth, () => ( 1e3));
|
|
887
1061
|
this.maxHeight = _nullishCoalesce(options.maxHeight, () => ( 1e3));
|
|
1062
|
+
this.textAlign = _nullishCoalesce(options.textAlign, () => ( "left"));
|
|
888
1063
|
this.setText(text);
|
|
889
1064
|
}
|
|
890
1065
|
/** Change the wrap boundary and re-run layout for the current text. */
|
|
@@ -893,13 +1068,53 @@ var MSDFTextEntity = (_class3 = class extends Entity {
|
|
|
893
1068
|
this.maxWidth = maxWidth;
|
|
894
1069
|
this.queueLayout();
|
|
895
1070
|
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Set horizontal alignment (`'justify'` stretches wrapped lines flush to
|
|
1073
|
+
* {@link setMaxWidth}'s width; the last line stays ragged) and re-run layout.
|
|
1074
|
+
*/
|
|
1075
|
+
setTextAlign(align) {
|
|
1076
|
+
if (this.textAlign === align) return;
|
|
1077
|
+
this.textAlign = align;
|
|
1078
|
+
this.queueLayout();
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Plug a hyphenator (word → parts). Break opportunities are inserted as soft
|
|
1082
|
+
* hyphens (U+00AD) into the string sent to layout, so a word that doesn't fit
|
|
1083
|
+
* can break with a visible hyphen. Soft hyphens already present in the text
|
|
1084
|
+
* work without one. Pass `null` to disable. The original text is preserved
|
|
1085
|
+
* for accessibility — only the layout string carries the hyphens.
|
|
1086
|
+
*/
|
|
1087
|
+
setHyphenator(fn) {
|
|
1088
|
+
this.hyphenator = fn;
|
|
1089
|
+
this.rebuildLayoutText();
|
|
1090
|
+
this.queueLayout();
|
|
1091
|
+
}
|
|
896
1092
|
setText(text) {
|
|
897
1093
|
if (this.text === text && this.layoutResult) return;
|
|
898
1094
|
this.text = text;
|
|
1095
|
+
this.rebuildLayoutText();
|
|
899
1096
|
this.queueLayout();
|
|
900
1097
|
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Recompute {@link layoutText} from {@link text}: with a hyphenator active,
|
|
1100
|
+
* split each whitespace-delimited word and rejoin its parts with U+00AD so
|
|
1101
|
+
* the worker sees the break opportunities. Without one, the layout string is
|
|
1102
|
+
* the text unchanged.
|
|
1103
|
+
*/
|
|
1104
|
+
rebuildLayoutText() {
|
|
1105
|
+
if (!this.hyphenator) {
|
|
1106
|
+
this.layoutText = this.text;
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
const SHY = "\xAD";
|
|
1110
|
+
this.layoutText = this.text.replace(/[^\s]+/g, (word) => {
|
|
1111
|
+
if (word.length <= 3 || word.includes(SHY)) return word;
|
|
1112
|
+
const parts = this.hyphenator(word);
|
|
1113
|
+
return parts.length > 1 ? parts.join(SHY) : word;
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
901
1116
|
queueLayout() {
|
|
902
|
-
_layout.LayoutWorkerManager.getInstance().queueLayout(this.id, this.
|
|
1117
|
+
_layout.LayoutWorkerManager.getInstance().queueLayout(this.id, this.layoutText, {
|
|
903
1118
|
fontId: this.font.id,
|
|
904
1119
|
fontSize: this.fontSize,
|
|
905
1120
|
maxWidth: this.maxWidth,
|
|
@@ -907,11 +1122,12 @@ var MSDFTextEntity = (_class3 = class extends Entity {
|
|
|
907
1122
|
fontData: this.font.data,
|
|
908
1123
|
letterSpacing: this.letterSpacing,
|
|
909
1124
|
lineHeight: this.lineHeight,
|
|
1125
|
+
textAlign: this.textAlign,
|
|
910
1126
|
callback: (res) => {
|
|
911
1127
|
if (res.seqId < this.lastRenderedSeqId) return;
|
|
912
1128
|
this.lastRenderedSeqId = res.seqId;
|
|
913
1129
|
this.layoutResult = res;
|
|
914
|
-
_optionalChain([this, 'access',
|
|
1130
|
+
_optionalChain([this, 'access', _108 => _108.scene, 'optionalAccess', _109 => _109.markDirty, 'call', _110 => _110()]);
|
|
915
1131
|
}
|
|
916
1132
|
});
|
|
917
1133
|
}
|
|
@@ -1016,7 +1232,7 @@ var MSDFTextEntity = (_class3 = class extends Entity {
|
|
|
1016
1232
|
}
|
|
1017
1233
|
}
|
|
1018
1234
|
destroy() {
|
|
1019
|
-
_layout.LayoutWorkerManager.
|
|
1235
|
+
_layout.LayoutWorkerManager.cancelLayoutForEntity(this.id);
|
|
1020
1236
|
super.destroy();
|
|
1021
1237
|
}
|
|
1022
1238
|
}, _class3);
|
|
@@ -1053,19 +1269,19 @@ function readSvgAttribute(source, name) {
|
|
|
1053
1269
|
return null;
|
|
1054
1270
|
}
|
|
1055
1271
|
var SVGEntity = (_class4 = class extends Entity {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1272
|
+
__init45() {this.svgSource = ""}
|
|
1273
|
+
__init46() {this.imageBitmap = null}
|
|
1274
|
+
__init47() {this.imageElement = null}
|
|
1275
|
+
__init48() {this.blobURL = null}
|
|
1276
|
+
__init49() {this.currentImg = null}
|
|
1277
|
+
__init50() {this.lodTimeout = null}
|
|
1278
|
+
__init51() {this.cachedDoc = null}
|
|
1279
|
+
__init52() {this.baseWidth = 100}
|
|
1280
|
+
__init53() {this.baseHeight = 100}
|
|
1281
|
+
__init54() {this.lastRasterizedScale = 1}
|
|
1282
|
+
__init55() {this.targetScale = 1}
|
|
1067
1283
|
constructor(svgSource, id) {
|
|
1068
|
-
super(id);_class4.prototype.
|
|
1284
|
+
super(id);_class4.prototype.__init45.call(this);_class4.prototype.__init46.call(this);_class4.prototype.__init47.call(this);_class4.prototype.__init48.call(this);_class4.prototype.__init49.call(this);_class4.prototype.__init50.call(this);_class4.prototype.__init51.call(this);_class4.prototype.__init52.call(this);_class4.prototype.__init53.call(this);_class4.prototype.__init54.call(this);_class4.prototype.__init55.call(this);;
|
|
1069
1285
|
this.setSVGSource(svgSource);
|
|
1070
1286
|
}
|
|
1071
1287
|
setSVGSource(svgSource) {
|