@vectojs/core 1.32.0 → 1.32.2

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,11 +2160,21 @@ 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()}
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}
2163
+ __init103() {this.a11yOrderContainers = /* @__PURE__ */ new Set()}
2164
+ /**
2165
+ * Nearest `clipChildren` ancestor per ordered element — its *region* — reused
2166
+ * per pass. Written by `enforceA11yDomOrder`'s collect walk, which already has
2167
+ * the entity in hand, so a region costs one comparison per node rather than an
2168
+ * ancestor walk per element.
2169
+ *
2170
+ * Absent means the element sits under no clipping ancestor and belongs to the
2171
+ * implicit root region. See {@link sortNormalElementsVisually}.
2172
+ */
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}
2119
2178
  /**
2120
2179
  * Monotonic render-frame counter, bumped once per authoritative `render()`
2121
2180
  * pass. Entities stamp their per-frame world-matrix cache with this value and
@@ -2124,7 +2183,7 @@ var Scene = (_class7 = class _Scene {
2124
2183
  * back to the ancestor walk. Public for the same reason `Entity._getTrig`/
2125
2184
  * `_setWorldCache` are: it is a cross-class render-internal contract.
2126
2185
  */
2127
- __init97() {this.currentFrame = 0}
2186
+ __init109() {this.currentFrame = 0}
2128
2187
  // ── WASM transform backend (invisible accelerator) ──────────────────────────
2129
2188
  // When `_transformBackend === 'wasm'`, the main render walk sources each
2130
2189
  // entity's world matrix from an SoA store composed by `_wasm` (see
@@ -2132,24 +2191,24 @@ var Scene = (_class7 = class _Scene {
2132
2191
  // fallback and the default: a null backend, a non-main renderer, or any entity
2133
2192
  // absent from the store all fall back to the JS composition, so WASM can only
2134
2193
  // ever change *how fast* a world matrix is produced, never *what* it is.
2135
- __init98() {this._wasm = null}
2136
- __init99() {this._transformBackend = "js"}
2194
+ __init110() {this._wasm = null}
2195
+ __init111() {this._transformBackend = "js"}
2137
2196
  // Resident store state (Stage 3). The store layout — slot assignment + sibling
2138
2197
  // runs — depends only on tree TOPOLOGY, so it is rebuilt only when the
2139
2198
  // structure changes (add/remove/reparent bump `_structureVersion`). Between
2140
2199
  // rebuilds the per-frame cost is: gather each entity's transform into the
2141
2200
  // resident wasm input view + run the kernel — no reallocation, no readback.
2142
- __init100() {this._treeStore = null}
2143
- __init101() {this._slotEntity = []}
2201
+ __init112() {this._treeStore = null}
2202
+ __init113() {this._slotEntity = []}
2144
2203
  // store slot -> entity (also validates slots)
2145
- __init102() {this._wasmInputs = null}
2146
- __init103() {this._wasmWorld = null}
2147
- __init104() {this._structureVersion = 0}
2148
- __init105() {this._storeStructureVersion = -1}
2204
+ __init114() {this._wasmInputs = null}
2205
+ __init115() {this._wasmWorld = null}
2206
+ __init116() {this._structureVersion = 0}
2207
+ __init117() {this._storeStructureVersion = -1}
2149
2208
  // Cached list of ComputeParticleEntity instances in the tree, keyed by the
2150
2209
  // structure version it was gathered at. Rebuilt only on a topology change.
2151
- __init106() {this._computeEntities = []}
2152
- __init107() {this._computeEntitiesVersion = -1}
2210
+ __init118() {this._computeEntities = []}
2211
+ __init119() {this._computeEntitiesVersion = -1}
2153
2212
  /** Invalidate the resident WASM store layout; the next wasm-mode frame rebuilds
2154
2213
  * it. Called by `Entity.add`/`remove` (topology changes only). */
2155
2214
  markStructureChanged() {
@@ -2234,7 +2293,7 @@ var Scene = (_class7 = class _Scene {
2234
2293
  * cached globally; the instance is per-Scene, which is the isolation that
2235
2294
  * actually matters.
2236
2295
  */
2237
- __init108() {this._wasmRuntime = null}
2296
+ __init120() {this._wasmRuntime = null}
2238
2297
  /**
2239
2298
  * Load (or reuse) this Scene's shared WASM runtime.
2240
2299
  *
@@ -2262,30 +2321,30 @@ var Scene = (_class7 = class _Scene {
2262
2321
  get wasmRuntime() {
2263
2322
  return this._wasmRuntime;
2264
2323
  }
2265
- __init109() {this._hitWasm = null}
2324
+ __init121() {this._hitWasm = null}
2266
2325
  // Cache key: which frame + structure version the grid was last (successfully,
2267
2326
  // non-overflowing) built for. findEntityAt is called ad-hoc (pointer
2268
2327
  // hover/click), not every frame, so the grid is refreshed lazily on demand
2269
2328
  // rather than proactively every render() — unlike the transform store, which
2270
2329
  // every frame's draw depends on.
2271
- __init110() {this._hitGridFrame = -1}
2272
- __init111() {this._hitGridOk = false}
2273
- __init112() {this._hitSlotEntity = []}
2274
- __init113() {this._hitBoundless = []}
2330
+ __init122() {this._hitGridFrame = -1}
2331
+ __init123() {this._hitGridOk = false}
2332
+ __init124() {this._hitSlotEntity = []}
2333
+ __init125() {this._hitBoundless = []}
2275
2334
  /** Reused buffer for the fused gather, so a pointer query allocates nothing. */
2276
- __init114() {this._hitGatherBuffer = null}
2335
+ __init126() {this._hitGatherBuffer = null}
2277
2336
  /**
2278
2337
  * Whether the last grid build sourced its AABBs from the WASM transform store
2279
2338
  * rather than recomputing them in JS. Diagnostic only — both paths must
2280
2339
  * produce the same entity for a given point.
2281
2340
  */
2282
- __init115() {this._hitFusedGather = false}
2341
+ __init127() {this._hitFusedGather = false}
2283
2342
  /**
2284
2343
  * Whether `compute_aabbs` has run against the current frame's world matrices.
2285
2344
  * The AABB pass is only meaningful after a `compose_*`, so the fused gather
2286
2345
  * must not read the views before then.
2287
2346
  */
2288
- __init116() {this._wasmAabbsFresh = false}
2347
+ __init128() {this._wasmAabbsFresh = false}
2289
2348
  /** Did the last hit-grid build use the fused (WASM-store) gather? */
2290
2349
  get hitGatherPath() {
2291
2350
  return this._hitFusedGather ? "fused" : "js";
@@ -2294,9 +2353,9 @@ var Scene = (_class7 = class _Scene {
2294
2353
  * Why the transform accelerator did or did not run on the most recent frame.
2295
2354
  * Written by the render walk and `_syncWasmStore`.
2296
2355
  */
2297
- __init117() {this._transformReason = "not-installed"}
2356
+ __init129() {this._transformReason = "not-installed"}
2298
2357
  /** Why the batched-driver accelerator did or did not run. */
2299
- __init118() {this._animReason = "not-installed"}
2358
+ __init130() {this._animReason = "not-installed"}
2300
2359
  /**
2301
2360
  * Why the hit-test accelerator did or did not serve the last pointer query.
2302
2361
  * The grid is built lazily on demand, not every frame, so this describes the
@@ -2304,11 +2363,11 @@ var Scene = (_class7 = class _Scene {
2304
2363
  * before a backend exists; `_ensureHitGrid` moves it to `'not-applicable'`
2305
2364
  * once one is installed but nothing has queried yet.
2306
2365
  */
2307
- __init119() {this._hitReason = "not-installed"}
2366
+ __init131() {this._hitReason = "not-installed"}
2308
2367
  /** Why the particle accelerator did or did not run. */
2309
- __init120() {this._particleReason = "not-applicable"}
2368
+ __init132() {this._particleReason = "not-applicable"}
2310
2369
  /** Which particle implementation actually simulated the most recent frame. */
2311
- __init121() {this._particlePath = "none"}
2370
+ __init133() {this._particlePath = "none"}
2312
2371
  /**
2313
2372
  * Per-frame status of every invisible accelerator: whether each is installed,
2314
2373
  * whether it actually ran on the most recent frame, and why.
@@ -2468,25 +2527,25 @@ var Scene = (_class7 = class _Scene {
2468
2527
  // EasingFn (which cannot cross into WASM) all fall through to it — WASM can
2469
2528
  // only ever change *how* a driver is advanced, never *what* value it lands
2470
2529
  // on.
2471
- __init122() {this._animWasm = null}
2530
+ __init134() {this._animWasm = null}
2472
2531
  // Entities with at least one active driver, added by Entity._spawnDriver.
2473
2532
  // Self-pruning: _tickBatchedDrivers drops an entry the first time it visits
2474
2533
  // an entity whose drivers have since all completed or been removed. This is
2475
2534
  // what lets the batch pass find its candidates in O(active drivers), not
2476
2535
  // O(tree size) — the exact mistake G3's first integrated benchmark made.
2477
- __init123() {this._activeDriverEntities = /* @__PURE__ */ new Set()}
2536
+ __init135() {this._activeDriverEntities = /* @__PURE__ */ new Set()}
2478
2537
  // Reused across frames instead of allocating a fresh array + N {entity,prop,
2479
2538
  // driver} objects every call — the integrated benchmark
2480
2539
  // (benchmarks/anim-wasm-scene) found that allocation churn was the
2481
2540
  // dominant integrated cost, not the wasm kernel itself. Parallel arrays,
2482
2541
  // truncated to the live count after each use so a stale tail slot never
2483
2542
  // pins a no-longer-active entity/driver in memory.
2484
- __init124() {this._springEntities = []}
2485
- __init125() {this._springProps = []}
2486
- __init126() {this._springDrivers = []}
2487
- __init127() {this._tweenEntities = []}
2488
- __init128() {this._tweenProps = []}
2489
- __init129() {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 = []}
2490
2549
  /**
2491
2550
  * Minimum number of batchable (spring, or named-easing tween) active drivers
2492
2551
  * before a frame engages the WASM batch path at all; below it, every driver
@@ -2558,7 +2617,7 @@ var Scene = (_class7 = class _Scene {
2558
2617
  * Setting {@link animDriverGateCount} overwrites all three, so existing code
2559
2618
  * that tuned the single knob keeps working unchanged.
2560
2619
  */
2561
- __init130() {this._animBatchedLastFrame = false}
2620
+ __init142() {this._animBatchedLastFrame = false}
2562
2621
  /**
2563
2622
  * Whether the WASM batch path actually ran on the most recent frame.
2564
2623
  *
@@ -2570,7 +2629,7 @@ var Scene = (_class7 = class _Scene {
2570
2629
  get animBatchedLastFrame() {
2571
2630
  return this._animBatchedLastFrame;
2572
2631
  }
2573
- __init131() {this.animGate = {
2632
+ __init143() {this.animGate = {
2574
2633
  spring: 128,
2575
2634
  tween: 256,
2576
2635
  mixed: 128
@@ -2608,7 +2667,7 @@ var Scene = (_class7 = class _Scene {
2608
2667
  // (benchmarks/particle-wasm). f32 (matches the WGSL shader), bit-identical to
2609
2668
  // a JS f32 reference oracle; updateCPU (f64) stays the permanent fallback when
2610
2669
  // no backend is installed or a scene runs on WebGPU.
2611
- __init132() {this._particleWasm = null}
2670
+ __init144() {this._particleWasm = null}
2612
2671
  /** Which backend runs the CPU particle simulation. Reflects only whether a
2613
2672
  * backend is installed (the WebGPU compute path, when active, is used first
2614
2673
  * regardless). */
@@ -2885,24 +2944,24 @@ var Scene = (_class7 = class _Scene {
2885
2944
  * sync, so retaining the order prevents a newly opened overlay from spending
2886
2945
  * its first frame below previously projected controls.
2887
2946
  */
2888
- __init133() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
2947
+ __init145() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
2889
2948
  // Optional WebGL point-cloud layer (see SceneOptions.pointBackend).
2890
- __init134() {this.pointRenderer = null}
2891
- __init135() {this.glCanvas = null}
2892
- __init136() {this.glContextLostHandler = null}
2893
- __init137() {this.glContextRestoredHandler = null}
2949
+ __init146() {this.pointRenderer = null}
2950
+ __init147() {this.glCanvas = null}
2951
+ __init148() {this.glContextLostHandler = null}
2952
+ __init149() {this.glContextRestoredHandler = null}
2894
2953
 
2895
2954
 
2896
2955
 
2897
- __init138() {this.disableWindowResize = false}
2956
+ __init150() {this.disableWindowResize = false}
2898
2957
  /** See {@link SceneOptions.maxDPR}. `undefined` = uncapped (real DPR). */
2899
2958
 
2900
2959
  // WebGPU properties
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}
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}
2906
2965
  get webgpuDisabled() {
2907
2966
  return this._webgpuDisabled || this.particleBackend === "cpu";
2908
2967
  }
@@ -2930,22 +2989,22 @@ var Scene = (_class7 = class _Scene {
2930
2989
  set webgpuDisabled(value) {
2931
2990
  this._webgpuDisabled = value;
2932
2991
  }
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}
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}
2938
2997
  /** True while the GPU canvas holds a presented particle frame (needs clearing when they leave). */
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}
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}
2944
3003
  /** Element the pointer listeners are bound to (parent container if present,
2945
3004
  * else the canvas). Stored so `destroy()` detaches from the same element. */
2946
- __init154() {this.pointerEventTarget = null}
2947
- __init155() {this.hasWarnedZeroSize = false}
2948
- __init156() {this.fontLoadHandler = null}
3005
+ __init166() {this.pointerEventTarget = null}
3006
+ __init167() {this.hasWarnedZeroSize = false}
3007
+ __init168() {this.fontLoadHandler = null}
2949
3008
  // ── Dev-mode warning infrastructure ──────────────────────────────
2950
3009
  //
2951
3010
  // Enable with `Scene.devMode = true` or by setting `globalThis.__DEV__`.
@@ -2979,7 +3038,7 @@ var Scene = (_class7 = class _Scene {
2979
3038
  return false;
2980
3039
  }
2981
3040
 
2982
- __init157() {this._devFrameCount = 0}
3041
+ __init169() {this._devFrameCount = 0}
2983
3042
  _devWarn(message) {
2984
3043
  if (!this._devActive) return;
2985
3044
  console.warn(`[vectojs/dev] ${message}`);
@@ -3056,7 +3115,7 @@ var Scene = (_class7 = class _Scene {
3056
3115
  };
3057
3116
  walkProjections(this.root);
3058
3117
  }
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);
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);
3060
3119
  this.canvas = canvas;
3061
3120
  this.debugA11y = _nullishCoalesce(options.debugA11y, () => ( false));
3062
3121
  this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
@@ -4107,6 +4166,7 @@ var Scene = (_class7 = class _Scene {
4107
4166
  if (node === this.root) {
4108
4167
  this.contentSemanticBudgetLeft = this.contentSemanticBudget;
4109
4168
  this.contentSemanticDeferred = false;
4169
+ this._syncSerial++;
4110
4170
  this.contentSelectionPresentThisSync = null;
4111
4171
  }
4112
4172
  if (node.isDOMPortal) {
@@ -4546,6 +4606,26 @@ var Scene = (_class7 = class _Scene {
4546
4606
  * rejected by an ancestor clip box that itself overlaps the viewport would sit
4547
4607
  * transparently on top of whatever is really drawn there.
4548
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
+ }
4549
4629
  projectionBoxVisible(node, tf, margin, viewportOnly = false) {
4550
4630
  if (!(node.width > 0 && node.height > 0)) return true;
4551
4631
  const { a, b, c, d, e, f } = tf;
@@ -4643,6 +4723,23 @@ var Scene = (_class7 = class _Scene {
4643
4723
  syncContentProjection(node) {
4644
4724
  if (!this.contentProjectionEnabled || !this.a11yRoot) return;
4645
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
+ }
4646
4743
  const releaseProjectionEl = () => {
4647
4744
  if (el) {
4648
4745
  this.clearContentGridState(node.id, el);
@@ -4826,6 +4923,28 @@ var Scene = (_class7 = class _Scene {
4826
4923
  const next = _nullishCoalesce(prior, () => ( {}));
4827
4924
  next.epoch = epoch;
4828
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
+ }
4829
4948
  next.a = a2;
4830
4949
  next.b = b2;
4831
4950
  next.c = c2;
@@ -5170,12 +5289,14 @@ var Scene = (_class7 = class _Scene {
5170
5289
  this.fullViewportElements.length = 0;
5171
5290
  this.normalElements.length = 0;
5172
5291
  this.activeIds.clear();
5173
- const collect = (node) => {
5292
+ this.a11yOrderRegions.clear();
5293
+ const collect = (node, region) => {
5174
5294
  if (node.isDOMPortal) return;
5175
5295
  const contentEl = this.contentElements.get(node.id);
5176
5296
  if (contentEl) {
5177
5297
  if (node.a11yFullViewport) this.fullViewportElements.push(contentEl);
5178
5298
  else this.normalElements.push(contentEl);
5299
+ if (region) this.a11yOrderRegions.set(contentEl, region);
5179
5300
  }
5180
5301
  if (this.shouldProjectA11y(node)) {
5181
5302
  const el = this.a11yElements.get(node.id);
@@ -5183,14 +5304,16 @@ var Scene = (_class7 = class _Scene {
5183
5304
  this.activeIds.add(node.id);
5184
5305
  if (node.a11yFullViewport) this.fullViewportElements.push(el);
5185
5306
  else this.normalElements.push(el);
5307
+ if (region) this.a11yOrderRegions.set(el, region);
5186
5308
  }
5187
5309
  }
5188
- for (const child of node.children) collect(child);
5310
+ const childRegion = node.clipChildren && node.width > 0 && node.height > 0 ? node : region;
5311
+ for (const child of node.children) collect(child, childRegion);
5189
5312
  if (node === this.root) {
5190
- for (const overlay of this.overlayRoot.children) collect(overlay);
5313
+ for (const overlay of this.overlayRoot.children) collect(overlay, null);
5191
5314
  }
5192
5315
  };
5193
- collect(this.root);
5316
+ collect(this.root, null);
5194
5317
  let elementsPruned = false;
5195
5318
  for (const [id, el] of this.a11yElements.entries()) {
5196
5319
  if (!this.activeIds.has(id)) {
@@ -5252,6 +5375,18 @@ var Scene = (_class7 = class _Scene {
5252
5375
  * parents, which no `insertBefore` ever acts on. Normalizing everything back
5253
5376
  * to world coordinates here would cost a transform per element per frame to
5254
5377
  * change nothing observable.
5378
+ *
5379
+ * Banding runs **per region** — per nearest `clipChildren` ancestor, recorded
5380
+ * by {@link enforceA11yDomOrder}'s collect walk — rather than once over the
5381
+ * whole scene. Purely visual banding is right for a screen reader but wrong
5382
+ * for selection: a DOM `Selection` covers everything between anchor and focus
5383
+ * in DOM order, so under one global banding a vertical drag through a
5384
+ * transcript also swallowed a sidebar whose headings happened to fall in the
5385
+ * same rows. Regions are laid out side by side, so ordering region-major keeps
5386
+ * each one a contiguous DOM run and a drag stays inside it, while reading
5387
+ * order *within* a region is unchanged. Regions are emitted in the order their
5388
+ * clipper is first reached by the depth-first walk, so a screen reader still
5389
+ * meets them in the author's declared order.
5255
5390
  */
5256
5391
  sortNormalElementsVisually() {
5257
5392
  const els = this.normalElements;
@@ -5279,30 +5414,40 @@ var Scene = (_class7 = class _Scene {
5279
5414
  }
5280
5415
  return { top, left };
5281
5416
  };
5282
- const order = els.map((el, i) => {
5417
+ const decorated = els.map((el, i) => {
5283
5418
  const { top, left } = absolute(el);
5284
5419
  return { el, i, top, left, container: containers.has(el) };
5285
5420
  });
5286
- order.sort((p, q) => p.top - q.top || p.i - q.i);
5421
+ const regions = this.a11yOrderRegions;
5422
+ const buckets = /* @__PURE__ */ new Map();
5423
+ for (const d of decorated) {
5424
+ const key = _nullishCoalesce(regions.get(d.el), () => ( null));
5425
+ const bucket = buckets.get(key);
5426
+ if (bucket) bucket.push(d);
5427
+ else buckets.set(key, [d]);
5428
+ }
5287
5429
  const bandBottom = (r) => r.top + (r.container ? 4 : heightOf(r.el));
5288
5430
  const sorted = [];
5289
- let rowStart = 0;
5290
- let rowBottom = order.length ? bandBottom(order[0]) : 0;
5291
- const flushRow = (end) => {
5292
- const row = order.slice(rowStart, end);
5293
- row.sort((p, q) => (rtl ? q.left - p.left : p.left - q.left) || p.i - q.i);
5294
- for (const r of row) sorted.push(r.el);
5295
- };
5296
- for (let k = 1; k < order.length; k++) {
5297
- if (order[k].top < rowBottom) {
5298
- rowBottom = Math.max(rowBottom, bandBottom(order[k]));
5299
- } else {
5300
- flushRow(k);
5301
- rowStart = k;
5302
- rowBottom = bandBottom(order[k]);
5431
+ for (const order of buckets.values()) {
5432
+ order.sort((p, q) => p.top - q.top || p.i - q.i);
5433
+ let rowStart = 0;
5434
+ let rowBottom = order.length ? bandBottom(order[0]) : 0;
5435
+ const flushRow = (end) => {
5436
+ const row = order.slice(rowStart, end);
5437
+ row.sort((p, q) => (rtl ? q.left - p.left : p.left - q.left) || p.i - q.i);
5438
+ for (const r of row) sorted.push(r.el);
5439
+ };
5440
+ for (let k = 1; k < order.length; k++) {
5441
+ if (order[k].top < rowBottom) {
5442
+ rowBottom = Math.max(rowBottom, bandBottom(order[k]));
5443
+ } else {
5444
+ flushRow(k);
5445
+ rowStart = k;
5446
+ rowBottom = bandBottom(order[k]);
5447
+ }
5303
5448
  }
5449
+ flushRow(order.length);
5304
5450
  }
5305
- flushRow(order.length);
5306
5451
  for (let i = 0; i < sorted.length; i++) els[i] = sorted[i];
5307
5452
  }
5308
5453
  /** Keep DOM/WebGL overlay layers aligned with the canvas's CSS box. */
@@ -5931,6 +6076,7 @@ var Scene = (_class7 = class _Scene {
5931
6076
  this.width = width;
5932
6077
  this.height = height;
5933
6078
  this.contentFontEpoch++;
6079
+ this.contentViewportEpoch++;
5934
6080
  if (typeof this.renderer.resize === "function") {
5935
6081
  if ("maxDPR" in this.renderer) this.renderer.maxDPR = this.maxDPR;
5936
6082
  this.renderer.resize(width, height);
@@ -6237,17 +6383,17 @@ var TextEntity = (_class8 = class extends _chunkTRT2TRCWjs.Entity {
6237
6383
 
6238
6384
 
6239
6385
 
6240
- __init158() {this.nodes = []}
6386
+ __init170() {this.nodes = []}
6241
6387
 
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}
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}
6247
6393
  /** Bumped by {@link applyLayout}; read by `Scene` to skip an unchanged sync. */
6248
- __init164() {this.contentEpoch = 0}
6394
+ __init176() {this.contentEpoch = 0}
6249
6395
  constructor(text, atlas, maxWidth, fontSize = 24) {
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);;
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);;
6251
6397
  this.text = text;
6252
6398
  this.atlas = atlas;
6253
6399
  this.fontSize = fontSize;
@@ -6366,15 +6512,15 @@ var TextEntity = (_class8 = class extends _chunkTRT2TRCWjs.Entity {
6366
6512
  // src/components/GridTextEntity.ts
6367
6513
  var GridTextEntity = (_class9 = class extends _chunkTRT2TRCWjs.Entity {
6368
6514
 
6369
- __init165() {this.fillStyle = "#ffffff"}
6370
- __init166() {this.grid = []}
6515
+ __init177() {this.fillStyle = "#ffffff"}
6516
+ __init178() {this.grid = []}
6371
6517
  // Array of rows
6372
- __init167() {this.cols = 0}
6373
- __init168() {this.rows = 0}
6518
+ __init179() {this.cols = 0}
6519
+ __init180() {this.rows = 0}
6374
6520
 
6375
6521
 
6376
6522
  constructor(_atlas, fontSize = 10) {
6377
- super();_class9.prototype.__init165.call(this);_class9.prototype.__init166.call(this);_class9.prototype.__init167.call(this);_class9.prototype.__init168.call(this);;
6523
+ super();_class9.prototype.__init177.call(this);_class9.prototype.__init178.call(this);_class9.prototype.__init179.call(this);_class9.prototype.__init180.call(this);;
6378
6524
  this.fontSize = fontSize;
6379
6525
  this.charWidth = fontSize * 1;
6380
6526
  this.charHeight = fontSize * 1.1;
@@ -6466,23 +6612,23 @@ var SplineEntity = (_class10 = class extends _chunkTRT2TRCWjs.Entity {
6466
6612
 
6467
6613
 
6468
6614
 
6469
- __init169() {this.offscreen = null}
6470
- __init170() {this.baked = false}
6615
+ __init181() {this.offscreen = null}
6616
+ __init182() {this.baked = false}
6471
6617
  /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
6472
- __init171() {this.bakedWidth = 0}
6473
- __init172() {this.bakedHeight = 0}
6618
+ __init183() {this.bakedWidth = 0}
6619
+ __init184() {this.bakedHeight = 0}
6474
6620
  /** Gradient strokes can't be baked to a solid-color bitmap; they render per-frame. */
6475
6621
 
6476
6622
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
6477
- __init173() {this.polylines = null}
6623
+ __init185() {this.polylines = null}
6478
6624
  /**
6479
6625
  * When `true`, the renderer draws a rounded-rect outline of the entity's
6480
6626
  * local bounds after painting the curves. Useful for drag feedback and
6481
6627
  * debugging hit areas. Defaults to `false`.
6482
6628
  */
6483
- __init174() {this.showBounds = false}
6629
+ __init186() {this.showBounds = false}
6484
6630
  constructor(doc, opts = {}) {
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);;
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);;
6486
6632
  this.doc = doc;
6487
6633
  this.lineWidth = _nullishCoalesce(opts.lineWidth, () => ( 2));
6488
6634
  this.cache = _nullishCoalesce(opts.cache, () => ( true));
@@ -6863,19 +7009,19 @@ var _math = require('@vectojs/math'); _createStarExport(_math);
6863
7009
  // src/tree/DOMPortalEntity.ts
6864
7010
  var DOMPortalEntity = (_class11 = class extends _chunkTRT2TRCWjs.Entity {
6865
7011
 
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 = ""}
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 = ""}
6877
7023
  constructor(domElement, width, height, id) {
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);;
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);;
6879
7025
  this.domElement = domElement;
6880
7026
  this.width = _nullishCoalesce(width, () => ( 0));
6881
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;
@@ -2111,6 +2160,16 @@ var Scene = class _Scene {
2111
2160
  * see {@link sortNormalElementsVisually}.
2112
2161
  */
2113
2162
  a11yOrderContainers = /* @__PURE__ */ new Set();
2163
+ /**
2164
+ * Nearest `clipChildren` ancestor per ordered element — its *region* — reused
2165
+ * per pass. Written by `enforceA11yDomOrder`'s collect walk, which already has
2166
+ * the entity in hand, so a region costs one comparison per node rather than an
2167
+ * ancestor walk per element.
2168
+ *
2169
+ * Absent means the element sits under no clipping ancestor and belongs to the
2170
+ * implicit root region. See {@link sortNormalElementsVisually}.
2171
+ */
2172
+ a11yOrderRegions = /* @__PURE__ */ new Map();
2114
2173
  activePortalsThisFrame = /* @__PURE__ */ new Set();
2115
2174
  activePortalsPrevFrame = /* @__PURE__ */ new Set();
2116
2175
  portalEntities = /* @__PURE__ */ new Map();
@@ -4106,6 +4165,7 @@ var Scene = class _Scene {
4106
4165
  if (node === this.root) {
4107
4166
  this.contentSemanticBudgetLeft = this.contentSemanticBudget;
4108
4167
  this.contentSemanticDeferred = false;
4168
+ this._syncSerial++;
4109
4169
  this.contentSelectionPresentThisSync = null;
4110
4170
  }
4111
4171
  if (node.isDOMPortal) {
@@ -4545,6 +4605,26 @@ var Scene = class _Scene {
4545
4605
  * rejected by an ancestor clip box that itself overlaps the viewport would sit
4546
4606
  * transparently on top of whatever is really drawn there.
4547
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
+ }
4548
4628
  projectionBoxVisible(node, tf, margin, viewportOnly = false) {
4549
4629
  if (!(node.width > 0 && node.height > 0)) return true;
4550
4630
  const { a, b, c, d, e, f } = tf;
@@ -4642,6 +4722,23 @@ var Scene = class _Scene {
4642
4722
  syncContentProjection(node) {
4643
4723
  if (!this.contentProjectionEnabled || !this.a11yRoot) return;
4644
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
+ }
4645
4742
  const releaseProjectionEl = () => {
4646
4743
  if (el) {
4647
4744
  this.clearContentGridState(node.id, el);
@@ -4825,6 +4922,28 @@ var Scene = class _Scene {
4825
4922
  const next = prior ?? {};
4826
4923
  next.epoch = epoch;
4827
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
+ }
4828
4947
  next.a = a2;
4829
4948
  next.b = b2;
4830
4949
  next.c = c2;
@@ -5169,12 +5288,14 @@ var Scene = class _Scene {
5169
5288
  this.fullViewportElements.length = 0;
5170
5289
  this.normalElements.length = 0;
5171
5290
  this.activeIds.clear();
5172
- const collect = (node) => {
5291
+ this.a11yOrderRegions.clear();
5292
+ const collect = (node, region) => {
5173
5293
  if (node.isDOMPortal) return;
5174
5294
  const contentEl = this.contentElements.get(node.id);
5175
5295
  if (contentEl) {
5176
5296
  if (node.a11yFullViewport) this.fullViewportElements.push(contentEl);
5177
5297
  else this.normalElements.push(contentEl);
5298
+ if (region) this.a11yOrderRegions.set(contentEl, region);
5178
5299
  }
5179
5300
  if (this.shouldProjectA11y(node)) {
5180
5301
  const el = this.a11yElements.get(node.id);
@@ -5182,14 +5303,16 @@ var Scene = class _Scene {
5182
5303
  this.activeIds.add(node.id);
5183
5304
  if (node.a11yFullViewport) this.fullViewportElements.push(el);
5184
5305
  else this.normalElements.push(el);
5306
+ if (region) this.a11yOrderRegions.set(el, region);
5185
5307
  }
5186
5308
  }
5187
- for (const child of node.children) collect(child);
5309
+ const childRegion = node.clipChildren && node.width > 0 && node.height > 0 ? node : region;
5310
+ for (const child of node.children) collect(child, childRegion);
5188
5311
  if (node === this.root) {
5189
- for (const overlay of this.overlayRoot.children) collect(overlay);
5312
+ for (const overlay of this.overlayRoot.children) collect(overlay, null);
5190
5313
  }
5191
5314
  };
5192
- collect(this.root);
5315
+ collect(this.root, null);
5193
5316
  let elementsPruned = false;
5194
5317
  for (const [id, el] of this.a11yElements.entries()) {
5195
5318
  if (!this.activeIds.has(id)) {
@@ -5251,6 +5374,18 @@ var Scene = class _Scene {
5251
5374
  * parents, which no `insertBefore` ever acts on. Normalizing everything back
5252
5375
  * to world coordinates here would cost a transform per element per frame to
5253
5376
  * change nothing observable.
5377
+ *
5378
+ * Banding runs **per region** — per nearest `clipChildren` ancestor, recorded
5379
+ * by {@link enforceA11yDomOrder}'s collect walk — rather than once over the
5380
+ * whole scene. Purely visual banding is right for a screen reader but wrong
5381
+ * for selection: a DOM `Selection` covers everything between anchor and focus
5382
+ * in DOM order, so under one global banding a vertical drag through a
5383
+ * transcript also swallowed a sidebar whose headings happened to fall in the
5384
+ * same rows. Regions are laid out side by side, so ordering region-major keeps
5385
+ * each one a contiguous DOM run and a drag stays inside it, while reading
5386
+ * order *within* a region is unchanged. Regions are emitted in the order their
5387
+ * clipper is first reached by the depth-first walk, so a screen reader still
5388
+ * meets them in the author's declared order.
5254
5389
  */
5255
5390
  sortNormalElementsVisually() {
5256
5391
  const els = this.normalElements;
@@ -5278,30 +5413,40 @@ var Scene = class _Scene {
5278
5413
  }
5279
5414
  return { top, left };
5280
5415
  };
5281
- const order = els.map((el, i) => {
5416
+ const decorated = els.map((el, i) => {
5282
5417
  const { top, left } = absolute(el);
5283
5418
  return { el, i, top, left, container: containers.has(el) };
5284
5419
  });
5285
- order.sort((p, q) => p.top - q.top || p.i - q.i);
5420
+ const regions = this.a11yOrderRegions;
5421
+ const buckets = /* @__PURE__ */ new Map();
5422
+ for (const d of decorated) {
5423
+ const key = regions.get(d.el) ?? null;
5424
+ const bucket = buckets.get(key);
5425
+ if (bucket) bucket.push(d);
5426
+ else buckets.set(key, [d]);
5427
+ }
5286
5428
  const bandBottom = (r) => r.top + (r.container ? 4 : heightOf(r.el));
5287
5429
  const sorted = [];
5288
- let rowStart = 0;
5289
- let rowBottom = order.length ? bandBottom(order[0]) : 0;
5290
- const flushRow = (end) => {
5291
- const row = order.slice(rowStart, end);
5292
- row.sort((p, q) => (rtl ? q.left - p.left : p.left - q.left) || p.i - q.i);
5293
- for (const r of row) sorted.push(r.el);
5294
- };
5295
- for (let k = 1; k < order.length; k++) {
5296
- if (order[k].top < rowBottom) {
5297
- rowBottom = Math.max(rowBottom, bandBottom(order[k]));
5298
- } else {
5299
- flushRow(k);
5300
- rowStart = k;
5301
- rowBottom = bandBottom(order[k]);
5430
+ for (const order of buckets.values()) {
5431
+ order.sort((p, q) => p.top - q.top || p.i - q.i);
5432
+ let rowStart = 0;
5433
+ let rowBottom = order.length ? bandBottom(order[0]) : 0;
5434
+ const flushRow = (end) => {
5435
+ const row = order.slice(rowStart, end);
5436
+ row.sort((p, q) => (rtl ? q.left - p.left : p.left - q.left) || p.i - q.i);
5437
+ for (const r of row) sorted.push(r.el);
5438
+ };
5439
+ for (let k = 1; k < order.length; k++) {
5440
+ if (order[k].top < rowBottom) {
5441
+ rowBottom = Math.max(rowBottom, bandBottom(order[k]));
5442
+ } else {
5443
+ flushRow(k);
5444
+ rowStart = k;
5445
+ rowBottom = bandBottom(order[k]);
5446
+ }
5302
5447
  }
5448
+ flushRow(order.length);
5303
5449
  }
5304
- flushRow(order.length);
5305
5450
  for (let i = 0; i < sorted.length; i++) els[i] = sorted[i];
5306
5451
  }
5307
5452
  /** Keep DOM/WebGL overlay layers aligned with the canvas's CSS box. */
@@ -5930,6 +6075,7 @@ var Scene = class _Scene {
5930
6075
  this.width = width;
5931
6076
  this.height = height;
5932
6077
  this.contentFontEpoch++;
6078
+ this.contentViewportEpoch++;
5933
6079
  if (typeof this.renderer.resize === "function") {
5934
6080
  if ("maxDPR" in this.renderer) this.renderer.maxDPR = this.maxDPR;
5935
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;
@@ -719,6 +768,16 @@ export declare class Scene {
719
768
  * see {@link sortNormalElementsVisually}.
720
769
  */
721
770
  private a11yOrderContainers;
771
+ /**
772
+ * Nearest `clipChildren` ancestor per ordered element — its *region* — reused
773
+ * per pass. Written by `enforceA11yDomOrder`'s collect walk, which already has
774
+ * the entity in hand, so a region costs one comparison per node rather than an
775
+ * ancestor walk per element.
776
+ *
777
+ * Absent means the element sits under no clipping ancestor and belongs to the
778
+ * implicit root region. See {@link sortNormalElementsVisually}.
779
+ */
780
+ private a11yOrderRegions;
722
781
  private activePortalsThisFrame;
723
782
  private activePortalsPrevFrame;
724
783
  private portalEntities;
@@ -1551,6 +1610,15 @@ export declare class Scene {
1551
1610
  * rejected by an ancestor clip box that itself overlaps the viewport would sit
1552
1611
  * transparently on top of whatever is really drawn there.
1553
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;
1554
1622
  private projectionBoxVisible;
1555
1623
  /**
1556
1624
  * The band of an entity's own y coordinates that is worth projecting, or
@@ -1609,6 +1677,18 @@ export declare class Scene {
1609
1677
  * parents, which no `insertBefore` ever acts on. Normalizing everything back
1610
1678
  * to world coordinates here would cost a transform per element per frame to
1611
1679
  * change nothing observable.
1680
+ *
1681
+ * Banding runs **per region** — per nearest `clipChildren` ancestor, recorded
1682
+ * by {@link enforceA11yDomOrder}'s collect walk — rather than once over the
1683
+ * whole scene. Purely visual banding is right for a screen reader but wrong
1684
+ * for selection: a DOM `Selection` covers everything between anchor and focus
1685
+ * in DOM order, so under one global banding a vertical drag through a
1686
+ * transcript also swallowed a sidebar whose headings happened to fall in the
1687
+ * same rows. Regions are laid out side by side, so ordering region-major keeps
1688
+ * each one a contiguous DOM run and a drag stays inside it, while reading
1689
+ * order *within* a region is unchanged. Regions are emitted in the order their
1690
+ * clipper is first reached by the depth-first walk, so a screen reader still
1691
+ * meets them in the author's declared order.
1612
1692
  */
1613
1693
  private sortNormalElementsVisually;
1614
1694
  /** Keep DOM/WebGL overlay layers aligned with the canvas's CSS box. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "1.32.0",
3
+ "version": "1.32.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },