@vectojs/core 1.31.0 → 1.32.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/index.js +208 -125
- package/dist/index.mjs +87 -4
- package/dist/tree/Scene.d.ts +118 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1380,6 +1380,7 @@ var SCENE_OPTION_KEYS = [
|
|
|
1380
1380
|
"autoThrottle",
|
|
1381
1381
|
"contentProjection",
|
|
1382
1382
|
"contentProjectionMargin",
|
|
1383
|
+
"contentSemanticBudget",
|
|
1383
1384
|
"contentSemanticMargin",
|
|
1384
1385
|
"debugA11y",
|
|
1385
1386
|
"disableWindowResize",
|
|
@@ -1433,6 +1434,7 @@ function parseInlinePx(value) {
|
|
|
1433
1434
|
const n = parseFloat(value);
|
|
1434
1435
|
return Number.isFinite(n) && n > 0 ? n : null;
|
|
1435
1436
|
}
|
|
1437
|
+
var DEFAULT_CONTENT_SEMANTIC_BUDGET = 256;
|
|
1436
1438
|
function projectionGridLineWindow(grid, projectionLines, band) {
|
|
1437
1439
|
const count = grid.lines.length;
|
|
1438
1440
|
const all = { start: 0, end: count, gated: false };
|
|
@@ -2008,42 +2010,77 @@ var Scene = (_class7 = class _Scene {
|
|
|
2008
2010
|
// any projected DOM. `undefined` → falls back to contentProjectionMargin, so
|
|
2009
2011
|
// the default keeps one gate. `Infinity` = every block keeps resident text.
|
|
2010
2012
|
__init66() {this.contentSemanticMargin = void 0}
|
|
2013
|
+
// How many coarse-tier blocks may be MATERIALIZED per sync, spreading the
|
|
2014
|
+
// resident tier's document-open cost across frames. `Infinity` = one
|
|
2015
|
+
// synchronous pass.
|
|
2016
|
+
__init67() {this.contentSemanticBudget = DEFAULT_CONTENT_SEMANTIC_BUDGET}
|
|
2017
|
+
// Remaining materializations in the CURRENT sync. Reset at the start of each
|
|
2018
|
+
// a11y walk; decremented per coarse block that creates its element.
|
|
2019
|
+
__init68() {this.contentSemanticBudgetLeft = 0}
|
|
2020
|
+
// Set when a sync deferred at least one block, so the scene knows to keep
|
|
2021
|
+
// drawing frames until the resident tier is complete. Without it a static
|
|
2022
|
+
// scene in `onDemand` mode would stop rendering with the document half
|
|
2023
|
+
// materialized and never finish.
|
|
2024
|
+
__init69() {this.contentSemanticDeferred = false}
|
|
2025
|
+
/**
|
|
2026
|
+
* Per-sync memo of "does the document hold a selection at all".
|
|
2027
|
+
*
|
|
2028
|
+
* Reading ANY property of a `Selection` (`anchorNode`, `rangeCount`, `type`,
|
|
2029
|
+
* `isCollapsed`) forces a synchronous layout, because Blink validates the
|
|
2030
|
+
* selection against current box geometry before answering. Measured in real
|
|
2031
|
+
* Chrome against a 1000-carrier subtree with layout dirtied between reads:
|
|
2032
|
+
* `anchorNode` 0.5ms, `rangeCount` 0.4ms, `type` 0.5ms, `isCollapsed` 0.5ms —
|
|
2033
|
+
* all indistinguishable from `offsetHeight` (0.5ms), against a 0ms floor for
|
|
2034
|
+
* mutating without reading. So there is no cheap property to probe with; the
|
|
2035
|
+
* only way to avoid the layout is to not touch the object at all.
|
|
2036
|
+
*
|
|
2037
|
+
* Materializing a block rebuilds its carriers, which asks whether the rebuild
|
|
2038
|
+
* would destroy a selection. Once per block, that read cost a forced layout
|
|
2039
|
+
* over the whole (and growing) projection subtree, which is what made
|
|
2040
|
+
* per-block cost rise with resident count: profiled at 1973 forced layouts
|
|
2041
|
+
* totalling 633ms of an 847ms 1000-block drain (75%).
|
|
2042
|
+
*
|
|
2043
|
+
* A selection is a single document-wide object and a sync walk cannot yield to
|
|
2044
|
+
* the user, so its presence cannot change mid-walk. Resolving it once per walk
|
|
2045
|
+
* turns O(blocks) forced layouts into O(1). `null` = not yet resolved.
|
|
2046
|
+
*/
|
|
2047
|
+
__init70() {this.contentSelectionPresentThisSync = null}
|
|
2011
2048
|
/**
|
|
2012
2049
|
* True while a text-selection drag that started on a projection's blank
|
|
2013
2050
|
* region (no text node under the press) is being driven manually — the
|
|
2014
2051
|
* browser has no native anchor for it, so mousemove extends the Selection
|
|
2015
2052
|
* from the position we resolved ourselves.
|
|
2016
2053
|
*/
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2054
|
+
__init71() {this.blankRegionSelectionDrag = false}
|
|
2055
|
+
__init72() {this.contentSelectionAnchor = null}
|
|
2056
|
+
__init73() {this.contentSelectionEndListener = null}
|
|
2020
2057
|
// Animation/interactive flags collected during the render walk (tree-walk
|
|
2021
2058
|
// fusion): the loop reads last frame's answers instead of re-walking the
|
|
2022
2059
|
// tree up to 4× per tick. Start true so the first tick stays conservative.
|
|
2023
|
-
|
|
2024
|
-
|
|
2060
|
+
__init74() {this.frameHadAnimation = true}
|
|
2061
|
+
__init75() {this.frameHadInteractive = true}
|
|
2025
2062
|
|
|
2026
2063
|
/** Active `(resolution: Ndppx)` media query watching for a runtime DPR change
|
|
2027
2064
|
* (window moved between monitors, browser zoom) so the canvas backing store
|
|
2028
2065
|
* can be re-scaled — otherwise it stays rasterized at the old DPR and blurs.
|
|
2029
2066
|
* A resolution media query only fires when leaving its exact value, so the
|
|
2030
2067
|
* handler re-arms a fresh query for the new DPR each time. */
|
|
2031
|
-
|
|
2068
|
+
__init76() {this.dprMediaQuery = null}
|
|
2032
2069
|
/** For embedded (`disableWindowResize`) scenes: observes the canvas element so
|
|
2033
2070
|
* a CSS/layout-driven size change re-runs `resize()`. A window `resize`
|
|
2034
2071
|
* listener never fires for these (the window isn't what changed), so without
|
|
2035
2072
|
* this an embedded canvas stayed at its initial size forever. */
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2073
|
+
__init77() {this.canvasResizeObserver = null}
|
|
2074
|
+
__init78() {this.dprChangeHandler = null}
|
|
2075
|
+
__init79() {this.focusedA11yElement = null}
|
|
2039
2076
|
/** Last geometry `syncOverlayGeometry` wrote, so an unchanged frame can skip the
|
|
2040
2077
|
* style writes entirely. Reset to `null` to force the next sync (a new overlay
|
|
2041
2078
|
* layer was created and has never been positioned). */
|
|
2042
|
-
|
|
2079
|
+
__init80() {this._overlayGeometry = null}
|
|
2043
2080
|
/** Shadow elements the pointer is currently inside. Lets a removal that happens
|
|
2044
2081
|
* mid-hover synthesize the `pointerleave` the browser never sends for a
|
|
2045
2082
|
* detached element, so the entity doesn't keep its hover state. */
|
|
2046
|
-
|
|
2083
|
+
__init81() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
|
|
2047
2084
|
/**
|
|
2048
2085
|
* Entity ids the application has pinned via {@link requestA11yProjection}.
|
|
2049
2086
|
*
|
|
@@ -2051,22 +2088,22 @@ var Scene = (_class7 = class _Scene {
|
|
|
2051
2088
|
* a stale id simply never matches. Cleared per-entity by
|
|
2052
2089
|
* {@link releaseA11yProjection}.
|
|
2053
2090
|
*/
|
|
2054
|
-
|
|
2091
|
+
__init82() {this.a11yProjectionRequests = /* @__PURE__ */ new Set()}
|
|
2055
2092
|
/** Persistent tabindex=-1 element in a11yRoot. When the focused a11y mirror is
|
|
2056
2093
|
* pruned (virtualization/streaming/removal) while it holds focus, we move
|
|
2057
2094
|
* focus here instead of letting the browser drop it to <body> — keeping the
|
|
2058
2095
|
* screen-reader virtual cursor inside the scene's a11y region. */
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2096
|
+
__init83() {this.focusSentinel = null}
|
|
2097
|
+
__init84() {this.caretBlinkTimer = null}
|
|
2098
|
+
__init85() {this.a11yNeedsReorder = true}
|
|
2099
|
+
__init86() {this.portalRoot = null}
|
|
2100
|
+
__init87() {this.fullViewportElements = []}
|
|
2101
|
+
__init88() {this.normalElements = []}
|
|
2102
|
+
__init89() {this.activeIds = /* @__PURE__ */ new Set()}
|
|
2066
2103
|
/** Per-parent insertion cursor, reused by `enforceA11yDomOrder`. */
|
|
2067
|
-
|
|
2104
|
+
__init90() {this.a11yOrderCursors = /* @__PURE__ */ new Map()}
|
|
2068
2105
|
/** Membership set for the elements being ordered, reused per reorder pass. */
|
|
2069
|
-
|
|
2106
|
+
__init91() {this.a11yOrderMembers = /* @__PURE__ */ new Set()}
|
|
2070
2107
|
/**
|
|
2071
2108
|
* Elements that are an *ancestor* of another ordered element, reused per pass.
|
|
2072
2109
|
*
|
|
@@ -2074,11 +2111,11 @@ var Scene = (_class7 = class _Scene {
|
|
|
2074
2111
|
* items) spans every descendant row, so it must not extend a visual row band —
|
|
2075
2112
|
* see {@link sortNormalElementsVisually}.
|
|
2076
2113
|
*/
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2114
|
+
__init92() {this.a11yOrderContainers = /* @__PURE__ */ new Set()}
|
|
2115
|
+
__init93() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
|
|
2116
|
+
__init94() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
|
|
2117
|
+
__init95() {this.portalEntities = /* @__PURE__ */ new Map()}
|
|
2118
|
+
__init96() {this.renderOrderCounter = 0}
|
|
2082
2119
|
/**
|
|
2083
2120
|
* Monotonic render-frame counter, bumped once per authoritative `render()`
|
|
2084
2121
|
* pass. Entities stamp their per-frame world-matrix cache with this value and
|
|
@@ -2087,7 +2124,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
2087
2124
|
* back to the ancestor walk. Public for the same reason `Entity._getTrig`/
|
|
2088
2125
|
* `_setWorldCache` are: it is a cross-class render-internal contract.
|
|
2089
2126
|
*/
|
|
2090
|
-
|
|
2127
|
+
__init97() {this.currentFrame = 0}
|
|
2091
2128
|
// ── WASM transform backend (invisible accelerator) ──────────────────────────
|
|
2092
2129
|
// When `_transformBackend === 'wasm'`, the main render walk sources each
|
|
2093
2130
|
// entity's world matrix from an SoA store composed by `_wasm` (see
|
|
@@ -2095,24 +2132,24 @@ var Scene = (_class7 = class _Scene {
|
|
|
2095
2132
|
// fallback and the default: a null backend, a non-main renderer, or any entity
|
|
2096
2133
|
// absent from the store all fall back to the JS composition, so WASM can only
|
|
2097
2134
|
// ever change *how fast* a world matrix is produced, never *what* it is.
|
|
2098
|
-
|
|
2099
|
-
|
|
2135
|
+
__init98() {this._wasm = null}
|
|
2136
|
+
__init99() {this._transformBackend = "js"}
|
|
2100
2137
|
// Resident store state (Stage 3). The store layout — slot assignment + sibling
|
|
2101
2138
|
// runs — depends only on tree TOPOLOGY, so it is rebuilt only when the
|
|
2102
2139
|
// structure changes (add/remove/reparent bump `_structureVersion`). Between
|
|
2103
2140
|
// rebuilds the per-frame cost is: gather each entity's transform into the
|
|
2104
2141
|
// resident wasm input view + run the kernel — no reallocation, no readback.
|
|
2105
|
-
|
|
2106
|
-
|
|
2142
|
+
__init100() {this._treeStore = null}
|
|
2143
|
+
__init101() {this._slotEntity = []}
|
|
2107
2144
|
// store slot -> entity (also validates slots)
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2145
|
+
__init102() {this._wasmInputs = null}
|
|
2146
|
+
__init103() {this._wasmWorld = null}
|
|
2147
|
+
__init104() {this._structureVersion = 0}
|
|
2148
|
+
__init105() {this._storeStructureVersion = -1}
|
|
2112
2149
|
// Cached list of ComputeParticleEntity instances in the tree, keyed by the
|
|
2113
2150
|
// structure version it was gathered at. Rebuilt only on a topology change.
|
|
2114
|
-
|
|
2115
|
-
|
|
2151
|
+
__init106() {this._computeEntities = []}
|
|
2152
|
+
__init107() {this._computeEntitiesVersion = -1}
|
|
2116
2153
|
/** Invalidate the resident WASM store layout; the next wasm-mode frame rebuilds
|
|
2117
2154
|
* it. Called by `Entity.add`/`remove` (topology changes only). */
|
|
2118
2155
|
markStructureChanged() {
|
|
@@ -2197,7 +2234,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
2197
2234
|
* cached globally; the instance is per-Scene, which is the isolation that
|
|
2198
2235
|
* actually matters.
|
|
2199
2236
|
*/
|
|
2200
|
-
|
|
2237
|
+
__init108() {this._wasmRuntime = null}
|
|
2201
2238
|
/**
|
|
2202
2239
|
* Load (or reuse) this Scene's shared WASM runtime.
|
|
2203
2240
|
*
|
|
@@ -2225,30 +2262,30 @@ var Scene = (_class7 = class _Scene {
|
|
|
2225
2262
|
get wasmRuntime() {
|
|
2226
2263
|
return this._wasmRuntime;
|
|
2227
2264
|
}
|
|
2228
|
-
|
|
2265
|
+
__init109() {this._hitWasm = null}
|
|
2229
2266
|
// Cache key: which frame + structure version the grid was last (successfully,
|
|
2230
2267
|
// non-overflowing) built for. findEntityAt is called ad-hoc (pointer
|
|
2231
2268
|
// hover/click), not every frame, so the grid is refreshed lazily on demand
|
|
2232
2269
|
// rather than proactively every render() — unlike the transform store, which
|
|
2233
2270
|
// every frame's draw depends on.
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2271
|
+
__init110() {this._hitGridFrame = -1}
|
|
2272
|
+
__init111() {this._hitGridOk = false}
|
|
2273
|
+
__init112() {this._hitSlotEntity = []}
|
|
2274
|
+
__init113() {this._hitBoundless = []}
|
|
2238
2275
|
/** Reused buffer for the fused gather, so a pointer query allocates nothing. */
|
|
2239
|
-
|
|
2276
|
+
__init114() {this._hitGatherBuffer = null}
|
|
2240
2277
|
/**
|
|
2241
2278
|
* Whether the last grid build sourced its AABBs from the WASM transform store
|
|
2242
2279
|
* rather than recomputing them in JS. Diagnostic only — both paths must
|
|
2243
2280
|
* produce the same entity for a given point.
|
|
2244
2281
|
*/
|
|
2245
|
-
|
|
2282
|
+
__init115() {this._hitFusedGather = false}
|
|
2246
2283
|
/**
|
|
2247
2284
|
* Whether `compute_aabbs` has run against the current frame's world matrices.
|
|
2248
2285
|
* The AABB pass is only meaningful after a `compose_*`, so the fused gather
|
|
2249
2286
|
* must not read the views before then.
|
|
2250
2287
|
*/
|
|
2251
|
-
|
|
2288
|
+
__init116() {this._wasmAabbsFresh = false}
|
|
2252
2289
|
/** Did the last hit-grid build use the fused (WASM-store) gather? */
|
|
2253
2290
|
get hitGatherPath() {
|
|
2254
2291
|
return this._hitFusedGather ? "fused" : "js";
|
|
@@ -2257,9 +2294,9 @@ var Scene = (_class7 = class _Scene {
|
|
|
2257
2294
|
* Why the transform accelerator did or did not run on the most recent frame.
|
|
2258
2295
|
* Written by the render walk and `_syncWasmStore`.
|
|
2259
2296
|
*/
|
|
2260
|
-
|
|
2297
|
+
__init117() {this._transformReason = "not-installed"}
|
|
2261
2298
|
/** Why the batched-driver accelerator did or did not run. */
|
|
2262
|
-
|
|
2299
|
+
__init118() {this._animReason = "not-installed"}
|
|
2263
2300
|
/**
|
|
2264
2301
|
* Why the hit-test accelerator did or did not serve the last pointer query.
|
|
2265
2302
|
* The grid is built lazily on demand, not every frame, so this describes the
|
|
@@ -2267,11 +2304,11 @@ var Scene = (_class7 = class _Scene {
|
|
|
2267
2304
|
* before a backend exists; `_ensureHitGrid` moves it to `'not-applicable'`
|
|
2268
2305
|
* once one is installed but nothing has queried yet.
|
|
2269
2306
|
*/
|
|
2270
|
-
|
|
2307
|
+
__init119() {this._hitReason = "not-installed"}
|
|
2271
2308
|
/** Why the particle accelerator did or did not run. */
|
|
2272
|
-
|
|
2309
|
+
__init120() {this._particleReason = "not-applicable"}
|
|
2273
2310
|
/** Which particle implementation actually simulated the most recent frame. */
|
|
2274
|
-
|
|
2311
|
+
__init121() {this._particlePath = "none"}
|
|
2275
2312
|
/**
|
|
2276
2313
|
* Per-frame status of every invisible accelerator: whether each is installed,
|
|
2277
2314
|
* whether it actually ran on the most recent frame, and why.
|
|
@@ -2431,25 +2468,25 @@ var Scene = (_class7 = class _Scene {
|
|
|
2431
2468
|
// EasingFn (which cannot cross into WASM) all fall through to it — WASM can
|
|
2432
2469
|
// only ever change *how* a driver is advanced, never *what* value it lands
|
|
2433
2470
|
// on.
|
|
2434
|
-
|
|
2471
|
+
__init122() {this._animWasm = null}
|
|
2435
2472
|
// Entities with at least one active driver, added by Entity._spawnDriver.
|
|
2436
2473
|
// Self-pruning: _tickBatchedDrivers drops an entry the first time it visits
|
|
2437
2474
|
// an entity whose drivers have since all completed or been removed. This is
|
|
2438
2475
|
// what lets the batch pass find its candidates in O(active drivers), not
|
|
2439
2476
|
// O(tree size) — the exact mistake G3's first integrated benchmark made.
|
|
2440
|
-
|
|
2477
|
+
__init123() {this._activeDriverEntities = /* @__PURE__ */ new Set()}
|
|
2441
2478
|
// Reused across frames instead of allocating a fresh array + N {entity,prop,
|
|
2442
2479
|
// driver} objects every call — the integrated benchmark
|
|
2443
2480
|
// (benchmarks/anim-wasm-scene) found that allocation churn was the
|
|
2444
2481
|
// dominant integrated cost, not the wasm kernel itself. Parallel arrays,
|
|
2445
2482
|
// truncated to the live count after each use so a stale tail slot never
|
|
2446
2483
|
// pins a no-longer-active entity/driver in memory.
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2484
|
+
__init124() {this._springEntities = []}
|
|
2485
|
+
__init125() {this._springProps = []}
|
|
2486
|
+
__init126() {this._springDrivers = []}
|
|
2487
|
+
__init127() {this._tweenEntities = []}
|
|
2488
|
+
__init128() {this._tweenProps = []}
|
|
2489
|
+
__init129() {this._tweenDrivers = []}
|
|
2453
2490
|
/**
|
|
2454
2491
|
* Minimum number of batchable (spring, or named-easing tween) active drivers
|
|
2455
2492
|
* before a frame engages the WASM batch path at all; below it, every driver
|
|
@@ -2521,7 +2558,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
2521
2558
|
* Setting {@link animDriverGateCount} overwrites all three, so existing code
|
|
2522
2559
|
* that tuned the single knob keeps working unchanged.
|
|
2523
2560
|
*/
|
|
2524
|
-
|
|
2561
|
+
__init130() {this._animBatchedLastFrame = false}
|
|
2525
2562
|
/**
|
|
2526
2563
|
* Whether the WASM batch path actually ran on the most recent frame.
|
|
2527
2564
|
*
|
|
@@ -2533,7 +2570,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
2533
2570
|
get animBatchedLastFrame() {
|
|
2534
2571
|
return this._animBatchedLastFrame;
|
|
2535
2572
|
}
|
|
2536
|
-
|
|
2573
|
+
__init131() {this.animGate = {
|
|
2537
2574
|
spring: 128,
|
|
2538
2575
|
tween: 256,
|
|
2539
2576
|
mixed: 128
|
|
@@ -2571,7 +2608,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
2571
2608
|
// (benchmarks/particle-wasm). f32 (matches the WGSL shader), bit-identical to
|
|
2572
2609
|
// a JS f32 reference oracle; updateCPU (f64) stays the permanent fallback when
|
|
2573
2610
|
// no backend is installed or a scene runs on WebGPU.
|
|
2574
|
-
|
|
2611
|
+
__init132() {this._particleWasm = null}
|
|
2575
2612
|
/** Which backend runs the CPU particle simulation. Reflects only whether a
|
|
2576
2613
|
* backend is installed (the WebGPU compute path, when active, is used first
|
|
2577
2614
|
* regardless). */
|
|
@@ -2848,24 +2885,24 @@ var Scene = (_class7 = class _Scene {
|
|
|
2848
2885
|
* sync, so retaining the order prevents a newly opened overlay from spending
|
|
2849
2886
|
* its first frame below previously projected controls.
|
|
2850
2887
|
*/
|
|
2851
|
-
|
|
2888
|
+
__init133() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
|
|
2852
2889
|
// Optional WebGL point-cloud layer (see SceneOptions.pointBackend).
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2890
|
+
__init134() {this.pointRenderer = null}
|
|
2891
|
+
__init135() {this.glCanvas = null}
|
|
2892
|
+
__init136() {this.glContextLostHandler = null}
|
|
2893
|
+
__init137() {this.glContextRestoredHandler = null}
|
|
2857
2894
|
|
|
2858
2895
|
|
|
2859
2896
|
|
|
2860
|
-
|
|
2897
|
+
__init138() {this.disableWindowResize = false}
|
|
2861
2898
|
/** See {@link SceneOptions.maxDPR}. `undefined` = uncapped (real DPR). */
|
|
2862
2899
|
|
|
2863
2900
|
// WebGPU properties
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2901
|
+
__init139() {this.destroyed = false}
|
|
2902
|
+
__init140() {this.device = null}
|
|
2903
|
+
__init141() {this.deviceLost = false}
|
|
2904
|
+
__init142() {this.particleBackend = "auto"}
|
|
2905
|
+
__init143() {this._webgpuDisabled = false}
|
|
2869
2906
|
get webgpuDisabled() {
|
|
2870
2907
|
return this._webgpuDisabled || this.particleBackend === "cpu";
|
|
2871
2908
|
}
|
|
@@ -2893,22 +2930,22 @@ var Scene = (_class7 = class _Scene {
|
|
|
2893
2930
|
set webgpuDisabled(value) {
|
|
2894
2931
|
this._webgpuDisabled = value;
|
|
2895
2932
|
}
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2933
|
+
__init144() {this.recoveryTimerId = null}
|
|
2934
|
+
__init145() {this.manager = null}
|
|
2935
|
+
__init146() {this.initializingWebGPU = false}
|
|
2936
|
+
__init147() {this.gpuCanvas = null}
|
|
2937
|
+
__init148() {this.gpuContext = null}
|
|
2901
2938
|
/** True while the GPU canvas holds a presented particle frame (needs clearing when they leave). */
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2939
|
+
__init149() {this.gpuHasContent = false}
|
|
2940
|
+
__init150() {this.mouseX = -9999}
|
|
2941
|
+
__init151() {this.mouseY = -9999}
|
|
2942
|
+
__init152() {this.pointerMoveListener = null}
|
|
2943
|
+
__init153() {this.pointerLeaveListener = null}
|
|
2907
2944
|
/** Element the pointer listeners are bound to (parent container if present,
|
|
2908
2945
|
* else the canvas). Stored so `destroy()` detaches from the same element. */
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2946
|
+
__init154() {this.pointerEventTarget = null}
|
|
2947
|
+
__init155() {this.hasWarnedZeroSize = false}
|
|
2948
|
+
__init156() {this.fontLoadHandler = null}
|
|
2912
2949
|
// ── Dev-mode warning infrastructure ──────────────────────────────
|
|
2913
2950
|
//
|
|
2914
2951
|
// Enable with `Scene.devMode = true` or by setting `globalThis.__DEV__`.
|
|
@@ -2942,7 +2979,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
2942
2979
|
return false;
|
|
2943
2980
|
}
|
|
2944
2981
|
|
|
2945
|
-
|
|
2982
|
+
__init157() {this._devFrameCount = 0}
|
|
2946
2983
|
_devWarn(message) {
|
|
2947
2984
|
if (!this._devActive) return;
|
|
2948
2985
|
console.warn(`[vectojs/dev] ${message}`);
|
|
@@ -3019,7 +3056,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
3019
3056
|
};
|
|
3020
3057
|
walkProjections(this.root);
|
|
3021
3058
|
}
|
|
3022
|
-
constructor(canvas, options = {}) {;_class7.prototype.__init27.call(this);_class7.prototype.__init28.call(this);_class7.prototype.__init29.call(this);_class7.prototype.__init30.call(this);_class7.prototype.__init31.call(this);_class7.prototype.__init32.call(this);_class7.prototype.__init33.call(this);_class7.prototype.__init34.call(this);_class7.prototype.__init35.call(this);_class7.prototype.__init36.call(this);_class7.prototype.__init37.call(this);_class7.prototype.__init38.call(this);_class7.prototype.__init39.call(this);_class7.prototype.__init40.call(this);_class7.prototype.__init41.call(this);_class7.prototype.__init42.call(this);_class7.prototype.__init43.call(this);_class7.prototype.__init44.call(this);_class7.prototype.__init45.call(this);_class7.prototype.__init46.call(this);_class7.prototype.__init47.call(this);_class7.prototype.__init48.call(this);_class7.prototype.__init49.call(this);_class7.prototype.__init50.call(this);_class7.prototype.__init51.call(this);_class7.prototype.__init52.call(this);_class7.prototype.__init53.call(this);_class7.prototype.__init54.call(this);_class7.prototype.__init55.call(this);_class7.prototype.__init56.call(this);_class7.prototype.__init57.call(this);_class7.prototype.__init58.call(this);_class7.prototype.__init59.call(this);_class7.prototype.__init60.call(this);_class7.prototype.__init61.call(this);_class7.prototype.__init62.call(this);_class7.prototype.__init63.call(this);_class7.prototype.__init64.call(this);_class7.prototype.__init65.call(this);_class7.prototype.__init66.call(this);_class7.prototype.__init67.call(this);_class7.prototype.__init68.call(this);_class7.prototype.__init69.call(this);_class7.prototype.__init70.call(this);_class7.prototype.__init71.call(this);_class7.prototype.__init72.call(this);_class7.prototype.__init73.call(this);_class7.prototype.__init74.call(this);_class7.prototype.__init75.call(this);_class7.prototype.__init76.call(this);_class7.prototype.__init77.call(this);_class7.prototype.__init78.call(this);_class7.prototype.__init79.call(this);_class7.prototype.__init80.call(this);_class7.prototype.__init81.call(this);_class7.prototype.__init82.call(this);_class7.prototype.__init83.call(this);_class7.prototype.__init84.call(this);_class7.prototype.__init85.call(this);_class7.prototype.__init86.call(this);_class7.prototype.__init87.call(this);_class7.prototype.__init88.call(this);_class7.prototype.__init89.call(this);_class7.prototype.__init90.call(this);_class7.prototype.__init91.call(this);_class7.prototype.__init92.call(this);_class7.prototype.__init93.call(this);_class7.prototype.__init94.call(this);_class7.prototype.__init95.call(this);_class7.prototype.__init96.call(this);_class7.prototype.__init97.call(this);_class7.prototype.__init98.call(this);_class7.prototype.__init99.call(this);_class7.prototype.__init100.call(this);_class7.prototype.__init101.call(this);_class7.prototype.__init102.call(this);_class7.prototype.__init103.call(this);_class7.prototype.__init104.call(this);_class7.prototype.__init105.call(this);_class7.prototype.__init106.call(this);_class7.prototype.__init107.call(this);_class7.prototype.__init108.call(this);_class7.prototype.__init109.call(this);_class7.prototype.__init110.call(this);_class7.prototype.__init111.call(this);_class7.prototype.__init112.call(this);_class7.prototype.__init113.call(this);_class7.prototype.__init114.call(this);_class7.prototype.__init115.call(this);_class7.prototype.__init116.call(this);_class7.prototype.__init117.call(this);_class7.prototype.__init118.call(this);_class7.prototype.__init119.call(this);_class7.prototype.__init120.call(this);_class7.prototype.__init121.call(this);_class7.prototype.__init122.call(this);_class7.prototype.__init123.call(this);_class7.prototype.__init124.call(this);_class7.prototype.__init125.call(this);_class7.prototype.__init126.call(this);_class7.prototype.__init127.call(this);_class7.prototype.__init128.call(this);_class7.prototype.__init129.call(this);_class7.prototype.__init130.call(this);_class7.prototype.__init131.call(this);_class7.prototype.__init132.call(this);_class7.prototype.__init133.call(this);_class7.prototype.__init134.call(this);_class7.prototype.__init135.call(this);_class7.prototype.__init136.call(this);_class7.prototype.__init137.call(this);_class7.prototype.__init138.call(this);_class7.prototype.__init139.call(this);_class7.prototype.__init140.call(this);_class7.prototype.__init141.call(this);_class7.prototype.__init142.call(this);_class7.prototype.__init143.call(this);_class7.prototype.__init144.call(this);_class7.prototype.__init145.call(this);_class7.prototype.__init146.call(this);_class7.prototype.__init147.call(this);_class7.prototype.__init148.call(this);_class7.prototype.__init149.call(this);_class7.prototype.__init150.call(this);_class7.prototype.__init151.call(this);_class7.prototype.__init152.call(this);_class7.prototype.__init153.call(this);
|
|
3059
|
+
constructor(canvas, options = {}) {;_class7.prototype.__init27.call(this);_class7.prototype.__init28.call(this);_class7.prototype.__init29.call(this);_class7.prototype.__init30.call(this);_class7.prototype.__init31.call(this);_class7.prototype.__init32.call(this);_class7.prototype.__init33.call(this);_class7.prototype.__init34.call(this);_class7.prototype.__init35.call(this);_class7.prototype.__init36.call(this);_class7.prototype.__init37.call(this);_class7.prototype.__init38.call(this);_class7.prototype.__init39.call(this);_class7.prototype.__init40.call(this);_class7.prototype.__init41.call(this);_class7.prototype.__init42.call(this);_class7.prototype.__init43.call(this);_class7.prototype.__init44.call(this);_class7.prototype.__init45.call(this);_class7.prototype.__init46.call(this);_class7.prototype.__init47.call(this);_class7.prototype.__init48.call(this);_class7.prototype.__init49.call(this);_class7.prototype.__init50.call(this);_class7.prototype.__init51.call(this);_class7.prototype.__init52.call(this);_class7.prototype.__init53.call(this);_class7.prototype.__init54.call(this);_class7.prototype.__init55.call(this);_class7.prototype.__init56.call(this);_class7.prototype.__init57.call(this);_class7.prototype.__init58.call(this);_class7.prototype.__init59.call(this);_class7.prototype.__init60.call(this);_class7.prototype.__init61.call(this);_class7.prototype.__init62.call(this);_class7.prototype.__init63.call(this);_class7.prototype.__init64.call(this);_class7.prototype.__init65.call(this);_class7.prototype.__init66.call(this);_class7.prototype.__init67.call(this);_class7.prototype.__init68.call(this);_class7.prototype.__init69.call(this);_class7.prototype.__init70.call(this);_class7.prototype.__init71.call(this);_class7.prototype.__init72.call(this);_class7.prototype.__init73.call(this);_class7.prototype.__init74.call(this);_class7.prototype.__init75.call(this);_class7.prototype.__init76.call(this);_class7.prototype.__init77.call(this);_class7.prototype.__init78.call(this);_class7.prototype.__init79.call(this);_class7.prototype.__init80.call(this);_class7.prototype.__init81.call(this);_class7.prototype.__init82.call(this);_class7.prototype.__init83.call(this);_class7.prototype.__init84.call(this);_class7.prototype.__init85.call(this);_class7.prototype.__init86.call(this);_class7.prototype.__init87.call(this);_class7.prototype.__init88.call(this);_class7.prototype.__init89.call(this);_class7.prototype.__init90.call(this);_class7.prototype.__init91.call(this);_class7.prototype.__init92.call(this);_class7.prototype.__init93.call(this);_class7.prototype.__init94.call(this);_class7.prototype.__init95.call(this);_class7.prototype.__init96.call(this);_class7.prototype.__init97.call(this);_class7.prototype.__init98.call(this);_class7.prototype.__init99.call(this);_class7.prototype.__init100.call(this);_class7.prototype.__init101.call(this);_class7.prototype.__init102.call(this);_class7.prototype.__init103.call(this);_class7.prototype.__init104.call(this);_class7.prototype.__init105.call(this);_class7.prototype.__init106.call(this);_class7.prototype.__init107.call(this);_class7.prototype.__init108.call(this);_class7.prototype.__init109.call(this);_class7.prototype.__init110.call(this);_class7.prototype.__init111.call(this);_class7.prototype.__init112.call(this);_class7.prototype.__init113.call(this);_class7.prototype.__init114.call(this);_class7.prototype.__init115.call(this);_class7.prototype.__init116.call(this);_class7.prototype.__init117.call(this);_class7.prototype.__init118.call(this);_class7.prototype.__init119.call(this);_class7.prototype.__init120.call(this);_class7.prototype.__init121.call(this);_class7.prototype.__init122.call(this);_class7.prototype.__init123.call(this);_class7.prototype.__init124.call(this);_class7.prototype.__init125.call(this);_class7.prototype.__init126.call(this);_class7.prototype.__init127.call(this);_class7.prototype.__init128.call(this);_class7.prototype.__init129.call(this);_class7.prototype.__init130.call(this);_class7.prototype.__init131.call(this);_class7.prototype.__init132.call(this);_class7.prototype.__init133.call(this);_class7.prototype.__init134.call(this);_class7.prototype.__init135.call(this);_class7.prototype.__init136.call(this);_class7.prototype.__init137.call(this);_class7.prototype.__init138.call(this);_class7.prototype.__init139.call(this);_class7.prototype.__init140.call(this);_class7.prototype.__init141.call(this);_class7.prototype.__init142.call(this);_class7.prototype.__init143.call(this);_class7.prototype.__init144.call(this);_class7.prototype.__init145.call(this);_class7.prototype.__init146.call(this);_class7.prototype.__init147.call(this);_class7.prototype.__init148.call(this);_class7.prototype.__init149.call(this);_class7.prototype.__init150.call(this);_class7.prototype.__init151.call(this);_class7.prototype.__init152.call(this);_class7.prototype.__init153.call(this);_class7.prototype.__init154.call(this);_class7.prototype.__init155.call(this);_class7.prototype.__init156.call(this);_class7.prototype.__init157.call(this);
|
|
3023
3060
|
this.canvas = canvas;
|
|
3024
3061
|
this.debugA11y = _nullishCoalesce(options.debugA11y, () => ( false));
|
|
3025
3062
|
this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
|
|
@@ -3044,6 +3081,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
3044
3081
|
this.contentProjectionEnabled = _nullishCoalesce(options.contentProjection, () => ( true));
|
|
3045
3082
|
this.contentProjectionMargin = options.contentProjectionMargin;
|
|
3046
3083
|
this.contentSemanticMargin = options.contentSemanticMargin;
|
|
3084
|
+
this.contentSemanticBudget = _nullishCoalesce(options.contentSemanticBudget, () => ( DEFAULT_CONTENT_SEMANTIC_BUDGET));
|
|
3047
3085
|
this.readingDirection = _nullishCoalesce(options.readingDirection, () => ( "ltr"));
|
|
3048
3086
|
this.renderMode = _nullishCoalesce(options.renderMode, () => ( "always"));
|
|
3049
3087
|
this._devActive = _Scene._devModeDetected();
|
|
@@ -3325,12 +3363,31 @@ var Scene = (_class7 = class _Scene {
|
|
|
3325
3363
|
}
|
|
3326
3364
|
return null;
|
|
3327
3365
|
}
|
|
3366
|
+
/**
|
|
3367
|
+
* Does the document hold a selection right now, memoized for this sync walk?
|
|
3368
|
+
*
|
|
3369
|
+
* Pays one forced layout per walk instead of one per rebuilt element — see
|
|
3370
|
+
* {@link Scene.contentSelectionPresentThisSync} for the measurements. When the
|
|
3371
|
+
* answer is `false` no element can own a selection, so every per-element
|
|
3372
|
+
* ownership test can be skipped without touching the object.
|
|
3373
|
+
*/
|
|
3374
|
+
contentSelectionPresent() {
|
|
3375
|
+
if (this.contentSelectionPresentThisSync !== null) {
|
|
3376
|
+
return this.contentSelectionPresentThisSync;
|
|
3377
|
+
}
|
|
3378
|
+
const selection = typeof window !== "undefined" && typeof window.getSelection === "function" ? window.getSelection() : null;
|
|
3379
|
+
const present = !!selection && (!!selection.anchorNode || !!selection.focusNode);
|
|
3380
|
+
this.contentSelectionPresentThisSync = present;
|
|
3381
|
+
return present;
|
|
3382
|
+
}
|
|
3328
3383
|
releaseContentSelectionForRebuild(el) {
|
|
3384
|
+
if (!this.contentSelectionAnchor && !this.contentSelectionPresent()) return;
|
|
3329
3385
|
const selection = typeof window !== "undefined" && typeof window.getSelection === "function" ? window.getSelection() : null;
|
|
3330
3386
|
const ownsSelection = this.contentSelectionAnchor && el.contains(this.contentSelectionAnchor.node) || (_optionalChain([selection, 'optionalAccess', _85 => _85.anchorNode]) ? el.contains(selection.anchorNode) : false) || (_optionalChain([selection, 'optionalAccess', _86 => _86.focusNode]) ? el.contains(selection.focusNode) : false);
|
|
3331
3387
|
if (!ownsSelection) return;
|
|
3332
3388
|
this.endContentSelectionDrag();
|
|
3333
3389
|
_optionalChain([selection, 'optionalAccess', _87 => _87.removeAllRanges, 'call', _88 => _88()]);
|
|
3390
|
+
this.contentSelectionPresentThisSync = null;
|
|
3334
3391
|
}
|
|
3335
3392
|
/**
|
|
3336
3393
|
* Rebuild a content-projection element's DOM (`rebuild`) while preserving a
|
|
@@ -3348,6 +3405,10 @@ var Scene = (_class7 = class _Scene {
|
|
|
3348
3405
|
* restore against).
|
|
3349
3406
|
*/
|
|
3350
3407
|
preserveContentSelectionAcrossRebuild(el, rebuild) {
|
|
3408
|
+
if (!this.contentSelectionAnchor && !this.contentSelectionPresent()) {
|
|
3409
|
+
rebuild();
|
|
3410
|
+
return;
|
|
3411
|
+
}
|
|
3351
3412
|
const selection = typeof window !== "undefined" && typeof window.getSelection === "function" ? window.getSelection() : null;
|
|
3352
3413
|
const owns = !!selection && !this.blankRegionSelectionDrag && ((selection.anchorNode ? el.contains(selection.anchorNode) : false) || (selection.focusNode ? el.contains(selection.focusNode) : false));
|
|
3353
3414
|
if (!owns || !selection.anchorNode || !selection.focusNode) {
|
|
@@ -4043,6 +4104,11 @@ var Scene = (_class7 = class _Scene {
|
|
|
4043
4104
|
}
|
|
4044
4105
|
syncA11y(node, container = null) {
|
|
4045
4106
|
if (!this.a11yRoot) return;
|
|
4107
|
+
if (node === this.root) {
|
|
4108
|
+
this.contentSemanticBudgetLeft = this.contentSemanticBudget;
|
|
4109
|
+
this.contentSemanticDeferred = false;
|
|
4110
|
+
this.contentSelectionPresentThisSync = null;
|
|
4111
|
+
}
|
|
4046
4112
|
if (node.isDOMPortal) {
|
|
4047
4113
|
return;
|
|
4048
4114
|
}
|
|
@@ -4461,7 +4527,9 @@ var Scene = (_class7 = class _Scene {
|
|
|
4461
4527
|
* transparent DOM node positioned over the drawn glyphs. Runs on the a11y
|
|
4462
4528
|
* sync cadence; all writes are dirty-checked. Off-viewport projections are
|
|
4463
4529
|
* hidden (`display: none`) so text-heavy scenes only materialize what is
|
|
4464
|
-
* visible to the browser's text machinery anyway
|
|
4530
|
+
* visible to the browser's text machinery anyway — except in the coarse
|
|
4531
|
+
* (resident) tier, which stays displayed because hiding it would make its text
|
|
4532
|
+
* unfindable and remove it from the accessibility tree, defeating the tier.
|
|
4465
4533
|
*/
|
|
4466
4534
|
/**
|
|
4467
4535
|
* Whether `node`'s world-space box, expanded by `margin` px on every side,
|
|
@@ -4470,8 +4538,15 @@ var Scene = (_class7 = class _Scene {
|
|
|
4470
4538
|
* at `margin = contentProjectionMargin`) and for the exact `display:none`
|
|
4471
4539
|
* visibility test (`margin = 0`). Boundless nodes (width/height 0) opt out of
|
|
4472
4540
|
* culling and always count as visible, matching the legacy behavior.
|
|
4541
|
+
*
|
|
4542
|
+
* `viewportOnly` skips the `clipChildren` ancestor walk, answering the narrower
|
|
4543
|
+
* question "does this box overlap the viewport at all". The coarse content tier
|
|
4544
|
+
* needs the two apart: text that is merely off-viewport is clipped by
|
|
4545
|
+
* `a11yRoot`'s own `overflow: hidden` and can safely stay displayed, while text
|
|
4546
|
+
* rejected by an ancestor clip box that itself overlaps the viewport would sit
|
|
4547
|
+
* transparently on top of whatever is really drawn there.
|
|
4473
4548
|
*/
|
|
4474
|
-
projectionBoxVisible(node, tf, margin) {
|
|
4549
|
+
projectionBoxVisible(node, tf, margin, viewportOnly = false) {
|
|
4475
4550
|
if (!(node.width > 0 && node.height > 0)) return true;
|
|
4476
4551
|
const { a, b, c, d, e, f } = tf;
|
|
4477
4552
|
const worldCorners = [];
|
|
@@ -4493,6 +4568,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
4493
4568
|
if (!(maxX >= -margin && minX <= this.width + margin && maxY >= -margin && minY <= this.height + margin)) {
|
|
4494
4569
|
return false;
|
|
4495
4570
|
}
|
|
4571
|
+
if (viewportOnly) return true;
|
|
4496
4572
|
for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
|
|
4497
4573
|
if (!ancestor.clipChildren || ancestor.width <= 0 || ancestor.height <= 0) continue;
|
|
4498
4574
|
let localMinX = Infinity;
|
|
@@ -4595,6 +4671,10 @@ var Scene = (_class7 = class _Scene {
|
|
|
4595
4671
|
return;
|
|
4596
4672
|
}
|
|
4597
4673
|
}
|
|
4674
|
+
if (tier === "coarse" && !el && this.contentSemanticBudgetLeft <= 0) {
|
|
4675
|
+
this.contentSemanticDeferred = true;
|
|
4676
|
+
return;
|
|
4677
|
+
}
|
|
4598
4678
|
const projection = node.getContentProjection(
|
|
4599
4679
|
lineBand ? { minY: lineBand.minY, maxY: lineBand.maxY } : void 0
|
|
4600
4680
|
);
|
|
@@ -4603,6 +4683,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
4603
4683
|
return;
|
|
4604
4684
|
}
|
|
4605
4685
|
if (!el) {
|
|
4686
|
+
if (tier === "coarse") this.contentSemanticBudgetLeft--;
|
|
4606
4687
|
el = document.createElement("div");
|
|
4607
4688
|
el.setAttribute("data-vecto-content", node.id);
|
|
4608
4689
|
const s = el.style;
|
|
@@ -4737,7 +4818,8 @@ var Scene = (_class7 = class _Scene {
|
|
|
4737
4818
|
if (node.width > 0) el.style.width = `${node.width}px`;
|
|
4738
4819
|
if (node.height > 0) el.style.height = `${node.height}px`;
|
|
4739
4820
|
el.style.transform = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0)`;
|
|
4740
|
-
const
|
|
4821
|
+
const residentTier = semanticMargin > interactionMargin;
|
|
4822
|
+
const display = visible || residentTier && !this.projectionBoxVisible(node, worldTf, 0, true) ? "" : "none";
|
|
4741
4823
|
if (el.style.display !== display) el.style.display = display;
|
|
4742
4824
|
if (epoch !== null) {
|
|
4743
4825
|
const { a: a2, b: b2, c: c2, d: d2, e: e2, f: f2 } = worldTf;
|
|
@@ -5380,7 +5462,7 @@ var Scene = (_class7 = class _Scene {
|
|
|
5380
5462
|
if (!this.isRunning) return;
|
|
5381
5463
|
if (!this._canvasOnScreen) return;
|
|
5382
5464
|
let cap = this.effectiveMaxFPS();
|
|
5383
|
-
const isIdle = !this.dirty && !this.frameHadAnimation;
|
|
5465
|
+
const isIdle = !this.dirty && !this.frameHadAnimation && !this.contentSemanticDeferred;
|
|
5384
5466
|
if (isIdle && this.autoThrottle && this.renderMode === "always" && this.maxFPS > 0) {
|
|
5385
5467
|
cap = Math.min(cap, 2);
|
|
5386
5468
|
}
|
|
@@ -6155,17 +6237,17 @@ var TextEntity = (_class8 = class extends _chunkTRT2TRCWjs.Entity {
|
|
|
6155
6237
|
|
|
6156
6238
|
|
|
6157
6239
|
|
|
6158
|
-
|
|
6240
|
+
__init158() {this.nodes = []}
|
|
6159
6241
|
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6242
|
+
__init159() {this.fillStyle = "#94a3b8"}
|
|
6243
|
+
__init160() {this.strokeStyle = null}
|
|
6244
|
+
__init161() {this.hoveredFillStyle = "#ffffff"}
|
|
6245
|
+
__init162() {this.lineWidth = 1}
|
|
6246
|
+
__init163() {this.isHovered = false}
|
|
6165
6247
|
/** Bumped by {@link applyLayout}; read by `Scene` to skip an unchanged sync. */
|
|
6166
|
-
|
|
6248
|
+
__init164() {this.contentEpoch = 0}
|
|
6167
6249
|
constructor(text, atlas, maxWidth, fontSize = 24) {
|
|
6168
|
-
super();_class8.prototype.
|
|
6250
|
+
super();_class8.prototype.__init158.call(this);_class8.prototype.__init159.call(this);_class8.prototype.__init160.call(this);_class8.prototype.__init161.call(this);_class8.prototype.__init162.call(this);_class8.prototype.__init163.call(this);_class8.prototype.__init164.call(this);;
|
|
6169
6251
|
this.text = text;
|
|
6170
6252
|
this.atlas = atlas;
|
|
6171
6253
|
this.fontSize = fontSize;
|
|
@@ -6284,15 +6366,15 @@ var TextEntity = (_class8 = class extends _chunkTRT2TRCWjs.Entity {
|
|
|
6284
6366
|
// src/components/GridTextEntity.ts
|
|
6285
6367
|
var GridTextEntity = (_class9 = class extends _chunkTRT2TRCWjs.Entity {
|
|
6286
6368
|
|
|
6287
|
-
|
|
6288
|
-
|
|
6369
|
+
__init165() {this.fillStyle = "#ffffff"}
|
|
6370
|
+
__init166() {this.grid = []}
|
|
6289
6371
|
// Array of rows
|
|
6290
|
-
|
|
6291
|
-
|
|
6372
|
+
__init167() {this.cols = 0}
|
|
6373
|
+
__init168() {this.rows = 0}
|
|
6292
6374
|
|
|
6293
6375
|
|
|
6294
6376
|
constructor(_atlas, fontSize = 10) {
|
|
6295
|
-
super();_class9.prototype.
|
|
6377
|
+
super();_class9.prototype.__init165.call(this);_class9.prototype.__init166.call(this);_class9.prototype.__init167.call(this);_class9.prototype.__init168.call(this);;
|
|
6296
6378
|
this.fontSize = fontSize;
|
|
6297
6379
|
this.charWidth = fontSize * 1;
|
|
6298
6380
|
this.charHeight = fontSize * 1.1;
|
|
@@ -6384,23 +6466,23 @@ var SplineEntity = (_class10 = class extends _chunkTRT2TRCWjs.Entity {
|
|
|
6384
6466
|
|
|
6385
6467
|
|
|
6386
6468
|
|
|
6387
|
-
|
|
6388
|
-
|
|
6469
|
+
__init169() {this.offscreen = null}
|
|
6470
|
+
__init170() {this.baked = false}
|
|
6389
6471
|
/** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
|
|
6390
|
-
|
|
6391
|
-
|
|
6472
|
+
__init171() {this.bakedWidth = 0}
|
|
6473
|
+
__init172() {this.bakedHeight = 0}
|
|
6392
6474
|
/** Gradient strokes can't be baked to a solid-color bitmap; they render per-frame. */
|
|
6393
6475
|
|
|
6394
6476
|
/** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
|
|
6395
|
-
|
|
6477
|
+
__init173() {this.polylines = null}
|
|
6396
6478
|
/**
|
|
6397
6479
|
* When `true`, the renderer draws a rounded-rect outline of the entity's
|
|
6398
6480
|
* local bounds after painting the curves. Useful for drag feedback and
|
|
6399
6481
|
* debugging hit areas. Defaults to `false`.
|
|
6400
6482
|
*/
|
|
6401
|
-
|
|
6483
|
+
__init174() {this.showBounds = false}
|
|
6402
6484
|
constructor(doc, opts = {}) {
|
|
6403
|
-
super();_class10.prototype.
|
|
6485
|
+
super();_class10.prototype.__init169.call(this);_class10.prototype.__init170.call(this);_class10.prototype.__init171.call(this);_class10.prototype.__init172.call(this);_class10.prototype.__init173.call(this);_class10.prototype.__init174.call(this);;
|
|
6404
6486
|
this.doc = doc;
|
|
6405
6487
|
this.lineWidth = _nullishCoalesce(opts.lineWidth, () => ( 2));
|
|
6406
6488
|
this.cache = _nullishCoalesce(opts.cache, () => ( true));
|
|
@@ -6781,19 +6863,19 @@ var _math = require('@vectojs/math'); _createStarExport(_math);
|
|
|
6781
6863
|
// src/tree/DOMPortalEntity.ts
|
|
6782
6864
|
var DOMPortalEntity = (_class11 = class extends _chunkTRT2TRCWjs.Entity {
|
|
6783
6865
|
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6866
|
+
__init175() {this.isDOMPortal = true}
|
|
6867
|
+
__init176() {this.domListeners = []}
|
|
6868
|
+
__init177() {this.resizeObserver = null}
|
|
6869
|
+
__init178() {this.domBound = false}
|
|
6870
|
+
__init179() {this.cachedWidth = 100}
|
|
6871
|
+
__init180() {this.cachedHeight = 100}
|
|
6872
|
+
__init181() {this.lastWidth = ""}
|
|
6873
|
+
__init182() {this.lastHeight = ""}
|
|
6874
|
+
__init183() {this.lastTransform = ""}
|
|
6875
|
+
__init184() {this.lastZIndex = ""}
|
|
6876
|
+
__init185() {this.lastOpacity = ""}
|
|
6795
6877
|
constructor(domElement, width, height, id) {
|
|
6796
|
-
super(id);_class11.prototype.
|
|
6878
|
+
super(id);_class11.prototype.__init175.call(this);_class11.prototype.__init176.call(this);_class11.prototype.__init177.call(this);_class11.prototype.__init178.call(this);_class11.prototype.__init179.call(this);_class11.prototype.__init180.call(this);_class11.prototype.__init181.call(this);_class11.prototype.__init182.call(this);_class11.prototype.__init183.call(this);_class11.prototype.__init184.call(this);_class11.prototype.__init185.call(this);;
|
|
6797
6879
|
this.domElement = domElement;
|
|
6798
6880
|
this.width = _nullishCoalesce(width, () => ( 0));
|
|
6799
6881
|
this.height = _nullishCoalesce(height, () => ( 0));
|
|
@@ -6951,4 +7033,5 @@ Scene.registerWebGPUParticleSystemManager(_chunkM73XB4ZKjs.WebGPUParticleSystemM
|
|
|
6951
7033
|
|
|
6952
7034
|
|
|
6953
7035
|
|
|
6954
|
-
|
|
7036
|
+
|
|
7037
|
+
exports.CanvasRenderer = _chunkM73XB4ZKjs.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DEFAULT_CONTENT_SEMANTIC_BUDGET = DEFAULT_CONTENT_SEMANTIC_BUDGET; exports.DOMPortalEntity = DOMPortalEntity; exports.Entity = _chunkTRT2TRCWjs.Entity; exports.GlyphRasterAtlas = _chunkM73XB4ZKjs.GlyphRasterAtlas; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.MSDFTextEntity = _chunkTRT2TRCWjs.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 = _chunkTRT2TRCWjs.SVGEntity; exports.SVGRenderer = _chunkM73XB4ZKjs.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkM73XB4ZKjs.TextRasterCache; exports.VECTO_USER_TIMING = VECTO_USER_TIMING; exports.VectoJSEvent = _chunkTRT2TRCWjs.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkM73XB4ZKjs.WebGPUParticleSystemManager; exports.beginVectoUserTiming = beginVectoUserTiming; exports.contentLineInHint = _chunkTRT2TRCWjs.contentLineInHint; exports.createWebGLPointRenderer = _chunkM73XB4ZKjs.createWebGLPointRenderer; exports.endVectoUserTiming = endVectoUserTiming; exports.installRendererDevTraps = _chunkM73XB4ZKjs.installRendererDevTraps; exports.isRendererDevMode = _chunkM73XB4ZKjs.isRendererDevMode; exports.isSafeUrl = _chunkM73XB4ZKjs.isSafeUrl; exports.loadSpline = loadSpline; exports.measureVectoUserTiming = measureVectoUserTiming; exports.parseColorToRGBA = _chunkM73XB4ZKjs.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkM73XB4ZKjs.sanitizeUrl; exports.setRendererDevMode = _chunkM73XB4ZKjs.setRendererDevMode;
|
package/dist/index.mjs
CHANGED
|
@@ -1379,6 +1379,7 @@ var SCENE_OPTION_KEYS = [
|
|
|
1379
1379
|
"autoThrottle",
|
|
1380
1380
|
"contentProjection",
|
|
1381
1381
|
"contentProjectionMargin",
|
|
1382
|
+
"contentSemanticBudget",
|
|
1382
1383
|
"contentSemanticMargin",
|
|
1383
1384
|
"debugA11y",
|
|
1384
1385
|
"disableWindowResize",
|
|
@@ -1432,6 +1433,7 @@ function parseInlinePx(value) {
|
|
|
1432
1433
|
const n = parseFloat(value);
|
|
1433
1434
|
return Number.isFinite(n) && n > 0 ? n : null;
|
|
1434
1435
|
}
|
|
1436
|
+
var DEFAULT_CONTENT_SEMANTIC_BUDGET = 256;
|
|
1435
1437
|
function projectionGridLineWindow(grid, projectionLines, band) {
|
|
1436
1438
|
const count = grid.lines.length;
|
|
1437
1439
|
const all = { start: 0, end: count, gated: false };
|
|
@@ -2007,6 +2009,41 @@ var Scene = class _Scene {
|
|
|
2007
2009
|
// any projected DOM. `undefined` → falls back to contentProjectionMargin, so
|
|
2008
2010
|
// the default keeps one gate. `Infinity` = every block keeps resident text.
|
|
2009
2011
|
contentSemanticMargin = void 0;
|
|
2012
|
+
// How many coarse-tier blocks may be MATERIALIZED per sync, spreading the
|
|
2013
|
+
// resident tier's document-open cost across frames. `Infinity` = one
|
|
2014
|
+
// synchronous pass.
|
|
2015
|
+
contentSemanticBudget = DEFAULT_CONTENT_SEMANTIC_BUDGET;
|
|
2016
|
+
// Remaining materializations in the CURRENT sync. Reset at the start of each
|
|
2017
|
+
// a11y walk; decremented per coarse block that creates its element.
|
|
2018
|
+
contentSemanticBudgetLeft = 0;
|
|
2019
|
+
// Set when a sync deferred at least one block, so the scene knows to keep
|
|
2020
|
+
// drawing frames until the resident tier is complete. Without it a static
|
|
2021
|
+
// scene in `onDemand` mode would stop rendering with the document half
|
|
2022
|
+
// materialized and never finish.
|
|
2023
|
+
contentSemanticDeferred = false;
|
|
2024
|
+
/**
|
|
2025
|
+
* Per-sync memo of "does the document hold a selection at all".
|
|
2026
|
+
*
|
|
2027
|
+
* Reading ANY property of a `Selection` (`anchorNode`, `rangeCount`, `type`,
|
|
2028
|
+
* `isCollapsed`) forces a synchronous layout, because Blink validates the
|
|
2029
|
+
* selection against current box geometry before answering. Measured in real
|
|
2030
|
+
* Chrome against a 1000-carrier subtree with layout dirtied between reads:
|
|
2031
|
+
* `anchorNode` 0.5ms, `rangeCount` 0.4ms, `type` 0.5ms, `isCollapsed` 0.5ms —
|
|
2032
|
+
* all indistinguishable from `offsetHeight` (0.5ms), against a 0ms floor for
|
|
2033
|
+
* mutating without reading. So there is no cheap property to probe with; the
|
|
2034
|
+
* only way to avoid the layout is to not touch the object at all.
|
|
2035
|
+
*
|
|
2036
|
+
* Materializing a block rebuilds its carriers, which asks whether the rebuild
|
|
2037
|
+
* would destroy a selection. Once per block, that read cost a forced layout
|
|
2038
|
+
* over the whole (and growing) projection subtree, which is what made
|
|
2039
|
+
* per-block cost rise with resident count: profiled at 1973 forced layouts
|
|
2040
|
+
* totalling 633ms of an 847ms 1000-block drain (75%).
|
|
2041
|
+
*
|
|
2042
|
+
* A selection is a single document-wide object and a sync walk cannot yield to
|
|
2043
|
+
* the user, so its presence cannot change mid-walk. Resolving it once per walk
|
|
2044
|
+
* turns O(blocks) forced layouts into O(1). `null` = not yet resolved.
|
|
2045
|
+
*/
|
|
2046
|
+
contentSelectionPresentThisSync = null;
|
|
2010
2047
|
/**
|
|
2011
2048
|
* True while a text-selection drag that started on a projection's blank
|
|
2012
2049
|
* region (no text node under the press) is being driven manually — the
|
|
@@ -3043,6 +3080,7 @@ var Scene = class _Scene {
|
|
|
3043
3080
|
this.contentProjectionEnabled = options.contentProjection ?? true;
|
|
3044
3081
|
this.contentProjectionMargin = options.contentProjectionMargin;
|
|
3045
3082
|
this.contentSemanticMargin = options.contentSemanticMargin;
|
|
3083
|
+
this.contentSemanticBudget = options.contentSemanticBudget ?? DEFAULT_CONTENT_SEMANTIC_BUDGET;
|
|
3046
3084
|
this.readingDirection = options.readingDirection ?? "ltr";
|
|
3047
3085
|
this.renderMode = options.renderMode ?? "always";
|
|
3048
3086
|
this._devActive = _Scene._devModeDetected();
|
|
@@ -3324,12 +3362,31 @@ var Scene = class _Scene {
|
|
|
3324
3362
|
}
|
|
3325
3363
|
return null;
|
|
3326
3364
|
}
|
|
3365
|
+
/**
|
|
3366
|
+
* Does the document hold a selection right now, memoized for this sync walk?
|
|
3367
|
+
*
|
|
3368
|
+
* Pays one forced layout per walk instead of one per rebuilt element — see
|
|
3369
|
+
* {@link Scene.contentSelectionPresentThisSync} for the measurements. When the
|
|
3370
|
+
* answer is `false` no element can own a selection, so every per-element
|
|
3371
|
+
* ownership test can be skipped without touching the object.
|
|
3372
|
+
*/
|
|
3373
|
+
contentSelectionPresent() {
|
|
3374
|
+
if (this.contentSelectionPresentThisSync !== null) {
|
|
3375
|
+
return this.contentSelectionPresentThisSync;
|
|
3376
|
+
}
|
|
3377
|
+
const selection = typeof window !== "undefined" && typeof window.getSelection === "function" ? window.getSelection() : null;
|
|
3378
|
+
const present = !!selection && (!!selection.anchorNode || !!selection.focusNode);
|
|
3379
|
+
this.contentSelectionPresentThisSync = present;
|
|
3380
|
+
return present;
|
|
3381
|
+
}
|
|
3327
3382
|
releaseContentSelectionForRebuild(el) {
|
|
3383
|
+
if (!this.contentSelectionAnchor && !this.contentSelectionPresent()) return;
|
|
3328
3384
|
const selection = typeof window !== "undefined" && typeof window.getSelection === "function" ? window.getSelection() : null;
|
|
3329
3385
|
const ownsSelection = this.contentSelectionAnchor && el.contains(this.contentSelectionAnchor.node) || (selection?.anchorNode ? el.contains(selection.anchorNode) : false) || (selection?.focusNode ? el.contains(selection.focusNode) : false);
|
|
3330
3386
|
if (!ownsSelection) return;
|
|
3331
3387
|
this.endContentSelectionDrag();
|
|
3332
3388
|
selection?.removeAllRanges();
|
|
3389
|
+
this.contentSelectionPresentThisSync = null;
|
|
3333
3390
|
}
|
|
3334
3391
|
/**
|
|
3335
3392
|
* Rebuild a content-projection element's DOM (`rebuild`) while preserving a
|
|
@@ -3347,6 +3404,10 @@ var Scene = class _Scene {
|
|
|
3347
3404
|
* restore against).
|
|
3348
3405
|
*/
|
|
3349
3406
|
preserveContentSelectionAcrossRebuild(el, rebuild) {
|
|
3407
|
+
if (!this.contentSelectionAnchor && !this.contentSelectionPresent()) {
|
|
3408
|
+
rebuild();
|
|
3409
|
+
return;
|
|
3410
|
+
}
|
|
3350
3411
|
const selection = typeof window !== "undefined" && typeof window.getSelection === "function" ? window.getSelection() : null;
|
|
3351
3412
|
const owns = !!selection && !this.blankRegionSelectionDrag && ((selection.anchorNode ? el.contains(selection.anchorNode) : false) || (selection.focusNode ? el.contains(selection.focusNode) : false));
|
|
3352
3413
|
if (!owns || !selection.anchorNode || !selection.focusNode) {
|
|
@@ -4042,6 +4103,11 @@ var Scene = class _Scene {
|
|
|
4042
4103
|
}
|
|
4043
4104
|
syncA11y(node, container = null) {
|
|
4044
4105
|
if (!this.a11yRoot) return;
|
|
4106
|
+
if (node === this.root) {
|
|
4107
|
+
this.contentSemanticBudgetLeft = this.contentSemanticBudget;
|
|
4108
|
+
this.contentSemanticDeferred = false;
|
|
4109
|
+
this.contentSelectionPresentThisSync = null;
|
|
4110
|
+
}
|
|
4045
4111
|
if (node.isDOMPortal) {
|
|
4046
4112
|
return;
|
|
4047
4113
|
}
|
|
@@ -4460,7 +4526,9 @@ var Scene = class _Scene {
|
|
|
4460
4526
|
* transparent DOM node positioned over the drawn glyphs. Runs on the a11y
|
|
4461
4527
|
* sync cadence; all writes are dirty-checked. Off-viewport projections are
|
|
4462
4528
|
* hidden (`display: none`) so text-heavy scenes only materialize what is
|
|
4463
|
-
* visible to the browser's text machinery anyway
|
|
4529
|
+
* visible to the browser's text machinery anyway — except in the coarse
|
|
4530
|
+
* (resident) tier, which stays displayed because hiding it would make its text
|
|
4531
|
+
* unfindable and remove it from the accessibility tree, defeating the tier.
|
|
4464
4532
|
*/
|
|
4465
4533
|
/**
|
|
4466
4534
|
* Whether `node`'s world-space box, expanded by `margin` px on every side,
|
|
@@ -4469,8 +4537,15 @@ var Scene = class _Scene {
|
|
|
4469
4537
|
* at `margin = contentProjectionMargin`) and for the exact `display:none`
|
|
4470
4538
|
* visibility test (`margin = 0`). Boundless nodes (width/height 0) opt out of
|
|
4471
4539
|
* culling and always count as visible, matching the legacy behavior.
|
|
4540
|
+
*
|
|
4541
|
+
* `viewportOnly` skips the `clipChildren` ancestor walk, answering the narrower
|
|
4542
|
+
* question "does this box overlap the viewport at all". The coarse content tier
|
|
4543
|
+
* needs the two apart: text that is merely off-viewport is clipped by
|
|
4544
|
+
* `a11yRoot`'s own `overflow: hidden` and can safely stay displayed, while text
|
|
4545
|
+
* rejected by an ancestor clip box that itself overlaps the viewport would sit
|
|
4546
|
+
* transparently on top of whatever is really drawn there.
|
|
4472
4547
|
*/
|
|
4473
|
-
projectionBoxVisible(node, tf, margin) {
|
|
4548
|
+
projectionBoxVisible(node, tf, margin, viewportOnly = false) {
|
|
4474
4549
|
if (!(node.width > 0 && node.height > 0)) return true;
|
|
4475
4550
|
const { a, b, c, d, e, f } = tf;
|
|
4476
4551
|
const worldCorners = [];
|
|
@@ -4492,6 +4567,7 @@ var Scene = class _Scene {
|
|
|
4492
4567
|
if (!(maxX >= -margin && minX <= this.width + margin && maxY >= -margin && minY <= this.height + margin)) {
|
|
4493
4568
|
return false;
|
|
4494
4569
|
}
|
|
4570
|
+
if (viewportOnly) return true;
|
|
4495
4571
|
for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
|
|
4496
4572
|
if (!ancestor.clipChildren || ancestor.width <= 0 || ancestor.height <= 0) continue;
|
|
4497
4573
|
let localMinX = Infinity;
|
|
@@ -4594,6 +4670,10 @@ var Scene = class _Scene {
|
|
|
4594
4670
|
return;
|
|
4595
4671
|
}
|
|
4596
4672
|
}
|
|
4673
|
+
if (tier === "coarse" && !el && this.contentSemanticBudgetLeft <= 0) {
|
|
4674
|
+
this.contentSemanticDeferred = true;
|
|
4675
|
+
return;
|
|
4676
|
+
}
|
|
4597
4677
|
const projection = node.getContentProjection(
|
|
4598
4678
|
lineBand ? { minY: lineBand.minY, maxY: lineBand.maxY } : void 0
|
|
4599
4679
|
);
|
|
@@ -4602,6 +4682,7 @@ var Scene = class _Scene {
|
|
|
4602
4682
|
return;
|
|
4603
4683
|
}
|
|
4604
4684
|
if (!el) {
|
|
4685
|
+
if (tier === "coarse") this.contentSemanticBudgetLeft--;
|
|
4605
4686
|
el = document.createElement("div");
|
|
4606
4687
|
el.setAttribute("data-vecto-content", node.id);
|
|
4607
4688
|
const s = el.style;
|
|
@@ -4736,7 +4817,8 @@ var Scene = class _Scene {
|
|
|
4736
4817
|
if (node.width > 0) el.style.width = `${node.width}px`;
|
|
4737
4818
|
if (node.height > 0) el.style.height = `${node.height}px`;
|
|
4738
4819
|
el.style.transform = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0)`;
|
|
4739
|
-
const
|
|
4820
|
+
const residentTier = semanticMargin > interactionMargin;
|
|
4821
|
+
const display = visible || residentTier && !this.projectionBoxVisible(node, worldTf, 0, true) ? "" : "none";
|
|
4740
4822
|
if (el.style.display !== display) el.style.display = display;
|
|
4741
4823
|
if (epoch !== null) {
|
|
4742
4824
|
const { a: a2, b: b2, c: c2, d: d2, e: e2, f: f2 } = worldTf;
|
|
@@ -5379,7 +5461,7 @@ var Scene = class _Scene {
|
|
|
5379
5461
|
if (!this.isRunning) return;
|
|
5380
5462
|
if (!this._canvasOnScreen) return;
|
|
5381
5463
|
let cap = this.effectiveMaxFPS();
|
|
5382
|
-
const isIdle = !this.dirty && !this.frameHadAnimation;
|
|
5464
|
+
const isIdle = !this.dirty && !this.frameHadAnimation && !this.contentSemanticDeferred;
|
|
5383
5465
|
if (isIdle && this.autoThrottle && this.renderMode === "always" && this.maxFPS > 0) {
|
|
5384
5466
|
cap = Math.min(cap, 2);
|
|
5385
5467
|
}
|
|
@@ -6910,6 +6992,7 @@ export {
|
|
|
6910
6992
|
CanvasRenderer,
|
|
6911
6993
|
Circle,
|
|
6912
6994
|
ComputeParticleEntity,
|
|
6995
|
+
DEFAULT_CONTENT_SEMANTIC_BUDGET,
|
|
6913
6996
|
DOMPortalEntity,
|
|
6914
6997
|
Entity,
|
|
6915
6998
|
GlyphRasterAtlas,
|
package/dist/tree/Scene.d.ts
CHANGED
|
@@ -220,15 +220,45 @@ export interface SceneOptions {
|
|
|
220
220
|
* unwindowed carrier band, not from resident text.
|
|
221
221
|
*
|
|
222
222
|
* Note the one-time cost. A resident tier materializes one element per block
|
|
223
|
-
* on the first sync — measured
|
|
224
|
-
*
|
|
223
|
+
* on the first sync — measured unbudgeted at 21.3ms for 1000 blocks and 139.5ms
|
|
224
|
+
* for 10000 on Chrome — as one synchronous block. Steady state is cheap
|
|
225
225
|
* (unchanged blocks skip via {@link Entity.getContentEpoch}), so this is a
|
|
226
|
-
* document-open stall, not a per-frame cost.
|
|
226
|
+
* document-open stall, not a per-frame cost. That stall is what
|
|
227
|
+
* {@link SceneOptions.contentSemanticBudget} spreads across frames.
|
|
227
228
|
*
|
|
228
229
|
* Default: whatever `contentProjectionMargin` resolves to, so omitting this
|
|
229
230
|
* leaves behaviour unchanged.
|
|
230
231
|
*/
|
|
231
232
|
contentSemanticMargin?: number;
|
|
233
|
+
/**
|
|
234
|
+
* How many resident (coarse-tier) blocks may be materialized in **one** sync,
|
|
235
|
+
* bounding the document-open stall a wide {@link
|
|
236
|
+
* SceneOptions.contentSemanticMargin} otherwise pays all at once.
|
|
237
|
+
*
|
|
238
|
+
* The cost of a resident tier is per node **created**, not per node held: 10000
|
|
239
|
+
* resident blocks cost ~3.0 ms/sync at steady state, while creating them costs
|
|
240
|
+
* ~0.03 ms each plus a per-pass floor that grows with how many are already
|
|
241
|
+
* resident. So the front-load is a *scheduling* problem, and this is the
|
|
242
|
+
* schedule — remaining blocks materialize on subsequent syncs, a few per frame,
|
|
243
|
+
* until the document is fully resident.
|
|
244
|
+
*
|
|
245
|
+
* What it does **not** change is the end state: the same blocks end up with the
|
|
246
|
+
* same DOM, only later. Nothing is dropped, so the reachability the semantic
|
|
247
|
+
* tier exists for is preserved; a block still waiting is simply not yet in the
|
|
248
|
+
* DOM, exactly as a block beyond the margin is not.
|
|
249
|
+
*
|
|
250
|
+
* Applies **only** to the coarse tier. A block inside the interaction margin is
|
|
251
|
+
* on screen and materializes immediately regardless of this budget — deferring
|
|
252
|
+
* visible text would make it briefly unselectable, which is a user-visible
|
|
253
|
+
* regression rather than a cost saving.
|
|
254
|
+
*
|
|
255
|
+
* `Infinity` disables the budget and restores one synchronous pass. Default:
|
|
256
|
+
* {@link DEFAULT_CONTENT_SEMANTIC_BUDGET}. Because the coarse tier exists only
|
|
257
|
+
* when `contentSemanticMargin` is wider than `contentProjectionMargin`, a scene
|
|
258
|
+
* that does not opt into a resident tier has no coarse blocks and is therefore
|
|
259
|
+
* unaffected by any value here.
|
|
260
|
+
*/
|
|
261
|
+
contentSemanticBudget?: number;
|
|
232
262
|
/**
|
|
233
263
|
* Reading direction used to order the accessibility/automation shadow tree so
|
|
234
264
|
* keyboard **tab order** and screen-reader traversal follow the *visual*
|
|
@@ -268,7 +298,7 @@ export interface SceneOptions {
|
|
|
268
298
|
* against. A new option must be added here too — the test suite asserts the two
|
|
269
299
|
* stay in sync.
|
|
270
300
|
*/
|
|
271
|
-
export declare const SCENE_OPTION_KEYS: readonly ['a11ySyncInterval', 'autoThrottle', 'contentProjection', 'contentProjectionMargin', 'contentSemanticMargin', 'debugA11y', 'disableWindowResize', 'maxDPR', 'maxFPS', 'particleBackend', 'pointBackend', 'readingDirection', 'renderer', 'renderMode', 'respectReducedMotion', 'userTiming'];
|
|
301
|
+
export declare const SCENE_OPTION_KEYS: readonly ['a11ySyncInterval', 'autoThrottle', 'contentProjection', 'contentProjectionMargin', 'contentSemanticBudget', 'contentSemanticMargin', 'debugA11y', 'disableWindowResize', 'maxDPR', 'maxFPS', 'particleBackend', 'pointBackend', 'readingDirection', 'renderer', 'renderMode', 'respectReducedMotion', 'userTiming'];
|
|
272
302
|
/** Frame-rate the loop is capped to when the OS requests reduced motion. */
|
|
273
303
|
export declare const REDUCED_MOTION_FPS = 30;
|
|
274
304
|
/**
|
|
@@ -359,6 +389,45 @@ export interface A11yTreeNode {
|
|
|
359
389
|
valuemax?: string;
|
|
360
390
|
children: A11yTreeNode[];
|
|
361
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Default {@link SceneOptions.contentSemanticBudget}: resident blocks
|
|
394
|
+
* materialized per sync.
|
|
395
|
+
*
|
|
396
|
+
* Sized against the two costs a pass actually pays, both measured in real headed
|
|
397
|
+
* Chrome on a 240Hz panel. Per created block is cheap and flat (~0.03ms). What
|
|
398
|
+
* dominates is style+layout of the projection subtree, which scales with how many
|
|
399
|
+
* blocks are already RESIDENT and is paid once per pass: traced at 10000 blocks,
|
|
400
|
+
* `UpdateLayoutTree` 391.7ms + `Layout` 305.8ms over 40 passes (~17ms each), with
|
|
401
|
+
* per-pass cost roughly doubling from the first pass to the last while the number
|
|
402
|
+
* created stayed constant.
|
|
403
|
+
*
|
|
404
|
+
* So total drain cost is approximately `passes × f(resident)`, and a SMALLER
|
|
405
|
+
* budget multiplies the term that does not shrink. Measured to completion, 3
|
|
406
|
+
* repeats, medians:
|
|
407
|
+
*
|
|
408
|
+
* ```text
|
|
409
|
+
* 1000 blocks budget 32 → 67.1ms total, 4.3ms worst pass
|
|
410
|
+
* budget 64 → 54.0ms total, 5.1ms worst pass
|
|
411
|
+
* budget 256 → 27.7ms total, 7.7ms worst pass
|
|
412
|
+
* Infinity → 24.1ms total, 23.6ms worst pass
|
|
413
|
+
* 10000 blocks budget 32 → 3773.2ms total, 42.6ms worst pass
|
|
414
|
+
* budget 64 → 1896.2ms total, 41.6ms worst pass
|
|
415
|
+
* budget 256 → 648.1ms total, 35.2ms worst pass
|
|
416
|
+
* Infinity → 319.4ms total, 307.3ms worst pass
|
|
417
|
+
* ```
|
|
418
|
+
*
|
|
419
|
+
* 256 is where the two goals stop trading against each other. Below it there is no
|
|
420
|
+
* frame-bound improvement at 10000 blocks — every budget lands at 35-43ms, because
|
|
421
|
+
* the worst pass is the LAST one laying out the complete subtree — while total time
|
|
422
|
+
* rises 6x. At 1000 blocks it still holds 7.7ms, inside a 60Hz frame, for less than
|
|
423
|
+
* half the total time of 64.
|
|
424
|
+
*
|
|
425
|
+
* This replaces an earlier default of 64, which was sized against a per-block cost
|
|
426
|
+
* of ~0.4ms. That figure was inflated by a forced layout per materialized block
|
|
427
|
+
* (see `contentSelectionPresentThisSync`); with that removed, 64 spends 6x the
|
|
428
|
+
* total time for no frame-bound gain.
|
|
429
|
+
*/
|
|
430
|
+
export declare const DEFAULT_CONTENT_SEMANTIC_BUDGET = 256;
|
|
362
431
|
/**
|
|
363
432
|
* Top-level orchestrator that owns the entity tree, drive the render loop,
|
|
364
433
|
* and maintains the accessibility/automation shadow layer.
|
|
@@ -560,6 +629,32 @@ export declare class Scene {
|
|
|
560
629
|
private contentProjectionEnabled;
|
|
561
630
|
private contentProjectionMargin;
|
|
562
631
|
private contentSemanticMargin;
|
|
632
|
+
private contentSemanticBudget;
|
|
633
|
+
private contentSemanticBudgetLeft;
|
|
634
|
+
private contentSemanticDeferred;
|
|
635
|
+
/**
|
|
636
|
+
* Per-sync memo of "does the document hold a selection at all".
|
|
637
|
+
*
|
|
638
|
+
* Reading ANY property of a `Selection` (`anchorNode`, `rangeCount`, `type`,
|
|
639
|
+
* `isCollapsed`) forces a synchronous layout, because Blink validates the
|
|
640
|
+
* selection against current box geometry before answering. Measured in real
|
|
641
|
+
* Chrome against a 1000-carrier subtree with layout dirtied between reads:
|
|
642
|
+
* `anchorNode` 0.5ms, `rangeCount` 0.4ms, `type` 0.5ms, `isCollapsed` 0.5ms —
|
|
643
|
+
* all indistinguishable from `offsetHeight` (0.5ms), against a 0ms floor for
|
|
644
|
+
* mutating without reading. So there is no cheap property to probe with; the
|
|
645
|
+
* only way to avoid the layout is to not touch the object at all.
|
|
646
|
+
*
|
|
647
|
+
* Materializing a block rebuilds its carriers, which asks whether the rebuild
|
|
648
|
+
* would destroy a selection. Once per block, that read cost a forced layout
|
|
649
|
+
* over the whole (and growing) projection subtree, which is what made
|
|
650
|
+
* per-block cost rise with resident count: profiled at 1973 forced layouts
|
|
651
|
+
* totalling 633ms of an 847ms 1000-block drain (75%).
|
|
652
|
+
*
|
|
653
|
+
* A selection is a single document-wide object and a sync walk cannot yield to
|
|
654
|
+
* the user, so its presence cannot change mid-walk. Resolving it once per walk
|
|
655
|
+
* turns O(blocks) forced layouts into O(1). `null` = not yet resolved.
|
|
656
|
+
*/
|
|
657
|
+
private contentSelectionPresentThisSync;
|
|
563
658
|
/**
|
|
564
659
|
* True while a text-selection drag that started on a projection's blank
|
|
565
660
|
* region (no text node under the press) is being driven manually — the
|
|
@@ -1101,6 +1196,15 @@ export declare class Scene {
|
|
|
1101
1196
|
* to the live DOM selection.
|
|
1102
1197
|
*/
|
|
1103
1198
|
private contentGridSelectionLine;
|
|
1199
|
+
/**
|
|
1200
|
+
* Does the document hold a selection right now, memoized for this sync walk?
|
|
1201
|
+
*
|
|
1202
|
+
* Pays one forced layout per walk instead of one per rebuilt element — see
|
|
1203
|
+
* {@link Scene.contentSelectionPresentThisSync} for the measurements. When the
|
|
1204
|
+
* answer is `false` no element can own a selection, so every per-element
|
|
1205
|
+
* ownership test can be skipped without touching the object.
|
|
1206
|
+
*/
|
|
1207
|
+
private contentSelectionPresent;
|
|
1104
1208
|
private releaseContentSelectionForRebuild;
|
|
1105
1209
|
/**
|
|
1106
1210
|
* Rebuild a content-projection element's DOM (`rebuild`) while preserving a
|
|
@@ -1428,7 +1532,9 @@ export declare class Scene {
|
|
|
1428
1532
|
* transparent DOM node positioned over the drawn glyphs. Runs on the a11y
|
|
1429
1533
|
* sync cadence; all writes are dirty-checked. Off-viewport projections are
|
|
1430
1534
|
* hidden (`display: none`) so text-heavy scenes only materialize what is
|
|
1431
|
-
* visible to the browser's text machinery anyway
|
|
1535
|
+
* visible to the browser's text machinery anyway — except in the coarse
|
|
1536
|
+
* (resident) tier, which stays displayed because hiding it would make its text
|
|
1537
|
+
* unfindable and remove it from the accessibility tree, defeating the tier.
|
|
1432
1538
|
*/
|
|
1433
1539
|
/**
|
|
1434
1540
|
* Whether `node`'s world-space box, expanded by `margin` px on every side,
|
|
@@ -1437,6 +1543,13 @@ export declare class Scene {
|
|
|
1437
1543
|
* at `margin = contentProjectionMargin`) and for the exact `display:none`
|
|
1438
1544
|
* visibility test (`margin = 0`). Boundless nodes (width/height 0) opt out of
|
|
1439
1545
|
* culling and always count as visible, matching the legacy behavior.
|
|
1546
|
+
*
|
|
1547
|
+
* `viewportOnly` skips the `clipChildren` ancestor walk, answering the narrower
|
|
1548
|
+
* question "does this box overlap the viewport at all". The coarse content tier
|
|
1549
|
+
* needs the two apart: text that is merely off-viewport is clipped by
|
|
1550
|
+
* `a11yRoot`'s own `overflow: hidden` and can safely stay displayed, while text
|
|
1551
|
+
* rejected by an ancestor clip box that itself overlaps the viewport would sit
|
|
1552
|
+
* transparently on top of whatever is really drawn there.
|
|
1440
1553
|
*/
|
|
1441
1554
|
private projectionBoxVisible;
|
|
1442
1555
|
/**
|