@vectojs/core 1.32.1 → 1.32.3

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 CHANGED
@@ -1998,30 +1998,79 @@ var Scene = (_class7 = class _Scene {
1998
1998
  __init60() {this.contentGridCalibrationStamp = ""}
1999
1999
  /** Invalidates grid font calibration after browser font availability changes. */
2000
2000
  __init61() {this.contentFontEpoch = 0}
2001
+ /**
2002
+ * Bumped whenever the viewport itself changes shape, which re-tiers blocks
2003
+ * without moving any of them.
2004
+ *
2005
+ * The settled-walk fast path in {@link syncContentProjection} decides a block
2006
+ * is unchanged from its own local transform plus its parent's world transform.
2007
+ * Both are viewport-independent, so a resize alone would slip past it and leave
2008
+ * every block holding DOM built for the old viewport — a block that should have
2009
+ * been promoted to the fine tier keeping no carriers, or a demoted one keeping
2010
+ * carriers it no longer needs. Scrolling needs no such epoch: it moves the root,
2011
+ * so every block's parent world transform changes and the check fails honestly.
2012
+ */
2013
+ __init62() {this.contentViewportEpoch = 0}
2014
+ /**
2015
+ * One-entry memo for a parent's world transform, held as scalars.
2016
+ *
2017
+ * The a11y walk visits all of one parent's children consecutively, so a
2018
+ * one-entry memo hits for every child after the first — turning an
2019
+ * O(children) sequence of `getWorldTransform()` calls, each of which
2020
+ * allocates a fresh object on its cache-hit path
2021
+ * ({@link Entity.getWorldTransform}), into one call per parent. Flattened to
2022
+ * scalars rather than a cached object so the memo itself never allocates.
2023
+ *
2024
+ * Keyed on `(entity, syncSerial)`: the serial is bumped once per top-level
2025
+ * sync, so a memo can never survive into a frame in which the parent has
2026
+ * moved.
2027
+ */
2028
+ __init63() {this._pwNode = null}
2029
+ __init64() {this._pwSerial = -1}
2030
+ __init65() {this._pwa = 1}
2031
+ __init66() {this._pwb = 0}
2032
+ __init67() {this._pwc = 0}
2033
+ __init68() {this._pwd = 1}
2034
+ __init69() {this._pwe = 0}
2035
+ __init70() {this._pwf = 0}
2036
+ /** Incremented once per top-level `syncA11y`, invalidating `_pwNode`. */
2037
+ __init71() {this._syncSerial = 0}
2038
+ /**
2039
+ * Disables the settled-walk fast path in {@link syncContentProjection}.
2040
+ *
2041
+ * Exists so a benchmark can measure both arms in ONE run on ONE commit, rather
2042
+ * than comparing two builds and inheriting every difference between them. Also
2043
+ * lets a test assert that a behaviour is genuinely unchanged by the fast path
2044
+ * rather than merely unobserved.
2045
+ *
2046
+ * Not part of the public API and not documented as an option: turning it on only
2047
+ * makes a settled document slower, never more correct.
2048
+ */
2049
+ __init72() {this.disableSettledFastPath = false}
2001
2050
  /** Cached Canvas-to-client scale for the current font/viewport epoch. */
2002
- __init62() {this.contentMetricScaleEpoch = -1}
2003
- __init63() {this.contentMetricScaleX = 1}
2004
- __init64() {this.contentProjectionEnabled = true}
2051
+ __init73() {this.contentMetricScaleEpoch = -1}
2052
+ __init74() {this.contentMetricScaleX = 1}
2053
+ __init75() {this.contentProjectionEnabled = true}
2005
2054
  // Virtualization margin (px) for the content-projection CARRIER band;
2006
2055
  // `undefined` → one viewport height, resolved at sync time. `Infinity` is
2007
2056
  // unsupported here: it unwindows every carrier, which is O(total glyphs).
2008
- __init65() {this.contentProjectionMargin = void 0}
2057
+ __init76() {this.contentProjectionMargin = void 0}
2009
2058
  // Virtualization margin (px) for the SEMANTIC tier — whether a block has
2010
2059
  // any projected DOM. `undefined` → falls back to contentProjectionMargin, so
2011
2060
  // the default keeps one gate. `Infinity` = every block keeps resident text.
2012
- __init66() {this.contentSemanticMargin = void 0}
2061
+ __init77() {this.contentSemanticMargin = void 0}
2013
2062
  // How many coarse-tier blocks may be MATERIALIZED per sync, spreading the
2014
2063
  // resident tier's document-open cost across frames. `Infinity` = one
2015
2064
  // synchronous pass.
2016
- __init67() {this.contentSemanticBudget = DEFAULT_CONTENT_SEMANTIC_BUDGET}
2065
+ __init78() {this.contentSemanticBudget = DEFAULT_CONTENT_SEMANTIC_BUDGET}
2017
2066
  // Remaining materializations in the CURRENT sync. Reset at the start of each
2018
2067
  // a11y walk; decremented per coarse block that creates its element.
2019
- __init68() {this.contentSemanticBudgetLeft = 0}
2068
+ __init79() {this.contentSemanticBudgetLeft = 0}
2020
2069
  // Set when a sync deferred at least one block, so the scene knows to keep
2021
2070
  // drawing frames until the resident tier is complete. Without it a static
2022
2071
  // scene in `onDemand` mode would stop rendering with the document half
2023
2072
  // materialized and never finish.
2024
- __init69() {this.contentSemanticDeferred = false}
2073
+ __init80() {this.contentSemanticDeferred = false}
2025
2074
  /**
2026
2075
  * Per-sync memo of "does the document hold a selection at all".
2027
2076
  *
@@ -2044,43 +2093,43 @@ var Scene = (_class7 = class _Scene {
2044
2093
  * the user, so its presence cannot change mid-walk. Resolving it once per walk
2045
2094
  * turns O(blocks) forced layouts into O(1). `null` = not yet resolved.
2046
2095
  */
2047
- __init70() {this.contentSelectionPresentThisSync = null}
2096
+ __init81() {this.contentSelectionPresentThisSync = null}
2048
2097
  /**
2049
2098
  * True while a text-selection drag that started on a projection's blank
2050
2099
  * region (no text node under the press) is being driven manually — the
2051
2100
  * browser has no native anchor for it, so mousemove extends the Selection
2052
2101
  * from the position we resolved ourselves.
2053
2102
  */
2054
- __init71() {this.blankRegionSelectionDrag = false}
2055
- __init72() {this.contentSelectionAnchor = null}
2056
- __init73() {this.contentSelectionEndListener = null}
2103
+ __init82() {this.blankRegionSelectionDrag = false}
2104
+ __init83() {this.contentSelectionAnchor = null}
2105
+ __init84() {this.contentSelectionEndListener = null}
2057
2106
  // Animation/interactive flags collected during the render walk (tree-walk
2058
2107
  // fusion): the loop reads last frame's answers instead of re-walking the
2059
2108
  // tree up to 4× per tick. Start true so the first tick stays conservative.
2060
- __init74() {this.frameHadAnimation = true}
2061
- __init75() {this.frameHadInteractive = true}
2109
+ __init85() {this.frameHadAnimation = true}
2110
+ __init86() {this.frameHadInteractive = true}
2062
2111
 
2063
2112
  /** Active `(resolution: Ndppx)` media query watching for a runtime DPR change
2064
2113
  * (window moved between monitors, browser zoom) so the canvas backing store
2065
2114
  * can be re-scaled — otherwise it stays rasterized at the old DPR and blurs.
2066
2115
  * A resolution media query only fires when leaving its exact value, so the
2067
2116
  * handler re-arms a fresh query for the new DPR each time. */
2068
- __init76() {this.dprMediaQuery = null}
2117
+ __init87() {this.dprMediaQuery = null}
2069
2118
  /** For embedded (`disableWindowResize`) scenes: observes the canvas element so
2070
2119
  * a CSS/layout-driven size change re-runs `resize()`. A window `resize`
2071
2120
  * listener never fires for these (the window isn't what changed), so without
2072
2121
  * this an embedded canvas stayed at its initial size forever. */
2073
- __init77() {this.canvasResizeObserver = null}
2074
- __init78() {this.dprChangeHandler = null}
2075
- __init79() {this.focusedA11yElement = null}
2122
+ __init88() {this.canvasResizeObserver = null}
2123
+ __init89() {this.dprChangeHandler = null}
2124
+ __init90() {this.focusedA11yElement = null}
2076
2125
  /** Last geometry `syncOverlayGeometry` wrote, so an unchanged frame can skip the
2077
2126
  * style writes entirely. Reset to `null` to force the next sync (a new overlay
2078
2127
  * layer was created and has never been positioned). */
2079
- __init80() {this._overlayGeometry = null}
2128
+ __init91() {this._overlayGeometry = null}
2080
2129
  /** Shadow elements the pointer is currently inside. Lets a removal that happens
2081
2130
  * mid-hover synthesize the `pointerleave` the browser never sends for a
2082
2131
  * detached element, so the entity doesn't keep its hover state. */
2083
- __init81() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
2132
+ __init92() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
2084
2133
  /**
2085
2134
  * Entity ids the application has pinned via {@link requestA11yProjection}.
2086
2135
  *
@@ -2088,22 +2137,22 @@ var Scene = (_class7 = class _Scene {
2088
2137
  * a stale id simply never matches. Cleared per-entity by
2089
2138
  * {@link releaseA11yProjection}.
2090
2139
  */
2091
- __init82() {this.a11yProjectionRequests = /* @__PURE__ */ new Set()}
2140
+ __init93() {this.a11yProjectionRequests = /* @__PURE__ */ new Set()}
2092
2141
  /** Persistent tabindex=-1 element in a11yRoot. When the focused a11y mirror is
2093
2142
  * pruned (virtualization/streaming/removal) while it holds focus, we move
2094
2143
  * focus here instead of letting the browser drop it to <body> — keeping the
2095
2144
  * screen-reader virtual cursor inside the scene's a11y region. */
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()}
2145
+ __init94() {this.focusSentinel = null}
2146
+ __init95() {this.caretBlinkTimer = null}
2147
+ __init96() {this.a11yNeedsReorder = true}
2148
+ __init97() {this.portalRoot = null}
2149
+ __init98() {this.fullViewportElements = []}
2150
+ __init99() {this.normalElements = []}
2151
+ __init100() {this.activeIds = /* @__PURE__ */ new Set()}
2103
2152
  /** Per-parent insertion cursor, reused by `enforceA11yDomOrder`. */
2104
- __init90() {this.a11yOrderCursors = /* @__PURE__ */ new Map()}
2153
+ __init101() {this.a11yOrderCursors = /* @__PURE__ */ new Map()}
2105
2154
  /** Membership set for the elements being ordered, reused per reorder pass. */
2106
- __init91() {this.a11yOrderMembers = /* @__PURE__ */ new Set()}
2155
+ __init102() {this.a11yOrderMembers = /* @__PURE__ */ new Set()}
2107
2156
  /**
2108
2157
  * Elements that are an *ancestor* of another ordered element, reused per pass.
2109
2158
  *
@@ -2111,7 +2160,7 @@ var Scene = (_class7 = class _Scene {
2111
2160
  * items) spans every descendant row, so it must not extend a visual row band —
2112
2161
  * see {@link sortNormalElementsVisually}.
2113
2162
  */
2114
- __init92() {this.a11yOrderContainers = /* @__PURE__ */ new Set()}
2163
+ __init103() {this.a11yOrderContainers = /* @__PURE__ */ new Set()}
2115
2164
  /**
2116
2165
  * Nearest `clipChildren` ancestor per ordered element — its *region* — reused
2117
2166
  * per pass. Written by `enforceA11yDomOrder`'s collect walk, which already has
@@ -2121,11 +2170,11 @@ var Scene = (_class7 = class _Scene {
2121
2170
  * Absent means the element sits under no clipping ancestor and belongs to the
2122
2171
  * implicit root region. See {@link sortNormalElementsVisually}.
2123
2172
  */
2124
- __init93() {this.a11yOrderRegions = /* @__PURE__ */ new Map()}
2125
- __init94() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
2126
- __init95() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
2127
- __init96() {this.portalEntities = /* @__PURE__ */ new Map()}
2128
- __init97() {this.renderOrderCounter = 0}
2173
+ __init104() {this.a11yOrderRegions = /* @__PURE__ */ new Map()}
2174
+ __init105() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
2175
+ __init106() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
2176
+ __init107() {this.portalEntities = /* @__PURE__ */ new Map()}
2177
+ __init108() {this.renderOrderCounter = 0}
2129
2178
  /**
2130
2179
  * Monotonic render-frame counter, bumped once per authoritative `render()`
2131
2180
  * pass. Entities stamp their per-frame world-matrix cache with this value and
@@ -2134,7 +2183,7 @@ var Scene = (_class7 = class _Scene {
2134
2183
  * back to the ancestor walk. Public for the same reason `Entity._getTrig`/
2135
2184
  * `_setWorldCache` are: it is a cross-class render-internal contract.
2136
2185
  */
2137
- __init98() {this.currentFrame = 0}
2186
+ __init109() {this.currentFrame = 0}
2138
2187
  // ── WASM transform backend (invisible accelerator) ──────────────────────────
2139
2188
  // When `_transformBackend === 'wasm'`, the main render walk sources each
2140
2189
  // entity's world matrix from an SoA store composed by `_wasm` (see
@@ -2142,24 +2191,24 @@ var Scene = (_class7 = class _Scene {
2142
2191
  // fallback and the default: a null backend, a non-main renderer, or any entity
2143
2192
  // absent from the store all fall back to the JS composition, so WASM can only
2144
2193
  // ever change *how fast* a world matrix is produced, never *what* it is.
2145
- __init99() {this._wasm = null}
2146
- __init100() {this._transformBackend = "js"}
2194
+ __init110() {this._wasm = null}
2195
+ __init111() {this._transformBackend = "js"}
2147
2196
  // Resident store state (Stage 3). The store layout — slot assignment + sibling
2148
2197
  // runs — depends only on tree TOPOLOGY, so it is rebuilt only when the
2149
2198
  // structure changes (add/remove/reparent bump `_structureVersion`). Between
2150
2199
  // rebuilds the per-frame cost is: gather each entity's transform into the
2151
2200
  // resident wasm input view + run the kernel — no reallocation, no readback.
2152
- __init101() {this._treeStore = null}
2153
- __init102() {this._slotEntity = []}
2201
+ __init112() {this._treeStore = null}
2202
+ __init113() {this._slotEntity = []}
2154
2203
  // store slot -> entity (also validates slots)
2155
- __init103() {this._wasmInputs = null}
2156
- __init104() {this._wasmWorld = null}
2157
- __init105() {this._structureVersion = 0}
2158
- __init106() {this._storeStructureVersion = -1}
2204
+ __init114() {this._wasmInputs = null}
2205
+ __init115() {this._wasmWorld = null}
2206
+ __init116() {this._structureVersion = 0}
2207
+ __init117() {this._storeStructureVersion = -1}
2159
2208
  // Cached list of ComputeParticleEntity instances in the tree, keyed by the
2160
2209
  // structure version it was gathered at. Rebuilt only on a topology change.
2161
- __init107() {this._computeEntities = []}
2162
- __init108() {this._computeEntitiesVersion = -1}
2210
+ __init118() {this._computeEntities = []}
2211
+ __init119() {this._computeEntitiesVersion = -1}
2163
2212
  /** Invalidate the resident WASM store layout; the next wasm-mode frame rebuilds
2164
2213
  * it. Called by `Entity.add`/`remove` (topology changes only). */
2165
2214
  markStructureChanged() {
@@ -2244,7 +2293,7 @@ var Scene = (_class7 = class _Scene {
2244
2293
  * cached globally; the instance is per-Scene, which is the isolation that
2245
2294
  * actually matters.
2246
2295
  */
2247
- __init109() {this._wasmRuntime = null}
2296
+ __init120() {this._wasmRuntime = null}
2248
2297
  /**
2249
2298
  * Load (or reuse) this Scene's shared WASM runtime.
2250
2299
  *
@@ -2272,30 +2321,30 @@ var Scene = (_class7 = class _Scene {
2272
2321
  get wasmRuntime() {
2273
2322
  return this._wasmRuntime;
2274
2323
  }
2275
- __init110() {this._hitWasm = null}
2324
+ __init121() {this._hitWasm = null}
2276
2325
  // Cache key: which frame + structure version the grid was last (successfully,
2277
2326
  // non-overflowing) built for. findEntityAt is called ad-hoc (pointer
2278
2327
  // hover/click), not every frame, so the grid is refreshed lazily on demand
2279
2328
  // rather than proactively every render() — unlike the transform store, which
2280
2329
  // every frame's draw depends on.
2281
- __init111() {this._hitGridFrame = -1}
2282
- __init112() {this._hitGridOk = false}
2283
- __init113() {this._hitSlotEntity = []}
2284
- __init114() {this._hitBoundless = []}
2330
+ __init122() {this._hitGridFrame = -1}
2331
+ __init123() {this._hitGridOk = false}
2332
+ __init124() {this._hitSlotEntity = []}
2333
+ __init125() {this._hitBoundless = []}
2285
2334
  /** Reused buffer for the fused gather, so a pointer query allocates nothing. */
2286
- __init115() {this._hitGatherBuffer = null}
2335
+ __init126() {this._hitGatherBuffer = null}
2287
2336
  /**
2288
2337
  * Whether the last grid build sourced its AABBs from the WASM transform store
2289
2338
  * rather than recomputing them in JS. Diagnostic only — both paths must
2290
2339
  * produce the same entity for a given point.
2291
2340
  */
2292
- __init116() {this._hitFusedGather = false}
2341
+ __init127() {this._hitFusedGather = false}
2293
2342
  /**
2294
2343
  * Whether `compute_aabbs` has run against the current frame's world matrices.
2295
2344
  * The AABB pass is only meaningful after a `compose_*`, so the fused gather
2296
2345
  * must not read the views before then.
2297
2346
  */
2298
- __init117() {this._wasmAabbsFresh = false}
2347
+ __init128() {this._wasmAabbsFresh = false}
2299
2348
  /** Did the last hit-grid build use the fused (WASM-store) gather? */
2300
2349
  get hitGatherPath() {
2301
2350
  return this._hitFusedGather ? "fused" : "js";
@@ -2304,9 +2353,9 @@ var Scene = (_class7 = class _Scene {
2304
2353
  * Why the transform accelerator did or did not run on the most recent frame.
2305
2354
  * Written by the render walk and `_syncWasmStore`.
2306
2355
  */
2307
- __init118() {this._transformReason = "not-installed"}
2356
+ __init129() {this._transformReason = "not-installed"}
2308
2357
  /** Why the batched-driver accelerator did or did not run. */
2309
- __init119() {this._animReason = "not-installed"}
2358
+ __init130() {this._animReason = "not-installed"}
2310
2359
  /**
2311
2360
  * Why the hit-test accelerator did or did not serve the last pointer query.
2312
2361
  * The grid is built lazily on demand, not every frame, so this describes the
@@ -2314,11 +2363,11 @@ var Scene = (_class7 = class _Scene {
2314
2363
  * before a backend exists; `_ensureHitGrid` moves it to `'not-applicable'`
2315
2364
  * once one is installed but nothing has queried yet.
2316
2365
  */
2317
- __init120() {this._hitReason = "not-installed"}
2366
+ __init131() {this._hitReason = "not-installed"}
2318
2367
  /** Why the particle accelerator did or did not run. */
2319
- __init121() {this._particleReason = "not-applicable"}
2368
+ __init132() {this._particleReason = "not-applicable"}
2320
2369
  /** Which particle implementation actually simulated the most recent frame. */
2321
- __init122() {this._particlePath = "none"}
2370
+ __init133() {this._particlePath = "none"}
2322
2371
  /**
2323
2372
  * Per-frame status of every invisible accelerator: whether each is installed,
2324
2373
  * whether it actually ran on the most recent frame, and why.
@@ -2478,25 +2527,25 @@ var Scene = (_class7 = class _Scene {
2478
2527
  // EasingFn (which cannot cross into WASM) all fall through to it — WASM can
2479
2528
  // only ever change *how* a driver is advanced, never *what* value it lands
2480
2529
  // on.
2481
- __init123() {this._animWasm = null}
2530
+ __init134() {this._animWasm = null}
2482
2531
  // Entities with at least one active driver, added by Entity._spawnDriver.
2483
2532
  // Self-pruning: _tickBatchedDrivers drops an entry the first time it visits
2484
2533
  // an entity whose drivers have since all completed or been removed. This is
2485
2534
  // what lets the batch pass find its candidates in O(active drivers), not
2486
2535
  // O(tree size) — the exact mistake G3's first integrated benchmark made.
2487
- __init124() {this._activeDriverEntities = /* @__PURE__ */ new Set()}
2536
+ __init135() {this._activeDriverEntities = /* @__PURE__ */ new Set()}
2488
2537
  // Reused across frames instead of allocating a fresh array + N {entity,prop,
2489
2538
  // driver} objects every call — the integrated benchmark
2490
2539
  // (benchmarks/anim-wasm-scene) found that allocation churn was the
2491
2540
  // dominant integrated cost, not the wasm kernel itself. Parallel arrays,
2492
2541
  // truncated to the live count after each use so a stale tail slot never
2493
2542
  // pins a no-longer-active entity/driver in memory.
2494
- __init125() {this._springEntities = []}
2495
- __init126() {this._springProps = []}
2496
- __init127() {this._springDrivers = []}
2497
- __init128() {this._tweenEntities = []}
2498
- __init129() {this._tweenProps = []}
2499
- __init130() {this._tweenDrivers = []}
2543
+ __init136() {this._springEntities = []}
2544
+ __init137() {this._springProps = []}
2545
+ __init138() {this._springDrivers = []}
2546
+ __init139() {this._tweenEntities = []}
2547
+ __init140() {this._tweenProps = []}
2548
+ __init141() {this._tweenDrivers = []}
2500
2549
  /**
2501
2550
  * Minimum number of batchable (spring, or named-easing tween) active drivers
2502
2551
  * before a frame engages the WASM batch path at all; below it, every driver
@@ -2568,7 +2617,7 @@ var Scene = (_class7 = class _Scene {
2568
2617
  * Setting {@link animDriverGateCount} overwrites all three, so existing code
2569
2618
  * that tuned the single knob keeps working unchanged.
2570
2619
  */
2571
- __init131() {this._animBatchedLastFrame = false}
2620
+ __init142() {this._animBatchedLastFrame = false}
2572
2621
  /**
2573
2622
  * Whether the WASM batch path actually ran on the most recent frame.
2574
2623
  *
@@ -2580,7 +2629,7 @@ var Scene = (_class7 = class _Scene {
2580
2629
  get animBatchedLastFrame() {
2581
2630
  return this._animBatchedLastFrame;
2582
2631
  }
2583
- __init132() {this.animGate = {
2632
+ __init143() {this.animGate = {
2584
2633
  spring: 128,
2585
2634
  tween: 256,
2586
2635
  mixed: 128
@@ -2618,7 +2667,7 @@ var Scene = (_class7 = class _Scene {
2618
2667
  // (benchmarks/particle-wasm). f32 (matches the WGSL shader), bit-identical to
2619
2668
  // a JS f32 reference oracle; updateCPU (f64) stays the permanent fallback when
2620
2669
  // no backend is installed or a scene runs on WebGPU.
2621
- __init133() {this._particleWasm = null}
2670
+ __init144() {this._particleWasm = null}
2622
2671
  /** Which backend runs the CPU particle simulation. Reflects only whether a
2623
2672
  * backend is installed (the WebGPU compute path, when active, is used first
2624
2673
  * regardless). */
@@ -2895,24 +2944,24 @@ var Scene = (_class7 = class _Scene {
2895
2944
  * sync, so retaining the order prevents a newly opened overlay from spending
2896
2945
  * its first frame below previously projected controls.
2897
2946
  */
2898
- __init134() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
2947
+ __init145() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
2899
2948
  // Optional WebGL point-cloud layer (see SceneOptions.pointBackend).
2900
- __init135() {this.pointRenderer = null}
2901
- __init136() {this.glCanvas = null}
2902
- __init137() {this.glContextLostHandler = null}
2903
- __init138() {this.glContextRestoredHandler = null}
2949
+ __init146() {this.pointRenderer = null}
2950
+ __init147() {this.glCanvas = null}
2951
+ __init148() {this.glContextLostHandler = null}
2952
+ __init149() {this.glContextRestoredHandler = null}
2904
2953
 
2905
2954
 
2906
2955
 
2907
- __init139() {this.disableWindowResize = false}
2956
+ __init150() {this.disableWindowResize = false}
2908
2957
  /** See {@link SceneOptions.maxDPR}. `undefined` = uncapped (real DPR). */
2909
2958
 
2910
2959
  // WebGPU properties
2911
- __init140() {this.destroyed = false}
2912
- __init141() {this.device = null}
2913
- __init142() {this.deviceLost = false}
2914
- __init143() {this.particleBackend = "auto"}
2915
- __init144() {this._webgpuDisabled = false}
2960
+ __init151() {this.destroyed = false}
2961
+ __init152() {this.device = null}
2962
+ __init153() {this.deviceLost = false}
2963
+ __init154() {this.particleBackend = "auto"}
2964
+ __init155() {this._webgpuDisabled = false}
2916
2965
  get webgpuDisabled() {
2917
2966
  return this._webgpuDisabled || this.particleBackend === "cpu";
2918
2967
  }
@@ -2940,22 +2989,22 @@ var Scene = (_class7 = class _Scene {
2940
2989
  set webgpuDisabled(value) {
2941
2990
  this._webgpuDisabled = value;
2942
2991
  }
2943
- __init145() {this.recoveryTimerId = null}
2944
- __init146() {this.manager = null}
2945
- __init147() {this.initializingWebGPU = false}
2946
- __init148() {this.gpuCanvas = null}
2947
- __init149() {this.gpuContext = null}
2992
+ __init156() {this.recoveryTimerId = null}
2993
+ __init157() {this.manager = null}
2994
+ __init158() {this.initializingWebGPU = false}
2995
+ __init159() {this.gpuCanvas = null}
2996
+ __init160() {this.gpuContext = null}
2948
2997
  /** True while the GPU canvas holds a presented particle frame (needs clearing when they leave). */
2949
- __init150() {this.gpuHasContent = false}
2950
- __init151() {this.mouseX = -9999}
2951
- __init152() {this.mouseY = -9999}
2952
- __init153() {this.pointerMoveListener = null}
2953
- __init154() {this.pointerLeaveListener = null}
2998
+ __init161() {this.gpuHasContent = false}
2999
+ __init162() {this.mouseX = -9999}
3000
+ __init163() {this.mouseY = -9999}
3001
+ __init164() {this.pointerMoveListener = null}
3002
+ __init165() {this.pointerLeaveListener = null}
2954
3003
  /** Element the pointer listeners are bound to (parent container if present,
2955
3004
  * else the canvas). Stored so `destroy()` detaches from the same element. */
2956
- __init155() {this.pointerEventTarget = null}
2957
- __init156() {this.hasWarnedZeroSize = false}
2958
- __init157() {this.fontLoadHandler = null}
3005
+ __init166() {this.pointerEventTarget = null}
3006
+ __init167() {this.hasWarnedZeroSize = false}
3007
+ __init168() {this.fontLoadHandler = null}
2959
3008
  // ── Dev-mode warning infrastructure ──────────────────────────────
2960
3009
  //
2961
3010
  // Enable with `Scene.devMode = true` or by setting `globalThis.__DEV__`.
@@ -2989,7 +3038,7 @@ var Scene = (_class7 = class _Scene {
2989
3038
  return false;
2990
3039
  }
2991
3040
 
2992
- __init158() {this._devFrameCount = 0}
3041
+ __init169() {this._devFrameCount = 0}
2993
3042
  _devWarn(message) {
2994
3043
  if (!this._devActive) return;
2995
3044
  console.warn(`[vectojs/dev] ${message}`);
@@ -3066,7 +3115,7 @@ var Scene = (_class7 = class _Scene {
3066
3115
  };
3067
3116
  walkProjections(this.root);
3068
3117
  }
3069
- 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);_class7.prototype.__init158.call(this);
3118
+ 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);_class7.prototype.__init158.call(this);_class7.prototype.__init159.call(this);_class7.prototype.__init160.call(this);_class7.prototype.__init161.call(this);_class7.prototype.__init162.call(this);_class7.prototype.__init163.call(this);_class7.prototype.__init164.call(this);_class7.prototype.__init165.call(this);_class7.prototype.__init166.call(this);_class7.prototype.__init167.call(this);_class7.prototype.__init168.call(this);_class7.prototype.__init169.call(this);
3070
3119
  this.canvas = canvas;
3071
3120
  this.debugA11y = _nullishCoalesce(options.debugA11y, () => ( false));
3072
3121
  this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
@@ -4117,6 +4166,7 @@ var Scene = (_class7 = class _Scene {
4117
4166
  if (node === this.root) {
4118
4167
  this.contentSemanticBudgetLeft = this.contentSemanticBudget;
4119
4168
  this.contentSemanticDeferred = false;
4169
+ this._syncSerial++;
4120
4170
  this.contentSelectionPresentThisSync = null;
4121
4171
  }
4122
4172
  if (node.isDOMPortal) {
@@ -4556,6 +4606,26 @@ var Scene = (_class7 = class _Scene {
4556
4606
  * rejected by an ancestor clip box that itself overlaps the viewport would sit
4557
4607
  * transparently on top of whatever is really drawn there.
4558
4608
  */
4609
+ /**
4610
+ * Load `parent`'s world transform into the `_pw*` scalar memo.
4611
+ *
4612
+ * Exists so the settled-walk fast path costs no allocation. The walk visits a
4613
+ * parent's children consecutively, so this recomputes at most once per parent
4614
+ * per sync and is a serial + identity comparison for every child after the
4615
+ * first. See the `_pwNode` field for why it is keyed the way it is.
4616
+ */
4617
+ readParentWorld(parent) {
4618
+ if (this._pwNode === parent && this._pwSerial === this._syncSerial) return;
4619
+ const tf = parent.getWorldTransform();
4620
+ this._pwa = tf.a;
4621
+ this._pwb = tf.b;
4622
+ this._pwc = tf.c;
4623
+ this._pwd = tf.d;
4624
+ this._pwe = tf.e;
4625
+ this._pwf = tf.f;
4626
+ this._pwNode = parent;
4627
+ this._pwSerial = this._syncSerial;
4628
+ }
4559
4629
  projectionBoxVisible(node, tf, margin, viewportOnly = false) {
4560
4630
  if (!(node.width > 0 && node.height > 0)) return true;
4561
4631
  const { a, b, c, d, e, f } = tf;
@@ -4653,6 +4723,23 @@ var Scene = (_class7 = class _Scene {
4653
4723
  syncContentProjection(node) {
4654
4724
  if (!this.contentProjectionEnabled || !this.a11yRoot) return;
4655
4725
  let el = this.contentElements.get(node.id);
4726
+ const prior0 = this.disableSettledFastPath ? void 0 : this.contentSyncState.get(node.id);
4727
+ if (prior0 !== void 0 && el !== void 0) {
4728
+ const epoch0 = node.getContentEpoch();
4729
+ if (epoch0 !== null && prior0.epoch === epoch0 && prior0.fontEpoch === this.contentFontEpoch && prior0.viewportEpoch === this.contentViewportEpoch && prior0.lx === node.x && prior0.ly === node.y && prior0.lScaleX === node.scaleX && prior0.lScaleY === node.scaleY && prior0.lRotation === node.rotation && prior0.width === node.width && prior0.height === node.height && prior0.interactive === node.interactive) {
4730
+ const parent = node.parent;
4731
+ if (parent === null) {
4732
+ if (prior0.pa === 1 && prior0.pb === 0 && prior0.pc === 0 && prior0.pd === 1 && prior0.pe === 0 && prior0.pf === 0) {
4733
+ return;
4734
+ }
4735
+ } else {
4736
+ this.readParentWorld(parent);
4737
+ if (prior0.pa === this._pwa && prior0.pb === this._pwb && prior0.pc === this._pwc && prior0.pd === this._pwd && prior0.pe === this._pwe && prior0.pf === this._pwf) {
4738
+ return;
4739
+ }
4740
+ }
4741
+ }
4742
+ }
4656
4743
  const releaseProjectionEl = () => {
4657
4744
  if (el) {
4658
4745
  this.clearContentGridState(node.id, el);
@@ -4836,6 +4923,28 @@ var Scene = (_class7 = class _Scene {
4836
4923
  const next = _nullishCoalesce(prior, () => ( {}));
4837
4924
  next.epoch = epoch;
4838
4925
  next.fontEpoch = this.contentFontEpoch;
4926
+ next.viewportEpoch = this.contentViewportEpoch;
4927
+ next.lx = node.x;
4928
+ next.ly = node.y;
4929
+ next.lScaleX = node.scaleX;
4930
+ next.lScaleY = node.scaleY;
4931
+ next.lRotation = node.rotation;
4932
+ if (node.parent === null) {
4933
+ next.pa = 1;
4934
+ next.pb = 0;
4935
+ next.pc = 0;
4936
+ next.pd = 1;
4937
+ next.pe = 0;
4938
+ next.pf = 0;
4939
+ } else {
4940
+ this.readParentWorld(node.parent);
4941
+ next.pa = this._pwa;
4942
+ next.pb = this._pwb;
4943
+ next.pc = this._pwc;
4944
+ next.pd = this._pwd;
4945
+ next.pe = this._pwe;
4946
+ next.pf = this._pwf;
4947
+ }
4839
4948
  next.a = a2;
4840
4949
  next.b = b2;
4841
4950
  next.c = c2;
@@ -5967,6 +6076,7 @@ var Scene = (_class7 = class _Scene {
5967
6076
  this.width = width;
5968
6077
  this.height = height;
5969
6078
  this.contentFontEpoch++;
6079
+ this.contentViewportEpoch++;
5970
6080
  if (typeof this.renderer.resize === "function") {
5971
6081
  if ("maxDPR" in this.renderer) this.renderer.maxDPR = this.maxDPR;
5972
6082
  this.renderer.resize(width, height);
@@ -6273,17 +6383,17 @@ var TextEntity = (_class8 = class extends _chunkTRT2TRCWjs.Entity {
6273
6383
 
6274
6384
 
6275
6385
 
6276
- __init159() {this.nodes = []}
6386
+ __init170() {this.nodes = []}
6277
6387
 
6278
- __init160() {this.fillStyle = "#94a3b8"}
6279
- __init161() {this.strokeStyle = null}
6280
- __init162() {this.hoveredFillStyle = "#ffffff"}
6281
- __init163() {this.lineWidth = 1}
6282
- __init164() {this.isHovered = false}
6388
+ __init171() {this.fillStyle = "#94a3b8"}
6389
+ __init172() {this.strokeStyle = null}
6390
+ __init173() {this.hoveredFillStyle = "#ffffff"}
6391
+ __init174() {this.lineWidth = 1}
6392
+ __init175() {this.isHovered = false}
6283
6393
  /** Bumped by {@link applyLayout}; read by `Scene` to skip an unchanged sync. */
6284
- __init165() {this.contentEpoch = 0}
6394
+ __init176() {this.contentEpoch = 0}
6285
6395
  constructor(text, atlas, maxWidth, fontSize = 24) {
6286
- super();_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);_class8.prototype.__init165.call(this);;
6396
+ super();_class8.prototype.__init170.call(this);_class8.prototype.__init171.call(this);_class8.prototype.__init172.call(this);_class8.prototype.__init173.call(this);_class8.prototype.__init174.call(this);_class8.prototype.__init175.call(this);_class8.prototype.__init176.call(this);;
6287
6397
  this.text = text;
6288
6398
  this.atlas = atlas;
6289
6399
  this.fontSize = fontSize;
@@ -6402,15 +6512,15 @@ var TextEntity = (_class8 = class extends _chunkTRT2TRCWjs.Entity {
6402
6512
  // src/components/GridTextEntity.ts
6403
6513
  var GridTextEntity = (_class9 = class extends _chunkTRT2TRCWjs.Entity {
6404
6514
 
6405
- __init166() {this.fillStyle = "#ffffff"}
6406
- __init167() {this.grid = []}
6515
+ __init177() {this.fillStyle = "#ffffff"}
6516
+ __init178() {this.grid = []}
6407
6517
  // Array of rows
6408
- __init168() {this.cols = 0}
6409
- __init169() {this.rows = 0}
6518
+ __init179() {this.cols = 0}
6519
+ __init180() {this.rows = 0}
6410
6520
 
6411
6521
 
6412
6522
  constructor(_atlas, fontSize = 10) {
6413
- super();_class9.prototype.__init166.call(this);_class9.prototype.__init167.call(this);_class9.prototype.__init168.call(this);_class9.prototype.__init169.call(this);;
6523
+ super();_class9.prototype.__init177.call(this);_class9.prototype.__init178.call(this);_class9.prototype.__init179.call(this);_class9.prototype.__init180.call(this);;
6414
6524
  this.fontSize = fontSize;
6415
6525
  this.charWidth = fontSize * 1;
6416
6526
  this.charHeight = fontSize * 1.1;
@@ -6502,23 +6612,23 @@ var SplineEntity = (_class10 = class extends _chunkTRT2TRCWjs.Entity {
6502
6612
 
6503
6613
 
6504
6614
 
6505
- __init170() {this.offscreen = null}
6506
- __init171() {this.baked = false}
6615
+ __init181() {this.offscreen = null}
6616
+ __init182() {this.baked = false}
6507
6617
  /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
6508
- __init172() {this.bakedWidth = 0}
6509
- __init173() {this.bakedHeight = 0}
6618
+ __init183() {this.bakedWidth = 0}
6619
+ __init184() {this.bakedHeight = 0}
6510
6620
  /** Gradient strokes can't be baked to a solid-color bitmap; they render per-frame. */
6511
6621
 
6512
6622
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
6513
- __init174() {this.polylines = null}
6623
+ __init185() {this.polylines = null}
6514
6624
  /**
6515
6625
  * When `true`, the renderer draws a rounded-rect outline of the entity's
6516
6626
  * local bounds after painting the curves. Useful for drag feedback and
6517
6627
  * debugging hit areas. Defaults to `false`.
6518
6628
  */
6519
- __init175() {this.showBounds = false}
6629
+ __init186() {this.showBounds = false}
6520
6630
  constructor(doc, opts = {}) {
6521
- super();_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);_class10.prototype.__init175.call(this);;
6631
+ super();_class10.prototype.__init181.call(this);_class10.prototype.__init182.call(this);_class10.prototype.__init183.call(this);_class10.prototype.__init184.call(this);_class10.prototype.__init185.call(this);_class10.prototype.__init186.call(this);;
6522
6632
  this.doc = doc;
6523
6633
  this.lineWidth = _nullishCoalesce(opts.lineWidth, () => ( 2));
6524
6634
  this.cache = _nullishCoalesce(opts.cache, () => ( true));
@@ -6899,19 +7009,19 @@ var _math = require('@vectojs/math'); _createStarExport(_math);
6899
7009
  // src/tree/DOMPortalEntity.ts
6900
7010
  var DOMPortalEntity = (_class11 = class extends _chunkTRT2TRCWjs.Entity {
6901
7011
 
6902
- __init176() {this.isDOMPortal = true}
6903
- __init177() {this.domListeners = []}
6904
- __init178() {this.resizeObserver = null}
6905
- __init179() {this.domBound = false}
6906
- __init180() {this.cachedWidth = 100}
6907
- __init181() {this.cachedHeight = 100}
6908
- __init182() {this.lastWidth = ""}
6909
- __init183() {this.lastHeight = ""}
6910
- __init184() {this.lastTransform = ""}
6911
- __init185() {this.lastZIndex = ""}
6912
- __init186() {this.lastOpacity = ""}
7012
+ __init187() {this.isDOMPortal = true}
7013
+ __init188() {this.domListeners = []}
7014
+ __init189() {this.resizeObserver = null}
7015
+ __init190() {this.domBound = false}
7016
+ __init191() {this.cachedWidth = 100}
7017
+ __init192() {this.cachedHeight = 100}
7018
+ __init193() {this.lastWidth = ""}
7019
+ __init194() {this.lastHeight = ""}
7020
+ __init195() {this.lastTransform = ""}
7021
+ __init196() {this.lastZIndex = ""}
7022
+ __init197() {this.lastOpacity = ""}
6913
7023
  constructor(domElement, width, height, id) {
6914
- super(id);_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);_class11.prototype.__init186.call(this);;
7024
+ super(id);_class11.prototype.__init187.call(this);_class11.prototype.__init188.call(this);_class11.prototype.__init189.call(this);_class11.prototype.__init190.call(this);_class11.prototype.__init191.call(this);_class11.prototype.__init192.call(this);_class11.prototype.__init193.call(this);_class11.prototype.__init194.call(this);_class11.prototype.__init195.call(this);_class11.prototype.__init196.call(this);_class11.prototype.__init197.call(this);;
6915
7025
  this.domElement = domElement;
6916
7026
  this.width = _nullishCoalesce(width, () => ( 0));
6917
7027
  this.height = _nullishCoalesce(height, () => ( 0));
package/dist/index.mjs CHANGED
@@ -1997,6 +1997,55 @@ var Scene = class _Scene {
1997
1997
  contentGridCalibrationStamp = "";
1998
1998
  /** Invalidates grid font calibration after browser font availability changes. */
1999
1999
  contentFontEpoch = 0;
2000
+ /**
2001
+ * Bumped whenever the viewport itself changes shape, which re-tiers blocks
2002
+ * without moving any of them.
2003
+ *
2004
+ * The settled-walk fast path in {@link syncContentProjection} decides a block
2005
+ * is unchanged from its own local transform plus its parent's world transform.
2006
+ * Both are viewport-independent, so a resize alone would slip past it and leave
2007
+ * every block holding DOM built for the old viewport — a block that should have
2008
+ * been promoted to the fine tier keeping no carriers, or a demoted one keeping
2009
+ * carriers it no longer needs. Scrolling needs no such epoch: it moves the root,
2010
+ * so every block's parent world transform changes and the check fails honestly.
2011
+ */
2012
+ contentViewportEpoch = 0;
2013
+ /**
2014
+ * One-entry memo for a parent's world transform, held as scalars.
2015
+ *
2016
+ * The a11y walk visits all of one parent's children consecutively, so a
2017
+ * one-entry memo hits for every child after the first — turning an
2018
+ * O(children) sequence of `getWorldTransform()` calls, each of which
2019
+ * allocates a fresh object on its cache-hit path
2020
+ * ({@link Entity.getWorldTransform}), into one call per parent. Flattened to
2021
+ * scalars rather than a cached object so the memo itself never allocates.
2022
+ *
2023
+ * Keyed on `(entity, syncSerial)`: the serial is bumped once per top-level
2024
+ * sync, so a memo can never survive into a frame in which the parent has
2025
+ * moved.
2026
+ */
2027
+ _pwNode = null;
2028
+ _pwSerial = -1;
2029
+ _pwa = 1;
2030
+ _pwb = 0;
2031
+ _pwc = 0;
2032
+ _pwd = 1;
2033
+ _pwe = 0;
2034
+ _pwf = 0;
2035
+ /** Incremented once per top-level `syncA11y`, invalidating `_pwNode`. */
2036
+ _syncSerial = 0;
2037
+ /**
2038
+ * Disables the settled-walk fast path in {@link syncContentProjection}.
2039
+ *
2040
+ * Exists so a benchmark can measure both arms in ONE run on ONE commit, rather
2041
+ * than comparing two builds and inheriting every difference between them. Also
2042
+ * lets a test assert that a behaviour is genuinely unchanged by the fast path
2043
+ * rather than merely unobserved.
2044
+ *
2045
+ * Not part of the public API and not documented as an option: turning it on only
2046
+ * makes a settled document slower, never more correct.
2047
+ */
2048
+ disableSettledFastPath = false;
2000
2049
  /** Cached Canvas-to-client scale for the current font/viewport epoch. */
2001
2050
  contentMetricScaleEpoch = -1;
2002
2051
  contentMetricScaleX = 1;
@@ -4116,6 +4165,7 @@ var Scene = class _Scene {
4116
4165
  if (node === this.root) {
4117
4166
  this.contentSemanticBudgetLeft = this.contentSemanticBudget;
4118
4167
  this.contentSemanticDeferred = false;
4168
+ this._syncSerial++;
4119
4169
  this.contentSelectionPresentThisSync = null;
4120
4170
  }
4121
4171
  if (node.isDOMPortal) {
@@ -4555,6 +4605,26 @@ var Scene = class _Scene {
4555
4605
  * rejected by an ancestor clip box that itself overlaps the viewport would sit
4556
4606
  * transparently on top of whatever is really drawn there.
4557
4607
  */
4608
+ /**
4609
+ * Load `parent`'s world transform into the `_pw*` scalar memo.
4610
+ *
4611
+ * Exists so the settled-walk fast path costs no allocation. The walk visits a
4612
+ * parent's children consecutively, so this recomputes at most once per parent
4613
+ * per sync and is a serial + identity comparison for every child after the
4614
+ * first. See the `_pwNode` field for why it is keyed the way it is.
4615
+ */
4616
+ readParentWorld(parent) {
4617
+ if (this._pwNode === parent && this._pwSerial === this._syncSerial) return;
4618
+ const tf = parent.getWorldTransform();
4619
+ this._pwa = tf.a;
4620
+ this._pwb = tf.b;
4621
+ this._pwc = tf.c;
4622
+ this._pwd = tf.d;
4623
+ this._pwe = tf.e;
4624
+ this._pwf = tf.f;
4625
+ this._pwNode = parent;
4626
+ this._pwSerial = this._syncSerial;
4627
+ }
4558
4628
  projectionBoxVisible(node, tf, margin, viewportOnly = false) {
4559
4629
  if (!(node.width > 0 && node.height > 0)) return true;
4560
4630
  const { a, b, c, d, e, f } = tf;
@@ -4652,6 +4722,23 @@ var Scene = class _Scene {
4652
4722
  syncContentProjection(node) {
4653
4723
  if (!this.contentProjectionEnabled || !this.a11yRoot) return;
4654
4724
  let el = this.contentElements.get(node.id);
4725
+ const prior0 = this.disableSettledFastPath ? void 0 : this.contentSyncState.get(node.id);
4726
+ if (prior0 !== void 0 && el !== void 0) {
4727
+ const epoch0 = node.getContentEpoch();
4728
+ if (epoch0 !== null && prior0.epoch === epoch0 && prior0.fontEpoch === this.contentFontEpoch && prior0.viewportEpoch === this.contentViewportEpoch && prior0.lx === node.x && prior0.ly === node.y && prior0.lScaleX === node.scaleX && prior0.lScaleY === node.scaleY && prior0.lRotation === node.rotation && prior0.width === node.width && prior0.height === node.height && prior0.interactive === node.interactive) {
4729
+ const parent = node.parent;
4730
+ if (parent === null) {
4731
+ if (prior0.pa === 1 && prior0.pb === 0 && prior0.pc === 0 && prior0.pd === 1 && prior0.pe === 0 && prior0.pf === 0) {
4732
+ return;
4733
+ }
4734
+ } else {
4735
+ this.readParentWorld(parent);
4736
+ if (prior0.pa === this._pwa && prior0.pb === this._pwb && prior0.pc === this._pwc && prior0.pd === this._pwd && prior0.pe === this._pwe && prior0.pf === this._pwf) {
4737
+ return;
4738
+ }
4739
+ }
4740
+ }
4741
+ }
4655
4742
  const releaseProjectionEl = () => {
4656
4743
  if (el) {
4657
4744
  this.clearContentGridState(node.id, el);
@@ -4835,6 +4922,28 @@ var Scene = class _Scene {
4835
4922
  const next = prior ?? {};
4836
4923
  next.epoch = epoch;
4837
4924
  next.fontEpoch = this.contentFontEpoch;
4925
+ next.viewportEpoch = this.contentViewportEpoch;
4926
+ next.lx = node.x;
4927
+ next.ly = node.y;
4928
+ next.lScaleX = node.scaleX;
4929
+ next.lScaleY = node.scaleY;
4930
+ next.lRotation = node.rotation;
4931
+ if (node.parent === null) {
4932
+ next.pa = 1;
4933
+ next.pb = 0;
4934
+ next.pc = 0;
4935
+ next.pd = 1;
4936
+ next.pe = 0;
4937
+ next.pf = 0;
4938
+ } else {
4939
+ this.readParentWorld(node.parent);
4940
+ next.pa = this._pwa;
4941
+ next.pb = this._pwb;
4942
+ next.pc = this._pwc;
4943
+ next.pd = this._pwd;
4944
+ next.pe = this._pwe;
4945
+ next.pf = this._pwf;
4946
+ }
4838
4947
  next.a = a2;
4839
4948
  next.b = b2;
4840
4949
  next.c = c2;
@@ -5966,6 +6075,7 @@ var Scene = class _Scene {
5966
6075
  this.width = width;
5967
6076
  this.height = height;
5968
6077
  this.contentFontEpoch++;
6078
+ this.contentViewportEpoch++;
5969
6079
  if (typeof this.renderer.resize === "function") {
5970
6080
  if ("maxDPR" in this.renderer) this.renderer.maxDPR = this.maxDPR;
5971
6081
  this.renderer.resize(width, height);
@@ -623,6 +623,55 @@ export declare class Scene {
623
623
  private contentGridCalibrationStamp;
624
624
  /** Invalidates grid font calibration after browser font availability changes. */
625
625
  private contentFontEpoch;
626
+ /**
627
+ * Bumped whenever the viewport itself changes shape, which re-tiers blocks
628
+ * without moving any of them.
629
+ *
630
+ * The settled-walk fast path in {@link syncContentProjection} decides a block
631
+ * is unchanged from its own local transform plus its parent's world transform.
632
+ * Both are viewport-independent, so a resize alone would slip past it and leave
633
+ * every block holding DOM built for the old viewport — a block that should have
634
+ * been promoted to the fine tier keeping no carriers, or a demoted one keeping
635
+ * carriers it no longer needs. Scrolling needs no such epoch: it moves the root,
636
+ * so every block's parent world transform changes and the check fails honestly.
637
+ */
638
+ private contentViewportEpoch;
639
+ /**
640
+ * One-entry memo for a parent's world transform, held as scalars.
641
+ *
642
+ * The a11y walk visits all of one parent's children consecutively, so a
643
+ * one-entry memo hits for every child after the first — turning an
644
+ * O(children) sequence of `getWorldTransform()` calls, each of which
645
+ * allocates a fresh object on its cache-hit path
646
+ * ({@link Entity.getWorldTransform}), into one call per parent. Flattened to
647
+ * scalars rather than a cached object so the memo itself never allocates.
648
+ *
649
+ * Keyed on `(entity, syncSerial)`: the serial is bumped once per top-level
650
+ * sync, so a memo can never survive into a frame in which the parent has
651
+ * moved.
652
+ */
653
+ private _pwNode;
654
+ private _pwSerial;
655
+ private _pwa;
656
+ private _pwb;
657
+ private _pwc;
658
+ private _pwd;
659
+ private _pwe;
660
+ private _pwf;
661
+ /** Incremented once per top-level `syncA11y`, invalidating `_pwNode`. */
662
+ private _syncSerial;
663
+ /**
664
+ * Disables the settled-walk fast path in {@link syncContentProjection}.
665
+ *
666
+ * Exists so a benchmark can measure both arms in ONE run on ONE commit, rather
667
+ * than comparing two builds and inheriting every difference between them. Also
668
+ * lets a test assert that a behaviour is genuinely unchanged by the fast path
669
+ * rather than merely unobserved.
670
+ *
671
+ * Not part of the public API and not documented as an option: turning it on only
672
+ * makes a settled document slower, never more correct.
673
+ */
674
+ disableSettledFastPath: boolean;
626
675
  /** Cached Canvas-to-client scale for the current font/viewport epoch. */
627
676
  private contentMetricScaleEpoch;
628
677
  private contentMetricScaleX;
@@ -1561,6 +1610,15 @@ export declare class Scene {
1561
1610
  * rejected by an ancestor clip box that itself overlaps the viewport would sit
1562
1611
  * transparently on top of whatever is really drawn there.
1563
1612
  */
1613
+ /**
1614
+ * Load `parent`'s world transform into the `_pw*` scalar memo.
1615
+ *
1616
+ * Exists so the settled-walk fast path costs no allocation. The walk visits a
1617
+ * parent's children consecutively, so this recomputes at most once per parent
1618
+ * per sync and is a serial + identity comparison for every child after the
1619
+ * first. See the `_pwNode` field for why it is keyed the way it is.
1620
+ */
1621
+ private readParentWorld;
1564
1622
  private projectionBoxVisible;
1565
1623
  /**
1566
1624
  * The band of an entity's own y coordinates that is worth projecting, or
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "1.32.1",
3
+ "version": "1.32.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "dependencies": {
67
67
  "@vectojs/animation": "^0.1.1",
68
- "@vectojs/layout": "^0.6.0",
68
+ "@vectojs/layout": "^0.7.0",
69
69
  "@vectojs/math": "^0.1.1",
70
70
  "@vectojs/text": "^0.3.0"
71
71
  },