@vielzeug/scroll 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/dom-virtual-list.cjs +1 -1
- package/dist/dom-virtual-list.cjs.map +1 -1
- package/dist/dom-virtual-list.d.ts +1 -1
- package/dist/dom-virtual-list.d.ts.map +1 -1
- package/dist/dom-virtual-list.js +1 -1
- package/dist/dom-virtual-list.js.map +1 -1
- package/dist/grid-virtualizer.cjs +1 -1
- package/dist/grid-virtualizer.cjs.map +1 -1
- package/dist/grid-virtualizer.d.ts +1 -1
- package/dist/grid-virtualizer.d.ts.map +1 -1
- package/dist/grid-virtualizer.js +1 -1
- package/dist/grid-virtualizer.js.map +1 -1
- package/dist/grouped-virtualizer.cjs +1 -1
- package/dist/grouped-virtualizer.cjs.map +1 -1
- package/dist/grouped-virtualizer.d.ts +1 -1
- package/dist/grouped-virtualizer.d.ts.map +1 -1
- package/dist/grouped-virtualizer.js +1 -1
- package/dist/grouped-virtualizer.js.map +1 -1
- package/dist/scroll.cjs +1 -1
- package/dist/scroll.cjs.map +1 -1
- package/dist/scroll.iife.js +1 -1
- package/dist/scroll.iife.js.map +1 -1
- package/dist/scroll.js +1 -1
- package/dist/scroll.js.map +1 -1
- package/dist/virtualizer.cjs +1 -1
- package/dist/virtualizer.cjs.map +1 -1
- package/dist/virtualizer.d.ts +12 -3
- package/dist/virtualizer.d.ts.map +1 -1
- package/dist/virtualizer.js +1 -1
- package/dist/virtualizer.js.map +1 -1
- package/package.json +2 -2
package/dist/scroll.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll.js","names":[],"sources":["../src/_dev.ts","../src/_utils.ts","../src/errors.ts","../src/_validation.ts","../src/_adapter.ts","../src/_axis1d.ts","../src/_sticky.ts","../src/virtualizer.ts","../src/dom-virtual-list.ts","../src/_alignment.ts","../src/grid-virtualizer.ts","../src/grouped-virtualizer.ts"],"sourcesContent":["const isDev = !(globalThis as { __SCROLL_PROD__?: boolean }).__SCROLL_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/scroll] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import { warn } from './_dev';\n\nexport type ScrollTarget = HTMLElement | Window;\n\nexport type VirtualKey = number | string;\n\n/** Shared measurement cache. Pass the same instance to multiple virtualizers\n * for scroll restoration or SSR pre-measurement. */\nexport type MeasurementCache = Map<VirtualKey, number>;\n\n/** Create a new, empty measurement cache. */\nexport function createMeasurementCache(): MeasurementCache {\n return new Map();\n}\n\nexport type Overscan = number | { end?: number; start?: number };\n\nexport const DEFAULT_ESTIMATE_SIZE = 36;\nexport const DEFAULT_OVERSCAN = 3;\n\n// ─── Numeric helpers ───────────────────────────────────────────────────────────\n\nexport function toNonNegativeInt(value: number, fallback = 0): number {\n if (!Number.isFinite(value)) return fallback;\n\n return Math.max(0, Math.floor(value));\n}\n\nexport function toPositiveNumber(value: number, fallback: number): number {\n if (!Number.isFinite(value) || value <= 0 || value > 1e7) return fallback;\n\n return value;\n}\n\nexport function isWindowTarget(target: ScrollTarget): target is Window {\n return (\n (typeof Window !== 'undefined' && target instanceof Window) ||\n (typeof (target as Window).innerHeight === 'number' && typeof (target as Window).document === 'object')\n );\n}\n\nexport function normalizeOverscan(overscan: Overscan | undefined, defaultVal: number): { end: number; start: number } {\n if (typeof overscan === 'number') {\n const n = toNonNegativeInt(overscan, defaultVal);\n\n return { end: n, start: n };\n }\n\n return {\n end: toNonNegativeInt(overscan?.end ?? defaultVal),\n start: toNonNegativeInt(overscan?.start ?? defaultVal),\n };\n}\n\n/**\n * Create a `ResizeObserver` that reports size changes on `el` via `onSize`, tied to\n * `signal` so it is disconnected automatically when the owning factory disposes —\n * even if the caller never calls the returned disconnect function.\n */\nexport function observeResize(\n signal: AbortSignal,\n el: HTMLElement,\n onSize: (entry: ResizeObserverEntry) => void,\n): () => void {\n const ro = new ResizeObserver((entries) => {\n for (const entry of entries) onSize(entry);\n });\n\n ro.observe(el);\n signal.addEventListener('abort', () => ro.disconnect(), { once: true });\n\n return () => ro.disconnect();\n}\n\nexport function resolveEstimateFn(\n estimate: number | ((index: number) => number) | undefined,\n defaultSize: number,\n): (index: number) => number {\n if (typeof estimate === 'function') {\n return (index: number) => {\n try {\n return toPositiveNumber(estimate(index), defaultSize);\n } catch (err) {\n warn(`estimateSize threw for index ${index}: ${err}`);\n\n return defaultSize;\n }\n };\n }\n\n const size = typeof estimate === 'number' ? toPositiveNumber(estimate, defaultSize) : defaultSize;\n\n return () => size;\n}\n","/** Base class for all scroll errors. */\nexport class ScrollError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a virtualizer receives an invalid static configuration. */\nexport class ScrollConfigurationError extends ScrollError {}\n\n/** Thrown when a virtual-list index is out of bounds. */\nexport class ScrollRangeError extends ScrollError {}\n","import { ScrollConfigurationError } from './errors';\n\nexport function requireNonNegativeInteger(value: number, name: string): number {\n if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {\n throw new ScrollConfigurationError(`${name} must be a finite non-negative integer.`);\n }\n\n return value;\n}\n\nexport function requireNonNegativeNumber(value: number, name: string): number {\n if (!Number.isFinite(value) || value < 0) {\n throw new ScrollConfigurationError(`${name} must be a finite non-negative number.`);\n }\n\n return value;\n}\n\nexport function requirePositiveNumber(value: number, name: string): number {\n if (!Number.isFinite(value) || value <= 0 || value > 1e7) {\n throw new ScrollConfigurationError(`${name} must be a finite positive number no greater than 10000000.`);\n }\n\n return value;\n}\n\nexport function validateOverscan(overscan: number | { end?: number; start?: number }, name = 'overscan'): void {\n if (typeof overscan === 'number') {\n requireNonNegativeInteger(overscan, name);\n\n return;\n }\n\n if (overscan === null || typeof overscan !== 'object' || Array.isArray(overscan)) {\n throw new ScrollConfigurationError(`${name} must be a finite non-negative integer or an overscan object.`);\n }\n\n if (overscan.start !== undefined) requireNonNegativeInteger(overscan.start, `${name}.start`);\n\n if (overscan.end !== undefined) requireNonNegativeInteger(overscan.end, `${name}.end`);\n}\n","import { isWindowTarget, type ScrollTarget } from './_utils';\n\n/** Single-axis read/write interface for either horizontal or vertical scroll. */\nexport type Axis = {\n readOffset: () => number;\n readViewportSize: () => number;\n writeOffset: (offset: number, behavior: ScrollBehavior) => void;\n};\n\n/** Unified two-axis scroll adapter returned by `createScrollAdapter`. */\nexport type ScrollAdapter = {\n /** Detach all listeners (scroll + resize). Call in _dispose(). */\n detach: () => void;\n /** Write both axes at once (avoids double scrollTo call). */\n scrollTo: (left: number, top: number, behavior: ScrollBehavior) => void;\n x: Axis;\n y: Axis;\n};\n\n/**\n * Attach scroll and resize listeners on a `ScrollTarget` and return a\n * two-axis `ScrollAdapter`. Handles both `Window` and `HTMLElement` targets.\n */\nexport function createScrollAdapter(target: ScrollTarget, onScroll: () => void, onResize: () => void): ScrollAdapter {\n if (isWindowTarget(target)) {\n target.addEventListener('scroll', onScroll, { passive: true });\n target.addEventListener('resize', onResize, { passive: true });\n\n return {\n detach() {\n target.removeEventListener('scroll', onScroll);\n target.removeEventListener('resize', onResize);\n },\n scrollTo: (left, top, behavior) => target.scrollTo({ behavior, left, top }),\n x: {\n readOffset: () => target.scrollX,\n readViewportSize: () => target.innerWidth,\n writeOffset: (offset, behavior) => target.scrollTo({ behavior, left: offset }),\n },\n y: {\n readOffset: () => target.scrollY,\n readViewportSize: () => target.innerHeight,\n writeOffset: (offset, behavior) => target.scrollTo({ behavior, top: offset }),\n },\n };\n }\n\n // ⚠️ ResizeObserver is assumed to exist (supported in all modern browsers).\n // In SSR or older environments, callers must polyfill before constructing a virtualizer.\n const resizeObserver = new ResizeObserver(onResize);\n\n target.addEventListener('scroll', onScroll, { passive: true });\n resizeObserver.observe(target);\n\n return {\n detach() {\n target.removeEventListener('scroll', onScroll);\n resizeObserver.disconnect();\n },\n scrollTo: (left, top, behavior) => target.scrollTo({ behavior, left, top }),\n x: {\n readOffset: () => target.scrollLeft,\n readViewportSize: () => target.clientWidth,\n writeOffset: (offset, behavior) => target.scrollTo({ behavior, left: offset }),\n },\n y: {\n readOffset: () => target.scrollTop,\n readViewportSize: () => target.clientHeight,\n writeOffset: (offset, behavior) => target.scrollTo({ behavior, top: offset }),\n },\n };\n}\n","/**\n * Shared 1D axis primitive: prefix-sum offset table, O(log n) binary search,\n * incremental rebuild, measurement batching via microtask, and dedup guards.\n *\n * Used directly by grid-virtualizer (numeric row/col indices) and composed\n * into virtualizer (which layers VirtualKey translation on top).\n */\n\n/** A single rendered virtual item — position and size on one axis. */\nexport interface VirtualItem {\n end: number;\n index: number;\n size: number;\n start: number;\n}\n\nexport interface Axis1DRange {\n renderEnd: number;\n renderStart: number;\n}\n\n/**\n * Create a self-contained 1D offset-table axis.\n *\n * @param initialCount - Number of items.\n * @param sizeAtFn - Returns the resolved size for a given index (measured or estimated).\n * The virtualizer passes a closure that reads the live measurement cache,\n * so changes to the cache are reflected automatically without re-setting.\n * @param initialGap - Gap between consecutive items (non-negative integer, pixels).\n */\nexport function createAxis1D(initialCount: number, sizeAtFn: (index: number) => number, initialGap: number) {\n let count = initialCount;\n let gap = initialGap;\n const sizeAt = sizeAtFn;\n\n let offsets: number[] = [];\n let totalSize = 0;\n\n // ─── Dedup guards ───────────────────────────────────────────────────────────\n // Track the last-emitted render range and total size so computeVisible() can\n // skip re-rendering when nothing changed.\n\n let prevStart = -1;\n let prevEnd = -1;\n let prevTotalSize = -1;\n\n // ─── Pending measurement flush ─────────────────────────────────────────────\n\n let pendingBuild = false;\n let minChangedIndex = Infinity;\n\n // ─── Offset helpers ─────────────────────────────────────────────────────────\n\n function startAt(i: number): number {\n return offsets[i] ?? 0;\n }\n\n function endAt(i: number): number {\n return startAt(i) + sizeAt(i);\n }\n\n // ─── Full rebuild ───────────────────────────────────────────────────────────\n\n /**\n * Full O(n) prefix-sum rebuild.\n * Pass `forceEmit = true` to reset dedup guards, ensuring computeVisible()\n * emits on the next call even if the rendered range is unchanged.\n */\n function rebuild(forceEmit = false): void {\n if (forceEmit) {\n prevStart = -1;\n prevEnd = -1;\n prevTotalSize = -1;\n }\n\n const next: number[] = [0];\n let pos = 0;\n\n for (let i = 0; i < count; i++) {\n pos += sizeAt(i) + (i < count - 1 ? gap : 0);\n next.push(pos);\n }\n\n offsets = next;\n totalSize = pos;\n }\n\n // ─── Incremental rebuild ────────────────────────────────────────────────────\n\n /**\n * O(count - fromIndex) incremental rebuild — used after measurement.\n * Always resets dedup guards because measurements change layout.\n */\n function rebuildFrom(fromIndex: number): void {\n if (!Number.isFinite(fromIndex) || fromIndex >= count) return;\n\n prevStart = -1;\n prevEnd = -1;\n prevTotalSize = -1;\n\n let pos = offsets[fromIndex] ?? 0;\n\n for (let i = fromIndex; i < count; i++) {\n offsets[i] = pos;\n pos += sizeAt(i) + (i < count - 1 ? gap : 0);\n }\n\n offsets[count] = pos;\n totalSize = pos;\n }\n\n // ─── Binary search ──────────────────────────────────────────────────────────\n\n function findFirst(viewportStart: number): number {\n let lo = 0;\n let hi = count - 1;\n\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n\n if (endAt(mid) <= viewportStart) lo = mid + 1;\n else hi = mid;\n }\n\n return lo;\n }\n\n function findLast(viewportEnd: number, from: number): number {\n let lo = from;\n let hi = count - 1;\n\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n\n if (startAt(mid) < viewportEnd) lo = mid;\n else hi = mid - 1;\n }\n\n return lo;\n }\n\n /**\n * Compute the render range for a given viewport window and overscan values.\n * Returns indices clamped to [0, count - 1].\n */\n function computeRange(\n viewportStart: number,\n viewportEnd: number,\n overscanStart: number,\n overscanEnd: number,\n ): Axis1DRange {\n const first = findFirst(viewportStart);\n const last = findLast(viewportEnd, first);\n\n return {\n renderEnd: Math.min(count - 1, last + overscanEnd),\n renderStart: Math.max(0, first - overscanStart),\n };\n }\n\n // ─── Dedup helpers ──────────────────────────────────────────────────────────\n\n /** Returns `true` when start/end/totalSize are all identical to the last commit. */\n function isDedupSame(start: number, end: number): boolean {\n return start === prevStart && end === prevEnd && totalSize === prevTotalSize;\n }\n\n function commitDedup(start: number, end: number): void {\n prevStart = start;\n prevEnd = end;\n prevTotalSize = totalSize;\n }\n\n function resetDedup(): void {\n prevStart = -1;\n prevEnd = -1;\n prevTotalSize = -1;\n }\n\n // ─── Microtask scheduling ───────────────────────────────────────────────────\n\n /**\n * Schedule an incremental rebuild on the next microtask, then call `onFlush`.\n * Multiple calls before the microtask fires are coalesced into one rebuild.\n * Returns without scheduling if a build is already pending.\n */\n function scheduleBuild(onFlush: () => void): void {\n if (pendingBuild) return;\n\n pendingBuild = true;\n queueMicrotask(() => {\n pendingBuild = false;\n\n const from = minChangedIndex;\n\n minChangedIndex = Infinity;\n rebuildFrom(from);\n onFlush();\n });\n }\n\n /** Mark an item index as having a changed measurement. Drives incremental rebuilds. */\n function markChanged(index: number): void {\n if (index < minChangedIndex) minChangedIndex = index;\n }\n\n // ─── Item descriptor ────────────────────────────────────────────────────────\n\n /** Build a VirtualItem for a given index using current offset table. */\n function itemAt(index: number): VirtualItem {\n const start = startAt(index);\n const size = sizeAt(index);\n\n return { end: start + size, index, size, start };\n }\n\n // ─── Returned object ────────────────────────────────────────────────────────\n\n return {\n commitDedup,\n computeRange,\n /**\n * Atomically read and reset minChangedIndex to Infinity.\n * Used by grid-virtualizer's coordinated flush so it can consume the\n * changed-index without going through axis1d's own scheduleBuild.\n */\n consumeMinChangedIndex(): number {\n const idx = minChangedIndex;\n\n minChangedIndex = Infinity;\n\n return idx;\n },\n get count(): number {\n return count;\n },\n endAt,\n findFirst,\n findLast,\n /** Current gap between items (pixels). Used by callers to dedup gap changes. */\n get gap(): number {\n return gap;\n },\n isDedupSame,\n itemAt,\n markChanged,\n get minChangedIndex(): number {\n return minChangedIndex;\n },\n get pendingBuild(): boolean {\n return pendingBuild;\n },\n get prevEnd(): number {\n return prevEnd;\n },\n get prevStart(): number {\n return prevStart;\n },\n get prevTotalSize(): number {\n return prevTotalSize;\n },\n rebuild,\n rebuildFrom,\n resetDedup,\n scheduleBuild,\n setCount(n: number): void {\n count = n;\n },\n setGap(n: number): void {\n gap = n;\n },\n sizeAt(i: number): number {\n return sizeAt(i);\n },\n startAt,\n get totalSize(): number {\n return totalSize;\n },\n };\n}\n\nexport type Axis1D = ReturnType<typeof createAxis1D>;\n","/**\n * Shared sticky item computation logic.\n * Used by virtualizer and grid-virtualizer for finding and pinning sticky items.\n */\n\nimport type { VirtualItem } from './_axis1d';\n\nexport interface StickyComputeContext {\n count: number;\n scrollOffset: number;\n sizeAt: (index: number) => number;\n startAt: (index: number) => number;\n stickyFn: ((index: number) => boolean) | null;\n}\n\n/**\n * Compute the sticky item(s) for a 1D scroll axis.\n * Returns the single sticky item that should be pinned at the viewport start,\n * accounting for the next sticky item's position.\n *\n * @param ctx Context with functions to query item positions and sizes\n * @returns Array containing at most one sticky item (pinned to viewport)\n */\nexport function computeStickyItems(ctx: StickyComputeContext): VirtualItem[] {\n if (!ctx.stickyFn || ctx.count === 0 || ctx.scrollOffset <= 0) return [];\n\n // Binary search to find the last item that starts before the viewport\n let lastAbove = -1;\n let lo = 0;\n let hi = ctx.count - 1;\n\n while (lo <= hi) {\n const mid = (lo + hi) >> 1;\n\n if (ctx.startAt(mid) < ctx.scrollOffset) {\n lastAbove = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n\n if (lastAbove < 0) return [];\n\n // Scan backwards from lastAbove to find the most recent sticky item\n let activeIdx = -1;\n\n for (let i = lastAbove; i >= 0; i--) {\n if (ctx.stickyFn(i)) {\n activeIdx = i;\n break;\n }\n }\n\n if (activeIdx === -1) return [];\n\n // Find the next sticky item to determine how far we can pin\n const activeSize = ctx.sizeAt(activeIdx);\n let nextStickyStart = Infinity;\n\n for (let i = activeIdx + 1; i < ctx.count; i++) {\n if (ctx.stickyFn(i)) {\n nextStickyStart = ctx.startAt(i);\n break;\n }\n }\n\n // Pin the active sticky item, but don't let it push past the next sticky item\n const pinnedStart = Math.min(ctx.scrollOffset, nextStickyStart - activeSize);\n\n return [{ end: pinnedStart + activeSize, index: activeIdx, size: activeSize, start: pinnedStart }];\n}\n","import type { Signal } from '@vielzeug/ripple';\n\nimport { createScrollAdapter } from './_adapter';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport { computeStickyItems } from './_sticky';\nimport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n normalizeOverscan,\n type Overscan,\n observeResize,\n resolveEstimateFn,\n type ScrollTarget,\n toNonNegativeInt,\n toPositiveNumber,\n type VirtualKey,\n} from './_utils';\nimport {\n requireNonNegativeInteger,\n requireNonNegativeNumber,\n requirePositiveNumber,\n validateOverscan,\n} from './_validation';\n\nexport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n type ScrollTarget,\n type VirtualItem,\n type VirtualKey,\n};\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualizerState {\n readonly items: VirtualItem[];\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n}\n\nexport interface VirtualizerOptions {\n /** Auto-attach ResizeObserver to visible items. */\n autoMeasure?: boolean;\n count: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: (index: number) => VirtualKey;\n horizontal?: boolean;\n initialOffset?: number;\n /** Enable keyboard scroll (arrow keys, Page Up/Down, Home, End). */\n keyboardScroll?: boolean;\n /** External measurement cache for scroll restoration or SSR pre-measurement. */\n measurementCache?: MeasurementCache;\n /** Called after every render cycle with the new state. Replace through `update()`. */\n onChange?: (state: VirtualizerState) => void;\n /** Called when scrolling has settled. Replace through `update()`. */\n onScrollEnd?: (offset: number) => void;\n /** Called when the scrolling state changes. Replace through `update()`. */\n onScrollingChange?: (isScrolling: boolean) => void;\n overscan?: Overscan;\n /**\n * Debounce delay (ms) used to detect scroll end when the native `scrollend`\n * event is unavailable. Defaults to 150.\n */\n scrollEndDelay?: number;\n /** Optional signal factory for reactive state. */\n signal?: (init: VirtualizerState) => Signal<VirtualizerState>;\n sticky?: (index: number) => boolean;\n}\n\n/**\n * Options accepted by `update()`. Explicit interface instead of Omit<...>\n * for better IDE hover and autocomplete (R7).\n *\n * Intentionally excludes `horizontal` (axis cannot change at runtime) and\n * `initialOffset` (one-time bootstrap value).\n */\nexport interface VirtualizerUpdateOptions {\n autoMeasure?: boolean;\n count?: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: ((index: number) => VirtualKey) | undefined;\n keyboardScroll?: boolean;\n /** Replace the active measurement cache. Existing entries in the new cache are used immediately on the next rebuild. */\n measurementCache?: MeasurementCache;\n onChange?: ((state: VirtualizerState) => void) | undefined;\n onScrollEnd?: ((offset: number) => void) | undefined;\n onScrollingChange?: ((isScrolling: boolean) => void) | undefined;\n overscan?: Overscan;\n scrollEndDelay?: number;\n sticky?: ((index: number) => boolean) | undefined;\n}\n\nexport interface ScrollToIndexOptions {\n align?: 'auto' | 'center' | 'end' | 'start';\n behavior?: ScrollBehavior;\n /** Called when the scroll animation completes (instant scrolls: next microtask). */\n onComplete?: () => void;\n}\n\nexport interface Virtualizer {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n dispose: () => void;\n readonly disposed: boolean;\n invalidate: () => void;\n /**\n * `true` when scrolled to (or within `threshold` px of) the end — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Check this **before** appending\n * items to decide whether to auto-follow them (e.g. a chat \"stick to bottom\" pattern) —\n * `createDomVirtualList`'s `stickToBottom` option does exactly this. Default `threshold`: `0`.\n */\n isAtEnd: (threshold?: number) => boolean;\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n /** Currently rendered items. Always populated. */\n readonly items: VirtualItem[];\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` and auto-measure `index` on resize. Returns a disconnect fn. */\n measureEl: (index: number, el: HTMLElement) => () => void;\n /** Prepend `additionalCount` items at the top while keeping the viewport visually stable. */\n prepend: (additionalCount: number) => void;\n /** Rebuild the full offset table and re-emit (use when item sizes may have changed). */\n refresh: () => void;\n readonly scrollOffset: number;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n update: (next: VirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createVirtualizer(target: ScrollTarget, options: VirtualizerOptions): Virtualizer {\n if (typeof options.estimateSize === 'number') requirePositiveNumber(options.estimateSize, 'estimateSize');\n\n if (options.gap !== undefined) requireNonNegativeInteger(options.gap, 'gap');\n\n if (options.initialOffset !== undefined) requireNonNegativeNumber(options.initialOffset, 'initialOffset');\n\n if (options.overscan !== undefined) validateOverscan(options.overscan);\n\n if (options.scrollEndDelay !== undefined) requireNonNegativeNumber(options.scrollEndDelay, 'scrollEndDelay');\n\n const horizontal = !!options.horizontal;\n const defaultItemKey = (index: number): VirtualKey => index;\n const enableKeyboardScroll = options.keyboardScroll ?? false;\n\n let count = requireNonNegativeInteger(options.count, 'count');\n let estimateFn = resolveEstimateFn(options.estimateSize, DEFAULT_ESTIMATE_SIZE);\n let gap = toNonNegativeInt(options.gap ?? 0);\n let getItemKey = options.getItemKey ?? defaultItemKey;\n let overscan = normalizeOverscan(options.overscan, DEFAULT_OVERSCAN);\n let stickyFn: ((index: number) => boolean) | null = options.sticky ?? null;\n let keyboardScrollEnabled = enableKeyboardScroll;\n let autoMeasureEnabled = options.autoMeasure ?? false;\n\n let onChange = options.onChange;\n let onScrollEnd = options.onScrollEnd;\n let onScrollingChange = options.onScrollingChange;\n let scrollEndDelay = options.scrollEndDelay ?? 150;\n\n // Optional signal for reactive state\n let stateSignal: Signal<VirtualizerState> | null = null;\n if (options.signal) {\n const initialState: VirtualizerState = { items: [], stickyItems: [], totalSize: 0 };\n stateSignal = options.signal(initialState);\n }\n\n // Helper to emit state to both callback and signal\n function emitState(state: VirtualizerState): void {\n if (stateSignal) stateSignal.value = state;\n onChange?.(state);\n }\n let scrollEndTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Detect native scrollend support (Chrome 114+, Firefox 109+)\n const hasNativeScrollEnd = 'onscrollend' in (target as EventTarget);\n\n function notifyScrollEnd(): void {\n if (!isScrolling) return;\n\n isScrolling = false;\n onScrollingChange?.(false);\n onScrollEnd?.(scrollOffset);\n }\n\n function handleScrollStart(): void {\n if (!isScrolling) {\n isScrolling = true;\n onScrollingChange?.(true);\n }\n\n if (!hasNativeScrollEnd) {\n if (scrollEndTimer !== null) clearTimeout(scrollEndTimer);\n\n scrollEndTimer = setTimeout(notifyScrollEnd, scrollEndDelay);\n }\n }\n\n // External or internal measurement cache.\n let measuredByKey: MeasurementCache = options.measurementCache ?? new Map();\n\n let items: VirtualItem[] = [];\n let stickyItems: VirtualItem[] = [];\n\n // ─── Axis setup ──────────────────────────────────────────────────────────────\n // The axis1d primitive owns offset management. The sizeAt closure captures\n // the live `measuredByKey`, `getItemKey`, and `estimateFn` by reference so\n // updates to those let-variables are picked up automatically.\n\n const ax = createAxis1D(count, (i) => measuredByKey.get(getItemKey(i)) ?? estimateFn(i), gap);\n\n // F6: Scroll anchor — prevents viewport jump when items above fold are measured.\n let anchorIndex = -1;\n let anchorDistanceFromTop = 0;\n\n let scrollOffset = 0;\n let viewportSize = 0;\n let prevScrollOffset = -1; // for sticky dedup\n let destroyed = false;\n let isScrolling = false;\n\n // ─── Scroll clamping ─────────────────────────────────────────────────────────\n\n function clampScrollOffset(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, ax.totalSize - viewportSize);\n\n return Math.min(maxOffset, Math.max(0, safe));\n }\n\n // ─── Compute and emit ─────────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (destroyed) return;\n\n if (count === 0 || viewportSize <= 0) {\n if (ax.prevStart !== -1 || ax.prevTotalSize !== ax.totalSize) {\n // Commit with sentinel renderStart=-1 so prevTotalSize is captured as the\n // current totalSize. Using resetDedup() would set prevTotalSize=-1, causing\n // the guard to fire on every subsequent scroll/resize (Bug 2).\n ax.commitDedup(-1, -1);\n\n items = [];\n stickyItems = [];\n\n emitState({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n return;\n }\n\n const { renderEnd, renderStart } = ax.computeRange(\n scrollOffset,\n scrollOffset + viewportSize,\n overscan.start,\n overscan.end,\n );\n\n // Sticky recomputes on every scroll pixel — bypass dedup in that case.\n const stickyScrolled = stickyFn !== null && scrollOffset !== prevScrollOffset;\n const rangeChanged = !ax.isDedupSame(renderStart, renderEnd);\n\n if (!rangeChanged && !stickyScrolled) return;\n\n ax.commitDedup(renderStart, renderEnd);\n prevScrollOffset = scrollOffset;\n\n const nextItems: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n nextItems.push(ax.itemAt(i));\n }\n\n items = nextItems;\n stickyItems = stickyFn\n ? computeStickyItems({ count, scrollOffset, sizeAt: ax.sizeAt, startAt: ax.startAt, stickyFn })\n : [];\n\n emitState({ items, stickyItems, totalSize: ax.totalSize });\n\n // Auto-measure visible items if enabled\n if (autoMeasureEnabled && items.length > 0) {\n // Queue microtask to allow DOM to render first\n queueMicrotask(() => {\n if (destroyed || target instanceof Window) return;\n\n for (const item of items) {\n const key = getItemKey(item.index);\n const el = (target as HTMLElement)?.querySelector?.(`[data-vz-key=\"${key}\"]`);\n\n if (el instanceof HTMLElement) {\n measureEl(item.index, el);\n }\n }\n });\n }\n }\n\n // ─── Measurement (R6: measure delegates to measureBatch) ─────────────────────\n\n function recordMeasurement(index: number, size: number): boolean {\n if (!Number.isFinite(index)) return false;\n\n const i = Math.floor(index);\n\n if (i < 0 || i >= count) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n const key = getItemKey(i);\n\n if (measuredByKey.get(key) === s) return false;\n\n measuredByKey.set(key, s);\n ax.markChanged(i);\n\n return true;\n }\n\n // F6: Record a scroll anchor before a batch of measurements arrives.\n function recordScrollAnchor(): void {\n if (scrollOffset <= 0 || count === 0) {\n anchorIndex = -1;\n\n return;\n }\n\n anchorIndex = ax.findFirst(scrollOffset);\n anchorDistanceFromTop = scrollOffset - ax.startAt(anchorIndex);\n }\n\n // F6: After rebuild, restore the anchor to prevent viewport jump.\n function restoreScrollAnchor(): void {\n if (anchorIndex < 0) return;\n\n const expectedOffset = ax.startAt(anchorIndex) + anchorDistanceFromTop;\n const delta = expectedOffset - scrollOffset;\n\n anchorIndex = -1;\n\n if (Math.abs(delta) >= 1) {\n const newOffset = clampScrollOffset(scrollOffset + delta);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n }\n }\n\n function measureBatch(entries: Array<{ index: number; size: number }>): void {\n if (destroyed) return;\n\n let changed = false;\n\n for (const { index, size } of entries) {\n if (recordMeasurement(index, size)) changed = true;\n }\n\n if (changed) {\n // Record anchor before the first pending build is scheduled (F6).\n if (!ax.pendingBuild) recordScrollAnchor();\n\n ax.scheduleBuild(() => {\n restoreScrollAnchor();\n computeVisible();\n });\n }\n }\n\n /** R6: Single-item convenience — delegates to measureBatch. */\n function measure(index: number, size: number): void {\n measureBatch([{ index, size }]);\n }\n\n /** Attach a ResizeObserver to `el`. Returns a disconnect function. */\n function measureEl(index: number, el: HTMLElement): () => void {\n if (destroyed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => {\n measure(index, horizontal ? entry.contentRect.width : entry.contentRect.height);\n });\n }\n\n // ─── Option updates ───────────────────────────────────────────────────────────\n\n function applyOptions(next: VirtualizerUpdateOptions): void {\n if (next.count !== undefined) requireNonNegativeInteger(next.count, 'count');\n\n if (typeof next.estimateSize === 'number') requirePositiveNumber(next.estimateSize, 'estimateSize');\n\n if (next.gap !== undefined) requireNonNegativeInteger(next.gap, 'gap');\n\n if (next.overscan !== undefined) validateOverscan(next.overscan);\n\n if (next.scrollEndDelay !== undefined) requireNonNegativeNumber(next.scrollEndDelay, 'scrollEndDelay');\n\n let needsRebuild = false;\n let needsCompute = false;\n\n if (Object.hasOwn(next, 'onChange')) onChange = next.onChange;\n\n if (Object.hasOwn(next, 'onScrollEnd')) onScrollEnd = next.onScrollEnd;\n\n if (Object.hasOwn(next, 'onScrollingChange')) onScrollingChange = next.onScrollingChange;\n\n if (next.scrollEndDelay !== undefined) {\n scrollEndDelay = next.scrollEndDelay;\n }\n\n if (next.count !== undefined) {\n const nextCount = next.count;\n\n if (nextCount !== count) {\n count = nextCount;\n ax.setCount(count);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'estimateSize')) {\n estimateFn = resolveEstimateFn(next.estimateSize, DEFAULT_ESTIMATE_SIZE);\n measuredByKey.clear();\n needsRebuild = true;\n }\n\n if (next.gap !== undefined) {\n const nextGap = next.gap;\n\n if (nextGap !== gap) {\n gap = nextGap;\n ax.setGap(gap);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'getItemKey')) {\n const nextKey = next.getItemKey ?? defaultItemKey;\n\n if (nextKey !== getItemKey) {\n getItemKey = nextKey;\n measuredByKey.clear();\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'measurementCache') && next.measurementCache !== undefined) {\n measuredByKey = next.measurementCache;\n needsRebuild = true;\n }\n\n if (next.overscan !== undefined) {\n const nextOverscan = normalizeOverscan(next.overscan, DEFAULT_OVERSCAN);\n\n if (nextOverscan.start !== overscan.start || nextOverscan.end !== overscan.end) {\n overscan = nextOverscan;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'sticky')) {\n const nextStickyFn = next.sticky ?? null;\n\n if (nextStickyFn !== stickyFn) {\n stickyFn = nextStickyFn;\n ax.resetDedup(); // force re-emit to flush updated stickyItems\n }\n\n needsCompute = true;\n }\n\n if (Object.hasOwn(next, 'keyboardScroll')) {\n keyboardScrollEnabled = next.keyboardScroll ?? false;\n }\n\n if (Object.hasOwn(next, 'autoMeasure')) {\n autoMeasureEnabled = next.autoMeasure ?? false;\n }\n\n if (needsRebuild) {\n recordScrollAnchor();\n ax.rebuild(true);\n restoreScrollAnchor();\n }\n\n if (needsCompute || needsRebuild) computeVisible();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────────\n\n function invalidate(): void {\n if (destroyed) return;\n\n measuredByKey.clear();\n ax.rebuild(true);\n computeVisible();\n }\n\n /** Full O(n) offset rebuild followed by re-emit. Also used internally for data-only re-emit when sizes unchanged. */\n function refresh(): void {\n if (destroyed) return;\n\n ax.rebuild(true);\n computeVisible();\n }\n\n function prepend(additionalCount: number): void {\n if (destroyed) return;\n\n const n = toNonNegativeInt(additionalCount);\n\n if (n === 0) return;\n\n count += n;\n ax.setCount(count);\n ax.rebuild(true);\n\n // Adjust scroll to keep the same content visible (F6-like stable prepend).\n const prependedHeight = ax.startAt(n);\n const newOffset = clampScrollOffset(scrollOffset + prependedHeight);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n\n computeVisible();\n }\n\n function update(next: VirtualizerUpdateOptions): void {\n if (destroyed) return;\n\n applyOptions(next);\n }\n\n function scrollToIndex(index: number, opts: ScrollToIndexOptions = {}): void {\n if (destroyed || count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, count - 1));\n const align = opts.align ?? 'auto';\n const behavior = opts.behavior ?? 'auto';\n const itemStart = ax.startAt(clampedIndex);\n const itemSize = ax.sizeAt(clampedIndex);\n const itemEnd = itemStart + itemSize;\n\n let targetOffset: number;\n\n if (align === 'start') {\n targetOffset = itemStart;\n } else if (align === 'end') {\n targetOffset = itemEnd - viewportSize;\n } else if (align === 'center') {\n targetOffset = itemStart - (viewportSize - itemSize) / 2;\n } else {\n const visibleStart = scrollOffset;\n const visibleEnd = visibleStart + viewportSize;\n\n if (itemStart >= visibleStart && itemEnd <= visibleEnd) {\n if (opts.onComplete) queueMicrotask(opts.onComplete);\n\n return;\n }\n\n targetOffset = itemStart < visibleStart ? itemStart : itemEnd - viewportSize;\n }\n\n domAxis.writeOffset(clampScrollOffset(targetOffset), behavior);\n\n if (opts.onComplete) {\n if (behavior === 'smooth' && 'onscrollend' in target) {\n (target as EventTarget).addEventListener('scrollend', opts.onComplete, { once: true });\n } else {\n queueMicrotask(opts.onComplete);\n }\n }\n }\n\n function scrollToOffset(offset: number, opts: { behavior?: ScrollBehavior } = {}): void {\n if (destroyed) return;\n\n domAxis.writeOffset(clampScrollOffset(offset), opts.behavior ?? 'auto');\n }\n\n function scrollToTop(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(0, opts);\n }\n\n function scrollToBottom(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(Math.max(0, ax.totalSize - viewportSize), opts);\n }\n\n function isAtEnd(threshold = 0): boolean {\n return ax.totalSize - (scrollOffset + viewportSize) <= threshold;\n }\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (destroyed) return;\n\n destroyed = true;\n ac.abort();\n\n if (scrollEndTimer !== null) {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = null;\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).removeEventListener('scrollend', notifyScrollEnd);\n }\n\n adapter.detach();\n }\n\n // ─── Bootstrap ───────────────────────────────────────────────────────────────\n\n function handleScroll(): void {\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n handleScrollStart();\n computeVisible();\n }\n\n function handleResize(): void {\n viewportSize = domAxis.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n const domAxis = horizontal ? adapter.x : adapter.y;\n\n // ─── Keyboard scroll support ──────────────────────────────────────────────────\n\n if (keyboardScrollEnabled) {\n const handleKeyDown = (e: Event): void => {\n if (destroyed) return;\n if (!(e instanceof KeyboardEvent)) return;\n if (count === 0) return;\n\n let handled = false;\n let newOffset = scrollOffset;\n const pageScrollSize = Math.max(viewportSize * 0.8, viewportSize - 100);\n\n switch (e.key) {\n case 'ArrowUp':\n case 'ArrowLeft': {\n const itemSize = typeof estimateFn(0) === 'number' ? estimateFn(0) : DEFAULT_ESTIMATE_SIZE;\n newOffset = Math.max(0, scrollOffset - (itemSize + gap));\n handled = true;\n break;\n }\n case 'ArrowDown':\n case 'ArrowRight': {\n const itemSize = typeof estimateFn(0) === 'number' ? estimateFn(0) : DEFAULT_ESTIMATE_SIZE;\n newOffset = Math.min(ax.totalSize - viewportSize, scrollOffset + (itemSize + gap));\n handled = true;\n break;\n }\n case 'PageUp':\n newOffset = Math.max(0, scrollOffset - pageScrollSize);\n handled = true;\n break;\n case 'PageDown':\n newOffset = Math.min(ax.totalSize - viewportSize, scrollOffset + pageScrollSize);\n handled = true;\n break;\n case 'Home':\n newOffset = 0;\n handled = true;\n break;\n case 'End':\n newOffset = Math.max(0, ax.totalSize - viewportSize);\n handled = true;\n break;\n }\n\n if (handled) {\n e.preventDefault();\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = clampScrollOffset(newOffset);\n computeVisible();\n }\n };\n\n if (typeof target === 'object' && target !== null && 'addEventListener' in target) {\n (target as EventTarget).addEventListener('keydown', handleKeyDown);\n ac.signal.addEventListener('abort', () => {\n (target as EventTarget).removeEventListener('keydown', handleKeyDown);\n });\n }\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).addEventListener('scrollend', notifyScrollEnd, { passive: true });\n }\n\n ax.rebuild(true);\n\n if (options.initialOffset !== undefined) {\n domAxis.writeOffset(clampScrollOffset(options.initialOffset), 'auto');\n }\n\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n viewportSize = domAxis.readViewportSize();\n\n computeVisible();\n\n return {\n get count() {\n return count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return destroyed;\n },\n invalidate,\n isAtEnd,\n get isScrolling() {\n return isScrolling;\n },\n get items() {\n return items;\n },\n measure,\n measureBatch,\n measureEl,\n prepend,\n refresh,\n get scrollOffset() {\n return scrollOffset;\n },\n scrollToBottom,\n scrollToIndex,\n scrollToOffset,\n scrollToTop,\n get stickyItems() {\n return stickyItems;\n },\n [Symbol.dispose]: _dispose,\n get totalSize() {\n return ax.totalSize;\n },\n update,\n };\n}\n","import type { Signal } from '@vielzeug/ripple';\n\nimport { DEFAULT_ESTIMATE_SIZE, DEFAULT_OVERSCAN, type MeasurementCache, type Overscan } from './_utils';\nimport {\n requireNonNegativeInteger,\n requireNonNegativeNumber,\n requirePositiveNumber,\n validateOverscan,\n} from './_validation';\nimport { ScrollConfigurationError, ScrollRangeError } from './errors';\nimport {\n createVirtualizer,\n type ScrollToIndexOptions,\n type VirtualItem,\n type Virtualizer,\n type VirtualizerState,\n type VirtualKey,\n} from './virtualizer';\n\nexport type {\n MeasurementCache,\n Overscan,\n ScrollToIndexOptions,\n VirtualItem,\n Virtualizer,\n VirtualizerState,\n VirtualKey,\n};\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\n/** A `VirtualItem` enriched with the corresponding data record. */\nexport type VirtualRenderItem<T> = VirtualItem & { readonly data: T };\n\n/**\n * Recycle a DOM node by key. If the pool has a live node for `key`, it is\n * returned and reused; otherwise `create()` is called to produce a new one.\n */\nexport type RecycleFn = (key: VirtualKey, create: () => HTMLElement) => HTMLElement;\n\nexport type DomVirtualListRenderArgs<T> = {\n items: Array<VirtualRenderItem<T>>;\n listEl: HTMLElement;\n recycle: RecycleFn;\n /** Sticky items from the underlying virtualizer, enriched with data. */\n stickyItems: Array<VirtualRenderItem<T>>;\n totalSize: number;\n};\n\nexport type StickToBottomOptions = {\n /** Enable/disable the behavior. Default: `true` once this object is provided. */\n enabled?: boolean;\n /**\n * Distance in pixels from the end still considered \"at the end\" — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Default: `48`.\n */\n threshold?: number;\n};\n\nexport type DomVirtualListOptions<T> = {\n /** Custom teardown that clears listEl. Defaults to `listEl.textContent = ''`. */\n clear?: (listEl: HTMLElement) => void;\n estimateSize?: number | ((index: number, item: T) => number);\n gap?: number;\n getItemKey?: (index: number, item: T) => VirtualKey;\n horizontal?: boolean;\n /** Enable keyboard navigation (Arrow/Page/Home/End keys). */\n keyboardScroll?: boolean;\n listElement: HTMLElement;\n /** External measurement cache for scroll restoration. */\n measurementCache?: MeasurementCache;\n overscan?: Overscan;\n render: (args: DomVirtualListRenderArgs<T>) => void;\n scrollElement: HTMLElement | Window;\n /**\n * Auto-scroll to the end after `setItems()` whenever the list was already at (or near) the\n * end just before the update — the chat \"stick to bottom on new message\" pattern. Fires on\n * *any* update while at the end, not just growth, so it also follows a streaming last\n * item that grows in place without changing `items.length`. Does nothing while the user\n * has scrolled away from the end. Pass `true` for defaults, or an options object.\n */\n stickToBottom?: boolean | StickToBottomOptions;\n /** Mark items as sticky headers. Receives the item index and the item data. */\n sticky?: (index: number, item: T) => boolean;\n /** Optional signal factory for reactive state. */\n signal?: (init: VirtualizerState) => Signal<VirtualizerState>;\n};\n\n/**\n * R11: Controller extends Virtualizer so all methods (scrollToIndex, refresh,\n * scrollToOffset, etc.) are accessible directly on the controller without\n * needing to unwrap an inner virtualizer handle.\n *\n * `prepend` and `update` are omitted — use `setItems()` for item updates and\n * there is no direct `prepend` concept in DomVirtualList.\n */\nexport type DomVirtualListController<T> = Omit<Virtualizer, 'prepend' | 'update'> & {\n setItems: (items: T[]) => void;\n};\n\nexport type VirtualScrollerOptions<T> = Omit<DomVirtualListOptions<T>, 'listElement' | 'scrollElement'> & {\n /** Additional CSS class names on the generated scroll container. */\n containerClass?: string;\n};\n\n// ─── Node pool ────────────────────────────────────────────────────────────────\n\nfunction createNodePool() {\n let live = new Map<VirtualKey, HTMLElement>();\n let nextLive = new Map<VirtualKey, HTMLElement>();\n const stale: HTMLElement[] = [];\n let inCycle = false;\n\n return {\n acquire(key: VirtualKey, create: () => HTMLElement): HTMLElement {\n if (!inCycle) return create();\n\n const existing = live.get(key);\n\n if (existing) {\n nextLive.set(key, existing);\n\n return existing;\n }\n\n const node = stale.pop() ?? create();\n\n nextLive.set(key, node);\n\n return node;\n },\n\n beginCycle(): void {\n nextLive = new Map();\n inCycle = true;\n },\n\n clear(): void {\n for (const node of live.values()) node.remove();\n\n live.clear();\n nextLive.clear();\n stale.length = 0;\n inCycle = false;\n },\n\n endCycle(): void {\n if (!inCycle) return;\n\n inCycle = false;\n\n for (const [key, node] of live) {\n if (!nextLive.has(key)) {\n node.remove();\n stale.push(node);\n }\n }\n\n live = nextLive;\n },\n };\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createDomVirtualList<T>(options: DomVirtualListOptions<T>): DomVirtualListController<T> {\n if (typeof options.estimateSize === 'number') requirePositiveNumber(options.estimateSize, 'estimateSize');\n\n if (options.gap !== undefined) requireNonNegativeInteger(options.gap, 'gap');\n\n if (options.overscan !== undefined) validateOverscan(options.overscan);\n\n if (options.stickToBottom !== undefined && typeof options.stickToBottom !== 'boolean') {\n if (options.stickToBottom === null || Array.isArray(options.stickToBottom)) {\n throw new ScrollConfigurationError('stickToBottom must be a boolean or options object.');\n }\n\n if (options.stickToBottom.threshold !== undefined) {\n requireNonNegativeNumber(options.stickToBottom.threshold, 'stickToBottom.threshold');\n }\n }\n\n let currentItems: T[] = [];\n let isDestroyed = false;\n const ac = new AbortController();\n const listEl = options.listElement;\n\n // Optional signal for reactive state\n let stateSignal: Signal<VirtualizerState> | null = null;\n if (options.signal) {\n const initialState: VirtualizerState = { items: [], stickyItems: [], totalSize: 0 };\n stateSignal = options.signal(initialState);\n }\n\n // Helper to emit state to both callback and signal\n function emitState(state: VirtualizerState): void {\n if (stateSignal) stateSignal.value = state;\n handleChange(state);\n }\n\n // Pool must be declared before virtualizer since handleChange (passed as onChange)\n // is invoked during createVirtualizer initialization via computeVisible.\n const pool = createNodePool();\n\n // Virtualizer is lazily created on the first non-empty setItems call.\n let virtualizer: Virtualizer | null = null;\n\n function resolveKey(index: number): VirtualKey {\n const item = currentItems[index];\n\n if (item !== undefined && options.getItemKey) return options.getItemKey(index, item);\n\n return index;\n }\n\n function resolveEstimate(index: number): number {\n if (options.estimateSize === undefined) return DEFAULT_ESTIMATE_SIZE;\n\n if (typeof options.estimateSize === 'number') return options.estimateSize;\n\n const item = currentItems[index];\n\n return item !== undefined ? options.estimateSize(index, item) : DEFAULT_ESTIMATE_SIZE;\n }\n\n function applyListSize(totalSize: number): void {\n if (options.horizontal) {\n listEl.style.height = '';\n listEl.style.width = `${totalSize}px`;\n } else {\n listEl.style.height = `${totalSize}px`;\n listEl.style.width = '';\n }\n }\n\n /**\n * R10: Throw rather than silently produce `undefined as T`.\n * This catches bugs where `vi.index` is out of range for `currentItems`.\n */\n function toRenderItem(vi: VirtualItem): VirtualRenderItem<T> {\n const data = currentItems[vi.index];\n\n if (data === undefined) {\n throw new ScrollRangeError(\n `toRenderItem: index ${vi.index} is out of range (currentItems.length=${currentItems.length})`,\n );\n }\n\n return { ...vi, data };\n }\n\n function handleChange(state: VirtualizerState): void {\n applyListSize(state.totalSize);\n\n pool.beginCycle();\n\n // R5: try/finally ensures endCycle() runs even if render() throws, keeping\n // the pool in a consistent state.\n try {\n options.render({\n items: state.items.map(toRenderItem),\n listEl,\n recycle: (key, create) => pool.acquire(key, create),\n stickyItems: state.stickyItems.map(toRenderItem),\n totalSize: state.totalSize,\n });\n } finally {\n pool.endCycle();\n }\n }\n\n function clearAndReset(): void {\n pool.clear();\n\n if (options.clear) {\n options.clear(listEl);\n } else {\n listEl.textContent = '';\n }\n\n listEl.style.height = '';\n listEl.style.width = '';\n listEl.style.position = '';\n listEl.style.contain = '';\n }\n\n const DEFAULT_STICK_THRESHOLD = 48;\n\n function resolveStickToBottom(): { enabled: boolean; threshold: number } {\n const opt = options.stickToBottom;\n\n if (!opt) return { enabled: false, threshold: DEFAULT_STICK_THRESHOLD };\n\n if (opt === true) return { enabled: true, threshold: DEFAULT_STICK_THRESHOLD };\n\n return { enabled: opt.enabled ?? true, threshold: opt.threshold ?? DEFAULT_STICK_THRESHOLD };\n }\n\n function spawnVirtualizer(): Virtualizer {\n const v = createVirtualizer(options.scrollElement, {\n count: currentItems.length,\n estimateSize: resolveEstimate,\n gap: options.gap,\n getItemKey: resolveKey,\n horizontal: options.horizontal,\n keyboardScroll: options.keyboardScroll,\n measurementCache: options.measurementCache,\n onChange: emitState,\n overscan: options.overscan ?? DEFAULT_OVERSCAN,\n sticky: options.sticky\n ? (index) => {\n const item = currentItems[index];\n\n return item !== undefined && options.sticky!(index, item);\n }\n : undefined,\n });\n\n virtualizer = v;\n listEl.style.position = 'relative';\n listEl.style.contain = 'layout';\n\n return v;\n }\n\n function _dispose(): void {\n if (isDestroyed) return;\n\n isDestroyed = true;\n ac.abort();\n virtualizer?.dispose();\n virtualizer = null;\n clearAndReset();\n }\n\n return {\n // ── Virtualizer passthrough (R11) ──────────────────────────────────────\n get count() {\n return virtualizer?.count ?? 0;\n },\n\n get disposalSignal() {\n return ac.signal;\n },\n\n dispose: _dispose,\n\n get disposed() {\n return isDestroyed;\n },\n\n invalidate() {\n if (isDestroyed) return;\n\n virtualizer?.invalidate();\n },\n\n isAtEnd(threshold) {\n return virtualizer?.isAtEnd(threshold) ?? true;\n },\n\n get isScrolling() {\n return virtualizer?.isScrolling ?? false;\n },\n\n get items() {\n return virtualizer?.items ?? [];\n },\n\n measure(index, size) {\n if (isDestroyed) return;\n\n virtualizer?.measure(index, size);\n },\n\n measureBatch(entries) {\n if (isDestroyed) return;\n\n virtualizer?.measureBatch(entries);\n },\n\n measureEl(index, el) {\n if (isDestroyed) return () => {};\n\n return virtualizer?.measureEl(index, el) ?? (() => {});\n },\n\n refresh() {\n if (isDestroyed) return;\n\n virtualizer?.refresh();\n },\n\n get scrollOffset() {\n return virtualizer?.scrollOffset ?? 0;\n },\n\n scrollToBottom(scrollOptions) {\n if (isDestroyed) return;\n\n virtualizer?.scrollToBottom(scrollOptions);\n },\n\n scrollToIndex(index, scrollOptions) {\n if (isDestroyed) return;\n\n virtualizer?.scrollToIndex(index, scrollOptions);\n },\n\n scrollToOffset(offset, scrollOptions) {\n if (isDestroyed) return;\n\n virtualizer?.scrollToOffset(offset, scrollOptions);\n },\n\n scrollToTop(scrollOptions) {\n if (isDestroyed) return;\n\n virtualizer?.scrollToTop(scrollOptions);\n },\n\n // ── DomVirtualList-specific ────────────────────────────────────────────\n setItems(items) {\n if (isDestroyed) return;\n\n // Read *before* mutating state — the \"was the list already at the end?\" check must\n // reflect the pre-update layout, not the one `render()` is about to produce below.\n const stick = resolveStickToBottom();\n const wasAtEnd = stick.enabled && (virtualizer?.isAtEnd(stick.threshold) ?? true);\n\n currentItems = items;\n\n if (items.length === 0) {\n virtualizer?.dispose();\n virtualizer = null;\n clearAndReset();\n\n return;\n }\n\n if (!virtualizer) {\n const v = spawnVirtualizer();\n\n if (wasAtEnd) v.scrollToBottom();\n\n return;\n }\n\n const countChanged = items.length !== virtualizer.count;\n\n // Only count needs explicit update — estimateSize and getItemKey are\n // closures that already reflect the latest currentItems automatically.\n virtualizer.update({ count: items.length });\n\n // When count changed, update() already triggered rebuild + computeVisible().\n // Only force re-emission when count is unchanged (data changed, count same).\n if (!countChanged) {\n // refresh() re-emits with current sizes for stable keys;\n // invalidate() clears position-based measurements when no stable keys.\n if (options.getItemKey) {\n virtualizer.refresh();\n } else {\n virtualizer.invalidate();\n }\n } else if (!options.getItemKey) {\n // Count changed AND no stable keys: position-based measurements are now\n // stale. Clear them so the next render remeasures from fresh estimates.\n virtualizer.invalidate();\n }\n\n if (wasAtEnd) virtualizer.scrollToBottom();\n },\n\n get stickyItems() {\n return virtualizer?.stickyItems ?? [];\n },\n\n [Symbol.dispose]: _dispose,\n\n get totalSize() {\n return virtualizer?.totalSize ?? 0;\n },\n };\n}\n\n// ─── F5: createVirtualScroller ────────────────────────────────────────────────\n\n/**\n * High-level factory that creates the scroll container and inner list element,\n * appends them to `container`, and returns a fully wired `DomVirtualListController`.\n *\n * @example\n * ```ts\n * const list = createVirtualScroller(document.getElementById('root')!, {\n * render({ items, listEl, recycle }) { … },\n * });\n * list.setItems(data);\n * ```\n */\nexport function createVirtualScroller<T>(\n container: HTMLElement,\n options: VirtualScrollerOptions<T>,\n): DomVirtualListController<T> {\n const scrollEl = document.createElement('div');\n\n scrollEl.style.cssText = options.horizontal\n ? 'overflow: auto hidden; width: 100%; height: 100%;'\n : 'overflow: hidden auto; width: 100%; height: 100%;';\n\n if (options.containerClass) scrollEl.className = options.containerClass;\n\n const listEl = document.createElement('div');\n\n scrollEl.appendChild(listEl);\n container.appendChild(scrollEl);\n\n let ctrl: DomVirtualListController<T>;\n\n try {\n ctrl = createDomVirtualList<T>({\n ...options,\n listElement: listEl,\n scrollElement: scrollEl,\n });\n } catch (e) {\n // Remove the scroll container if construction fails so we don't leak DOM nodes.\n scrollEl.remove();\n throw e;\n }\n\n // Override dispose and [Symbol.dispose] to also remove the scroll container.\n // Capture the original dispose before overwriting so there's no self-reference.\n const innerDispose = ctrl.dispose.bind(ctrl);\n\n return Object.assign(ctrl, {\n dispose() {\n innerDispose();\n scrollEl.remove();\n },\n [Symbol.dispose]() {\n innerDispose();\n scrollEl.remove();\n },\n });\n}\n","export type ScrollAlignment = 'auto' | 'center' | 'end' | 'start';\n\nexport function alignOffset(\n itemStart: number,\n itemEnd: number,\n itemSize: number,\n currentOffset: number,\n viewportSize: number,\n align: ScrollAlignment,\n): number | null {\n if (align === 'start') return itemStart;\n\n if (align === 'end') return itemEnd - viewportSize;\n\n if (align === 'center') return itemStart - (viewportSize - itemSize) / 2;\n\n if (itemStart >= currentOffset && itemEnd <= currentOffset + viewportSize) return null;\n\n return itemStart < currentOffset ? itemStart : itemEnd - viewportSize;\n}\n","import type { Signal } from '@vielzeug/ripple';\n\nimport { createScrollAdapter } from './_adapter';\nimport { alignOffset } from './_alignment';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport {\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n normalizeOverscan,\n type Overscan,\n observeResize,\n resolveEstimateFn,\n type ScrollTarget,\n toNonNegativeInt,\n toPositiveNumber,\n} from './_utils';\nimport {\n requireNonNegativeInteger,\n requireNonNegativeNumber,\n requirePositiveNumber,\n validateOverscan,\n} from './_validation';\n\nexport type { VirtualItem };\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface GridVirtualizerState {\n /**\n * Visible column descriptors.\n * Callers form the cross-product `rows × cols` as needed — no O(r×c) Cell\n * allocation per scroll tick (R8).\n */\n readonly cols: VirtualItem[];\n /** Visible row descriptors. */\n readonly rows: VirtualItem[];\n readonly totalHeight: number;\n readonly totalWidth: number;\n}\n\nexport interface GridRangeChangeEvent {\n firstCol: number;\n firstRow: number;\n lastCol: number;\n lastRow: number;\n}\n\nexport interface ScrollToCellOptions {\n behavior?: ScrollBehavior;\n colAlign?: 'auto' | 'center' | 'end' | 'start';\n rowAlign?: 'auto' | 'center' | 'end' | 'start';\n}\n\nexport interface GridVirtualizerOptions {\n colCount: number;\n colGap?: number;\n /** External measurement cache for columns. Share across instances for scroll restoration. */\n colMeasurementCache?: Map<number, number>;\n estimateColSize?: number | ((col: number) => number);\n estimateRowSize?: number | ((row: number) => number);\n initialScrollLeft?: number;\n initialScrollTop?: number;\n /** Enable keyboard navigation (Arrow/Page/Home/End keys). */\n keyboardScroll?: boolean;\n /** Called after every render cycle with the new state. Replace through `update()`. */\n onChange?: (state: GridVirtualizerState) => void;\n /** Zero-allocation alternative to onChange for range-based consumers. */\n onRangeChange?: (range: GridRangeChangeEvent) => void;\n overscanX?: Overscan;\n overscanY?: Overscan;\n rowCount: number;\n rowGap?: number;\n /** External measurement cache for rows. Share across instances for scroll restoration. */\n rowMeasurementCache?: Map<number, number>;\n /** Optional signal factory for reactive state. */\n signal?: (init: GridVirtualizerState) => Signal<GridVirtualizerState>;\n}\n\n/**\n * Options accepted by `update()`. Explicit interface instead of\n * `Partial<Omit<...>>` for better IDE hover (R7).\n */\nexport interface GridVirtualizerUpdateOptions {\n colCount?: number;\n colGap?: number;\n estimateColSize?: number | ((col: number) => number);\n estimateRowSize?: number | ((row: number) => number);\n keyboardScroll?: boolean;\n onChange?: ((state: GridVirtualizerState) => void) | undefined;\n onRangeChange?: ((range: GridRangeChangeEvent) => void) | undefined;\n overscanX?: Overscan;\n overscanY?: Overscan;\n rowCount?: number;\n rowGap?: number;\n}\n\nexport interface GridVirtualizer {\n readonly cols: VirtualItem[];\n readonly disposalSignal: AbortSignal;\n dispose: () => void;\n readonly disposed: boolean;\n invalidate: () => void;\n /** Measure rows and columns in a single coordinated rebuild pass (R4). */\n measureBatch: (rows: Array<{ index: number; size: number }>, cols: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` to auto-measure the column width on resize. Returns disconnect fn. */\n measureColEl: (col: number, el: HTMLElement) => () => void;\n measureColumn: (col: number, size: number) => void;\n measureRow: (row: number, size: number) => void;\n /** Attach a ResizeObserver to `el` to auto-measure the row height on resize. Returns disconnect fn. */\n measureRowEl: (row: number, el: HTMLElement) => () => void;\n /** Prepend `additionalRowCount` rows at the top while keeping the viewport visually stable. */\n prependRows: (additionalRowCount: number) => void;\n refresh: () => void;\n readonly rows: VirtualItem[];\n readonly scrollLeft: number;\n scrollToCell: (row: number, col: number, options?: ScrollToCellOptions) => void;\n /** Scroll to bring a row into view without changing the horizontal position. */\n scrollToColumn: (col: number, options?: Pick<ScrollToCellOptions, 'behavior' | 'colAlign'>) => void;\n readonly scrollTop: number;\n /** Scroll to bring a column into view without changing the vertical position. */\n scrollToRow: (row: number, options?: Pick<ScrollToCellOptions, 'behavior' | 'rowAlign'>) => void;\n readonly totalHeight: number;\n readonly totalWidth: number;\n update: (next: GridVirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createGridVirtualizer(target: ScrollTarget, options: GridVirtualizerOptions): GridVirtualizer {\n if (typeof options.estimateRowSize === 'number') requirePositiveNumber(options.estimateRowSize, 'estimateRowSize');\n\n if (typeof options.estimateColSize === 'number') requirePositiveNumber(options.estimateColSize, 'estimateColSize');\n\n if (options.initialScrollTop !== undefined) requireNonNegativeNumber(options.initialScrollTop, 'initialScrollTop');\n\n if (options.initialScrollLeft !== undefined) requireNonNegativeNumber(options.initialScrollLeft, 'initialScrollLeft');\n\n if (options.rowGap !== undefined) requireNonNegativeInteger(options.rowGap, 'rowGap');\n\n if (options.colGap !== undefined) requireNonNegativeInteger(options.colGap, 'colGap');\n\n if (options.overscanY !== undefined) validateOverscan(options.overscanY, 'overscanY');\n\n if (options.overscanX !== undefined) validateOverscan(options.overscanX, 'overscanX');\n\n let rowCount = requireNonNegativeInteger(options.rowCount, 'rowCount');\n let colCount = requireNonNegativeInteger(options.colCount, 'colCount');\n let estimateRowFn = resolveEstimateFn(options.estimateRowSize, DEFAULT_ESTIMATE_SIZE);\n let estimateColFn = resolveEstimateFn(options.estimateColSize, DEFAULT_ESTIMATE_SIZE);\n let overscanY = normalizeOverscan(options.overscanY, DEFAULT_OVERSCAN);\n let overscanX = normalizeOverscan(options.overscanX, DEFAULT_OVERSCAN);\n let keyboardScrollEnabled = options.keyboardScroll ?? false;\n\n let onChange = options.onChange;\n let onRangeChange = options.onRangeChange;\n\n // Optional signal for reactive state\n let stateSignal: Signal<GridVirtualizerState> | null = null;\n if (options.signal) {\n const initialState: GridVirtualizerState = { cols: [], rows: [], totalHeight: 0, totalWidth: 0 };\n stateSignal = options.signal(initialState);\n }\n\n // Helper to emit state to both callback and signal\n function emitState(state: GridVirtualizerState): void {\n if (stateSignal) stateSignal.value = state;\n onChange?.(state);\n }\n\n // External or internal measurement caches (numeric row/col index keys).\n const measuredRows: Map<number, number> = options.rowMeasurementCache ?? new Map();\n const measuredCols: Map<number, number> = options.colMeasurementCache ?? new Map();\n\n let scrollTop = 0;\n let scrollLeft = 0;\n let viewportHeight = 0;\n let viewportWidth = 0;\n let rows: VirtualItem[] = [];\n let cols: VirtualItem[] = [];\n let disposed = false;\n\n // ─── Axis1D instances (R1) ─────────────────────────────────────────────────\n // sizeAt closures capture live `measuredRows/Cols` and `estimateFn` vars.\n\n const rowAx = createAxis1D(\n rowCount,\n (r) => measuredRows.get(r) ?? estimateRowFn(r),\n toNonNegativeInt(options.rowGap ?? 0),\n );\n\n const colAx = createAxis1D(\n colCount,\n (c) => measuredCols.get(c) ?? estimateColFn(c),\n toNonNegativeInt(options.colGap ?? 0),\n );\n\n // R4: Single coordinated flush flag — prevents two separate microtasks.\n let pendingBatchBuild = false;\n\n // ─── Clamping ──────────────────────────────────────────────────────────────\n\n function clampTop(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n\n return Math.min(Math.max(0, rowAx.totalSize - viewportHeight), Math.max(0, safe));\n }\n\n function clampLeft(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n\n return Math.min(Math.max(0, colAx.totalSize - viewportWidth), Math.max(0, safe));\n }\n\n // ─── Compute and emit ──────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (disposed) return;\n\n const totalHeight = rowAx.totalSize;\n const totalWidth = colAx.totalSize;\n\n if (rowCount === 0 || colCount === 0 || viewportHeight <= 0 || viewportWidth <= 0) {\n const wasEmpty =\n rowAx.prevStart === -1 && rowAx.prevTotalSize === totalHeight && colAx.prevTotalSize === totalWidth;\n\n if (!wasEmpty) {\n // commitDedup(-1, -1) captures the current totalSize so the guard doesn't\n // fire on every subsequent event while the empty state persists (Bug 2).\n rowAx.commitDedup(-1, -1);\n colAx.commitDedup(-1, -1);\n rows = [];\n cols = [];\n emitState({ cols: [], rows: [], totalHeight, totalWidth });\n }\n\n return;\n }\n\n const { renderEnd: renderLastRow, renderStart: renderFirstRow } = rowAx.computeRange(\n scrollTop,\n scrollTop + viewportHeight,\n overscanY.start,\n overscanY.end,\n );\n\n const { renderEnd: renderLastCol, renderStart: renderFirstCol } = colAx.computeRange(\n scrollLeft,\n scrollLeft + viewportWidth,\n overscanX.start,\n overscanX.end,\n );\n\n const rowSame = rowAx.isDedupSame(renderFirstRow, renderLastRow);\n const colSame = colAx.isDedupSame(renderFirstCol, renderLastCol);\n\n if (rowSame && colSame) return;\n\n rowAx.commitDedup(renderFirstRow, renderLastRow);\n colAx.commitDedup(renderFirstCol, renderLastCol);\n\n const rangeChanged = !rowSame || !colSame;\n\n if (rangeChanged && onRangeChange) {\n onRangeChange({\n firstCol: renderFirstCol,\n firstRow: renderFirstRow,\n lastCol: renderLastCol,\n lastRow: renderLastRow,\n });\n }\n\n // R8: Build rows[] and cols[] separately — no cross-product VirtualCell[].\n const nextRows: VirtualItem[] = [];\n\n for (let r = renderFirstRow; r <= renderLastRow; r++) {\n nextRows.push(rowAx.itemAt(r));\n }\n\n const nextCols: VirtualItem[] = [];\n\n for (let c = renderFirstCol; c <= renderLastCol; c++) {\n nextCols.push(colAx.itemAt(c));\n }\n\n rows = nextRows;\n cols = nextCols;\n\n emitState({ cols, rows, totalHeight, totalWidth });\n }\n\n // ─── Measurement helpers ───────────────────────────────────────────────────\n\n function recordRowMeasurement(row: number, size: number): boolean {\n if (!Number.isFinite(row)) return false;\n\n const r = Math.floor(row);\n\n if (r < 0 || r >= rowCount) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n if (measuredRows.get(r) === s) return false;\n\n measuredRows.set(r, s);\n rowAx.markChanged(r);\n\n return true;\n }\n\n function recordColMeasurement(col: number, size: number): boolean {\n if (!Number.isFinite(col)) return false;\n\n const c = Math.floor(col);\n\n if (c < 0 || c >= colCount) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n if (measuredCols.get(c) === s) return false;\n\n measuredCols.set(c, s);\n colAx.markChanged(c);\n\n return true;\n }\n\n /**\n * R4: Single coordinated flush for both axes.\n * Replaces the previous two independent microtask schedules.\n */\n function scheduleFlush(): void {\n if (pendingBatchBuild) return;\n\n pendingBatchBuild = true;\n queueMicrotask(() => {\n pendingBatchBuild = false;\n\n // consumeMinChangedIndex() atomically reads and resets minChangedIndex.\n // Without the reset, every subsequent flush would rebuild from index 0\n // instead of the actual earliest changed index (Bug 3).\n const fromRow = rowAx.consumeMinChangedIndex();\n\n if (fromRow !== Infinity) rowAx.rebuildFrom(fromRow);\n\n const fromCol = colAx.consumeMinChangedIndex();\n\n if (fromCol !== Infinity) colAx.rebuildFrom(fromCol);\n\n computeVisible();\n });\n }\n\n // ─── Public measurement API ────────────────────────────────────────────────\n\n function measureRow(row: number, size: number): void {\n if (disposed) return;\n\n if (recordRowMeasurement(row, size)) scheduleFlush();\n }\n\n function measureColumn(col: number, size: number): void {\n if (disposed) return;\n\n if (recordColMeasurement(col, size)) scheduleFlush();\n }\n\n /** Measure rows and columns in a single coordinated rebuild pass (R4). */\n function measureBatch(\n rowEntries: Array<{ index: number; size: number }>,\n colEntries: Array<{ index: number; size: number }>,\n ): void {\n if (disposed) return;\n\n let changed = false;\n\n for (const { index, size } of rowEntries) {\n if (recordRowMeasurement(index, size)) changed = true;\n }\n\n for (const { index, size } of colEntries) {\n if (recordColMeasurement(index, size)) changed = true;\n }\n\n if (changed) scheduleFlush();\n }\n\n function measureRowEl(row: number, el: HTMLElement): () => void {\n if (disposed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => measureRow(row, entry.contentRect.height));\n }\n\n function measureColEl(col: number, el: HTMLElement): () => void {\n if (disposed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => measureColumn(col, entry.contentRect.width));\n }\n\n function invalidate(): void {\n if (disposed) return;\n\n measuredRows.clear();\n measuredCols.clear();\n rowAx.rebuild(true);\n colAx.rebuild(true);\n computeVisible();\n }\n\n function refresh(): void {\n if (disposed) return;\n\n rowAx.rebuild(true);\n colAx.rebuild(true);\n computeVisible();\n }\n\n function prependRows(additionalRowCount: number): void {\n if (disposed) return;\n\n const n = toNonNegativeInt(additionalRowCount);\n\n if (n === 0) return;\n\n rowCount += n;\n rowAx.setCount(rowCount);\n rowAx.rebuild(true);\n\n const prependedHeight = rowAx.startAt(n);\n const newScrollTop = clampTop(scrollTop + prependedHeight);\n\n adapter.scrollTo(scrollLeft, newScrollTop, 'auto');\n scrollTop = newScrollTop;\n\n computeVisible();\n }\n\n function scrollToRow(row: number, opts: Pick<ScrollToCellOptions, 'behavior' | 'rowAlign'> = {}): void {\n if (disposed || rowCount === 0) return;\n\n const safeRow = Math.max(0, Math.min(Math.floor(Number.isFinite(row) ? row : 0), rowCount - 1));\n const behavior = opts.behavior ?? 'auto';\n const rowStart = rowAx.startAt(safeRow);\n const rowSize = rowAx.sizeAt(safeRow);\n const targetTop = alignOffset(\n rowStart,\n rowStart + rowSize,\n rowSize,\n scrollTop,\n viewportHeight,\n opts.rowAlign ?? 'auto',\n );\n\n if (targetTop === null) return;\n\n adapter.scrollTo(scrollLeft, clampTop(targetTop), behavior);\n }\n\n function scrollToColumn(col: number, opts: Pick<ScrollToCellOptions, 'behavior' | 'colAlign'> = {}): void {\n if (disposed || colCount === 0) return;\n\n const safeCol = Math.max(0, Math.min(Math.floor(Number.isFinite(col) ? col : 0), colCount - 1));\n const behavior = opts.behavior ?? 'auto';\n const colStart = colAx.startAt(safeCol);\n const colSize = colAx.sizeAt(safeCol);\n const targetLeft = alignOffset(\n colStart,\n colStart + colSize,\n colSize,\n scrollLeft,\n viewportWidth,\n opts.colAlign ?? 'auto',\n );\n\n if (targetLeft === null) return;\n\n adapter.scrollTo(clampLeft(targetLeft), scrollTop, behavior);\n }\n\n function scrollToCell(row: number, col: number, opts: ScrollToCellOptions = {}): void {\n if (disposed || rowCount === 0 || colCount === 0) return;\n\n const safeRow = Math.max(0, Math.min(Math.floor(Number.isFinite(row) ? row : 0), rowCount - 1));\n const safeCol = Math.max(0, Math.min(Math.floor(Number.isFinite(col) ? col : 0), colCount - 1));\n const behavior = opts.behavior ?? 'auto';\n\n const rowStart = rowAx.startAt(safeRow);\n const rowSize = rowAx.sizeAt(safeRow);\n const colStart = colAx.startAt(safeCol);\n const colSize = colAx.sizeAt(safeCol);\n\n const targetTop = alignOffset(\n rowStart,\n rowStart + rowSize,\n rowSize,\n scrollTop,\n viewportHeight,\n opts.rowAlign ?? 'auto',\n );\n const targetLeft = alignOffset(\n colStart,\n colStart + colSize,\n colSize,\n scrollLeft,\n viewportWidth,\n opts.colAlign ?? 'auto',\n );\n\n if (targetTop === null && targetLeft === null) return;\n\n adapter.scrollTo(\n targetLeft !== null ? clampLeft(targetLeft) : scrollLeft,\n targetTop !== null ? clampTop(targetTop) : scrollTop,\n behavior,\n );\n }\n\n function update(next: GridVirtualizerUpdateOptions): void {\n if (disposed) return;\n\n if (next.rowCount !== undefined) requireNonNegativeInteger(next.rowCount, 'rowCount');\n\n if (next.colCount !== undefined) requireNonNegativeInteger(next.colCount, 'colCount');\n\n if (typeof next.estimateRowSize === 'number') requirePositiveNumber(next.estimateRowSize, 'estimateRowSize');\n\n if (typeof next.estimateColSize === 'number') requirePositiveNumber(next.estimateColSize, 'estimateColSize');\n\n if (next.rowGap !== undefined) requireNonNegativeInteger(next.rowGap, 'rowGap');\n\n if (next.colGap !== undefined) requireNonNegativeInteger(next.colGap, 'colGap');\n\n if (next.overscanY !== undefined) validateOverscan(next.overscanY, 'overscanY');\n\n if (next.overscanX !== undefined) validateOverscan(next.overscanX, 'overscanX');\n\n let rebuildRows = false;\n let rebuildCols = false;\n let needsCompute = false;\n\n if (Object.hasOwn(next, 'onChange')) onChange = next.onChange;\n\n if (Object.hasOwn(next, 'onRangeChange')) onRangeChange = next.onRangeChange;\n\n if (next.rowCount !== undefined) {\n const n = next.rowCount;\n\n if (n !== rowCount) {\n rowCount = n;\n rowAx.setCount(rowCount);\n rebuildRows = true;\n }\n }\n\n if (next.colCount !== undefined) {\n const n = next.colCount;\n\n if (n !== colCount) {\n colCount = n;\n colAx.setCount(colCount);\n rebuildCols = true;\n }\n }\n\n if (next.rowGap !== undefined) {\n const n = next.rowGap;\n\n if (n !== rowAx.gap) {\n rowAx.setGap(n);\n rebuildRows = true;\n }\n }\n\n if (next.colGap !== undefined) {\n const n = next.colGap;\n\n if (n !== colAx.gap) {\n colAx.setGap(n);\n rebuildCols = true;\n }\n }\n\n if ('estimateRowSize' in next) {\n estimateRowFn = resolveEstimateFn(next.estimateRowSize, DEFAULT_ESTIMATE_SIZE);\n measuredRows.clear();\n rebuildRows = true;\n }\n\n if ('estimateColSize' in next) {\n estimateColFn = resolveEstimateFn(next.estimateColSize, DEFAULT_ESTIMATE_SIZE);\n measuredCols.clear();\n rebuildCols = true;\n }\n\n if (next.overscanY !== undefined) {\n const n = normalizeOverscan(next.overscanY, DEFAULT_OVERSCAN);\n\n if (n.start !== overscanY.start || n.end !== overscanY.end) {\n overscanY = n;\n needsCompute = true;\n }\n }\n\n if (next.overscanX !== undefined) {\n const n = normalizeOverscan(next.overscanX, DEFAULT_OVERSCAN);\n\n if (n.start !== overscanX.start || n.end !== overscanX.end) {\n overscanX = n;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'keyboardScroll')) {\n keyboardScrollEnabled = next.keyboardScroll ?? false;\n }\n\n if (rebuildRows) rowAx.rebuild(true);\n\n if (rebuildCols) colAx.rebuild(true);\n\n if (rebuildRows || rebuildCols || needsCompute) computeVisible();\n }\n\n const ac = new AbortController();\n\n function disposeImpl(): void {\n if (disposed) return;\n\n disposed = true;\n ac.abort();\n adapter.detach();\n }\n\n // ─── Bootstrap (R9: read viewport BEFORE writing initial scroll) ───────────\n\n function handleScroll(): void {\n scrollTop = clampTop(adapter.y.readOffset());\n scrollLeft = clampLeft(adapter.x.readOffset());\n computeVisible();\n }\n\n function handleResize(): void {\n viewportHeight = adapter.y.readViewportSize();\n viewportWidth = adapter.x.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n\n rowAx.rebuild(true);\n colAx.rebuild(true);\n\n // R9: Read viewport dimensions BEFORE writing initial scroll positions so that\n // computeVisible() sees a valid viewport on the very first call.\n viewportHeight = adapter.y.readViewportSize();\n viewportWidth = adapter.x.readViewportSize();\n\n if (options.initialScrollTop !== undefined) {\n adapter.scrollTo(options.initialScrollLeft ?? 0, clampTop(options.initialScrollTop), 'auto');\n } else if (options.initialScrollLeft !== undefined) {\n adapter.scrollTo(clampLeft(options.initialScrollLeft), 0, 'auto');\n }\n\n scrollTop = clampTop(adapter.y.readOffset());\n scrollLeft = clampLeft(adapter.x.readOffset());\n\n computeVisible();\n\n // ─── Keyboard scroll support ──────────────────────────────────────────────────\n\n if (keyboardScrollEnabled) {\n const handleKeyDown = (e: Event): void => {\n if (disposed) return;\n if (!(e instanceof KeyboardEvent)) return;\n if (rowCount === 0 || colCount === 0) return;\n\n let handled = false;\n let newTop = scrollTop;\n let newLeft = scrollLeft;\n const pageScrollSize = Math.max(viewportHeight * 0.8, viewportHeight - 100);\n const rowStepSize =\n (typeof estimateRowFn(0) === 'number' ? estimateRowFn(0) : DEFAULT_ESTIMATE_SIZE) +\n toNonNegativeInt(options.rowGap ?? 0);\n const colStepSize =\n (typeof estimateColFn(0) === 'number' ? estimateColFn(0) : DEFAULT_ESTIMATE_SIZE) +\n toNonNegativeInt(options.colGap ?? 0);\n\n switch (e.key) {\n case 'ArrowUp':\n newTop = Math.max(0, scrollTop - rowStepSize);\n handled = true;\n break;\n case 'ArrowDown':\n newTop = Math.min(rowAx.totalSize - viewportHeight, scrollTop + rowStepSize);\n handled = true;\n break;\n case 'ArrowLeft':\n newLeft = Math.max(0, scrollLeft - colStepSize);\n handled = true;\n break;\n case 'ArrowRight':\n newLeft = Math.min(colAx.totalSize - viewportWidth, scrollLeft + colStepSize);\n handled = true;\n break;\n case 'PageUp':\n newTop = Math.max(0, scrollTop - pageScrollSize);\n handled = true;\n break;\n case 'PageDown':\n newTop = Math.min(rowAx.totalSize - viewportHeight, scrollTop + pageScrollSize);\n handled = true;\n break;\n case 'Home':\n newTop = 0;\n handled = true;\n break;\n case 'End':\n newTop = Math.max(0, rowAx.totalSize - viewportHeight);\n handled = true;\n break;\n }\n\n if (handled) {\n e.preventDefault();\n adapter.scrollTo(newLeft, newTop, 'auto');\n scrollTop = clampTop(newTop);\n scrollLeft = clampLeft(newLeft);\n if (!disposed) computeVisible();\n }\n };\n\n if (typeof target === 'object' && target !== null && 'addEventListener' in target) {\n (target as EventTarget).addEventListener('keydown', handleKeyDown);\n ac.signal.addEventListener('abort', () => {\n (target as EventTarget).removeEventListener('keydown', handleKeyDown);\n });\n }\n }\n\n return {\n get cols() {\n return cols;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: disposeImpl,\n get disposed() {\n return disposed;\n },\n invalidate,\n measureBatch,\n measureColEl,\n measureColumn,\n measureRow,\n measureRowEl,\n prependRows,\n refresh,\n get rows() {\n return rows;\n },\n get scrollLeft() {\n return scrollLeft;\n },\n scrollToCell,\n scrollToColumn,\n get scrollTop() {\n return scrollTop;\n },\n scrollToRow,\n [Symbol.dispose]: disposeImpl,\n get totalHeight() {\n return rowAx.totalSize;\n },\n get totalWidth() {\n return colAx.totalSize;\n },\n update,\n };\n}\n","import type { Signal } from '@vielzeug/ripple';\n\nimport { resolveEstimateFn } from './_utils';\nimport { requireNonNegativeNumber, requirePositiveNumber, validateOverscan } from './_validation';\nimport {\n createVirtualizer,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n type ScrollTarget,\n type ScrollToIndexOptions,\n type VirtualItem,\n type VirtualizerState,\n type VirtualizerUpdateOptions,\n type VirtualKey,\n} from './virtualizer';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface GroupSection<T> {\n items: T[];\n label: string;\n}\n\nexport interface GroupVirtualItem<T> extends VirtualItem {\n /** Item data. */\n data: T;\n /** Index within the section. */\n itemIndex: number;\n sectionIndex: number;\n}\n\nexport interface GroupVirtualHeader extends VirtualItem {\n label: string;\n sectionIndex: number;\n}\n\nexport interface GroupVirtualizerState<T> {\n readonly headers: GroupVirtualHeader[];\n readonly items: Array<GroupVirtualItem<T>>;\n readonly stickyHeader: GroupVirtualHeader | null;\n readonly totalSize: number;\n}\n\n/**\n * Options accepted by `GroupVirtualizer.update()`. Overrides are applied\n * together with the new sections on the next render cycle.\n *\n * Intentionally excludes `horizontal` (axis cannot change at runtime) and\n * `initialOffset` (one-time bootstrap).\n */\nexport interface GroupVirtualizerUpdateOptions<T> {\n estimateHeaderSize?: number | ((section: GroupSection<T>, sectionIndex: number) => number);\n estimateItemSize?: number | ((item: T, itemIndex: number, sectionIndex: number) => number);\n getItemKey?: (item: T, itemIndex: number, sectionIndex: number) => VirtualKey;\n measurementCache?: MeasurementCache;\n onChange?: ((state: GroupVirtualizerState<T>) => void) | undefined;\n onScrollEnd?: ((offset: number) => void) | undefined;\n onScrollingChange?: ((isScrolling: boolean) => void) | undefined;\n overscan?: Overscan;\n scrollEndDelay?: number;\n}\n\nexport interface GroupVirtualizerOptions<T> {\n estimateHeaderSize?: number | ((section: GroupSection<T>, sectionIndex: number) => number);\n estimateItemSize?: number | ((item: T, itemIndex: number, sectionIndex: number) => number);\n getItemKey?: (item: T, itemIndex: number, sectionIndex: number) => VirtualKey;\n horizontal?: boolean;\n measurementCache?: MeasurementCache;\n /** Called after every render cycle with the new state. Replace through `update()`. */\n onChange?: (state: GroupVirtualizerState<T>) => void;\n /** Called when scrolling settles. Replace through `update()`. */\n onScrollEnd?: (offset: number) => void;\n /** Called when the scrolling state changes. Replace through `update()`. */\n onScrollingChange?: (isScrolling: boolean) => void;\n overscan?: Overscan;\n /**\n * Debounce delay (ms) for scroll-end detection. Defaults to 150.\n * See `VirtualizerOptions.scrollEndDelay` for details.\n */\n scrollEndDelay?: number;\n sections: Array<GroupSection<T>>;\n /** Optional signal factory for reactive state. */\n signal?: (init: GroupVirtualizerState<T>) => Signal<GroupVirtualizerState<T>>;\n}\n\nexport interface GroupVirtualizer<T> {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n dispose: () => void;\n readonly disposed: boolean;\n invalidate: () => void;\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n readonly items: ReadonlyArray<GroupVirtualItem<T>>;\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n measureEl: (index: number, el: HTMLElement) => () => void;\n refresh: () => void;\n readonly scrollOffset: number;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToItem: (sectionIndex: number, itemIndex: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToSection: (sectionIndex: number, options?: ScrollToIndexOptions) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n update: (sections: Array<GroupSection<T>>, opts?: GroupVirtualizerUpdateOptions<T>) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Flat entry map ───────────────────────────────────────────────────────────\n\ntype FlatHeader = { isHeader: true; itemIndex: -1; label: string; sectionIndex: number };\ntype FlatItem<T> = { isHeader: false; item: T; itemIndex: number; sectionIndex: number };\ntype FlatEntry<T> = FlatHeader | FlatItem<T>;\n\nfunction buildFlatEntries<T>(sections: Array<GroupSection<T>>): FlatEntry<T>[] {\n const flat: FlatEntry<T>[] = [];\n\n for (let s = 0; s < sections.length; s++) {\n const section = sections[s]!;\n\n flat.push({ isHeader: true, itemIndex: -1, label: section.label, sectionIndex: s });\n\n for (let i = 0; i < section.items.length; i++) {\n flat.push({ isHeader: false, item: section.items[i]!, itemIndex: i, sectionIndex: s });\n }\n }\n\n return flat;\n}\n\n// ─── Implementation ───────────────────────────────────────────────────────────\n\nexport function createGroupedVirtualizer<T>(\n target: ScrollTarget,\n options: GroupVirtualizerOptions<T>,\n): GroupVirtualizer<T> {\n if (typeof options.estimateHeaderSize === 'number')\n requirePositiveNumber(options.estimateHeaderSize, 'estimateHeaderSize');\n\n if (typeof options.estimateItemSize === 'number') requirePositiveNumber(options.estimateItemSize, 'estimateItemSize');\n\n if (options.overscan !== undefined) validateOverscan(options.overscan);\n\n if (options.scrollEndDelay !== undefined) requireNonNegativeNumber(options.scrollEndDelay, 'scrollEndDelay');\n\n let sections = options.sections;\n let flat = buildFlatEntries(sections);\n\n function buildEstimateHeader(\n estimateHeaderSize: GroupVirtualizerOptions<T>['estimateHeaderSize'],\n ): (index: number) => number {\n return typeof estimateHeaderSize === 'function'\n ? (index: number) => {\n const entry = flat[index]!;\n\n return (estimateHeaderSize as (section: GroupSection<T>, sectionIndex: number) => number)(\n sections[entry.sectionIndex]!,\n entry.sectionIndex,\n );\n }\n : resolveEstimateFn(\n typeof estimateHeaderSize === 'number' ? estimateHeaderSize : undefined,\n DEFAULT_ESTIMATE_SIZE,\n );\n }\n\n let estimateHeaderFn = buildEstimateHeader(options.estimateHeaderSize);\n let estimateItemUserFn: ((item: T, itemIndex: number, sectionIndex: number) => number) | null =\n typeof options.estimateItemSize === 'function' ? options.estimateItemSize : null;\n let estimateItemFixedFn: ((index: number) => number) | null = estimateItemUserFn\n ? null\n : resolveEstimateFn(\n typeof options.estimateItemSize === 'number' ? options.estimateItemSize : undefined,\n DEFAULT_ESTIMATE_SIZE,\n );\n let userGetItemKey = options.getItemKey;\n\n function estimateFn(globalIndex: number): number {\n const entry = flat[globalIndex];\n\n if (!entry) return DEFAULT_ESTIMATE_SIZE;\n\n if (entry.isHeader) return estimateHeaderFn(globalIndex);\n\n if (estimateItemUserFn) return estimateItemUserFn(entry.item, entry.itemIndex, entry.sectionIndex);\n\n return estimateItemFixedFn!(globalIndex);\n }\n\n function getItemKey(globalIndex: number): VirtualKey {\n const entry = flat[globalIndex];\n\n if (!entry) return globalIndex;\n\n if (entry.isHeader) return `__header_${entry.sectionIndex}`;\n\n if (userGetItemKey) return userGetItemKey(entry.item, entry.itemIndex, entry.sectionIndex);\n\n return globalIndex;\n }\n\n let destroyed = false;\n let lastItems: Array<GroupVirtualItem<T>> = [];\n let onChange = options.onChange;\n let onScrollEnd = options.onScrollEnd;\n let onScrollingChange = options.onScrollingChange;\n\n // Optional signal for reactive state\n let stateSignal: Signal<GroupVirtualizerState<T>> | null = null;\n if (options.signal) {\n const initialState: GroupVirtualizerState<T> = { headers: [], items: [], stickyHeader: null, totalSize: 0 };\n stateSignal = options.signal(initialState);\n }\n\n // Helper to emit state to both callback and signal\n function emitState(state: GroupVirtualizerState<T>): void {\n if (stateSignal) stateSignal.value = state;\n onChange?.(state);\n }\n\n function mapState(state: VirtualizerState): GroupVirtualizerState<T> {\n const items: Array<GroupVirtualItem<T>> = [];\n const headers: GroupVirtualHeader[] = [];\n let stickyHeader: GroupVirtualHeader | null = null;\n\n for (const vi of state.items) {\n const entry = flat[vi.index];\n\n if (!entry) continue;\n\n if (entry.isHeader) {\n headers.push({ ...vi, label: entry.label ?? '', sectionIndex: entry.sectionIndex });\n } else {\n items.push({ ...vi, data: entry.item, itemIndex: entry.itemIndex, sectionIndex: entry.sectionIndex });\n }\n }\n\n if (state.stickyItems.length > 0) {\n const svi = state.stickyItems[0]!;\n const entry = flat[svi.index];\n\n if (entry?.isHeader) {\n stickyHeader = { ...svi, label: entry.label ?? '', sectionIndex: entry.sectionIndex };\n }\n }\n\n lastItems = items;\n\n return { headers, items, stickyHeader, totalSize: state.totalSize };\n }\n\n const virtualizer = createVirtualizer(target, {\n count: flat.length,\n estimateSize: estimateFn,\n getItemKey,\n horizontal: options.horizontal,\n measurementCache: options.measurementCache,\n onChange: (state) => {\n const mapped = mapState(state);\n\n emitState(mapped);\n },\n onScrollEnd: (offset) => onScrollEnd?.(offset),\n onScrollingChange: (isScrolling) => onScrollingChange?.(isScrolling),\n overscan: options.overscan ?? DEFAULT_OVERSCAN,\n scrollEndDelay: options.scrollEndDelay,\n sticky: (i) => flat[i]?.isHeader ?? false,\n });\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (destroyed) return;\n\n destroyed = true;\n ac.abort();\n virtualizer.dispose();\n }\n\n // Build a flat-index lookup: [sectionIndex, itemIndex] → flat index.\n // Returns -1 when sectionIndex is out of range so callers can no-op safely.\n function flatIndexOf(sectionIndex: number, itemIndex?: number): number {\n if (sectionIndex < 0 || sectionIndex >= sections.length) return -1;\n\n let pos = 0;\n\n for (let s = 0; s < sections.length; s++) {\n if (s === sectionIndex) {\n if (itemIndex === undefined) return pos; // header\n\n return pos + 1 + itemIndex;\n }\n\n pos += 1 + sections[s]!.items.length;\n }\n\n return -1; // unreachable\n }\n\n return {\n get count() {\n return virtualizer.count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return destroyed;\n },\n invalidate() {\n if (destroyed) return;\n\n virtualizer.invalidate();\n },\n get isScrolling() {\n return virtualizer.isScrolling;\n },\n get items() {\n return lastItems;\n },\n measure(index: number, size: number) {\n if (destroyed) return;\n\n virtualizer.measure(index, size);\n },\n measureBatch(entries: Array<{ index: number; size: number }>) {\n if (destroyed) return;\n\n virtualizer.measureBatch(entries);\n },\n measureEl(index: number, el: HTMLElement) {\n if (destroyed) return () => {};\n\n return virtualizer.measureEl(index, el);\n },\n refresh() {\n if (destroyed) return;\n\n virtualizer.refresh();\n },\n get scrollOffset() {\n return virtualizer.scrollOffset;\n },\n scrollToBottom(opts?: { behavior?: ScrollBehavior }) {\n if (destroyed) return;\n\n virtualizer.scrollToBottom(opts);\n },\n scrollToIndex(index: number, opts?: ScrollToIndexOptions) {\n if (destroyed) return;\n\n virtualizer.scrollToIndex(index, opts);\n },\n scrollToItem(sectionIndex, itemIndex, opts = {}) {\n if (destroyed) return;\n\n const globalIndex = flatIndexOf(sectionIndex, itemIndex);\n\n if (globalIndex < 0) return;\n\n virtualizer.scrollToIndex(globalIndex, opts);\n },\n scrollToOffset(offset: number, opts?: { behavior?: ScrollBehavior }) {\n if (destroyed) return;\n\n virtualizer.scrollToOffset(offset, opts);\n },\n scrollToSection(sectionIndex, opts = {}) {\n if (destroyed) return;\n\n const globalIndex = flatIndexOf(sectionIndex);\n\n if (globalIndex < 0) return;\n\n virtualizer.scrollToIndex(globalIndex, opts);\n },\n scrollToTop(opts?: { behavior?: ScrollBehavior }) {\n if (destroyed) return;\n\n virtualizer.scrollToTop(opts);\n },\n get stickyItems() {\n return virtualizer.stickyItems;\n },\n [Symbol.dispose]: _dispose,\n get totalSize() {\n return virtualizer.totalSize;\n },\n /**\n * Replace all sections. Closures for estimateFn and getItemKey already\n * capture the live `flat`/`sections` references — only count is passed to\n * the underlying virtualizer to avoid discarding measured sizes on every\n * data refresh. `refresh()` rebuilds offsets while preserving the cache.\n */\n update(nextSections, opts?: GroupVirtualizerUpdateOptions<T>) {\n if (destroyed) return;\n\n if (opts) {\n if (typeof opts.estimateHeaderSize === 'number')\n requirePositiveNumber(opts.estimateHeaderSize, 'estimateHeaderSize');\n\n if (typeof opts.estimateItemSize === 'number') requirePositiveNumber(opts.estimateItemSize, 'estimateItemSize');\n\n if (opts.overscan !== undefined) validateOverscan(opts.overscan);\n\n if (opts.scrollEndDelay !== undefined) requireNonNegativeNumber(opts.scrollEndDelay, 'scrollEndDelay');\n\n if (Object.hasOwn(opts, 'onChange')) onChange = opts.onChange;\n\n if (Object.hasOwn(opts, 'onScrollEnd')) onScrollEnd = opts.onScrollEnd;\n\n if (Object.hasOwn(opts, 'onScrollingChange')) onScrollingChange = opts.onScrollingChange;\n\n // Apply option overrides before rebuilding flat entries.\n if (opts.estimateHeaderSize !== undefined) {\n estimateHeaderFn = buildEstimateHeader(opts.estimateHeaderSize);\n }\n\n if (opts.estimateItemSize !== undefined) {\n estimateItemUserFn = typeof opts.estimateItemSize === 'function' ? opts.estimateItemSize : null;\n estimateItemFixedFn = estimateItemUserFn\n ? null\n : resolveEstimateFn(\n typeof opts.estimateItemSize === 'number' ? opts.estimateItemSize : undefined,\n DEFAULT_ESTIMATE_SIZE,\n );\n }\n\n if (opts.getItemKey !== undefined) userGetItemKey = opts.getItemKey;\n }\n\n sections = nextSections;\n\n const prevCount = flat.length;\n\n flat = buildFlatEntries(sections);\n\n const virtUpdateOpts: VirtualizerUpdateOptions = { count: flat.length };\n\n if (opts?.measurementCache !== undefined) virtUpdateOpts.measurementCache = opts.measurementCache;\n\n if (opts?.overscan !== undefined) virtUpdateOpts.overscan = opts.overscan;\n\n if (opts?.scrollEndDelay !== undefined) virtUpdateOpts.scrollEndDelay = opts.scrollEndDelay;\n\n if (\n flat.length !== prevCount ||\n opts?.measurementCache !== undefined ||\n opts?.overscan !== undefined ||\n opts?.scrollEndDelay !== undefined\n ) {\n virtualizer.update(virtUpdateOpts);\n } else {\n virtualizer.refresh();\n }\n },\n };\n}\n"],"mappings":"ACWA,SAAgB,GAA2C,CACzD,OAAO,IAAI,GACb,CAIA,IAAa,EAAwB,GACxB,EAAmB,EAIhC,SAAgB,EAAiB,EAAe,EAAW,EAAW,CAGpE,OAFK,OAAO,SAAS,CAAK,EAEnB,KAAK,IAAI,EAAG,KAAK,MAAM,CAAK,CAAC,EAFA,CAGtC,CAEA,SAAgB,EAAiB,EAAe,EAA0B,CAGxE,MAFI,CAAC,OAAO,SAAS,CAAK,GAAK,GAAS,GAAK,EAAQ,IAAY,EAE1D,CACT,CAEA,SAAgB,EAAe,EAAwC,CACrE,OACG,OAAO,OAAW,KAAe,aAAkB,QACnD,OAAQ,EAAkB,aAAgB,UAAY,OAAQ,EAAkB,UAAa,QAElG,CAEA,SAAgB,EAAkB,EAAgC,EAAoD,CACpH,GAAI,OAAO,GAAa,SAAU,CAChC,IAAM,EAAI,EAAiB,EAAU,CAAU,EAE/C,MAAO,CAAE,IAAK,EAAG,MAAO,CAAE,CAC5B,CAEA,MAAO,CACL,IAAK,EAAiB,GAAU,KAAO,CAAU,EACjD,MAAO,EAAiB,GAAU,OAAS,CAAU,CACvD,CACF,CAOA,SAAgB,EACd,EACA,EACA,EACY,CACZ,IAAM,EAAK,IAAI,eAAgB,GAAY,CACzC,IAAK,IAAM,KAAS,EAAS,EAAO,CAAK,CAC3C,CAAC,EAKD,OAHA,EAAG,QAAQ,CAAE,EACb,EAAO,iBAAiB,YAAe,EAAG,WAAW,EAAG,CAAE,KAAM,EAAK,CAAC,MAEzD,EAAG,WAAW,CAC7B,CAEA,SAAgB,EACd,EACA,EAC2B,CAC3B,GAAI,OAAO,GAAa,WACtB,MAAQ,IAAkB,CACxB,GAAI,CACF,OAAO,EAAiB,EAAS,CAAK,EAAG,CAAW,CACtD,OAAS,EAAK,CAGZ,MAFK,GAAgC,EAAhC,EAA0C,EAA1C,EAEE,CACT,CACF,EAGF,IAAM,EAAO,OAAO,GAAa,SAAW,EAAiB,EAAU,CAAW,EAAI,EAEtF,UAAa,CACf,CC5FA,IAAa,EAAb,cAAiC,KAAM,CACrC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,EAAb,cAA8C,CAAY,CAAC,EAG9C,EAAb,cAAsC,CAAY,CAAC,ECXnD,SAAgB,EAA0B,EAAe,EAAsB,CAC7E,GAAI,CAAC,OAAO,SAAS,CAAK,GAAK,CAAC,OAAO,UAAU,CAAK,GAAK,EAAQ,EACjE,MAAM,IAAI,EAAyB,GAAG,EAAK,wCAAwC,EAGrF,OAAO,CACT,CAEA,SAAgB,EAAyB,EAAe,EAAsB,CAC5E,GAAI,CAAC,OAAO,SAAS,CAAK,GAAK,EAAQ,EACrC,MAAM,IAAI,EAAyB,GAAG,EAAK,uCAAuC,EAGpF,OAAO,CACT,CAEA,SAAgB,EAAsB,EAAe,EAAsB,CACzE,GAAI,CAAC,OAAO,SAAS,CAAK,GAAK,GAAS,GAAK,EAAQ,IACnD,MAAM,IAAI,EAAyB,GAAG,EAAK,4DAA4D,EAGzG,OAAO,CACT,CAEA,SAAgB,EAAiB,EAAqD,EAAO,WAAkB,CAC7G,GAAI,OAAO,GAAa,SAAU,CAChC,EAA0B,EAAU,CAAI,EAExC,MACF,CAEA,GAAyB,OAAO,GAAa,WAAzC,GAAqD,MAAM,QAAQ,CAAQ,EAC7E,MAAM,IAAI,EAAyB,GAAG,EAAK,8DAA8D,EAGvG,EAAS,QAAU,IAAA,IAAW,EAA0B,EAAS,MAAO,GAAG,EAAK,OAAO,EAEvF,EAAS,MAAQ,IAAA,IAAW,EAA0B,EAAS,IAAK,GAAG,EAAK,KAAK,CACvF,CCjBA,SAAgB,EAAoB,EAAsB,EAAsB,EAAqC,CACnH,GAAI,EAAe,CAAM,EAIvB,OAHA,EAAO,iBAAiB,SAAU,EAAU,CAAE,QAAS,EAAK,CAAC,EAC7D,EAAO,iBAAiB,SAAU,EAAU,CAAE,QAAS,EAAK,CAAC,EAEtD,CACL,QAAS,CACP,EAAO,oBAAoB,SAAU,CAAQ,EAC7C,EAAO,oBAAoB,SAAU,CAAQ,CAC/C,EACA,UAAW,EAAM,EAAK,IAAa,EAAO,SAAS,CAAE,WAAU,OAAM,KAAI,CAAC,EAC1E,EAAG,CACD,eAAkB,EAAO,QACzB,qBAAwB,EAAO,WAC/B,aAAc,EAAQ,IAAa,EAAO,SAAS,CAAE,WAAU,KAAM,CAAO,CAAC,CAC/E,EACA,EAAG,CACD,eAAkB,EAAO,QACzB,qBAAwB,EAAO,YAC/B,aAAc,EAAQ,IAAa,EAAO,SAAS,CAAE,WAAU,IAAK,CAAO,CAAC,CAC9E,CACF,EAKF,IAAM,EAAiB,IAAI,eAAe,CAAQ,EAKlD,OAHA,EAAO,iBAAiB,SAAU,EAAU,CAAE,QAAS,EAAK,CAAC,EAC7D,EAAe,QAAQ,CAAM,EAEtB,CACL,QAAS,CACP,EAAO,oBAAoB,SAAU,CAAQ,EAC7C,EAAe,WAAW,CAC5B,EACA,UAAW,EAAM,EAAK,IAAa,EAAO,SAAS,CAAE,WAAU,OAAM,KAAI,CAAC,EAC1E,EAAG,CACD,eAAkB,EAAO,WACzB,qBAAwB,EAAO,YAC/B,aAAc,EAAQ,IAAa,EAAO,SAAS,CAAE,WAAU,KAAM,CAAO,CAAC,CAC/E,EACA,EAAG,CACD,eAAkB,EAAO,UACzB,qBAAwB,EAAO,aAC/B,aAAc,EAAQ,IAAa,EAAO,SAAS,CAAE,WAAU,IAAK,CAAO,CAAC,CAC9E,CACF,CACF,CCzCA,SAAgB,EAAa,EAAsB,EAAqC,EAAoB,CAC1G,IAAI,EAAQ,EACR,EAAM,EACJ,EAAS,EAEX,EAAoB,CAAC,EACrB,EAAY,EAMZ,EAAY,GACZ,EAAU,GACV,EAAgB,GAIhB,EAAe,GACf,EAAkB,IAItB,SAAS,EAAQ,EAAmB,CAClC,OAAO,EAAQ,IAAM,CACvB,CAEA,SAAS,EAAM,EAAmB,CAChC,OAAO,EAAQ,CAAC,EAAI,EAAO,CAAC,CAC9B,CASA,SAAS,EAAQ,EAAY,GAAa,CACpC,IACF,EAAY,GACZ,EAAU,GACV,EAAgB,IAGlB,IAAM,EAAiB,CAAC,CAAC,EACrB,EAAM,EAEV,IAAK,IAAI,EAAI,EAAG,EAAI,EAAO,IACzB,GAAO,EAAO,CAAC,GAAK,EAAI,EAAQ,EAAI,EAAM,GAC1C,EAAK,KAAK,CAAG,EAGf,EAAU,EACV,EAAY,CACd,CAQA,SAAS,EAAY,EAAyB,CAC5C,GAAI,CAAC,OAAO,SAAS,CAAS,GAAK,GAAa,EAAO,OAEvD,EAAY,GACZ,EAAU,GACV,EAAgB,GAEhB,IAAI,EAAM,EAAQ,IAAc,EAEhC,IAAK,IAAI,EAAI,EAAW,EAAI,EAAO,IACjC,EAAQ,GAAK,EACb,GAAO,EAAO,CAAC,GAAK,EAAI,EAAQ,EAAI,EAAM,GAG5C,EAAQ,GAAS,EACjB,EAAY,CACd,CAIA,SAAS,EAAU,EAA+B,CAChD,IAAI,EAAK,EACL,EAAK,EAAQ,EAEjB,KAAO,EAAK,GAAI,CACd,IAAM,EAAO,EAAK,GAAO,EAErB,EAAM,CAAG,GAAK,EAAe,EAAK,EAAM,EACvC,EAAK,CACZ,CAEA,OAAO,CACT,CAEA,SAAS,EAAS,EAAqB,EAAsB,CAC3D,IAAI,EAAK,EACL,EAAK,EAAQ,EAEjB,KAAO,EAAK,GAAI,CACd,IAAM,EAAO,EAAK,EAAK,GAAM,EAEzB,EAAQ,CAAG,EAAI,EAAa,EAAK,EAChC,EAAK,EAAM,CAClB,CAEA,OAAO,CACT,CAMA,SAAS,EACP,EACA,EACA,EACA,EACa,CACb,IAAM,EAAQ,EAAU,CAAa,EAC/B,EAAO,EAAS,EAAa,CAAK,EAExC,MAAO,CACL,UAAW,KAAK,IAAI,EAAQ,EAAG,EAAO,CAAW,EACjD,YAAa,KAAK,IAAI,EAAG,EAAQ,CAAa,CAChD,CACF,CAKA,SAAS,EAAY,EAAe,EAAsB,CACxD,OAAO,IAAU,GAAa,IAAQ,GAAW,IAAc,CACjE,CAEA,SAAS,EAAY,EAAe,EAAmB,CACrD,EAAY,EACZ,EAAU,EACV,EAAgB,CAClB,CAEA,SAAS,GAAmB,CAC1B,EAAY,GACZ,EAAU,GACV,EAAgB,EAClB,CASA,SAAS,EAAc,EAA2B,CAC5C,IAEJ,EAAe,GACf,mBAAqB,CACnB,EAAe,GAEf,IAAM,EAAO,EAEb,EAAkB,IAClB,EAAY,CAAI,EAChB,EAAQ,CACV,CAAC,EACH,CAGA,SAAS,EAAY,EAAqB,CACpC,EAAQ,IAAiB,EAAkB,EACjD,CAKA,SAAS,EAAO,EAA4B,CAC1C,IAAM,EAAQ,EAAQ,CAAK,EACrB,EAAO,EAAO,CAAK,EAEzB,MAAO,CAAE,IAAK,EAAQ,EAAM,QAAO,OAAM,OAAM,CACjD,CAIA,MAAO,CACL,cACA,eAMA,wBAAiC,CAC/B,IAAM,EAAM,EAIZ,MAFA,GAAkB,IAEX,CACT,EACA,IAAI,OAAgB,CAClB,OAAO,CACT,EACA,QACA,YACA,WAEA,IAAI,KAAc,CAChB,OAAO,CACT,EACA,cACA,SACA,cACA,IAAI,iBAA0B,CAC5B,OAAO,CACT,EACA,IAAI,cAAwB,CAC1B,OAAO,CACT,EACA,IAAI,SAAkB,CACpB,OAAO,CACT,EACA,IAAI,WAAoB,CACtB,OAAO,CACT,EACA,IAAI,eAAwB,CAC1B,OAAO,CACT,EACA,UACA,cACA,aACA,gBACA,SAAS,EAAiB,CACxB,EAAQ,CACV,EACA,OAAO,EAAiB,CACtB,EAAM,CACR,EACA,OAAO,EAAmB,CACxB,OAAO,EAAO,CAAC,CACjB,EACA,UACA,IAAI,WAAoB,CACtB,OAAO,CACT,CACF,CACF,CChQA,SAAgB,EAAmB,EAA0C,CAC3E,GAAI,CAAC,EAAI,UAAY,EAAI,QAAU,GAAK,EAAI,cAAgB,EAAG,MAAO,CAAC,EAGvE,IAAI,EAAY,GACZ,EAAK,EACL,EAAK,EAAI,MAAQ,EAErB,KAAO,GAAM,GAAI,CACf,IAAM,EAAO,EAAK,GAAO,EAErB,EAAI,QAAQ,CAAG,EAAI,EAAI,cACzB,EAAY,EACZ,EAAK,EAAM,GAEX,EAAK,EAAM,CAEf,CAEA,GAAI,EAAY,EAAG,MAAO,CAAC,EAG3B,IAAI,EAAY,GAEhB,IAAK,IAAI,EAAI,EAAW,GAAK,EAAG,IAC9B,GAAI,EAAI,SAAS,CAAC,EAAG,CACnB,EAAY,EACZ,KACF,CAGF,GAAI,IAAc,GAAI,MAAO,CAAC,EAG9B,IAAM,EAAa,EAAI,OAAO,CAAS,EACnC,EAAkB,IAEtB,IAAK,IAAI,EAAI,EAAY,EAAG,EAAI,EAAI,MAAO,IACzC,GAAI,EAAI,SAAS,CAAC,EAAG,CACnB,EAAkB,EAAI,QAAQ,CAAC,EAC/B,KACF,CAIF,IAAM,EAAc,KAAK,IAAI,EAAI,aAAc,EAAkB,CAAU,EAE3E,MAAO,CAAC,CAAE,IAAK,EAAc,EAAY,MAAO,EAAW,KAAM,EAAY,MAAO,CAAY,CAAC,CACnG,CCyEA,SAAgB,EAAkB,EAAsB,EAA0C,CAC5F,OAAO,EAAQ,cAAiB,UAAU,EAAsB,EAAQ,aAAc,cAAc,EAEpG,EAAQ,MAAQ,IAAA,IAAW,EAA0B,EAAQ,IAAK,KAAK,EAEvE,EAAQ,gBAAkB,IAAA,IAAW,EAAyB,EAAQ,cAAe,eAAe,EAEpG,EAAQ,WAAa,IAAA,IAAW,EAAiB,EAAQ,QAAQ,EAEjE,EAAQ,iBAAmB,IAAA,IAAW,EAAyB,EAAQ,eAAgB,gBAAgB,EAE3G,IAAM,EAAa,CAAC,CAAC,EAAQ,WACvB,EAAkB,GAA8B,EAChD,EAAuB,EAAQ,gBAAkB,GAEnD,EAAQ,EAA0B,EAAQ,MAAO,OAAO,EACxD,EAAa,EAAkB,EAAQ,aAAA,EAAmC,EAC1E,EAAM,EAAiB,EAAQ,KAAO,CAAC,EACvC,EAAa,EAAQ,YAAc,EACnC,EAAW,EAAkB,EAAQ,SAAA,CAA0B,EAC/D,EAAgD,EAAQ,QAAU,KAClE,EAAwB,EACxB,EAAqB,EAAQ,aAAe,GAE5C,EAAW,EAAQ,SACnB,EAAc,EAAQ,YACtB,EAAoB,EAAQ,kBAC5B,EAAiB,EAAQ,gBAAkB,IAG3C,EAA+C,KAC/C,EAAQ,SAEV,EAAc,EAAQ,OAAO,CADY,MAAO,CAAC,EAAG,YAAa,CAAC,EAAG,UAAW,CACnD,CAAY,GAI3C,SAAS,EAAU,EAA+B,CAC5C,IAAa,EAAY,MAAQ,GACrC,IAAW,CAAK,CAClB,CACA,IAAI,EAAuD,KAGrD,EAAqB,gBAAkB,EAE7C,SAAS,GAAwB,CAC1B,IAEL,EAAc,GACd,IAAoB,EAAK,EACzB,IAAc,CAAY,EAC5B,CAEA,SAAS,GAA0B,CAC5B,IACH,EAAc,GACd,IAAoB,EAAI,GAGrB,IACC,IAAmB,MAAM,aAAa,CAAc,EAExD,EAAiB,WAAW,EAAiB,CAAc,EAE/D,CAGA,IAAI,EAAkC,EAAQ,kBAAoB,IAAI,IAElE,EAAuB,CAAC,EACxB,EAA6B,CAAC,EAO5B,EAAK,EAAa,EAAQ,GAAM,EAAc,IAAI,EAAW,CAAC,CAAC,GAAK,EAAW,CAAC,EAAG,CAAG,EAGxF,EAAc,GACd,EAAwB,EAExB,EAAe,EACf,EAAe,EACf,EAAmB,GACnB,EAAY,GACZ,EAAc,GAIlB,SAAS,EAAkB,EAAwB,CACjD,IAAM,EAAO,OAAO,SAAS,CAAM,EAAI,EAAS,EAC1C,EAAY,KAAK,IAAI,EAAG,EAAG,UAAY,CAAY,EAEzD,OAAO,KAAK,IAAI,EAAW,KAAK,IAAI,EAAG,CAAI,CAAC,CAC9C,CAIA,SAAS,GAAuB,CAC9B,GAAI,EAAW,OAEf,GAAI,IAAU,GAAK,GAAgB,EAAG,EAChC,EAAG,YAAc,IAAM,EAAG,gBAAkB,EAAG,aAIjD,EAAG,YAAY,GAAI,EAAE,EAErB,EAAQ,CAAC,EACT,EAAc,CAAC,EAEf,EAAU,CAAE,QAAO,cAAa,UAAW,EAAG,SAAU,CAAC,GAG3D,MACF,CAEA,GAAM,CAAE,YAAW,eAAgB,EAAG,aACpC,EACA,EAAe,EACf,EAAS,MACT,EAAS,GACX,EAGM,EAAiB,IAAa,MAAQ,IAAiB,EAG7D,GAFsB,EAAG,YAAY,EAAa,CAAS,GAEtC,CAAC,EAAgB,OAEtC,EAAG,YAAY,EAAa,CAAS,EACrC,EAAmB,EAEnB,IAAM,EAA2B,CAAC,EAElC,IAAK,IAAI,EAAI,EAAa,GAAK,EAAW,IACxC,EAAU,KAAK,EAAG,OAAO,CAAC,CAAC,EAG7B,EAAQ,EACR,EAAc,EACV,EAAmB,CAAE,QAAO,eAAc,OAAQ,EAAG,OAAQ,QAAS,EAAG,QAAS,UAAS,CAAC,EAC5F,CAAC,EAEL,EAAU,CAAE,QAAO,cAAa,UAAW,EAAG,SAAU,CAAC,EAGrD,GAAsB,EAAM,OAAS,GAEvC,mBAAqB,CACf,QAAa,aAAkB,QAEnC,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAM,EAAW,EAAK,KAAK,EAC3B,EAAM,GAAwB,gBAAgB,iBAAiB,EAAI,GAAG,EAExE,aAAc,aAChB,GAAU,EAAK,MAAO,CAAE,CAE5B,CACF,CAAC,CAEL,CAIA,SAAS,GAAkB,EAAe,EAAuB,CAC/D,GAAI,CAAC,OAAO,SAAS,CAAK,EAAG,MAAO,GAEpC,IAAM,EAAI,KAAK,MAAM,CAAK,EAE1B,GAAI,EAAI,GAAK,GAAK,EAAO,MAAO,GAEhC,IAAM,EAAI,EAAiB,EAAM,EAAE,EAEnC,GAAI,GAAK,EAAG,MAAO,GAEnB,IAAM,EAAM,EAAW,CAAC,EAOxB,OALI,EAAc,IAAI,CAAG,IAAM,IAE/B,EAAc,IAAI,EAAK,CAAC,EACxB,EAAG,YAAY,CAAC,EAET,GACT,CAGA,SAAS,IAA2B,CAClC,GAAI,GAAgB,GAAK,IAAU,EAAG,CACpC,EAAc,GAEd,MACF,CAEA,EAAc,EAAG,UAAU,CAAY,EACvC,EAAwB,EAAe,EAAG,QAAQ,CAAW,CAC/D,CAGA,SAAS,GAA4B,CACnC,GAAI,EAAc,EAAG,OAGrB,IAAM,EADiB,EAAG,QAAQ,CAAW,EAAI,EAClB,EAI/B,GAFA,EAAc,GAEV,KAAK,IAAI,CAAK,GAAK,EAAG,CACxB,IAAM,EAAY,EAAkB,EAAe,CAAK,EAExD,EAAQ,YAAY,EAAW,MAAM,EACrC,EAAe,CACjB,CACF,CAEA,SAAS,EAAa,EAAuD,CAC3E,GAAI,EAAW,OAEf,IAAI,EAAU,GAEd,IAAK,GAAM,CAAE,QAAO,UAAU,EACxB,GAAkB,EAAO,CAAI,IAAG,EAAU,IAG5C,IAEG,EAAG,cAAc,GAAmB,EAEzC,EAAG,kBAAoB,CACrB,EAAoB,EACpB,EAAe,CACjB,CAAC,EAEL,CAGA,SAAS,GAAQ,EAAe,EAAoB,CAClD,EAAa,CAAC,CAAE,QAAO,MAAK,CAAC,CAAC,CAChC,CAGA,SAAS,GAAU,EAAe,EAA6B,CAG7D,OAFI,MAAwB,CAAC,EAEtB,EAAc,EAAG,OAAQ,EAAK,GAAU,CAC7C,GAAQ,EAAO,EAAa,EAAM,YAAY,MAAQ,EAAM,YAAY,MAAM,CAChF,CAAC,CACH,CAIA,SAAS,GAAa,EAAsC,CACtD,EAAK,QAAU,IAAA,IAAW,EAA0B,EAAK,MAAO,OAAO,EAEvE,OAAO,EAAK,cAAiB,UAAU,EAAsB,EAAK,aAAc,cAAc,EAE9F,EAAK,MAAQ,IAAA,IAAW,EAA0B,EAAK,IAAK,KAAK,EAEjE,EAAK,WAAa,IAAA,IAAW,EAAiB,EAAK,QAAQ,EAE3D,EAAK,iBAAmB,IAAA,IAAW,EAAyB,EAAK,eAAgB,gBAAgB,EAErG,IAAI,EAAe,GACf,EAAe,GAYnB,GAVI,OAAO,OAAO,EAAM,UAAU,IAAG,EAAW,EAAK,UAEjD,OAAO,OAAO,EAAM,aAAa,IAAG,EAAc,EAAK,aAEvD,OAAO,OAAO,EAAM,mBAAmB,IAAG,EAAoB,EAAK,mBAEnE,EAAK,iBAAmB,IAAA,KAC1B,EAAiB,EAAK,gBAGpB,EAAK,QAAU,IAAA,GAAW,CAC5B,IAAM,EAAY,EAAK,MAEnB,IAAc,IAChB,EAAQ,EACR,EAAG,SAAS,CAAK,EACjB,EAAe,GAEnB,CAQA,GANI,OAAO,OAAO,EAAM,cAAc,IACpC,EAAa,EAAkB,EAAK,aAAA,EAAmC,EACvE,EAAc,MAAM,EACpB,EAAe,IAGb,EAAK,MAAQ,IAAA,GAAW,CAC1B,IAAM,EAAU,EAAK,IAEjB,IAAY,IACd,EAAM,EACN,EAAG,OAAO,CAAG,EACb,EAAe,GAEnB,CAEA,GAAI,OAAO,OAAO,EAAM,YAAY,EAAG,CACrC,IAAM,EAAU,EAAK,YAAc,EAE/B,IAAY,IACd,EAAa,EACb,EAAc,MAAM,EACpB,EAAe,GAEnB,CAOA,GALI,OAAO,OAAO,EAAM,kBAAkB,GAAK,EAAK,mBAAqB,IAAA,KACvE,EAAgB,EAAK,iBACrB,EAAe,IAGb,EAAK,WAAa,IAAA,GAAW,CAC/B,IAAM,EAAe,EAAkB,EAAK,SAAA,CAA0B,GAElE,EAAa,QAAU,EAAS,OAAS,EAAa,MAAQ,EAAS,OACzE,EAAW,EACX,EAAe,GAEnB,CAEA,GAAI,OAAO,OAAO,EAAM,QAAQ,EAAG,CACjC,IAAM,EAAe,EAAK,QAAU,KAEhC,IAAiB,IACnB,EAAW,EACX,EAAG,WAAW,GAGhB,EAAe,EACjB,CAEI,OAAO,OAAO,EAAM,gBAAgB,IACtC,EAAwB,EAAK,gBAAkB,IAG7C,OAAO,OAAO,EAAM,aAAa,IACnC,EAAqB,EAAK,aAAe,IAGvC,IACF,GAAmB,EACnB,EAAG,QAAQ,EAAI,EACf,EAAoB,IAGlB,GAAgB,IAAc,EAAe,CACnD,CAIA,SAAS,GAAmB,CACtB,IAEJ,EAAc,MAAM,EACpB,EAAG,QAAQ,EAAI,EACf,EAAe,EACjB,CAGA,SAAS,IAAgB,CACnB,IAEJ,EAAG,QAAQ,EAAI,EACf,EAAe,EACjB,CAEA,SAAS,GAAQ,EAA+B,CAC9C,GAAI,EAAW,OAEf,IAAM,EAAI,EAAiB,CAAe,EAE1C,GAAI,IAAM,EAAG,OAEb,GAAS,EACT,EAAG,SAAS,CAAK,EACjB,EAAG,QAAQ,EAAI,EAGf,IAAM,EAAkB,EAAG,QAAQ,CAAC,EAC9B,EAAY,EAAkB,EAAe,CAAe,EAElE,EAAQ,YAAY,EAAW,MAAM,EACrC,EAAe,EAEf,EAAe,CACjB,CAEA,SAAS,GAAO,EAAsC,CAChD,GAEJ,GAAa,CAAI,CACnB,CAEA,SAAS,EAAc,EAAe,EAA6B,CAAC,EAAS,CAC3E,GAAI,GAAa,GAAS,EAAG,OAG7B,IAAM,EAAe,KAAK,IAAI,EAAG,KAAK,IADpB,OAAO,SAAS,CAAK,EAAI,KAAK,MAAM,CAAK,EAAI,EACV,EAAQ,CAAC,CAAC,EACzD,EAAQ,EAAK,OAAS,OACtB,EAAW,EAAK,UAAY,OAC5B,EAAY,EAAG,QAAQ,CAAY,EACnC,EAAW,EAAG,OAAO,CAAY,EACjC,EAAU,EAAY,EAExB,EAEJ,GAAI,IAAU,QACZ,EAAe,OACV,GAAI,IAAU,MACnB,EAAe,EAAU,OACpB,GAAI,IAAU,SACnB,EAAe,GAAa,EAAe,GAAY,MAClD,CACL,IAAM,EAAe,EACf,EAAa,EAAe,EAElC,GAAI,GAAa,GAAgB,GAAW,EAAY,CAClD,EAAK,YAAY,eAAe,EAAK,UAAU,EAEnD,MACF,CAEA,EAAe,EAAY,EAAe,EAAY,EAAU,CAClE,CAEA,EAAQ,YAAY,EAAkB,CAAY,EAAG,CAAQ,EAEzD,EAAK,aACH,IAAa,UAAY,gBAAiB,EAC5C,EAAwB,iBAAiB,YAAa,EAAK,WAAY,CAAE,KAAM,EAAK,CAAC,EAErF,eAAe,EAAK,UAAU,EAGpC,CAEA,SAAS,GAAe,EAAgB,EAAsC,CAAC,EAAS,CAClF,GAEJ,EAAQ,YAAY,EAAkB,CAAM,EAAG,EAAK,UAAY,MAAM,CACxE,CAEA,SAAS,GAAY,EAAsC,CAAC,EAAS,CACnE,GAAe,EAAG,CAAI,CACxB,CAEA,SAAS,GAAe,EAAsC,CAAC,EAAS,CACtE,GAAe,KAAK,IAAI,EAAG,EAAG,UAAY,CAAY,EAAG,CAAI,CAC/D,CAEA,SAAS,GAAQ,EAAY,EAAY,CACvC,OAAO,EAAG,WAAa,EAAe,IAAiB,CACzD,CAEA,IAAM,EAAK,IAAI,gBAEf,SAAS,IAAiB,CACpB,IAEJ,EAAY,GACZ,EAAG,MAAM,EAEL,IAAmB,OACrB,aAAa,CAAc,EAC3B,EAAiB,MAGf,GACF,EAAwB,oBAAoB,YAAa,CAAe,EAG1E,GAAQ,OAAO,EACjB,CAIA,SAAS,IAAqB,CAC5B,EAAe,EAAkB,EAAQ,WAAW,CAAC,EACrD,EAAkB,EAClB,EAAe,CACjB,CAEA,SAAS,IAAqB,CAC5B,EAAe,EAAQ,iBAAiB,EACxC,EAAe,CACjB,CAEA,IAAM,GAAU,EAAoB,EAAQ,GAAc,EAAY,EAChE,EAAU,EAAa,GAAQ,EAAI,GAAQ,EAIjD,GAAI,EAAuB,CACzB,IAAM,EAAiB,GAAmB,CAGxC,GAFI,GACA,EAAE,aAAa,gBACf,IAAU,EAAG,OAEjB,IAAI,EAAU,GACV,EAAY,EACV,EAAiB,KAAK,IAAI,EAAe,GAAK,EAAe,GAAG,EAEtE,OAAQ,EAAE,IAAV,CACE,IAAK,UACL,IAAK,YAAa,CAChB,IAAM,EAAW,OAAO,EAAW,CAAC,GAAM,SAAW,EAAW,CAAC,EAAA,GACjE,EAAY,KAAK,IAAI,EAAG,GAAgB,EAAW,EAAI,EACvD,EAAU,GACV,KACF,CACA,IAAK,YACL,IAAK,aAAc,CACjB,IAAM,EAAW,OAAO,EAAW,CAAC,GAAM,SAAW,EAAW,CAAC,EAAA,GACjE,EAAY,KAAK,IAAI,EAAG,UAAY,EAAc,GAAgB,EAAW,EAAI,EACjF,EAAU,GACV,KACF,CACA,IAAK,SACH,EAAY,KAAK,IAAI,EAAG,EAAe,CAAc,EACrD,EAAU,GACV,MACF,IAAK,WACH,EAAY,KAAK,IAAI,EAAG,UAAY,EAAc,EAAe,CAAc,EAC/E,EAAU,GACV,MACF,IAAK,OACH,EAAY,EACZ,EAAU,GACV,MACF,IAAK,MACH,EAAY,KAAK,IAAI,EAAG,EAAG,UAAY,CAAY,EACnD,EAAU,EAEd,CAEI,IACF,EAAE,eAAe,EACjB,EAAQ,YAAY,EAAW,MAAM,EACrC,EAAe,EAAkB,CAAS,EAC1C,EAAe,EAEnB,EAEI,OAAO,GAAW,UAAY,GAAmB,qBAAsB,IACzE,EAAwB,iBAAiB,UAAW,CAAa,EACjE,EAAG,OAAO,iBAAiB,YAAe,CACxC,EAAwB,oBAAoB,UAAW,CAAa,CACtE,CAAC,EAEL,CAiBA,OAfI,GACF,EAAwB,iBAAiB,YAAa,EAAiB,CAAE,QAAS,EAAK,CAAC,EAG1F,EAAG,QAAQ,EAAI,EAEX,EAAQ,gBAAkB,IAAA,IAC5B,EAAQ,YAAY,EAAkB,EAAQ,aAAa,EAAG,MAAM,EAGtE,EAAe,EAAkB,EAAQ,WAAW,CAAC,EACrD,EAAe,EAAQ,iBAAiB,EAExC,EAAe,EAER,CACL,IAAI,OAAQ,CACV,OAAO,CACT,EACA,IAAI,gBAAiB,CACnB,OAAO,EAAG,MACZ,EACA,QAAS,GACT,IAAI,UAAW,CACb,OAAO,CACT,EACA,aACA,WACA,IAAI,aAAc,CAChB,OAAO,CACT,EACA,IAAI,OAAQ,CACV,OAAO,CACT,EACA,WACA,eACA,aACA,WACA,WACA,IAAI,cAAe,CACjB,OAAO,CACT,EACA,kBACA,gBACA,kBACA,eACA,IAAI,aAAc,CAChB,OAAO,CACT,GACC,OAAO,SAAU,GAClB,IAAI,WAAY,CACd,OAAO,EAAG,SACZ,EACA,SACF,CACF,CC5oBA,SAAS,GAAiB,CACxB,IAAI,EAAO,IAAI,IACX,EAAW,IAAI,IACb,EAAuB,CAAC,EAC1B,EAAU,GAEd,MAAO,CACL,QAAQ,EAAiB,EAAwC,CAC/D,GAAI,CAAC,EAAS,OAAO,EAAO,EAE5B,IAAM,EAAW,EAAK,IAAI,CAAG,EAE7B,GAAI,EAGF,OAFA,EAAS,IAAI,EAAK,CAAQ,EAEnB,EAGT,IAAM,EAAO,EAAM,IAAI,GAAK,EAAO,EAInC,OAFA,EAAS,IAAI,EAAK,CAAI,EAEf,CACT,EAEA,YAAmB,CACjB,EAAW,IAAI,IACf,EAAU,EACZ,EAEA,OAAc,CACZ,IAAK,IAAM,KAAQ,EAAK,OAAO,EAAG,EAAK,OAAO,EAE9C,EAAK,MAAM,EACX,EAAS,MAAM,EACf,EAAM,OAAS,EACf,EAAU,EACZ,EAEA,UAAiB,CACV,KAEL,GAAU,GAEV,IAAK,GAAM,CAAC,EAAK,KAAS,EACnB,EAAS,IAAI,CAAG,IACnB,EAAK,OAAO,EACZ,EAAM,KAAK,CAAI,GAInB,EAAO,CATG,CAUZ,CACF,CACF,CAIA,SAAgB,EAAwB,EAAgE,CAOtG,GANI,OAAO,EAAQ,cAAiB,UAAU,EAAsB,EAAQ,aAAc,cAAc,EAEpG,EAAQ,MAAQ,IAAA,IAAW,EAA0B,EAAQ,IAAK,KAAK,EAEvE,EAAQ,WAAa,IAAA,IAAW,EAAiB,EAAQ,QAAQ,EAEjE,EAAQ,gBAAkB,IAAA,IAAa,OAAO,EAAQ,eAAkB,UAAW,CACrF,GAAI,EAAQ,gBAAkB,MAAQ,MAAM,QAAQ,EAAQ,aAAa,EACvE,MAAM,IAAI,EAAyB,oDAAoD,EAGrF,EAAQ,cAAc,YAAc,IAAA,IACtC,EAAyB,EAAQ,cAAc,UAAW,yBAAyB,CAEvF,CAEA,IAAI,EAAoB,CAAC,EACrB,EAAc,GACZ,EAAK,IAAI,gBACT,EAAS,EAAQ,YAGnB,EAA+C,KAC/C,EAAQ,SAEV,EAAc,EAAQ,OAAO,CADY,MAAO,CAAC,EAAG,YAAa,CAAC,EAAG,UAAW,CACnD,CAAY,GAI3C,SAAS,EAAU,EAA+B,CAC5C,IAAa,EAAY,MAAQ,GACrC,EAAa,CAAK,CACpB,CAIA,IAAM,EAAO,EAAe,EAGxB,EAAkC,KAEtC,SAAS,EAAW,EAA2B,CAC7C,IAAM,EAAO,EAAa,GAI1B,OAFI,IAAS,IAAA,IAAa,EAAQ,WAAmB,EAAQ,WAAW,EAAO,CAAI,EAE5E,CACT,CAEA,SAAS,EAAgB,EAAuB,CAC9C,GAAI,EAAQ,eAAiB,IAAA,GAAW,MAAA,IAExC,GAAI,OAAO,EAAQ,cAAiB,SAAU,OAAO,EAAQ,aAE7D,IAAM,EAAO,EAAa,GAE1B,OAAO,IAAS,IAAA,GAA4C,GAAhC,EAAQ,aAAa,EAAO,CAAI,CAC9D,CAEA,SAAS,EAAc,EAAyB,CAC1C,EAAQ,YACV,EAAO,MAAM,OAAS,GACtB,EAAO,MAAM,MAAQ,GAAG,EAAU,MAElC,EAAO,MAAM,OAAS,GAAG,EAAU,IACnC,EAAO,MAAM,MAAQ,GAEzB,CAMA,SAAS,EAAa,EAAuC,CAC3D,IAAM,EAAO,EAAa,EAAG,OAE7B,GAAI,IAAS,IAAA,GACX,MAAM,IAAI,EACR,uBAAuB,EAAG,MAAM,wCAAwC,EAAa,OAAO,EAC9F,EAGF,MAAO,CAAE,GAAG,EAAI,MAAK,CACvB,CAEA,SAAS,EAAa,EAA+B,CACnD,EAAc,EAAM,SAAS,EAE7B,EAAK,WAAW,EAIhB,GAAI,CACF,EAAQ,OAAO,CACb,MAAO,EAAM,MAAM,IAAI,CAAY,EACnC,SACA,SAAU,EAAK,IAAW,EAAK,QAAQ,EAAK,CAAM,EAClD,YAAa,EAAM,YAAY,IAAI,CAAY,EAC/C,UAAW,EAAM,SACnB,CAAC,CACH,QAAU,CACR,EAAK,SAAS,CAChB,CACF,CAEA,SAAS,GAAsB,CAC7B,EAAK,MAAM,EAEP,EAAQ,MACV,EAAQ,MAAM,CAAM,EAEpB,EAAO,YAAc,GAGvB,EAAO,MAAM,OAAS,GACtB,EAAO,MAAM,MAAQ,GACrB,EAAO,MAAM,SAAW,GACxB,EAAO,MAAM,QAAU,EACzB,CAIA,SAAS,GAAgE,CACvE,IAAM,EAAM,EAAQ,cAMpB,OAJK,EAED,IAAQ,GAAa,CAAE,QAAS,GAAM,UAAW,EAAwB,EAEtE,CAAE,QAAS,EAAI,SAAW,GAAM,UAAW,EAAI,WAAa,EAAwB,EAJ1E,CAAE,QAAS,GAAO,UAAW,EAAwB,CAKxE,CAEA,SAAS,GAAgC,CACvC,IAAM,EAAI,EAAkB,EAAQ,cAAe,CACjD,MAAO,EAAa,OACpB,aAAc,EACd,IAAK,EAAQ,IACb,WAAY,EACZ,WAAY,EAAQ,WACpB,eAAgB,EAAQ,eACxB,iBAAkB,EAAQ,iBAC1B,SAAU,EACV,SAAU,EAAQ,UAAA,EAClB,OAAQ,EAAQ,OACX,GAAU,CACT,IAAM,EAAO,EAAa,GAE1B,OAAO,IAAS,IAAA,IAAa,EAAQ,OAAQ,EAAO,CAAI,CAC1D,EACA,IAAA,EACN,CAAC,EAMD,MAJA,GAAc,EACd,EAAO,MAAM,SAAW,WACxB,EAAO,MAAM,QAAU,SAEhB,CACT,CAEA,SAAS,GAAiB,CACpB,IAEJ,EAAc,GACd,EAAG,MAAM,EACT,GAAa,QAAQ,EACrB,EAAc,KACd,EAAc,EAChB,CAEA,MAAO,CAEL,IAAI,OAAQ,CACV,OAAO,GAAa,OAAS,CAC/B,EAEA,IAAI,gBAAiB,CACnB,OAAO,EAAG,MACZ,EAEA,QAAS,EAET,IAAI,UAAW,CACb,OAAO,CACT,EAEA,YAAa,CACP,GAEJ,GAAa,WAAW,CAC1B,EAEA,QAAQ,EAAW,CACjB,OAAO,GAAa,QAAQ,CAAS,GAAK,EAC5C,EAEA,IAAI,aAAc,CAChB,OAAO,GAAa,aAAe,EACrC,EAEA,IAAI,OAAQ,CACV,OAAO,GAAa,OAAS,CAAC,CAChC,EAEA,QAAQ,EAAO,EAAM,CACf,GAEJ,GAAa,QAAQ,EAAO,CAAI,CAClC,EAEA,aAAa,EAAS,CAChB,GAEJ,GAAa,aAAa,CAAO,CACnC,EAEA,UAAU,EAAO,EAAI,CAGnB,OAFI,MAA0B,CAAC,EAExB,GAAa,UAAU,EAAO,CAAE,QAAY,CAAC,EACtD,EAEA,SAAU,CACJ,GAEJ,GAAa,QAAQ,CACvB,EAEA,IAAI,cAAe,CACjB,OAAO,GAAa,cAAgB,CACtC,EAEA,eAAe,EAAe,CACxB,GAEJ,GAAa,eAAe,CAAa,CAC3C,EAEA,cAAc,EAAO,EAAe,CAC9B,GAEJ,GAAa,cAAc,EAAO,CAAa,CACjD,EAEA,eAAe,EAAQ,EAAe,CAChC,GAEJ,GAAa,eAAe,EAAQ,CAAa,CACnD,EAEA,YAAY,EAAe,CACrB,GAEJ,GAAa,YAAY,CAAa,CACxC,EAGA,SAAS,EAAO,CACd,GAAI,EAAa,OAIjB,IAAM,EAAQ,EAAqB,EAC7B,EAAW,EAAM,UAAY,GAAa,QAAQ,EAAM,SAAS,GAAK,IAI5E,GAFA,EAAe,EAEX,EAAM,SAAW,EAAG,CACtB,GAAa,QAAQ,EACrB,EAAc,KACd,EAAc,EAEd,MACF,CAEA,GAAI,CAAC,EAAa,CAChB,IAAM,EAAI,EAAiB,EAEvB,GAAU,EAAE,eAAe,EAE/B,MACF,CAEA,IAAM,EAAe,EAAM,SAAW,EAAY,MAIlD,EAAY,OAAO,CAAE,MAAO,EAAM,MAAO,CAAC,EAIrC,EAQO,EAAQ,YAGlB,EAAY,WAAW,EARnB,EAAQ,WACV,EAAY,QAAQ,EAEpB,EAAY,WAAW,EAQvB,GAAU,EAAY,eAAe,CAC3C,EAEA,IAAI,aAAc,CAChB,OAAO,GAAa,aAAe,CAAC,CACtC,GAEC,OAAO,SAAU,EAElB,IAAI,WAAY,CACd,OAAO,GAAa,WAAa,CACnC,CACF,CACF,CAgBA,SAAgB,EACd,EACA,EAC6B,CAC7B,IAAM,EAAW,SAAS,cAAc,KAAK,EAE7C,EAAS,MAAM,QAAU,EAAQ,WAC7B,oDACA,oDAEA,EAAQ,iBAAgB,EAAS,UAAY,EAAQ,gBAEzD,IAAM,EAAS,SAAS,cAAc,KAAK,EAE3C,EAAS,YAAY,CAAM,EAC3B,EAAU,YAAY,CAAQ,EAE9B,IAAI,EAEJ,GAAI,CACF,EAAO,EAAwB,CAC7B,GAAG,EACH,YAAa,EACb,cAAe,CACjB,CAAC,CACH,OAAS,EAAG,CAGV,MADA,EAAS,OAAO,EACV,CACR,CAIA,IAAM,EAAe,EAAK,QAAQ,KAAK,CAAI,EAE3C,OAAO,OAAO,OAAO,EAAM,CACzB,SAAU,CACR,EAAa,EACb,EAAS,OAAO,CAClB,EACA,CAAC,OAAO,UAAW,CACjB,EAAa,EACb,EAAS,OAAO,CAClB,CACF,CAAC,CACH,CC9hBA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACA,EACe,CASf,OARI,IAAU,QAAgB,EAE1B,IAAU,MAAc,EAAU,EAElC,IAAU,SAAiB,GAAa,EAAe,GAAY,EAEnE,GAAa,GAAiB,GAAW,EAAgB,EAAqB,KAE3E,EAAY,EAAgB,EAAY,EAAU,CAC3D,CC8GA,SAAgB,EAAsB,EAAsB,EAAkD,CACxG,OAAO,EAAQ,iBAAoB,UAAU,EAAsB,EAAQ,gBAAiB,iBAAiB,EAE7G,OAAO,EAAQ,iBAAoB,UAAU,EAAsB,EAAQ,gBAAiB,iBAAiB,EAE7G,EAAQ,mBAAqB,IAAA,IAAW,EAAyB,EAAQ,iBAAkB,kBAAkB,EAE7G,EAAQ,oBAAsB,IAAA,IAAW,EAAyB,EAAQ,kBAAmB,mBAAmB,EAEhH,EAAQ,SAAW,IAAA,IAAW,EAA0B,EAAQ,OAAQ,QAAQ,EAEhF,EAAQ,SAAW,IAAA,IAAW,EAA0B,EAAQ,OAAQ,QAAQ,EAEhF,EAAQ,YAAc,IAAA,IAAW,EAAiB,EAAQ,UAAW,WAAW,EAEhF,EAAQ,YAAc,IAAA,IAAW,EAAiB,EAAQ,UAAW,WAAW,EAEpF,IAAI,EAAW,EAA0B,EAAQ,SAAU,UAAU,EACjE,EAAW,EAA0B,EAAQ,SAAU,UAAU,EACjE,EAAgB,EAAkB,EAAQ,gBAAA,EAAsC,EAChF,EAAgB,EAAkB,EAAQ,gBAAA,EAAsC,EAChF,EAAY,EAAkB,EAAQ,UAAA,CAA2B,EACjE,EAAY,EAAkB,EAAQ,UAAA,CAA2B,EACjE,EAAwB,EAAQ,gBAAkB,GAElD,EAAW,EAAQ,SACnB,EAAgB,EAAQ,cAGxB,EAAmD,KACnD,EAAQ,SAEV,EAAc,EAAQ,OAAO,CADgB,KAAM,CAAC,EAAG,KAAM,CAAC,EAAG,YAAa,EAAG,WAAY,CAChE,CAAY,GAI3C,SAAS,EAAU,EAAmC,CAChD,IAAa,EAAY,MAAQ,GACrC,IAAW,CAAK,CAClB,CAGA,IAAM,EAAoC,EAAQ,qBAAuB,IAAI,IACvE,EAAoC,EAAQ,qBAAuB,IAAI,IAEzE,EAAY,EACZ,EAAa,EACb,EAAiB,EACjB,EAAgB,EAChB,EAAsB,CAAC,EACvB,EAAsB,CAAC,EACvB,EAAW,GAKT,EAAQ,EACZ,EACC,GAAM,EAAa,IAAI,CAAC,GAAK,EAAc,CAAC,EAC7C,EAAiB,EAAQ,QAAU,CAAC,CACtC,EAEM,EAAQ,EACZ,EACC,GAAM,EAAa,IAAI,CAAC,GAAK,EAAc,CAAC,EAC7C,EAAiB,EAAQ,QAAU,CAAC,CACtC,EAGI,EAAoB,GAIxB,SAAS,EAAS,EAAwB,CACxC,IAAM,EAAO,OAAO,SAAS,CAAM,EAAI,EAAS,EAEhD,OAAO,KAAK,IAAI,KAAK,IAAI,EAAG,EAAM,UAAY,CAAc,EAAG,KAAK,IAAI,EAAG,CAAI,CAAC,CAClF,CAEA,SAAS,EAAU,EAAwB,CACzC,IAAM,EAAO,OAAO,SAAS,CAAM,EAAI,EAAS,EAEhD,OAAO,KAAK,IAAI,KAAK,IAAI,EAAG,EAAM,UAAY,CAAa,EAAG,KAAK,IAAI,EAAG,CAAI,CAAC,CACjF,CAIA,SAAS,GAAuB,CAC9B,GAAI,EAAU,OAEd,IAAM,EAAc,EAAM,UACpB,EAAa,EAAM,UAEzB,GAAI,IAAa,GAAK,IAAa,GAAK,GAAkB,GAAK,GAAiB,EAAG,EAE/E,EAAM,YAAc,IAAM,EAAM,gBAAkB,GAAe,EAAM,gBAAkB,KAKzF,EAAM,YAAY,GAAI,EAAE,EACxB,EAAM,YAAY,GAAI,EAAE,EACxB,EAAO,CAAC,EACR,EAAO,CAAC,EACR,EAAU,CAAE,KAAM,CAAC,EAAG,KAAM,CAAC,EAAG,cAAa,YAAW,CAAC,GAG3D,MACF,CAEA,GAAM,CAAE,UAAW,EAAe,YAAa,GAAmB,EAAM,aACtE,EACA,EAAY,EACZ,EAAU,MACV,EAAU,GACZ,EAEM,CAAE,UAAW,EAAe,YAAa,GAAmB,EAAM,aACtE,EACA,EAAa,EACb,EAAU,MACV,EAAU,GACZ,EAEM,EAAU,EAAM,YAAY,EAAgB,CAAa,EACzD,EAAU,EAAM,YAAY,EAAgB,CAAa,EAE/D,GAAI,GAAW,EAAS,OAExB,EAAM,YAAY,EAAgB,CAAa,EAC/C,EAAM,YAAY,EAAgB,CAAa,GAE1B,CAAC,GAAW,CAAC,IAEd,GAClB,EAAc,CACZ,SAAU,EACV,SAAU,EACV,QAAS,EACT,QAAS,CACX,CAAC,EAIH,IAAM,EAA0B,CAAC,EAEjC,IAAK,IAAI,EAAI,EAAgB,GAAK,EAAe,IAC/C,EAAS,KAAK,EAAM,OAAO,CAAC,CAAC,EAG/B,IAAM,EAA0B,CAAC,EAEjC,IAAK,IAAI,EAAI,EAAgB,GAAK,EAAe,IAC/C,EAAS,KAAK,EAAM,OAAO,CAAC,CAAC,EAG/B,EAAO,EACP,EAAO,EAEP,EAAU,CAAE,OAAM,OAAM,cAAa,YAAW,CAAC,CACnD,CAIA,SAAS,EAAqB,EAAa,EAAuB,CAChE,GAAI,CAAC,OAAO,SAAS,CAAG,EAAG,MAAO,GAElC,IAAM,EAAI,KAAK,MAAM,CAAG,EAExB,GAAI,EAAI,GAAK,GAAK,EAAU,MAAO,GAEnC,IAAM,EAAI,EAAiB,EAAM,EAAE,EASnC,OAPI,GAAK,GAEL,EAAa,IAAI,CAAC,IAAM,EAAU,IAEtC,EAAa,IAAI,EAAG,CAAC,EACrB,EAAM,YAAY,CAAC,EAEZ,GACT,CAEA,SAAS,EAAqB,EAAa,EAAuB,CAChE,GAAI,CAAC,OAAO,SAAS,CAAG,EAAG,MAAO,GAElC,IAAM,EAAI,KAAK,MAAM,CAAG,EAExB,GAAI,EAAI,GAAK,GAAK,EAAU,MAAO,GAEnC,IAAM,EAAI,EAAiB,EAAM,EAAE,EASnC,OAPI,GAAK,GAEL,EAAa,IAAI,CAAC,IAAM,EAAU,IAEtC,EAAa,IAAI,EAAG,CAAC,EACrB,EAAM,YAAY,CAAC,EAEZ,GACT,CAMA,SAAS,GAAsB,CACzB,IAEJ,EAAoB,GACpB,mBAAqB,CACnB,EAAoB,GAKpB,IAAM,EAAU,EAAM,uBAAuB,EAEzC,IAAY,KAAU,EAAM,YAAY,CAAO,EAEnD,IAAM,EAAU,EAAM,uBAAuB,EAEzC,IAAY,KAAU,EAAM,YAAY,CAAO,EAEnD,EAAe,CACjB,CAAC,EACH,CAIA,SAAS,EAAW,EAAa,EAAoB,CAC/C,GAEA,EAAqB,EAAK,CAAI,GAAG,EAAc,CACrD,CAEA,SAAS,EAAc,EAAa,EAAoB,CAClD,GAEA,EAAqB,EAAK,CAAI,GAAG,EAAc,CACrD,CAGA,SAAS,EACP,EACA,EACM,CACN,GAAI,EAAU,OAEd,IAAI,EAAU,GAEd,IAAK,GAAM,CAAE,QAAO,UAAU,EACxB,EAAqB,EAAO,CAAI,IAAG,EAAU,IAGnD,IAAK,GAAM,CAAE,QAAO,UAAU,EACxB,EAAqB,EAAO,CAAI,IAAG,EAAU,IAG/C,GAAS,EAAc,CAC7B,CAEA,SAAS,EAAa,EAAa,EAA6B,CAG9D,OAFI,MAAuB,CAAC,EAErB,EAAc,EAAG,OAAQ,EAAK,GAAU,EAAW,EAAK,EAAM,YAAY,MAAM,CAAC,CAC1F,CAEA,SAAS,EAAa,EAAa,EAA6B,CAG9D,OAFI,MAAuB,CAAC,EAErB,EAAc,EAAG,OAAQ,EAAK,GAAU,EAAc,EAAK,EAAM,YAAY,KAAK,CAAC,CAC5F,CAEA,SAAS,IAAmB,CACtB,IAEJ,EAAa,MAAM,EACnB,EAAa,MAAM,EACnB,EAAM,QAAQ,EAAI,EAClB,EAAM,QAAQ,EAAI,EAClB,EAAe,EACjB,CAEA,SAAS,IAAgB,CACnB,IAEJ,EAAM,QAAQ,EAAI,EAClB,EAAM,QAAQ,EAAI,EAClB,EAAe,EACjB,CAEA,SAAS,EAAY,EAAkC,CACrD,GAAI,EAAU,OAEd,IAAM,EAAI,EAAiB,CAAkB,EAE7C,GAAI,IAAM,EAAG,OAEb,GAAY,EACZ,EAAM,SAAS,CAAQ,EACvB,EAAM,QAAQ,EAAI,EAElB,IAAM,EAAkB,EAAM,QAAQ,CAAC,EACjC,EAAe,EAAS,EAAY,CAAe,EAEzD,EAAQ,SAAS,EAAY,EAAc,MAAM,EACjD,EAAY,EAEZ,EAAe,CACjB,CAEA,SAAS,EAAY,EAAa,EAA2D,CAAC,EAAS,CACrG,GAAI,GAAY,IAAa,EAAG,OAEhC,IAAM,EAAU,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,MAAM,OAAO,SAAS,CAAG,EAAI,EAAM,CAAC,EAAG,EAAW,CAAC,CAAC,EACxF,EAAW,EAAK,UAAY,OAC5B,EAAW,EAAM,QAAQ,CAAO,EAChC,EAAU,EAAM,OAAO,CAAO,EAC9B,EAAY,EAChB,EACA,EAAW,EACX,EACA,EACA,EACA,EAAK,UAAY,MACnB,EAEI,IAAc,MAElB,EAAQ,SAAS,EAAY,EAAS,CAAS,EAAG,CAAQ,CAC5D,CAEA,SAAS,GAAe,EAAa,EAA2D,CAAC,EAAS,CACxG,GAAI,GAAY,IAAa,EAAG,OAEhC,IAAM,EAAU,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,MAAM,OAAO,SAAS,CAAG,EAAI,EAAM,CAAC,EAAG,EAAW,CAAC,CAAC,EACxF,EAAW,EAAK,UAAY,OAC5B,EAAW,EAAM,QAAQ,CAAO,EAChC,EAAU,EAAM,OAAO,CAAO,EAC9B,EAAa,EACjB,EACA,EAAW,EACX,EACA,EACA,EACA,EAAK,UAAY,MACnB,EAEI,IAAe,MAEnB,EAAQ,SAAS,EAAU,CAAU,EAAG,EAAW,CAAQ,CAC7D,CAEA,SAAS,GAAa,EAAa,EAAa,EAA4B,CAAC,EAAS,CACpF,GAAI,GAAY,IAAa,GAAK,IAAa,EAAG,OAElD,IAAM,EAAU,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,MAAM,OAAO,SAAS,CAAG,EAAI,EAAM,CAAC,EAAG,EAAW,CAAC,CAAC,EACxF,EAAU,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,MAAM,OAAO,SAAS,CAAG,EAAI,EAAM,CAAC,EAAG,EAAW,CAAC,CAAC,EACxF,EAAW,EAAK,UAAY,OAE5B,EAAW,EAAM,QAAQ,CAAO,EAChC,EAAU,EAAM,OAAO,CAAO,EAC9B,EAAW,EAAM,QAAQ,CAAO,EAChC,EAAU,EAAM,OAAO,CAAO,EAE9B,EAAY,EAChB,EACA,EAAW,EACX,EACA,EACA,EACA,EAAK,UAAY,MACnB,EACM,EAAa,EACjB,EACA,EAAW,EACX,EACA,EACA,EACA,EAAK,UAAY,MACnB,GAEI,IAAc,MAAQ,IAAe,OAEzC,EAAQ,SACN,IAAe,KAA+B,EAAxB,EAAU,CAAU,EAC1C,IAAc,KAA6B,EAAtB,EAAS,CAAS,EACvC,CACF,CACF,CAEA,SAAS,GAAO,EAA0C,CACxD,GAAI,EAAU,OAEV,EAAK,WAAa,IAAA,IAAW,EAA0B,EAAK,SAAU,UAAU,EAEhF,EAAK,WAAa,IAAA,IAAW,EAA0B,EAAK,SAAU,UAAU,EAEhF,OAAO,EAAK,iBAAoB,UAAU,EAAsB,EAAK,gBAAiB,iBAAiB,EAEvG,OAAO,EAAK,iBAAoB,UAAU,EAAsB,EAAK,gBAAiB,iBAAiB,EAEvG,EAAK,SAAW,IAAA,IAAW,EAA0B,EAAK,OAAQ,QAAQ,EAE1E,EAAK,SAAW,IAAA,IAAW,EAA0B,EAAK,OAAQ,QAAQ,EAE1E,EAAK,YAAc,IAAA,IAAW,EAAiB,EAAK,UAAW,WAAW,EAE1E,EAAK,YAAc,IAAA,IAAW,EAAiB,EAAK,UAAW,WAAW,EAE9E,IAAI,EAAc,GACd,EAAc,GACd,EAAe,GAMnB,GAJI,OAAO,OAAO,EAAM,UAAU,IAAG,EAAW,EAAK,UAEjD,OAAO,OAAO,EAAM,eAAe,IAAG,EAAgB,EAAK,eAE3D,EAAK,WAAa,IAAA,GAAW,CAC/B,IAAM,EAAI,EAAK,SAEX,IAAM,IACR,EAAW,EACX,EAAM,SAAS,CAAQ,EACvB,EAAc,GAElB,CAEA,GAAI,EAAK,WAAa,IAAA,GAAW,CAC/B,IAAM,EAAI,EAAK,SAEX,IAAM,IACR,EAAW,EACX,EAAM,SAAS,CAAQ,EACvB,EAAc,GAElB,CAEA,GAAI,EAAK,SAAW,IAAA,GAAW,CAC7B,IAAM,EAAI,EAAK,OAEX,IAAM,EAAM,MACd,EAAM,OAAO,CAAC,EACd,EAAc,GAElB,CAEA,GAAI,EAAK,SAAW,IAAA,GAAW,CAC7B,IAAM,EAAI,EAAK,OAEX,IAAM,EAAM,MACd,EAAM,OAAO,CAAC,EACd,EAAc,GAElB,CAcA,GAZI,oBAAqB,IACvB,EAAgB,EAAkB,EAAK,gBAAA,EAAsC,EAC7E,EAAa,MAAM,EACnB,EAAc,IAGZ,oBAAqB,IACvB,EAAgB,EAAkB,EAAK,gBAAA,EAAsC,EAC7E,EAAa,MAAM,EACnB,EAAc,IAGZ,EAAK,YAAc,IAAA,GAAW,CAChC,IAAM,EAAI,EAAkB,EAAK,UAAA,CAA2B,GAExD,EAAE,QAAU,EAAU,OAAS,EAAE,MAAQ,EAAU,OACrD,EAAY,EACZ,EAAe,GAEnB,CAEA,GAAI,EAAK,YAAc,IAAA,GAAW,CAChC,IAAM,EAAI,EAAkB,EAAK,UAAA,CAA2B,GAExD,EAAE,QAAU,EAAU,OAAS,EAAE,MAAQ,EAAU,OACrD,EAAY,EACZ,EAAe,GAEnB,CAEI,OAAO,OAAO,EAAM,gBAAgB,IACtC,EAAwB,EAAK,gBAAkB,IAG7C,GAAa,EAAM,QAAQ,EAAI,EAE/B,GAAa,EAAM,QAAQ,EAAI,GAE/B,GAAe,GAAe,IAAc,EAAe,CACjE,CAEA,IAAM,EAAK,IAAI,gBAEf,SAAS,IAAoB,CACvB,IAEJ,EAAW,GACX,EAAG,MAAM,EACT,EAAQ,OAAO,EACjB,CAIA,SAAS,IAAqB,CAC5B,EAAY,EAAS,EAAQ,EAAE,WAAW,CAAC,EAC3C,EAAa,EAAU,EAAQ,EAAE,WAAW,CAAC,EAC7C,EAAe,CACjB,CAEA,SAAS,IAAqB,CAC5B,EAAiB,EAAQ,EAAE,iBAAiB,EAC5C,EAAgB,EAAQ,EAAE,iBAAiB,EAC3C,EAAe,CACjB,CAEA,IAAM,EAAU,EAAoB,EAAQ,GAAc,EAAY,EAuBtE,GArBA,EAAM,QAAQ,EAAI,EAClB,EAAM,QAAQ,EAAI,EAIlB,EAAiB,EAAQ,EAAE,iBAAiB,EAC5C,EAAgB,EAAQ,EAAE,iBAAiB,EAEvC,EAAQ,mBAAqB,IAAA,GAEtB,EAAQ,oBAAsB,IAAA,IACvC,EAAQ,SAAS,EAAU,EAAQ,iBAAiB,EAAG,EAAG,MAAM,EAFhE,EAAQ,SAAS,EAAQ,mBAAqB,EAAG,EAAS,EAAQ,gBAAgB,EAAG,MAAM,EAK7F,EAAY,EAAS,EAAQ,EAAE,WAAW,CAAC,EAC3C,EAAa,EAAU,EAAQ,EAAE,WAAW,CAAC,EAE7C,EAAe,EAIX,EAAuB,CACzB,IAAM,EAAiB,GAAmB,CAGxC,GAFI,GACA,EAAE,aAAa,gBACf,IAAa,GAAK,IAAa,EAAG,OAEtC,IAAI,EAAU,GACV,EAAS,EACT,EAAU,EACR,EAAiB,KAAK,IAAI,EAAiB,GAAK,EAAiB,GAAG,EACpE,GACH,OAAO,EAAc,CAAC,GAAM,SAAW,EAAc,CAAC,EAAA,IACvD,EAAiB,EAAQ,QAAU,CAAC,EAChC,GACH,OAAO,EAAc,CAAC,GAAM,SAAW,EAAc,CAAC,EAAA,IACvD,EAAiB,EAAQ,QAAU,CAAC,EAEtC,OAAQ,EAAE,IAAV,CACE,IAAK,UACH,EAAS,KAAK,IAAI,EAAG,EAAY,CAAW,EAC5C,EAAU,GACV,MACF,IAAK,YACH,EAAS,KAAK,IAAI,EAAM,UAAY,EAAgB,EAAY,CAAW,EAC3E,EAAU,GACV,MACF,IAAK,YACH,EAAU,KAAK,IAAI,EAAG,EAAa,CAAW,EAC9C,EAAU,GACV,MACF,IAAK,aACH,EAAU,KAAK,IAAI,EAAM,UAAY,EAAe,EAAa,CAAW,EAC5E,EAAU,GACV,MACF,IAAK,SACH,EAAS,KAAK,IAAI,EAAG,EAAY,CAAc,EAC/C,EAAU,GACV,MACF,IAAK,WACH,EAAS,KAAK,IAAI,EAAM,UAAY,EAAgB,EAAY,CAAc,EAC9E,EAAU,GACV,MACF,IAAK,OACH,EAAS,EACT,EAAU,GACV,MACF,IAAK,MACH,EAAS,KAAK,IAAI,EAAG,EAAM,UAAY,CAAc,EACrD,EAAU,EAEd,CAEI,IACF,EAAE,eAAe,EACjB,EAAQ,SAAS,EAAS,EAAQ,MAAM,EACxC,EAAY,EAAS,CAAM,EAC3B,EAAa,EAAU,CAAO,EACzB,GAAU,EAAe,EAElC,EAEI,OAAO,GAAW,UAAY,GAAmB,qBAAsB,IACzE,EAAwB,iBAAiB,UAAW,CAAa,EACjE,EAAG,OAAO,iBAAiB,YAAe,CACxC,EAAwB,oBAAoB,UAAW,CAAa,CACtE,CAAC,EAEL,CAEA,MAAO,CACL,IAAI,MAAO,CACT,OAAO,CACT,EACA,IAAI,gBAAiB,CACnB,OAAO,EAAG,MACZ,EACA,QAAS,GACT,IAAI,UAAW,CACb,OAAO,CACT,EACA,cACA,eACA,eACA,gBACA,aACA,eACA,cACA,WACA,IAAI,MAAO,CACT,OAAO,CACT,EACA,IAAI,YAAa,CACf,OAAO,CACT,EACA,gBACA,kBACA,IAAI,WAAY,CACd,OAAO,CACT,EACA,eACC,OAAO,SAAU,GAClB,IAAI,aAAc,CAChB,OAAO,EAAM,SACf,EACA,IAAI,YAAa,CACf,OAAO,EAAM,SACf,EACA,SACF,CACF,CCxpBA,SAAS,EAAoB,EAAkD,CAC7E,IAAM,EAAuB,CAAC,EAE9B,IAAK,IAAI,EAAI,EAAG,EAAI,EAAS,OAAQ,IAAK,CACxC,IAAM,EAAU,EAAS,GAEzB,EAAK,KAAK,CAAE,SAAU,GAAM,UAAW,GAAI,MAAO,EAAQ,MAAO,aAAc,CAAE,CAAC,EAElF,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,MAAM,OAAQ,IACxC,EAAK,KAAK,CAAE,SAAU,GAAO,KAAM,EAAQ,MAAM,GAAK,UAAW,EAAG,aAAc,CAAE,CAAC,CAEzF,CAEA,OAAO,CACT,CAIA,SAAgB,EACd,EACA,EACqB,CACjB,OAAO,EAAQ,oBAAuB,UACxC,EAAsB,EAAQ,mBAAoB,oBAAoB,EAEpE,OAAO,EAAQ,kBAAqB,UAAU,EAAsB,EAAQ,iBAAkB,kBAAkB,EAEhH,EAAQ,WAAa,IAAA,IAAW,EAAiB,EAAQ,QAAQ,EAEjE,EAAQ,iBAAmB,IAAA,IAAW,EAAyB,EAAQ,eAAgB,gBAAgB,EAE3G,IAAI,EAAW,EAAQ,SACnB,EAAO,EAAiB,CAAQ,EAEpC,SAAS,EACP,EAC2B,CAC3B,OAAO,OAAO,GAAuB,WAChC,GAAkB,CACjB,IAAM,EAAQ,EAAK,GAEnB,OAAQ,EACN,EAAS,EAAM,cACf,EAAM,YACR,CACF,EACA,EACE,OAAO,GAAuB,SAAW,EAAqB,IAAA,GAAA,EAEhE,CACN,CAEA,IAAI,EAAmB,EAAoB,EAAQ,kBAAkB,EACjE,EACF,OAAO,EAAQ,kBAAqB,WAAa,EAAQ,iBAAmB,KAC1E,EAA0D,EAC1D,KACA,EACE,OAAO,EAAQ,kBAAqB,SAAW,EAAQ,iBAAmB,IAAA,GAAA,EAE5E,EACA,EAAiB,EAAQ,WAE7B,SAAS,EAAW,EAA6B,CAC/C,IAAM,EAAQ,EAAK,GAQnB,OANK,EAED,EAAM,SAAiB,EAAiB,CAAW,EAEnD,EAA2B,EAAmB,EAAM,KAAM,EAAM,UAAW,EAAM,YAAY,EAE1F,EAAqB,CAAW,EAN3B,EAOd,CAEA,SAAS,EAAW,EAAiC,CACnD,IAAM,EAAQ,EAAK,GAQnB,OANK,EAED,EAAM,SAAiB,YAAY,EAAM,eAEzC,EAAuB,EAAe,EAAM,KAAM,EAAM,UAAW,EAAM,YAAY,EAElF,EANY,CAOrB,CAEA,IAAI,EAAY,GACZ,EAAwC,CAAC,EACzC,EAAW,EAAQ,SACnB,EAAc,EAAQ,YACtB,EAAoB,EAAQ,kBAG5B,EAAuD,KACvD,EAAQ,SAEV,EAAc,EAAQ,OAAO,CADoB,QAAS,CAAC,EAAG,MAAO,CAAC,EAAG,aAAc,KAAM,UAAW,CAC3E,CAAY,GAI3C,SAAS,EAAU,EAAuC,CACpD,IAAa,EAAY,MAAQ,GACrC,IAAW,CAAK,CAClB,CAEA,SAAS,EAAS,EAAmD,CACnE,IAAM,EAAoC,CAAC,EACrC,EAAgC,CAAC,EACnC,EAA0C,KAE9C,IAAK,IAAM,KAAM,EAAM,MAAO,CAC5B,IAAM,EAAQ,EAAK,EAAG,OAEjB,IAED,EAAM,SACR,EAAQ,KAAK,CAAE,GAAG,EAAI,MAAO,EAAM,OAAS,GAAI,aAAc,EAAM,YAAa,CAAC,EAElF,EAAM,KAAK,CAAE,GAAG,EAAI,KAAM,EAAM,KAAM,UAAW,EAAM,UAAW,aAAc,EAAM,YAAa,CAAC,EAExG,CAEA,GAAI,EAAM,YAAY,OAAS,EAAG,CAChC,IAAM,EAAM,EAAM,YAAY,GACxB,EAAQ,EAAK,EAAI,OAEnB,GAAO,WACT,EAAe,CAAE,GAAG,EAAK,MAAO,EAAM,OAAS,GAAI,aAAc,EAAM,YAAa,EAExF,CAIA,MAFA,GAAY,EAEL,CAAE,UAAS,QAAO,eAAc,UAAW,EAAM,SAAU,CACpE,CAEA,IAAM,EAAc,EAAkB,EAAQ,CAC5C,MAAO,EAAK,OACZ,aAAc,EACd,aACA,WAAY,EAAQ,WACpB,iBAAkB,EAAQ,iBAC1B,SAAW,GAAU,CAGnB,EAFe,EAAS,CAEd,CAAM,CAClB,EACA,YAAc,GAAW,IAAc,CAAM,EAC7C,kBAAoB,GAAgB,IAAoB,CAAW,EACnE,SAAU,EAAQ,UAAA,EAClB,eAAgB,EAAQ,eACxB,OAAS,GAAM,EAAK,EAAE,EAAE,UAAY,EACtC,CAAC,EAEK,EAAK,IAAI,gBAEf,SAAS,GAAiB,CACpB,IAEJ,EAAY,GACZ,EAAG,MAAM,EACT,EAAY,QAAQ,EACtB,CAIA,SAAS,EAAY,EAAsB,EAA4B,CACrE,GAAI,EAAe,GAAK,GAAgB,EAAS,OAAQ,MAAO,GAEhE,IAAI,EAAM,EAEV,IAAK,IAAI,EAAI,EAAG,EAAI,EAAS,OAAQ,IAAK,CACxC,GAAI,IAAM,EAGR,OAFI,IAAc,IAAA,GAAkB,EAE7B,EAAM,EAAI,EAGnB,GAAO,EAAI,EAAS,EAAE,CAAE,MAAM,MAChC,CAEA,MAAO,EACT,CAEA,MAAO,CACL,IAAI,OAAQ,CACV,OAAO,EAAY,KACrB,EACA,IAAI,gBAAiB,CACnB,OAAO,EAAG,MACZ,EACA,QAAS,EACT,IAAI,UAAW,CACb,OAAO,CACT,EACA,YAAa,CACP,GAEJ,EAAY,WAAW,CACzB,EACA,IAAI,aAAc,CAChB,OAAO,EAAY,WACrB,EACA,IAAI,OAAQ,CACV,OAAO,CACT,EACA,QAAQ,EAAe,EAAc,CAC/B,GAEJ,EAAY,QAAQ,EAAO,CAAI,CACjC,EACA,aAAa,EAAiD,CACxD,GAEJ,EAAY,aAAa,CAAO,CAClC,EACA,UAAU,EAAe,EAAiB,CAGxC,OAFI,MAAwB,CAAC,EAEtB,EAAY,UAAU,EAAO,CAAE,CACxC,EACA,SAAU,CACJ,GAEJ,EAAY,QAAQ,CACtB,EACA,IAAI,cAAe,CACjB,OAAO,EAAY,YACrB,EACA,eAAe,EAAsC,CAC/C,GAEJ,EAAY,eAAe,CAAI,CACjC,EACA,cAAc,EAAe,EAA6B,CACpD,GAEJ,EAAY,cAAc,EAAO,CAAI,CACvC,EACA,aAAa,EAAc,EAAW,EAAO,CAAC,EAAG,CAC/C,GAAI,EAAW,OAEf,IAAM,EAAc,EAAY,EAAc,CAAS,EAEnD,EAAc,GAElB,EAAY,cAAc,EAAa,CAAI,CAC7C,EACA,eAAe,EAAgB,EAAsC,CAC/D,GAEJ,EAAY,eAAe,EAAQ,CAAI,CACzC,EACA,gBAAgB,EAAc,EAAO,CAAC,EAAG,CACvC,GAAI,EAAW,OAEf,IAAM,EAAc,EAAY,CAAY,EAExC,EAAc,GAElB,EAAY,cAAc,EAAa,CAAI,CAC7C,EACA,YAAY,EAAsC,CAC5C,GAEJ,EAAY,YAAY,CAAI,CAC9B,EACA,IAAI,aAAc,CAChB,OAAO,EAAY,WACrB,GACC,OAAO,SAAU,EAClB,IAAI,WAAY,CACd,OAAO,EAAY,SACrB,EAOA,OAAO,EAAc,EAAyC,CAC5D,GAAI,EAAW,OAEX,IACE,OAAO,EAAK,oBAAuB,UACrC,EAAsB,EAAK,mBAAoB,oBAAoB,EAEjE,OAAO,EAAK,kBAAqB,UAAU,EAAsB,EAAK,iBAAkB,kBAAkB,EAE1G,EAAK,WAAa,IAAA,IAAW,EAAiB,EAAK,QAAQ,EAE3D,EAAK,iBAAmB,IAAA,IAAW,EAAyB,EAAK,eAAgB,gBAAgB,EAEjG,OAAO,OAAO,EAAM,UAAU,IAAG,EAAW,EAAK,UAEjD,OAAO,OAAO,EAAM,aAAa,IAAG,EAAc,EAAK,aAEvD,OAAO,OAAO,EAAM,mBAAmB,IAAG,EAAoB,EAAK,mBAGnE,EAAK,qBAAuB,IAAA,KAC9B,EAAmB,EAAoB,EAAK,kBAAkB,GAG5D,EAAK,mBAAqB,IAAA,KAC5B,EAAqB,OAAO,EAAK,kBAAqB,WAAa,EAAK,iBAAmB,KAC3F,EAAsB,EAClB,KACA,EACE,OAAO,EAAK,kBAAqB,SAAW,EAAK,iBAAmB,IAAA,GAAA,EAEtE,GAGF,EAAK,aAAe,IAAA,KAAW,EAAiB,EAAK,aAG3D,EAAW,EAEX,IAAM,EAAY,EAAK,OAEvB,EAAO,EAAiB,CAAQ,EAEhC,IAAM,EAA2C,CAAE,MAAO,EAAK,MAAO,EAElE,GAAM,mBAAqB,IAAA,KAAW,EAAe,iBAAmB,EAAK,kBAE7E,GAAM,WAAa,IAAA,KAAW,EAAe,SAAW,EAAK,UAE7D,GAAM,iBAAmB,IAAA,KAAW,EAAe,eAAiB,EAAK,gBAG3E,EAAK,SAAW,GAChB,GAAM,mBAAqB,IAAA,IAC3B,GAAM,WAAa,IAAA,IACnB,GAAM,iBAAmB,IAAA,GAEzB,EAAY,OAAO,CAAc,EAEjC,EAAY,QAAQ,CAExB,CACF,CACF"}
|
|
1
|
+
{"version":3,"file":"scroll.js","names":[],"sources":["../src/_dev.ts","../src/_utils.ts","../src/errors.ts","../src/_validation.ts","../src/_adapter.ts","../src/_axis1d.ts","../src/_sticky.ts","../src/virtualizer.ts","../src/dom-virtual-list.ts","../src/_alignment.ts","../src/grid-virtualizer.ts","../src/grouped-virtualizer.ts"],"sourcesContent":["const isDev = !(globalThis as { __SCROLL_PROD__?: boolean }).__SCROLL_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/scroll] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import { warn } from './_dev';\n\nexport type ScrollTarget = HTMLElement | Window;\n\nexport type VirtualKey = number | string;\n\n/** Shared measurement cache. Pass the same instance to multiple virtualizers\n * for scroll restoration or SSR pre-measurement. */\nexport type MeasurementCache = Map<VirtualKey, number>;\n\n/** Create a new, empty measurement cache. */\nexport function createMeasurementCache(): MeasurementCache {\n return new Map();\n}\n\nexport type Overscan = number | { end?: number; start?: number };\n\nexport const DEFAULT_ESTIMATE_SIZE = 36;\nexport const DEFAULT_OVERSCAN = 3;\n\n// ─── Numeric helpers ───────────────────────────────────────────────────────────\n\nexport function toNonNegativeInt(value: number, fallback = 0): number {\n if (!Number.isFinite(value)) return fallback;\n\n return Math.max(0, Math.floor(value));\n}\n\nexport function toPositiveNumber(value: number, fallback: number): number {\n if (!Number.isFinite(value) || value <= 0 || value > 1e7) return fallback;\n\n return value;\n}\n\nexport function isWindowTarget(target: ScrollTarget): target is Window {\n return (\n (typeof Window !== 'undefined' && target instanceof Window) ||\n (typeof (target as Window).innerHeight === 'number' && typeof (target as Window).document === 'object')\n );\n}\n\nexport function normalizeOverscan(overscan: Overscan | undefined, defaultVal: number): { end: number; start: number } {\n if (typeof overscan === 'number') {\n const n = toNonNegativeInt(overscan, defaultVal);\n\n return { end: n, start: n };\n }\n\n return {\n end: toNonNegativeInt(overscan?.end ?? defaultVal),\n start: toNonNegativeInt(overscan?.start ?? defaultVal),\n };\n}\n\n/**\n * Create a `ResizeObserver` that reports size changes on `el` via `onSize`, tied to\n * `signal` so it is disconnected automatically when the owning factory disposes —\n * even if the caller never calls the returned disconnect function.\n */\nexport function observeResize(\n signal: AbortSignal,\n el: HTMLElement,\n onSize: (entry: ResizeObserverEntry) => void,\n): () => void {\n const ro = new ResizeObserver((entries) => {\n for (const entry of entries) onSize(entry);\n });\n\n ro.observe(el);\n signal.addEventListener('abort', () => ro.disconnect(), { once: true });\n\n return () => ro.disconnect();\n}\n\nexport function resolveEstimateFn(\n estimate: number | ((index: number) => number) | undefined,\n defaultSize: number,\n): (index: number) => number {\n if (typeof estimate === 'function') {\n return (index: number) => {\n try {\n return toPositiveNumber(estimate(index), defaultSize);\n } catch (err) {\n warn(`estimateSize threw for index ${index}: ${err}`);\n\n return defaultSize;\n }\n };\n }\n\n const size = typeof estimate === 'number' ? toPositiveNumber(estimate, defaultSize) : defaultSize;\n\n return () => size;\n}\n","/** Base class for all scroll errors. */\nexport class ScrollError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a virtualizer receives an invalid static configuration. */\nexport class ScrollConfigurationError extends ScrollError {}\n\n/** Thrown when a virtual-list index is out of bounds. */\nexport class ScrollRangeError extends ScrollError {}\n","import { ScrollConfigurationError } from './errors';\n\nexport function requireNonNegativeInteger(value: number, name: string): number {\n if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {\n throw new ScrollConfigurationError(`${name} must be a finite non-negative integer.`);\n }\n\n return value;\n}\n\nexport function requireNonNegativeNumber(value: number, name: string): number {\n if (!Number.isFinite(value) || value < 0) {\n throw new ScrollConfigurationError(`${name} must be a finite non-negative number.`);\n }\n\n return value;\n}\n\nexport function requirePositiveNumber(value: number, name: string): number {\n if (!Number.isFinite(value) || value <= 0 || value > 1e7) {\n throw new ScrollConfigurationError(`${name} must be a finite positive number no greater than 10000000.`);\n }\n\n return value;\n}\n\nexport function validateOverscan(overscan: number | { end?: number; start?: number }, name = 'overscan'): void {\n if (typeof overscan === 'number') {\n requireNonNegativeInteger(overscan, name);\n\n return;\n }\n\n if (overscan === null || typeof overscan !== 'object' || Array.isArray(overscan)) {\n throw new ScrollConfigurationError(`${name} must be a finite non-negative integer or an overscan object.`);\n }\n\n if (overscan.start !== undefined) requireNonNegativeInteger(overscan.start, `${name}.start`);\n\n if (overscan.end !== undefined) requireNonNegativeInteger(overscan.end, `${name}.end`);\n}\n","import { isWindowTarget, type ScrollTarget } from './_utils';\n\n/** Single-axis read/write interface for either horizontal or vertical scroll. */\nexport type Axis = {\n readOffset: () => number;\n readViewportSize: () => number;\n writeOffset: (offset: number, behavior: ScrollBehavior) => void;\n};\n\n/** Unified two-axis scroll adapter returned by `createScrollAdapter`. */\nexport type ScrollAdapter = {\n /** Detach all listeners (scroll + resize). Call in _dispose(). */\n detach: () => void;\n /** Write both axes at once (avoids double scrollTo call). */\n scrollTo: (left: number, top: number, behavior: ScrollBehavior) => void;\n x: Axis;\n y: Axis;\n};\n\n/**\n * Attach scroll and resize listeners on a `ScrollTarget` and return a\n * two-axis `ScrollAdapter`. Handles both `Window` and `HTMLElement` targets.\n */\nexport function createScrollAdapter(target: ScrollTarget, onScroll: () => void, onResize: () => void): ScrollAdapter {\n if (isWindowTarget(target)) {\n target.addEventListener('scroll', onScroll, { passive: true });\n target.addEventListener('resize', onResize, { passive: true });\n\n return {\n detach() {\n target.removeEventListener('scroll', onScroll);\n target.removeEventListener('resize', onResize);\n },\n scrollTo: (left, top, behavior) => target.scrollTo({ behavior, left, top }),\n x: {\n readOffset: () => target.scrollX,\n readViewportSize: () => target.innerWidth,\n writeOffset: (offset, behavior) => target.scrollTo({ behavior, left: offset }),\n },\n y: {\n readOffset: () => target.scrollY,\n readViewportSize: () => target.innerHeight,\n writeOffset: (offset, behavior) => target.scrollTo({ behavior, top: offset }),\n },\n };\n }\n\n // ⚠️ ResizeObserver is assumed to exist (supported in all modern browsers).\n // In SSR or older environments, callers must polyfill before constructing a virtualizer.\n const resizeObserver = new ResizeObserver(onResize);\n\n target.addEventListener('scroll', onScroll, { passive: true });\n resizeObserver.observe(target);\n\n return {\n detach() {\n target.removeEventListener('scroll', onScroll);\n resizeObserver.disconnect();\n },\n scrollTo: (left, top, behavior) => target.scrollTo({ behavior, left, top }),\n x: {\n readOffset: () => target.scrollLeft,\n readViewportSize: () => target.clientWidth,\n writeOffset: (offset, behavior) => target.scrollTo({ behavior, left: offset }),\n },\n y: {\n readOffset: () => target.scrollTop,\n readViewportSize: () => target.clientHeight,\n writeOffset: (offset, behavior) => target.scrollTo({ behavior, top: offset }),\n },\n };\n}\n","/**\n * Shared 1D axis primitive: prefix-sum offset table, O(log n) binary search,\n * incremental rebuild, measurement batching via microtask, and dedup guards.\n *\n * Used directly by grid-virtualizer (numeric row/col indices) and composed\n * into virtualizer (which layers VirtualKey translation on top).\n */\n\n/** A single rendered virtual item — position and size on one axis. */\nexport interface VirtualItem {\n end: number;\n index: number;\n size: number;\n start: number;\n}\n\nexport interface Axis1DRange {\n renderEnd: number;\n renderStart: number;\n}\n\n/**\n * Create a self-contained 1D offset-table axis.\n *\n * @param initialCount - Number of items.\n * @param sizeAtFn - Returns the resolved size for a given index (measured or estimated).\n * The virtualizer passes a closure that reads the live measurement cache,\n * so changes to the cache are reflected automatically without re-setting.\n * @param initialGap - Gap between consecutive items (non-negative integer, pixels).\n */\nexport function createAxis1D(initialCount: number, sizeAtFn: (index: number) => number, initialGap: number) {\n let count = initialCount;\n let gap = initialGap;\n const sizeAt = sizeAtFn;\n\n let offsets: number[] = [];\n let totalSize = 0;\n\n // ─── Dedup guards ───────────────────────────────────────────────────────────\n // Track the last-emitted render range and total size so computeVisible() can\n // skip re-rendering when nothing changed.\n\n let prevStart = -1;\n let prevEnd = -1;\n let prevTotalSize = -1;\n\n // ─── Pending measurement flush ─────────────────────────────────────────────\n\n let pendingBuild = false;\n let minChangedIndex = Infinity;\n\n // ─── Offset helpers ─────────────────────────────────────────────────────────\n\n function startAt(i: number): number {\n return offsets[i] ?? 0;\n }\n\n function endAt(i: number): number {\n return startAt(i) + sizeAt(i);\n }\n\n // ─── Full rebuild ───────────────────────────────────────────────────────────\n\n /**\n * Full O(n) prefix-sum rebuild.\n * Pass `forceEmit = true` to reset dedup guards, ensuring computeVisible()\n * emits on the next call even if the rendered range is unchanged.\n */\n function rebuild(forceEmit = false): void {\n if (forceEmit) {\n prevStart = -1;\n prevEnd = -1;\n prevTotalSize = -1;\n }\n\n const next: number[] = [0];\n let pos = 0;\n\n for (let i = 0; i < count; i++) {\n pos += sizeAt(i) + (i < count - 1 ? gap : 0);\n next.push(pos);\n }\n\n offsets = next;\n totalSize = pos;\n }\n\n // ─── Incremental rebuild ────────────────────────────────────────────────────\n\n /**\n * O(count - fromIndex) incremental rebuild — used after measurement.\n * Always resets dedup guards because measurements change layout.\n */\n function rebuildFrom(fromIndex: number): void {\n if (!Number.isFinite(fromIndex) || fromIndex >= count) return;\n\n prevStart = -1;\n prevEnd = -1;\n prevTotalSize = -1;\n\n let pos = offsets[fromIndex] ?? 0;\n\n for (let i = fromIndex; i < count; i++) {\n offsets[i] = pos;\n pos += sizeAt(i) + (i < count - 1 ? gap : 0);\n }\n\n offsets[count] = pos;\n totalSize = pos;\n }\n\n // ─── Binary search ──────────────────────────────────────────────────────────\n\n function findFirst(viewportStart: number): number {\n let lo = 0;\n let hi = count - 1;\n\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n\n if (endAt(mid) <= viewportStart) lo = mid + 1;\n else hi = mid;\n }\n\n return lo;\n }\n\n function findLast(viewportEnd: number, from: number): number {\n let lo = from;\n let hi = count - 1;\n\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n\n if (startAt(mid) < viewportEnd) lo = mid;\n else hi = mid - 1;\n }\n\n return lo;\n }\n\n /**\n * Compute the render range for a given viewport window and overscan values.\n * Returns indices clamped to [0, count - 1].\n */\n function computeRange(\n viewportStart: number,\n viewportEnd: number,\n overscanStart: number,\n overscanEnd: number,\n ): Axis1DRange {\n const first = findFirst(viewportStart);\n const last = findLast(viewportEnd, first);\n\n return {\n renderEnd: Math.min(count - 1, last + overscanEnd),\n renderStart: Math.max(0, first - overscanStart),\n };\n }\n\n // ─── Dedup helpers ──────────────────────────────────────────────────────────\n\n /** Returns `true` when start/end/totalSize are all identical to the last commit. */\n function isDedupSame(start: number, end: number): boolean {\n return start === prevStart && end === prevEnd && totalSize === prevTotalSize;\n }\n\n function commitDedup(start: number, end: number): void {\n prevStart = start;\n prevEnd = end;\n prevTotalSize = totalSize;\n }\n\n function resetDedup(): void {\n prevStart = -1;\n prevEnd = -1;\n prevTotalSize = -1;\n }\n\n // ─── Microtask scheduling ───────────────────────────────────────────────────\n\n /**\n * Schedule an incremental rebuild on the next microtask, then call `onFlush`.\n * Multiple calls before the microtask fires are coalesced into one rebuild.\n * Returns without scheduling if a build is already pending.\n */\n function scheduleBuild(onFlush: () => void): void {\n if (pendingBuild) return;\n\n pendingBuild = true;\n queueMicrotask(() => {\n pendingBuild = false;\n\n const from = minChangedIndex;\n\n minChangedIndex = Infinity;\n rebuildFrom(from);\n onFlush();\n });\n }\n\n /** Mark an item index as having a changed measurement. Drives incremental rebuilds. */\n function markChanged(index: number): void {\n if (index < minChangedIndex) minChangedIndex = index;\n }\n\n // ─── Item descriptor ────────────────────────────────────────────────────────\n\n /** Build a VirtualItem for a given index using current offset table. */\n function itemAt(index: number): VirtualItem {\n const start = startAt(index);\n const size = sizeAt(index);\n\n return { end: start + size, index, size, start };\n }\n\n // ─── Returned object ────────────────────────────────────────────────────────\n\n return {\n commitDedup,\n computeRange,\n /**\n * Atomically read and reset minChangedIndex to Infinity.\n * Used by grid-virtualizer's coordinated flush so it can consume the\n * changed-index without going through axis1d's own scheduleBuild.\n */\n consumeMinChangedIndex(): number {\n const idx = minChangedIndex;\n\n minChangedIndex = Infinity;\n\n return idx;\n },\n get count(): number {\n return count;\n },\n endAt,\n findFirst,\n findLast,\n /** Current gap between items (pixels). Used by callers to dedup gap changes. */\n get gap(): number {\n return gap;\n },\n isDedupSame,\n itemAt,\n markChanged,\n get minChangedIndex(): number {\n return minChangedIndex;\n },\n get pendingBuild(): boolean {\n return pendingBuild;\n },\n get prevEnd(): number {\n return prevEnd;\n },\n get prevStart(): number {\n return prevStart;\n },\n get prevTotalSize(): number {\n return prevTotalSize;\n },\n rebuild,\n rebuildFrom,\n resetDedup,\n scheduleBuild,\n setCount(n: number): void {\n count = n;\n },\n setGap(n: number): void {\n gap = n;\n },\n sizeAt(i: number): number {\n return sizeAt(i);\n },\n startAt,\n get totalSize(): number {\n return totalSize;\n },\n };\n}\n\nexport type Axis1D = ReturnType<typeof createAxis1D>;\n","/**\n * Shared sticky item computation logic.\n * Used by virtualizer and grid-virtualizer for finding and pinning sticky items.\n */\n\nimport type { VirtualItem } from './_axis1d';\n\nexport interface StickyComputeContext {\n count: number;\n scrollOffset: number;\n sizeAt: (index: number) => number;\n startAt: (index: number) => number;\n stickyFn: ((index: number) => boolean) | null;\n}\n\n/**\n * Compute the sticky item(s) for a 1D scroll axis.\n * Returns the single sticky item that should be pinned at the viewport start,\n * accounting for the next sticky item's position.\n *\n * @param ctx Context with functions to query item positions and sizes\n * @returns Array containing at most one sticky item (pinned to viewport)\n */\nexport function computeStickyItems(ctx: StickyComputeContext): VirtualItem[] {\n if (!ctx.stickyFn || ctx.count === 0 || ctx.scrollOffset <= 0) return [];\n\n // Binary search to find the last item that starts before the viewport\n let lastAbove = -1;\n let lo = 0;\n let hi = ctx.count - 1;\n\n while (lo <= hi) {\n const mid = (lo + hi) >> 1;\n\n if (ctx.startAt(mid) < ctx.scrollOffset) {\n lastAbove = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n\n if (lastAbove < 0) return [];\n\n // Scan backwards from lastAbove to find the most recent sticky item\n let activeIdx = -1;\n\n for (let i = lastAbove; i >= 0; i--) {\n if (ctx.stickyFn(i)) {\n activeIdx = i;\n break;\n }\n }\n\n if (activeIdx === -1) return [];\n\n // Find the next sticky item to determine how far we can pin\n const activeSize = ctx.sizeAt(activeIdx);\n let nextStickyStart = Infinity;\n\n for (let i = activeIdx + 1; i < ctx.count; i++) {\n if (ctx.stickyFn(i)) {\n nextStickyStart = ctx.startAt(i);\n break;\n }\n }\n\n // Pin the active sticky item, but don't let it push past the next sticky item\n const pinnedStart = Math.min(ctx.scrollOffset, nextStickyStart - activeSize);\n\n return [{ end: pinnedStart + activeSize, index: activeIdx, size: activeSize, start: pinnedStart }];\n}\n","import type { Signal } from '@vielzeug/ripple';\n\nimport { createScrollAdapter } from './_adapter';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport { computeStickyItems } from './_sticky';\nimport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n normalizeOverscan,\n type Overscan,\n observeResize,\n resolveEstimateFn,\n type ScrollTarget,\n toNonNegativeInt,\n toPositiveNumber,\n type VirtualKey,\n} from './_utils';\nimport {\n requireNonNegativeInteger,\n requireNonNegativeNumber,\n requirePositiveNumber,\n validateOverscan,\n} from './_validation';\n\nexport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n type ScrollTarget,\n type VirtualItem,\n type VirtualKey,\n};\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualizerState {\n readonly items: VirtualItem[];\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n}\n\nexport interface VirtualizerOptions {\n /**\n * Auto-attach ResizeObserver to visible items.\n * Elements must have a `data-vz-key` attribute set to the item key\n * so the virtualizer can locate and measure them after render.\n */\n autoMeasure?: boolean;\n count: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: (index: number) => VirtualKey;\n horizontal?: boolean;\n initialOffset?: number;\n /** Enable keyboard scroll (arrow keys, Page Up/Down, Home, End). */\n keyboardScroll?: boolean;\n /** External measurement cache for scroll restoration or SSR pre-measurement. */\n measurementCache?: MeasurementCache;\n /** Called after every render cycle with the new state. Replace through `update()`. */\n onChange?: (state: VirtualizerState) => void;\n /** Called when scrolling has settled. Replace through `update()`. */\n onScrollEnd?: (offset: number) => void;\n /** Called when the scrolling state changes. Replace through `update()`. */\n onScrollingChange?: (isScrolling: boolean) => void;\n overscan?: Overscan;\n /**\n * Debounce delay (ms) used to detect scroll end when the native `scrollend`\n * event is unavailable. Defaults to 150.\n */\n scrollEndDelay?: number;\n sticky?: (index: number) => boolean;\n /** Optional signal factory for reactive state. */\n toSignal?: (init: VirtualizerState) => Signal<VirtualizerState>;\n}\n\n/**\n * Options accepted by `update()`. Explicit interface instead of Omit<...>\n * for better IDE hover and autocomplete (R7).\n *\n * Intentionally excludes `horizontal` (axis cannot change at runtime) and\n * `initialOffset` (one-time bootstrap value).\n */\nexport interface VirtualizerUpdateOptions {\n /**\n * Auto-attach ResizeObserver to visible items.\n * Elements must have a `data-vz-key` attribute set to the item key\n * so the virtualizer can locate and measure them after render.\n */\n autoMeasure?: boolean;\n count?: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: ((index: number) => VirtualKey) | undefined;\n keyboardScroll?: boolean;\n /** Replace the active measurement cache. Existing entries in the new cache are used immediately on the next rebuild. */\n measurementCache?: MeasurementCache;\n onChange?: ((state: VirtualizerState) => void) | undefined;\n onScrollEnd?: ((offset: number) => void) | undefined;\n onScrollingChange?: ((isScrolling: boolean) => void) | undefined;\n overscan?: Overscan;\n scrollEndDelay?: number;\n sticky?: ((index: number) => boolean) | undefined;\n}\n\nexport interface ScrollToIndexOptions {\n align?: 'auto' | 'center' | 'end' | 'start';\n behavior?: ScrollBehavior;\n /** Called when the scroll animation completes (instant scrolls: next microtask). */\n onComplete?: () => void;\n}\n\nexport interface Virtualizer {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n dispose: () => void;\n readonly disposed: boolean;\n invalidate: () => void;\n /**\n * `true` when scrolled to (or within `threshold` px of) the end — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Check this **before** appending\n * items to decide whether to auto-follow them (e.g. a chat \"stick to bottom\" pattern) —\n * `createDomVirtualList`'s `stickToBottom` option does exactly this. Default `threshold`: `0`.\n */\n isAtEnd: (threshold?: number) => boolean;\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n /** Currently rendered items. Always populated. */\n readonly items: VirtualItem[];\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` and auto-measure `index` on resize. Returns a disconnect fn. */\n measureEl: (index: number, el: HTMLElement) => () => void;\n /** Prepend `additionalCount` items at the top while keeping the viewport visually stable. */\n prepend: (additionalCount: number) => void;\n /** Rebuild the full offset table and re-emit (use when item sizes may have changed). */\n refresh: () => void;\n readonly scrollOffset: number;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n update: (next: VirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createVirtualizer(target: ScrollTarget, options: VirtualizerOptions): Virtualizer {\n if (typeof options.estimateSize === 'number') requirePositiveNumber(options.estimateSize, 'estimateSize');\n\n if (options.gap !== undefined) requireNonNegativeInteger(options.gap, 'gap');\n\n if (options.initialOffset !== undefined) requireNonNegativeNumber(options.initialOffset, 'initialOffset');\n\n if (options.overscan !== undefined) validateOverscan(options.overscan);\n\n if (options.scrollEndDelay !== undefined) requireNonNegativeNumber(options.scrollEndDelay, 'scrollEndDelay');\n\n const horizontal = !!options.horizontal;\n const defaultItemKey = (index: number): VirtualKey => index;\n const enableKeyboardScroll = options.keyboardScroll ?? false;\n\n let count = requireNonNegativeInteger(options.count, 'count');\n let estimateFn = resolveEstimateFn(options.estimateSize, DEFAULT_ESTIMATE_SIZE);\n let gap = toNonNegativeInt(options.gap ?? 0);\n let getItemKey = options.getItemKey ?? defaultItemKey;\n let overscan = normalizeOverscan(options.overscan, DEFAULT_OVERSCAN);\n let stickyFn: ((index: number) => boolean) | null = options.sticky ?? null;\n let keyboardScrollEnabled = enableKeyboardScroll;\n let autoMeasureEnabled = options.autoMeasure ?? false;\n\n let onChange = options.onChange;\n let onScrollEnd = options.onScrollEnd;\n let onScrollingChange = options.onScrollingChange;\n let scrollEndDelay = options.scrollEndDelay ?? 150;\n\n // Optional signal for reactive state\n let stateSignal: Signal<VirtualizerState> | null = null;\n if (options.toSignal) {\n const initialState: VirtualizerState = { items: [], stickyItems: [], totalSize: 0 };\n stateSignal = options.toSignal(initialState);\n }\n\n // Helper to emit state to both callback and signal\n function emitState(state: VirtualizerState): void {\n if (stateSignal) stateSignal.value = state;\n onChange?.(state);\n }\n let scrollEndTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Detect native scrollend support (Chrome 114+, Firefox 109+)\n const hasNativeScrollEnd = 'onscrollend' in (target as EventTarget);\n\n function notifyScrollEnd(): void {\n if (!isScrolling) return;\n\n isScrolling = false;\n onScrollingChange?.(false);\n onScrollEnd?.(scrollOffset);\n }\n\n function handleScrollStart(): void {\n if (!isScrolling) {\n isScrolling = true;\n onScrollingChange?.(true);\n }\n\n if (!hasNativeScrollEnd) {\n if (scrollEndTimer !== null) clearTimeout(scrollEndTimer);\n\n scrollEndTimer = setTimeout(notifyScrollEnd, scrollEndDelay);\n }\n }\n\n // External or internal measurement cache.\n let measuredByKey: MeasurementCache = options.measurementCache ?? new Map();\n\n let items: VirtualItem[] = [];\n let stickyItems: VirtualItem[] = [];\n\n // ─── Axis setup ──────────────────────────────────────────────────────────────\n // The axis1d primitive owns offset management. The sizeAt closure captures\n // the live `measuredByKey`, `getItemKey`, and `estimateFn` by reference so\n // updates to those let-variables are picked up automatically.\n\n const ax = createAxis1D(count, (i) => measuredByKey.get(getItemKey(i)) ?? estimateFn(i), gap);\n\n // F6: Scroll anchor — prevents viewport jump when items above fold are measured.\n let anchorIndex = -1;\n let anchorDistanceFromTop = 0;\n\n let scrollOffset = 0;\n let viewportSize = 0;\n let prevScrollOffset = -1; // for sticky dedup\n let disposed = false;\n let isScrolling = false;\n\n // ─── Scroll clamping ─────────────────────────────────────────────────────────\n\n function clampScrollOffset(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, ax.totalSize - viewportSize);\n\n return Math.min(maxOffset, Math.max(0, safe));\n }\n\n // ─── Compute and emit ─────────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (disposed) return;\n\n if (count === 0 || viewportSize <= 0) {\n if (ax.prevStart !== -1 || ax.prevTotalSize !== ax.totalSize) {\n // Commit with sentinel renderStart=-1 so prevTotalSize is captured as the\n // current totalSize. Using resetDedup() would set prevTotalSize=-1, causing\n // the guard to fire on every subsequent scroll/resize (Bug 2).\n ax.commitDedup(-1, -1);\n\n items = [];\n stickyItems = [];\n\n emitState({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n return;\n }\n\n const { renderEnd, renderStart } = ax.computeRange(\n scrollOffset,\n scrollOffset + viewportSize,\n overscan.start,\n overscan.end,\n );\n\n // Sticky recomputes on every scroll pixel — bypass dedup in that case.\n const stickyScrolled = stickyFn !== null && scrollOffset !== prevScrollOffset;\n const rangeChanged = !ax.isDedupSame(renderStart, renderEnd);\n\n if (!rangeChanged && !stickyScrolled) return;\n\n ax.commitDedup(renderStart, renderEnd);\n prevScrollOffset = scrollOffset;\n\n const nextItems: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n nextItems.push(ax.itemAt(i));\n }\n\n items = nextItems;\n stickyItems = stickyFn\n ? computeStickyItems({ count, scrollOffset, sizeAt: ax.sizeAt, startAt: ax.startAt, stickyFn })\n : [];\n\n emitState({ items, stickyItems, totalSize: ax.totalSize });\n\n // Auto-measure visible items if enabled\n if (autoMeasureEnabled && items.length > 0) {\n // Queue microtask to allow DOM to render first\n queueMicrotask(() => {\n if (disposed || target instanceof Window) return;\n\n for (const item of items) {\n const key = getItemKey(item.index);\n const el = (target as HTMLElement)?.querySelector?.(`[data-vz-key=\"${key}\"]`);\n\n if (el instanceof HTMLElement) {\n measureEl(item.index, el);\n }\n }\n });\n }\n }\n\n // ─── Measurement (R6: measure delegates to measureBatch) ─────────────────────\n\n function recordMeasurement(index: number, size: number): boolean {\n if (!Number.isFinite(index)) return false;\n\n const i = Math.floor(index);\n\n if (i < 0 || i >= count) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n const key = getItemKey(i);\n\n if (measuredByKey.get(key) === s) return false;\n\n measuredByKey.set(key, s);\n ax.markChanged(i);\n\n return true;\n }\n\n // F6: Record a scroll anchor before a batch of measurements arrives.\n function recordScrollAnchor(): void {\n if (scrollOffset <= 0 || count === 0) {\n anchorIndex = -1;\n\n return;\n }\n\n anchorIndex = ax.findFirst(scrollOffset);\n anchorDistanceFromTop = scrollOffset - ax.startAt(anchorIndex);\n }\n\n // F6: After rebuild, restore the anchor to prevent viewport jump.\n function restoreScrollAnchor(): void {\n if (anchorIndex < 0) return;\n\n const expectedOffset = ax.startAt(anchorIndex) + anchorDistanceFromTop;\n const delta = expectedOffset - scrollOffset;\n\n anchorIndex = -1;\n\n if (Math.abs(delta) >= 1) {\n const newOffset = clampScrollOffset(scrollOffset + delta);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n }\n }\n\n function measureBatch(entries: Array<{ index: number; size: number }>): void {\n if (disposed) return;\n\n let changed = false;\n\n for (const { index, size } of entries) {\n if (recordMeasurement(index, size)) changed = true;\n }\n\n if (changed) {\n // Record anchor before the first pending build is scheduled (F6).\n if (!ax.pendingBuild) recordScrollAnchor();\n\n ax.scheduleBuild(() => {\n restoreScrollAnchor();\n computeVisible();\n });\n }\n }\n\n /** R6: Single-item convenience — delegates to measureBatch. */\n function measure(index: number, size: number): void {\n measureBatch([{ index, size }]);\n }\n\n /** Attach a ResizeObserver to `el`. Returns a disconnect function. */\n function measureEl(index: number, el: HTMLElement): () => void {\n if (disposed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => {\n measure(index, horizontal ? entry.contentRect.width : entry.contentRect.height);\n });\n }\n\n // ─── Option updates ───────────────────────────────────────────────────────────\n\n function applyOptions(next: VirtualizerUpdateOptions): void {\n if (next.count !== undefined) requireNonNegativeInteger(next.count, 'count');\n\n if (typeof next.estimateSize === 'number') requirePositiveNumber(next.estimateSize, 'estimateSize');\n\n if (next.gap !== undefined) requireNonNegativeInteger(next.gap, 'gap');\n\n if (next.overscan !== undefined) validateOverscan(next.overscan);\n\n if (next.scrollEndDelay !== undefined) requireNonNegativeNumber(next.scrollEndDelay, 'scrollEndDelay');\n\n let needsRebuild = false;\n let needsCompute = false;\n\n if (Object.hasOwn(next, 'onChange')) onChange = next.onChange;\n\n if (Object.hasOwn(next, 'onScrollEnd')) onScrollEnd = next.onScrollEnd;\n\n if (Object.hasOwn(next, 'onScrollingChange')) onScrollingChange = next.onScrollingChange;\n\n if (next.scrollEndDelay !== undefined) {\n scrollEndDelay = next.scrollEndDelay;\n }\n\n if (next.count !== undefined) {\n const nextCount = next.count;\n\n if (nextCount !== count) {\n count = nextCount;\n ax.setCount(count);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'estimateSize')) {\n estimateFn = resolveEstimateFn(next.estimateSize, DEFAULT_ESTIMATE_SIZE);\n measuredByKey.clear();\n needsRebuild = true;\n }\n\n if (next.gap !== undefined) {\n const nextGap = next.gap;\n\n if (nextGap !== gap) {\n gap = nextGap;\n ax.setGap(gap);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'getItemKey')) {\n const nextKey = next.getItemKey ?? defaultItemKey;\n\n if (nextKey !== getItemKey) {\n getItemKey = nextKey;\n measuredByKey.clear();\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'measurementCache') && next.measurementCache !== undefined) {\n measuredByKey = next.measurementCache;\n needsRebuild = true;\n }\n\n if (next.overscan !== undefined) {\n const nextOverscan = normalizeOverscan(next.overscan, DEFAULT_OVERSCAN);\n\n if (nextOverscan.start !== overscan.start || nextOverscan.end !== overscan.end) {\n overscan = nextOverscan;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'sticky')) {\n const nextStickyFn = next.sticky ?? null;\n\n if (nextStickyFn !== stickyFn) {\n stickyFn = nextStickyFn;\n ax.resetDedup(); // force re-emit to flush updated stickyItems\n }\n\n needsCompute = true;\n }\n\n if (Object.hasOwn(next, 'keyboardScroll')) {\n keyboardScrollEnabled = next.keyboardScroll ?? false;\n }\n\n if (Object.hasOwn(next, 'autoMeasure')) {\n autoMeasureEnabled = next.autoMeasure ?? false;\n }\n\n if (needsRebuild) {\n recordScrollAnchor();\n ax.rebuild(true);\n restoreScrollAnchor();\n }\n\n if (needsCompute || needsRebuild) computeVisible();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────────\n\n function invalidate(): void {\n if (disposed) return;\n\n measuredByKey.clear();\n ax.rebuild(true);\n computeVisible();\n }\n\n /** Full O(n) offset rebuild followed by re-emit. Also used internally for data-only re-emit when sizes unchanged. */\n function refresh(): void {\n if (disposed) return;\n\n ax.rebuild(true);\n computeVisible();\n }\n\n function prepend(additionalCount: number): void {\n if (disposed) return;\n\n const n = toNonNegativeInt(additionalCount);\n\n if (n === 0) return;\n\n count += n;\n ax.setCount(count);\n ax.rebuild(true);\n\n // Adjust scroll to keep the same content visible (F6-like stable prepend).\n const prependedHeight = ax.startAt(n);\n const newOffset = clampScrollOffset(scrollOffset + prependedHeight);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n\n computeVisible();\n }\n\n function update(next: VirtualizerUpdateOptions): void {\n if (disposed) return;\n\n applyOptions(next);\n }\n\n function scrollToIndex(index: number, opts: ScrollToIndexOptions = {}): void {\n if (disposed || count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, count - 1));\n const align = opts.align ?? 'auto';\n const behavior = opts.behavior ?? 'auto';\n const itemStart = ax.startAt(clampedIndex);\n const itemSize = ax.sizeAt(clampedIndex);\n const itemEnd = itemStart + itemSize;\n\n let targetOffset: number;\n\n if (align === 'start') {\n targetOffset = itemStart;\n } else if (align === 'end') {\n targetOffset = itemEnd - viewportSize;\n } else if (align === 'center') {\n targetOffset = itemStart - (viewportSize - itemSize) / 2;\n } else {\n const visibleStart = scrollOffset;\n const visibleEnd = visibleStart + viewportSize;\n\n if (itemStart >= visibleStart && itemEnd <= visibleEnd) {\n if (opts.onComplete) queueMicrotask(opts.onComplete);\n\n return;\n }\n\n targetOffset = itemStart < visibleStart ? itemStart : itemEnd - viewportSize;\n }\n\n domAxis.writeOffset(clampScrollOffset(targetOffset), behavior);\n\n if (opts.onComplete) {\n if (behavior === 'smooth' && 'onscrollend' in target) {\n (target as EventTarget).addEventListener('scrollend', opts.onComplete, { once: true });\n } else {\n queueMicrotask(opts.onComplete);\n }\n }\n }\n\n function scrollToOffset(offset: number, opts: { behavior?: ScrollBehavior } = {}): void {\n if (disposed) return;\n\n domAxis.writeOffset(clampScrollOffset(offset), opts.behavior ?? 'auto');\n }\n\n function scrollToTop(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(0, opts);\n }\n\n function scrollToBottom(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(Math.max(0, ax.totalSize - viewportSize), opts);\n }\n\n function isAtEnd(threshold = 0): boolean {\n return ax.totalSize - (scrollOffset + viewportSize) <= threshold;\n }\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (disposed) return;\n\n disposed = true;\n ac.abort();\n\n if (scrollEndTimer !== null) {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = null;\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).removeEventListener('scrollend', notifyScrollEnd);\n }\n\n adapter.detach();\n }\n\n // ─── Bootstrap ───────────────────────────────────────────────────────────────\n\n function handleScroll(): void {\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n handleScrollStart();\n computeVisible();\n }\n\n function handleResize(): void {\n viewportSize = domAxis.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n const domAxis = horizontal ? adapter.x : adapter.y;\n\n // ─── Keyboard scroll support ──────────────────────────────────────────────────\n\n if (keyboardScrollEnabled) {\n const handleKeyDown = (e: Event): void => {\n if (disposed) return;\n if (!(e instanceof KeyboardEvent)) return;\n if (count === 0) return;\n\n let handled = false;\n let newOffset = scrollOffset;\n const pageScrollSize = Math.max(viewportSize * 0.8, viewportSize - 100);\n\n switch (e.key) {\n case 'ArrowUp':\n case 'ArrowLeft': {\n const itemSize = typeof estimateFn(0) === 'number' ? estimateFn(0) : DEFAULT_ESTIMATE_SIZE;\n newOffset = Math.max(0, scrollOffset - (itemSize + gap));\n handled = true;\n break;\n }\n case 'ArrowDown':\n case 'ArrowRight': {\n const itemSize = typeof estimateFn(0) === 'number' ? estimateFn(0) : DEFAULT_ESTIMATE_SIZE;\n newOffset = Math.min(ax.totalSize - viewportSize, scrollOffset + (itemSize + gap));\n handled = true;\n break;\n }\n case 'PageUp':\n newOffset = Math.max(0, scrollOffset - pageScrollSize);\n handled = true;\n break;\n case 'PageDown':\n newOffset = Math.min(ax.totalSize - viewportSize, scrollOffset + pageScrollSize);\n handled = true;\n break;\n case 'Home':\n newOffset = 0;\n handled = true;\n break;\n case 'End':\n newOffset = Math.max(0, ax.totalSize - viewportSize);\n handled = true;\n break;\n }\n\n if (handled) {\n e.preventDefault();\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = clampScrollOffset(newOffset);\n computeVisible();\n }\n };\n\n if (typeof target === 'object' && target !== null && 'addEventListener' in target) {\n (target as EventTarget).addEventListener('keydown', handleKeyDown);\n ac.signal.addEventListener('abort', () => {\n (target as EventTarget).removeEventListener('keydown', handleKeyDown);\n });\n }\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).addEventListener('scrollend', notifyScrollEnd, { passive: true });\n }\n\n ax.rebuild(true);\n\n if (options.initialOffset !== undefined) {\n domAxis.writeOffset(clampScrollOffset(options.initialOffset), 'auto');\n }\n\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n viewportSize = domAxis.readViewportSize();\n\n computeVisible();\n\n return {\n get count() {\n return count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return disposed;\n },\n invalidate,\n isAtEnd,\n get isScrolling() {\n return isScrolling;\n },\n get items() {\n return items;\n },\n measure,\n measureBatch,\n measureEl,\n prepend,\n refresh,\n get scrollOffset() {\n return scrollOffset;\n },\n scrollToBottom,\n scrollToIndex,\n scrollToOffset,\n scrollToTop,\n get stickyItems() {\n return stickyItems;\n },\n [Symbol.dispose]: _dispose,\n get totalSize() {\n return ax.totalSize;\n },\n update,\n };\n}\n","import type { Signal } from '@vielzeug/ripple';\n\nimport { DEFAULT_ESTIMATE_SIZE, DEFAULT_OVERSCAN, type MeasurementCache, type Overscan } from './_utils';\nimport {\n requireNonNegativeInteger,\n requireNonNegativeNumber,\n requirePositiveNumber,\n validateOverscan,\n} from './_validation';\nimport { ScrollConfigurationError, ScrollRangeError } from './errors';\nimport {\n createVirtualizer,\n type ScrollToIndexOptions,\n type VirtualItem,\n type Virtualizer,\n type VirtualizerState,\n type VirtualKey,\n} from './virtualizer';\n\nexport type {\n MeasurementCache,\n Overscan,\n ScrollToIndexOptions,\n VirtualItem,\n Virtualizer,\n VirtualizerState,\n VirtualKey,\n};\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\n/** A `VirtualItem` enriched with the corresponding data record. */\nexport type VirtualRenderItem<T> = VirtualItem & { readonly data: T };\n\n/**\n * Recycle a DOM node by key. If the pool has a live node for `key`, it is\n * returned and reused; otherwise `create()` is called to produce a new one.\n */\nexport type RecycleFn = (key: VirtualKey, create: () => HTMLElement) => HTMLElement;\n\nexport type DomVirtualListRenderArgs<T> = {\n items: Array<VirtualRenderItem<T>>;\n listEl: HTMLElement;\n recycle: RecycleFn;\n /** Sticky items from the underlying virtualizer, enriched with data. */\n stickyItems: Array<VirtualRenderItem<T>>;\n totalSize: number;\n};\n\nexport type StickToBottomOptions = {\n /** Enable/disable the behavior. Default: `true` once this object is provided. */\n enabled?: boolean;\n /**\n * Distance in pixels from the end still considered \"at the end\" — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Default: `48`.\n */\n threshold?: number;\n};\n\nexport type DomVirtualListOptions<T> = {\n /** Custom teardown that clears listEl. Defaults to `listEl.textContent = ''`. */\n clear?: (listEl: HTMLElement) => void;\n estimateSize?: number | ((index: number, item: T) => number);\n gap?: number;\n getItemKey?: (index: number, item: T) => VirtualKey;\n horizontal?: boolean;\n /** Enable keyboard navigation (Arrow/Page/Home/End keys). */\n keyboardScroll?: boolean;\n listElement: HTMLElement;\n /** External measurement cache for scroll restoration. */\n measurementCache?: MeasurementCache;\n overscan?: Overscan;\n render: (args: DomVirtualListRenderArgs<T>) => void;\n scrollElement: HTMLElement | Window;\n /**\n * Auto-scroll to the end after `setItems()` whenever the list was already at (or near) the\n * end just before the update — the chat \"stick to bottom on new message\" pattern. Fires on\n * *any* update while at the end, not just growth, so it also follows a streaming last\n * item that grows in place without changing `items.length`. Does nothing while the user\n * has scrolled away from the end. Pass `true` for defaults, or an options object.\n */\n stickToBottom?: boolean | StickToBottomOptions;\n /** Mark items as sticky headers. Receives the item index and the item data. */\n sticky?: (index: number, item: T) => boolean;\n /** Optional signal factory for reactive state. */\n toSignal?: (init: VirtualizerState) => Signal<VirtualizerState>;\n};\n\n/**\n * R11: Controller extends Virtualizer so all methods (scrollToIndex, refresh,\n * scrollToOffset, etc.) are accessible directly on the controller without\n * needing to unwrap an inner virtualizer handle.\n *\n * `prepend` and `update` are omitted — use `setItems()` for item updates and\n * there is no direct `prepend` concept in DomVirtualList.\n */\nexport type DomVirtualListController<T> = Omit<Virtualizer, 'prepend' | 'update'> & {\n setItems: (items: T[]) => void;\n};\n\nexport type VirtualScrollerOptions<T> = Omit<DomVirtualListOptions<T>, 'listElement' | 'scrollElement'> & {\n /** Additional CSS class names on the generated scroll container. */\n containerClass?: string;\n};\n\n// ─── Node pool ────────────────────────────────────────────────────────────────\n\nfunction createNodePool() {\n let live = new Map<VirtualKey, HTMLElement>();\n let nextLive = new Map<VirtualKey, HTMLElement>();\n const stale: HTMLElement[] = [];\n let inCycle = false;\n\n return {\n acquire(key: VirtualKey, create: () => HTMLElement): HTMLElement {\n if (!inCycle) return create();\n\n const existing = live.get(key);\n\n if (existing) {\n nextLive.set(key, existing);\n\n return existing;\n }\n\n const node = stale.pop() ?? create();\n\n nextLive.set(key, node);\n\n return node;\n },\n\n beginCycle(): void {\n nextLive = new Map();\n inCycle = true;\n },\n\n clear(): void {\n for (const node of live.values()) node.remove();\n\n live.clear();\n nextLive.clear();\n stale.length = 0;\n inCycle = false;\n },\n\n endCycle(): void {\n if (!inCycle) return;\n\n inCycle = false;\n\n for (const [key, node] of live) {\n if (!nextLive.has(key)) {\n node.remove();\n stale.push(node);\n }\n }\n\n live = nextLive;\n },\n };\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createDomVirtualList<T>(options: DomVirtualListOptions<T>): DomVirtualListController<T> {\n if (typeof options.estimateSize === 'number') requirePositiveNumber(options.estimateSize, 'estimateSize');\n\n if (options.gap !== undefined) requireNonNegativeInteger(options.gap, 'gap');\n\n if (options.overscan !== undefined) validateOverscan(options.overscan);\n\n if (options.stickToBottom !== undefined && typeof options.stickToBottom !== 'boolean') {\n if (options.stickToBottom === null || Array.isArray(options.stickToBottom)) {\n throw new ScrollConfigurationError('stickToBottom must be a boolean or options object.');\n }\n\n if (options.stickToBottom.threshold !== undefined) {\n requireNonNegativeNumber(options.stickToBottom.threshold, 'stickToBottom.threshold');\n }\n }\n\n let currentItems: T[] = [];\n let disposed = false;\n const ac = new AbortController();\n const listEl = options.listElement;\n\n // Optional signal for reactive state\n let stateSignal: Signal<VirtualizerState> | null = null;\n if (options.toSignal) {\n const initialState: VirtualizerState = { items: [], stickyItems: [], totalSize: 0 };\n stateSignal = options.toSignal(initialState);\n }\n\n // Helper to emit state to both callback and signal\n function emitState(state: VirtualizerState): void {\n if (stateSignal) stateSignal.value = state;\n handleChange(state);\n }\n\n // Pool must be declared before virtualizer since handleChange (passed as onChange)\n // is invoked during createVirtualizer initialization via computeVisible.\n const pool = createNodePool();\n\n // Virtualizer is lazily created on the first non-empty setItems call.\n let virtualizer: Virtualizer | null = null;\n\n function resolveKey(index: number): VirtualKey {\n const item = currentItems[index];\n\n if (item !== undefined && options.getItemKey) return options.getItemKey(index, item);\n\n return index;\n }\n\n function resolveEstimate(index: number): number {\n if (options.estimateSize === undefined) return DEFAULT_ESTIMATE_SIZE;\n\n if (typeof options.estimateSize === 'number') return options.estimateSize;\n\n const item = currentItems[index];\n\n return item !== undefined ? options.estimateSize(index, item) : DEFAULT_ESTIMATE_SIZE;\n }\n\n function applyListSize(totalSize: number): void {\n if (options.horizontal) {\n listEl.style.height = '';\n listEl.style.width = `${totalSize}px`;\n } else {\n listEl.style.height = `${totalSize}px`;\n listEl.style.width = '';\n }\n }\n\n /**\n * R10: Throw rather than silently produce `undefined as T`.\n * This catches bugs where `vi.index` is out of range for `currentItems`.\n */\n function toRenderItem(vi: VirtualItem): VirtualRenderItem<T> {\n const data = currentItems[vi.index];\n\n if (data === undefined) {\n throw new ScrollRangeError(\n `toRenderItem: index ${vi.index} is out of range (currentItems.length=${currentItems.length})`,\n );\n }\n\n return { ...vi, data };\n }\n\n function handleChange(state: VirtualizerState): void {\n applyListSize(state.totalSize);\n\n pool.beginCycle();\n\n // R5: try/finally ensures endCycle() runs even if render() throws, keeping\n // the pool in a consistent state.\n try {\n options.render({\n items: state.items.map(toRenderItem),\n listEl,\n recycle: (key, create) => pool.acquire(key, create),\n stickyItems: state.stickyItems.map(toRenderItem),\n totalSize: state.totalSize,\n });\n } finally {\n pool.endCycle();\n }\n }\n\n function clearAndReset(): void {\n pool.clear();\n\n if (options.clear) {\n options.clear(listEl);\n } else {\n listEl.textContent = '';\n }\n\n listEl.style.height = '';\n listEl.style.width = '';\n listEl.style.position = '';\n listEl.style.contain = '';\n }\n\n const DEFAULT_STICK_THRESHOLD = 48;\n\n function resolveStickToBottom(): { enabled: boolean; threshold: number } {\n const opt = options.stickToBottom;\n\n if (!opt) return { enabled: false, threshold: DEFAULT_STICK_THRESHOLD };\n\n if (opt === true) return { enabled: true, threshold: DEFAULT_STICK_THRESHOLD };\n\n return { enabled: opt.enabled ?? true, threshold: opt.threshold ?? DEFAULT_STICK_THRESHOLD };\n }\n\n function spawnVirtualizer(): Virtualizer {\n const v = createVirtualizer(options.scrollElement, {\n count: currentItems.length,\n estimateSize: resolveEstimate,\n gap: options.gap,\n getItemKey: resolveKey,\n horizontal: options.horizontal,\n keyboardScroll: options.keyboardScroll,\n measurementCache: options.measurementCache,\n onChange: emitState,\n overscan: options.overscan ?? DEFAULT_OVERSCAN,\n sticky: options.sticky\n ? (index) => {\n const item = currentItems[index];\n\n return item !== undefined && options.sticky!(index, item);\n }\n : undefined,\n });\n\n virtualizer = v;\n listEl.style.position = 'relative';\n listEl.style.contain = 'layout';\n\n return v;\n }\n\n function _dispose(): void {\n if (disposed) return;\n\n disposed = true;\n ac.abort();\n virtualizer?.dispose();\n virtualizer = null;\n clearAndReset();\n }\n\n return {\n // ── Virtualizer passthrough (R11) ──────────────────────────────────────\n get count() {\n return virtualizer?.count ?? 0;\n },\n\n get disposalSignal() {\n return ac.signal;\n },\n\n dispose: _dispose,\n\n get disposed() {\n return disposed;\n },\n\n invalidate() {\n if (disposed) return;\n\n virtualizer?.invalidate();\n },\n\n isAtEnd(threshold) {\n return virtualizer?.isAtEnd(threshold) ?? true;\n },\n\n get isScrolling() {\n return virtualizer?.isScrolling ?? false;\n },\n\n get items() {\n return virtualizer?.items ?? [];\n },\n\n measure(index, size) {\n if (disposed) return;\n\n virtualizer?.measure(index, size);\n },\n\n measureBatch(entries) {\n if (disposed) return;\n\n virtualizer?.measureBatch(entries);\n },\n\n measureEl(index, el) {\n if (disposed) return () => {};\n\n return virtualizer?.measureEl(index, el) ?? (() => {});\n },\n\n refresh() {\n if (disposed) return;\n\n virtualizer?.refresh();\n },\n\n get scrollOffset() {\n return virtualizer?.scrollOffset ?? 0;\n },\n\n scrollToBottom(scrollOptions) {\n if (disposed) return;\n\n virtualizer?.scrollToBottom(scrollOptions);\n },\n\n scrollToIndex(index, scrollOptions) {\n if (disposed) return;\n\n virtualizer?.scrollToIndex(index, scrollOptions);\n },\n\n scrollToOffset(offset, scrollOptions) {\n if (disposed) return;\n\n virtualizer?.scrollToOffset(offset, scrollOptions);\n },\n\n scrollToTop(scrollOptions) {\n if (disposed) return;\n\n virtualizer?.scrollToTop(scrollOptions);\n },\n\n // ── DomVirtualList-specific ────────────────────────────────────────────\n setItems(items) {\n if (disposed) return;\n\n // Read *before* mutating state — the \"was the list already at the end?\" check must\n // reflect the pre-update layout, not the one `render()` is about to produce below.\n const stick = resolveStickToBottom();\n const wasAtEnd = stick.enabled && (virtualizer?.isAtEnd(stick.threshold) ?? true);\n\n currentItems = items;\n\n if (items.length === 0) {\n virtualizer?.dispose();\n virtualizer = null;\n clearAndReset();\n\n return;\n }\n\n if (!virtualizer) {\n const v = spawnVirtualizer();\n\n if (wasAtEnd) v.scrollToBottom();\n\n return;\n }\n\n const countChanged = items.length !== virtualizer.count;\n\n // Only count needs explicit update — estimateSize and getItemKey are\n // closures that already reflect the latest currentItems automatically.\n virtualizer.update({ count: items.length });\n\n // When count changed, update() already triggered rebuild + computeVisible().\n // Only force re-emission when count is unchanged (data changed, count same).\n if (!countChanged) {\n // refresh() re-emits with current sizes for stable keys;\n // invalidate() clears position-based measurements when no stable keys.\n if (options.getItemKey) {\n virtualizer.refresh();\n } else {\n virtualizer.invalidate();\n }\n } else if (!options.getItemKey) {\n // Count changed AND no stable keys: position-based measurements are now\n // stale. Clear them so the next render remeasures from fresh estimates.\n virtualizer.invalidate();\n }\n\n if (wasAtEnd) virtualizer.scrollToBottom();\n },\n\n get stickyItems() {\n return virtualizer?.stickyItems ?? [];\n },\n\n [Symbol.dispose]: _dispose,\n\n get totalSize() {\n return virtualizer?.totalSize ?? 0;\n },\n };\n}\n\n// ─── F5: createVirtualScroller ────────────────────────────────────────────────\n\n/**\n * High-level factory that creates the scroll container and inner list element,\n * appends them to `container`, and returns a fully wired `DomVirtualListController`.\n *\n * @example\n * ```ts\n * const list = createVirtualScroller(document.getElementById('root')!, {\n * render({ items, listEl, recycle }) { … },\n * });\n * list.setItems(data);\n * ```\n */\nexport function createVirtualScroller<T>(\n container: HTMLElement,\n options: VirtualScrollerOptions<T>,\n): DomVirtualListController<T> {\n const scrollEl = document.createElement('div');\n\n scrollEl.style.cssText = options.horizontal\n ? 'overflow: auto hidden; width: 100%; height: 100%;'\n : 'overflow: hidden auto; width: 100%; height: 100%;';\n\n if (options.containerClass) scrollEl.className = options.containerClass;\n\n const listEl = document.createElement('div');\n\n scrollEl.appendChild(listEl);\n container.appendChild(scrollEl);\n\n let ctrl: DomVirtualListController<T>;\n\n try {\n ctrl = createDomVirtualList<T>({\n ...options,\n listElement: listEl,\n scrollElement: scrollEl,\n });\n } catch (e) {\n // Remove the scroll container if construction fails so we don't leak DOM nodes.\n scrollEl.remove();\n throw e;\n }\n\n // Override dispose and [Symbol.dispose] to also remove the scroll container.\n // Capture the original dispose before overwriting so there's no self-reference.\n const innerDispose = ctrl.dispose.bind(ctrl);\n\n return Object.assign(ctrl, {\n dispose() {\n innerDispose();\n scrollEl.remove();\n },\n [Symbol.dispose]() {\n innerDispose();\n scrollEl.remove();\n },\n });\n}\n","export type ScrollAlignment = 'auto' | 'center' | 'end' | 'start';\n\nexport function alignOffset(\n itemStart: number,\n itemEnd: number,\n itemSize: number,\n currentOffset: number,\n viewportSize: number,\n align: ScrollAlignment,\n): number | null {\n if (align === 'start') return itemStart;\n\n if (align === 'end') return itemEnd - viewportSize;\n\n if (align === 'center') return itemStart - (viewportSize - itemSize) / 2;\n\n if (itemStart >= currentOffset && itemEnd <= currentOffset + viewportSize) return null;\n\n return itemStart < currentOffset ? itemStart : itemEnd - viewportSize;\n}\n","import type { Signal } from '@vielzeug/ripple';\n\nimport { createScrollAdapter } from './_adapter';\nimport { alignOffset } from './_alignment';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport {\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n normalizeOverscan,\n type Overscan,\n observeResize,\n resolveEstimateFn,\n type ScrollTarget,\n toNonNegativeInt,\n toPositiveNumber,\n} from './_utils';\nimport {\n requireNonNegativeInteger,\n requireNonNegativeNumber,\n requirePositiveNumber,\n validateOverscan,\n} from './_validation';\n\nexport type { VirtualItem };\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface GridVirtualizerState {\n /**\n * Visible column descriptors.\n * Callers form the cross-product `rows × cols` as needed — no O(r×c) Cell\n * allocation per scroll tick (R8).\n */\n readonly cols: VirtualItem[];\n /** Visible row descriptors. */\n readonly rows: VirtualItem[];\n readonly totalHeight: number;\n readonly totalWidth: number;\n}\n\nexport interface GridRangeChangeEvent {\n firstCol: number;\n firstRow: number;\n lastCol: number;\n lastRow: number;\n}\n\nexport interface ScrollToCellOptions {\n behavior?: ScrollBehavior;\n colAlign?: 'auto' | 'center' | 'end' | 'start';\n rowAlign?: 'auto' | 'center' | 'end' | 'start';\n}\n\nexport interface GridVirtualizerOptions {\n colCount: number;\n colGap?: number;\n /** External measurement cache for columns. Share across instances for scroll restoration. */\n colMeasurementCache?: Map<number, number>;\n estimateColSize?: number | ((col: number) => number);\n estimateRowSize?: number | ((row: number) => number);\n initialScrollLeft?: number;\n initialScrollTop?: number;\n /** Enable keyboard navigation (Arrow/Page/Home/End keys). */\n keyboardScroll?: boolean;\n /** Called after every render cycle with the new state. Replace through `update()`. */\n onChange?: (state: GridVirtualizerState) => void;\n /** Zero-allocation alternative to onChange for range-based consumers. */\n onRangeChange?: (range: GridRangeChangeEvent) => void;\n overscanX?: Overscan;\n overscanY?: Overscan;\n rowCount: number;\n rowGap?: number;\n /** External measurement cache for rows. Share across instances for scroll restoration. */\n rowMeasurementCache?: Map<number, number>;\n /** Optional signal factory for reactive state. */\n toSignal?: (init: GridVirtualizerState) => Signal<GridVirtualizerState>;\n}\n\n/**\n * Options accepted by `update()`. Explicit interface instead of\n * `Partial<Omit<...>>` for better IDE hover (R7).\n */\nexport interface GridVirtualizerUpdateOptions {\n colCount?: number;\n colGap?: number;\n estimateColSize?: number | ((col: number) => number);\n estimateRowSize?: number | ((row: number) => number);\n keyboardScroll?: boolean;\n onChange?: ((state: GridVirtualizerState) => void) | undefined;\n onRangeChange?: ((range: GridRangeChangeEvent) => void) | undefined;\n overscanX?: Overscan;\n overscanY?: Overscan;\n rowCount?: number;\n rowGap?: number;\n}\n\nexport interface GridVirtualizer {\n readonly cols: VirtualItem[];\n readonly disposalSignal: AbortSignal;\n dispose: () => void;\n readonly disposed: boolean;\n invalidate: () => void;\n /** Measure rows and columns in a single coordinated rebuild pass (R4). */\n measureBatch: (rows: Array<{ index: number; size: number }>, cols: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` to auto-measure the column width on resize. Returns disconnect fn. */\n measureColEl: (col: number, el: HTMLElement) => () => void;\n measureColumn: (col: number, size: number) => void;\n measureRow: (row: number, size: number) => void;\n /** Attach a ResizeObserver to `el` to auto-measure the row height on resize. Returns disconnect fn. */\n measureRowEl: (row: number, el: HTMLElement) => () => void;\n /** Prepend `additionalRowCount` rows at the top while keeping the viewport visually stable. */\n prependRows: (additionalRowCount: number) => void;\n refresh: () => void;\n readonly rows: VirtualItem[];\n readonly scrollLeft: number;\n scrollToCell: (row: number, col: number, options?: ScrollToCellOptions) => void;\n /** Scroll to bring a row into view without changing the horizontal position. */\n scrollToColumn: (col: number, options?: Pick<ScrollToCellOptions, 'behavior' | 'colAlign'>) => void;\n readonly scrollTop: number;\n /** Scroll to bring a column into view without changing the vertical position. */\n scrollToRow: (row: number, options?: Pick<ScrollToCellOptions, 'behavior' | 'rowAlign'>) => void;\n readonly totalHeight: number;\n readonly totalWidth: number;\n update: (next: GridVirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createGridVirtualizer(target: ScrollTarget, options: GridVirtualizerOptions): GridVirtualizer {\n if (typeof options.estimateRowSize === 'number') requirePositiveNumber(options.estimateRowSize, 'estimateRowSize');\n\n if (typeof options.estimateColSize === 'number') requirePositiveNumber(options.estimateColSize, 'estimateColSize');\n\n if (options.initialScrollTop !== undefined) requireNonNegativeNumber(options.initialScrollTop, 'initialScrollTop');\n\n if (options.initialScrollLeft !== undefined) requireNonNegativeNumber(options.initialScrollLeft, 'initialScrollLeft');\n\n if (options.rowGap !== undefined) requireNonNegativeInteger(options.rowGap, 'rowGap');\n\n if (options.colGap !== undefined) requireNonNegativeInteger(options.colGap, 'colGap');\n\n if (options.overscanY !== undefined) validateOverscan(options.overscanY, 'overscanY');\n\n if (options.overscanX !== undefined) validateOverscan(options.overscanX, 'overscanX');\n\n let rowCount = requireNonNegativeInteger(options.rowCount, 'rowCount');\n let colCount = requireNonNegativeInteger(options.colCount, 'colCount');\n let estimateRowFn = resolveEstimateFn(options.estimateRowSize, DEFAULT_ESTIMATE_SIZE);\n let estimateColFn = resolveEstimateFn(options.estimateColSize, DEFAULT_ESTIMATE_SIZE);\n let overscanY = normalizeOverscan(options.overscanY, DEFAULT_OVERSCAN);\n let overscanX = normalizeOverscan(options.overscanX, DEFAULT_OVERSCAN);\n let keyboardScrollEnabled = options.keyboardScroll ?? false;\n\n let onChange = options.onChange;\n let onRangeChange = options.onRangeChange;\n\n // Optional signal for reactive state\n let stateSignal: Signal<GridVirtualizerState> | null = null;\n if (options.toSignal) {\n const initialState: GridVirtualizerState = { cols: [], rows: [], totalHeight: 0, totalWidth: 0 };\n stateSignal = options.toSignal(initialState);\n }\n\n // Helper to emit state to both callback and signal\n function emitState(state: GridVirtualizerState): void {\n if (stateSignal) stateSignal.value = state;\n onChange?.(state);\n }\n\n // External or internal measurement caches (numeric row/col index keys).\n const measuredRows: Map<number, number> = options.rowMeasurementCache ?? new Map();\n const measuredCols: Map<number, number> = options.colMeasurementCache ?? new Map();\n\n let scrollTop = 0;\n let scrollLeft = 0;\n let viewportHeight = 0;\n let viewportWidth = 0;\n let rows: VirtualItem[] = [];\n let cols: VirtualItem[] = [];\n let disposed = false;\n\n // ─── Axis1D instances (R1) ─────────────────────────────────────────────────\n // sizeAt closures capture live `measuredRows/Cols` and `estimateFn` vars.\n\n const rowAx = createAxis1D(\n rowCount,\n (r) => measuredRows.get(r) ?? estimateRowFn(r),\n toNonNegativeInt(options.rowGap ?? 0),\n );\n\n const colAx = createAxis1D(\n colCount,\n (c) => measuredCols.get(c) ?? estimateColFn(c),\n toNonNegativeInt(options.colGap ?? 0),\n );\n\n // R4: Single coordinated flush flag — prevents two separate microtasks.\n let pendingBatchBuild = false;\n\n // ─── Clamping ──────────────────────────────────────────────────────────────\n\n function clampTop(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n\n return Math.min(Math.max(0, rowAx.totalSize - viewportHeight), Math.max(0, safe));\n }\n\n function clampLeft(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n\n return Math.min(Math.max(0, colAx.totalSize - viewportWidth), Math.max(0, safe));\n }\n\n // ─── Compute and emit ──────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (disposed) return;\n\n const totalHeight = rowAx.totalSize;\n const totalWidth = colAx.totalSize;\n\n if (rowCount === 0 || colCount === 0 || viewportHeight <= 0 || viewportWidth <= 0) {\n const wasEmpty =\n rowAx.prevStart === -1 && rowAx.prevTotalSize === totalHeight && colAx.prevTotalSize === totalWidth;\n\n if (!wasEmpty) {\n // commitDedup(-1, -1) captures the current totalSize so the guard doesn't\n // fire on every subsequent event while the empty state persists (Bug 2).\n rowAx.commitDedup(-1, -1);\n colAx.commitDedup(-1, -1);\n rows = [];\n cols = [];\n emitState({ cols: [], rows: [], totalHeight, totalWidth });\n }\n\n return;\n }\n\n const { renderEnd: renderLastRow, renderStart: renderFirstRow } = rowAx.computeRange(\n scrollTop,\n scrollTop + viewportHeight,\n overscanY.start,\n overscanY.end,\n );\n\n const { renderEnd: renderLastCol, renderStart: renderFirstCol } = colAx.computeRange(\n scrollLeft,\n scrollLeft + viewportWidth,\n overscanX.start,\n overscanX.end,\n );\n\n const rowSame = rowAx.isDedupSame(renderFirstRow, renderLastRow);\n const colSame = colAx.isDedupSame(renderFirstCol, renderLastCol);\n\n if (rowSame && colSame) return;\n\n rowAx.commitDedup(renderFirstRow, renderLastRow);\n colAx.commitDedup(renderFirstCol, renderLastCol);\n\n const rangeChanged = !rowSame || !colSame;\n\n if (rangeChanged && onRangeChange) {\n onRangeChange({\n firstCol: renderFirstCol,\n firstRow: renderFirstRow,\n lastCol: renderLastCol,\n lastRow: renderLastRow,\n });\n }\n\n // R8: Build rows[] and cols[] separately — no cross-product VirtualCell[].\n const nextRows: VirtualItem[] = [];\n\n for (let r = renderFirstRow; r <= renderLastRow; r++) {\n nextRows.push(rowAx.itemAt(r));\n }\n\n const nextCols: VirtualItem[] = [];\n\n for (let c = renderFirstCol; c <= renderLastCol; c++) {\n nextCols.push(colAx.itemAt(c));\n }\n\n rows = nextRows;\n cols = nextCols;\n\n emitState({ cols, rows, totalHeight, totalWidth });\n }\n\n // ─── Measurement helpers ───────────────────────────────────────────────────\n\n function recordRowMeasurement(row: number, size: number): boolean {\n if (!Number.isFinite(row)) return false;\n\n const r = Math.floor(row);\n\n if (r < 0 || r >= rowCount) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n if (measuredRows.get(r) === s) return false;\n\n measuredRows.set(r, s);\n rowAx.markChanged(r);\n\n return true;\n }\n\n function recordColMeasurement(col: number, size: number): boolean {\n if (!Number.isFinite(col)) return false;\n\n const c = Math.floor(col);\n\n if (c < 0 || c >= colCount) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n if (measuredCols.get(c) === s) return false;\n\n measuredCols.set(c, s);\n colAx.markChanged(c);\n\n return true;\n }\n\n /**\n * R4: Single coordinated flush for both axes.\n * Replaces the previous two independent microtask schedules.\n */\n function scheduleFlush(): void {\n if (pendingBatchBuild) return;\n\n pendingBatchBuild = true;\n queueMicrotask(() => {\n pendingBatchBuild = false;\n\n // consumeMinChangedIndex() atomically reads and resets minChangedIndex.\n // Without the reset, every subsequent flush would rebuild from index 0\n // instead of the actual earliest changed index (Bug 3).\n const fromRow = rowAx.consumeMinChangedIndex();\n\n if (fromRow !== Infinity) rowAx.rebuildFrom(fromRow);\n\n const fromCol = colAx.consumeMinChangedIndex();\n\n if (fromCol !== Infinity) colAx.rebuildFrom(fromCol);\n\n computeVisible();\n });\n }\n\n // ─── Public measurement API ────────────────────────────────────────────────\n\n function measureRow(row: number, size: number): void {\n if (disposed) return;\n\n if (recordRowMeasurement(row, size)) scheduleFlush();\n }\n\n function measureColumn(col: number, size: number): void {\n if (disposed) return;\n\n if (recordColMeasurement(col, size)) scheduleFlush();\n }\n\n /** Measure rows and columns in a single coordinated rebuild pass (R4). */\n function measureBatch(\n rowEntries: Array<{ index: number; size: number }>,\n colEntries: Array<{ index: number; size: number }>,\n ): void {\n if (disposed) return;\n\n let changed = false;\n\n for (const { index, size } of rowEntries) {\n if (recordRowMeasurement(index, size)) changed = true;\n }\n\n for (const { index, size } of colEntries) {\n if (recordColMeasurement(index, size)) changed = true;\n }\n\n if (changed) scheduleFlush();\n }\n\n function measureRowEl(row: number, el: HTMLElement): () => void {\n if (disposed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => measureRow(row, entry.contentRect.height));\n }\n\n function measureColEl(col: number, el: HTMLElement): () => void {\n if (disposed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => measureColumn(col, entry.contentRect.width));\n }\n\n function invalidate(): void {\n if (disposed) return;\n\n measuredRows.clear();\n measuredCols.clear();\n rowAx.rebuild(true);\n colAx.rebuild(true);\n computeVisible();\n }\n\n function refresh(): void {\n if (disposed) return;\n\n rowAx.rebuild(true);\n colAx.rebuild(true);\n computeVisible();\n }\n\n function prependRows(additionalRowCount: number): void {\n if (disposed) return;\n\n const n = toNonNegativeInt(additionalRowCount);\n\n if (n === 0) return;\n\n rowCount += n;\n rowAx.setCount(rowCount);\n rowAx.rebuild(true);\n\n const prependedHeight = rowAx.startAt(n);\n const newScrollTop = clampTop(scrollTop + prependedHeight);\n\n adapter.scrollTo(scrollLeft, newScrollTop, 'auto');\n scrollTop = newScrollTop;\n\n computeVisible();\n }\n\n function scrollToRow(row: number, opts: Pick<ScrollToCellOptions, 'behavior' | 'rowAlign'> = {}): void {\n if (disposed || rowCount === 0) return;\n\n const safeRow = Math.max(0, Math.min(Math.floor(Number.isFinite(row) ? row : 0), rowCount - 1));\n const behavior = opts.behavior ?? 'auto';\n const rowStart = rowAx.startAt(safeRow);\n const rowSize = rowAx.sizeAt(safeRow);\n const targetTop = alignOffset(\n rowStart,\n rowStart + rowSize,\n rowSize,\n scrollTop,\n viewportHeight,\n opts.rowAlign ?? 'auto',\n );\n\n if (targetTop === null) return;\n\n adapter.scrollTo(scrollLeft, clampTop(targetTop), behavior);\n }\n\n function scrollToColumn(col: number, opts: Pick<ScrollToCellOptions, 'behavior' | 'colAlign'> = {}): void {\n if (disposed || colCount === 0) return;\n\n const safeCol = Math.max(0, Math.min(Math.floor(Number.isFinite(col) ? col : 0), colCount - 1));\n const behavior = opts.behavior ?? 'auto';\n const colStart = colAx.startAt(safeCol);\n const colSize = colAx.sizeAt(safeCol);\n const targetLeft = alignOffset(\n colStart,\n colStart + colSize,\n colSize,\n scrollLeft,\n viewportWidth,\n opts.colAlign ?? 'auto',\n );\n\n if (targetLeft === null) return;\n\n adapter.scrollTo(clampLeft(targetLeft), scrollTop, behavior);\n }\n\n function scrollToCell(row: number, col: number, opts: ScrollToCellOptions = {}): void {\n if (disposed || rowCount === 0 || colCount === 0) return;\n\n const safeRow = Math.max(0, Math.min(Math.floor(Number.isFinite(row) ? row : 0), rowCount - 1));\n const safeCol = Math.max(0, Math.min(Math.floor(Number.isFinite(col) ? col : 0), colCount - 1));\n const behavior = opts.behavior ?? 'auto';\n\n const rowStart = rowAx.startAt(safeRow);\n const rowSize = rowAx.sizeAt(safeRow);\n const colStart = colAx.startAt(safeCol);\n const colSize = colAx.sizeAt(safeCol);\n\n const targetTop = alignOffset(\n rowStart,\n rowStart + rowSize,\n rowSize,\n scrollTop,\n viewportHeight,\n opts.rowAlign ?? 'auto',\n );\n const targetLeft = alignOffset(\n colStart,\n colStart + colSize,\n colSize,\n scrollLeft,\n viewportWidth,\n opts.colAlign ?? 'auto',\n );\n\n if (targetTop === null && targetLeft === null) return;\n\n adapter.scrollTo(\n targetLeft !== null ? clampLeft(targetLeft) : scrollLeft,\n targetTop !== null ? clampTop(targetTop) : scrollTop,\n behavior,\n );\n }\n\n function update(next: GridVirtualizerUpdateOptions): void {\n if (disposed) return;\n\n if (next.rowCount !== undefined) requireNonNegativeInteger(next.rowCount, 'rowCount');\n\n if (next.colCount !== undefined) requireNonNegativeInteger(next.colCount, 'colCount');\n\n if (typeof next.estimateRowSize === 'number') requirePositiveNumber(next.estimateRowSize, 'estimateRowSize');\n\n if (typeof next.estimateColSize === 'number') requirePositiveNumber(next.estimateColSize, 'estimateColSize');\n\n if (next.rowGap !== undefined) requireNonNegativeInteger(next.rowGap, 'rowGap');\n\n if (next.colGap !== undefined) requireNonNegativeInteger(next.colGap, 'colGap');\n\n if (next.overscanY !== undefined) validateOverscan(next.overscanY, 'overscanY');\n\n if (next.overscanX !== undefined) validateOverscan(next.overscanX, 'overscanX');\n\n let rebuildRows = false;\n let rebuildCols = false;\n let needsCompute = false;\n\n if (Object.hasOwn(next, 'onChange')) onChange = next.onChange;\n\n if (Object.hasOwn(next, 'onRangeChange')) onRangeChange = next.onRangeChange;\n\n if (next.rowCount !== undefined) {\n const n = next.rowCount;\n\n if (n !== rowCount) {\n rowCount = n;\n rowAx.setCount(rowCount);\n rebuildRows = true;\n }\n }\n\n if (next.colCount !== undefined) {\n const n = next.colCount;\n\n if (n !== colCount) {\n colCount = n;\n colAx.setCount(colCount);\n rebuildCols = true;\n }\n }\n\n if (next.rowGap !== undefined) {\n const n = next.rowGap;\n\n if (n !== rowAx.gap) {\n rowAx.setGap(n);\n rebuildRows = true;\n }\n }\n\n if (next.colGap !== undefined) {\n const n = next.colGap;\n\n if (n !== colAx.gap) {\n colAx.setGap(n);\n rebuildCols = true;\n }\n }\n\n if ('estimateRowSize' in next) {\n estimateRowFn = resolveEstimateFn(next.estimateRowSize, DEFAULT_ESTIMATE_SIZE);\n measuredRows.clear();\n rebuildRows = true;\n }\n\n if ('estimateColSize' in next) {\n estimateColFn = resolveEstimateFn(next.estimateColSize, DEFAULT_ESTIMATE_SIZE);\n measuredCols.clear();\n rebuildCols = true;\n }\n\n if (next.overscanY !== undefined) {\n const n = normalizeOverscan(next.overscanY, DEFAULT_OVERSCAN);\n\n if (n.start !== overscanY.start || n.end !== overscanY.end) {\n overscanY = n;\n needsCompute = true;\n }\n }\n\n if (next.overscanX !== undefined) {\n const n = normalizeOverscan(next.overscanX, DEFAULT_OVERSCAN);\n\n if (n.start !== overscanX.start || n.end !== overscanX.end) {\n overscanX = n;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'keyboardScroll')) {\n keyboardScrollEnabled = next.keyboardScroll ?? false;\n }\n\n if (rebuildRows) rowAx.rebuild(true);\n\n if (rebuildCols) colAx.rebuild(true);\n\n if (rebuildRows || rebuildCols || needsCompute) computeVisible();\n }\n\n const ac = new AbortController();\n\n function disposeImpl(): void {\n if (disposed) return;\n\n disposed = true;\n ac.abort();\n adapter.detach();\n }\n\n // ─── Bootstrap (R9: read viewport BEFORE writing initial scroll) ───────────\n\n function handleScroll(): void {\n scrollTop = clampTop(adapter.y.readOffset());\n scrollLeft = clampLeft(adapter.x.readOffset());\n computeVisible();\n }\n\n function handleResize(): void {\n viewportHeight = adapter.y.readViewportSize();\n viewportWidth = adapter.x.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n\n rowAx.rebuild(true);\n colAx.rebuild(true);\n\n // R9: Read viewport dimensions BEFORE writing initial scroll positions so that\n // computeVisible() sees a valid viewport on the very first call.\n viewportHeight = adapter.y.readViewportSize();\n viewportWidth = adapter.x.readViewportSize();\n\n if (options.initialScrollTop !== undefined) {\n adapter.scrollTo(options.initialScrollLeft ?? 0, clampTop(options.initialScrollTop), 'auto');\n } else if (options.initialScrollLeft !== undefined) {\n adapter.scrollTo(clampLeft(options.initialScrollLeft), 0, 'auto');\n }\n\n scrollTop = clampTop(adapter.y.readOffset());\n scrollLeft = clampLeft(adapter.x.readOffset());\n\n computeVisible();\n\n // ─── Keyboard scroll support ──────────────────────────────────────────────────\n\n if (keyboardScrollEnabled) {\n const handleKeyDown = (e: Event): void => {\n if (disposed) return;\n if (!(e instanceof KeyboardEvent)) return;\n if (rowCount === 0 || colCount === 0) return;\n\n let handled = false;\n let newTop = scrollTop;\n let newLeft = scrollLeft;\n const pageScrollSize = Math.max(viewportHeight * 0.8, viewportHeight - 100);\n const rowStepSize =\n (typeof estimateRowFn(0) === 'number' ? estimateRowFn(0) : DEFAULT_ESTIMATE_SIZE) +\n toNonNegativeInt(options.rowGap ?? 0);\n const colStepSize =\n (typeof estimateColFn(0) === 'number' ? estimateColFn(0) : DEFAULT_ESTIMATE_SIZE) +\n toNonNegativeInt(options.colGap ?? 0);\n\n switch (e.key) {\n case 'ArrowUp':\n newTop = Math.max(0, scrollTop - rowStepSize);\n handled = true;\n break;\n case 'ArrowDown':\n newTop = Math.min(rowAx.totalSize - viewportHeight, scrollTop + rowStepSize);\n handled = true;\n break;\n case 'ArrowLeft':\n newLeft = Math.max(0, scrollLeft - colStepSize);\n handled = true;\n break;\n case 'ArrowRight':\n newLeft = Math.min(colAx.totalSize - viewportWidth, scrollLeft + colStepSize);\n handled = true;\n break;\n case 'PageUp':\n newTop = Math.max(0, scrollTop - pageScrollSize);\n handled = true;\n break;\n case 'PageDown':\n newTop = Math.min(rowAx.totalSize - viewportHeight, scrollTop + pageScrollSize);\n handled = true;\n break;\n case 'Home':\n newTop = 0;\n handled = true;\n break;\n case 'End':\n newTop = Math.max(0, rowAx.totalSize - viewportHeight);\n handled = true;\n break;\n }\n\n if (handled) {\n e.preventDefault();\n adapter.scrollTo(newLeft, newTop, 'auto');\n scrollTop = clampTop(newTop);\n scrollLeft = clampLeft(newLeft);\n if (!disposed) computeVisible();\n }\n };\n\n if (typeof target === 'object' && target !== null && 'addEventListener' in target) {\n (target as EventTarget).addEventListener('keydown', handleKeyDown);\n ac.signal.addEventListener('abort', () => {\n (target as EventTarget).removeEventListener('keydown', handleKeyDown);\n });\n }\n }\n\n return {\n get cols() {\n return cols;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: disposeImpl,\n get disposed() {\n return disposed;\n },\n invalidate,\n measureBatch,\n measureColEl,\n measureColumn,\n measureRow,\n measureRowEl,\n prependRows,\n refresh,\n get rows() {\n return rows;\n },\n get scrollLeft() {\n return scrollLeft;\n },\n scrollToCell,\n scrollToColumn,\n get scrollTop() {\n return scrollTop;\n },\n scrollToRow,\n [Symbol.dispose]: disposeImpl,\n get totalHeight() {\n return rowAx.totalSize;\n },\n get totalWidth() {\n return colAx.totalSize;\n },\n update,\n };\n}\n","import type { Signal } from '@vielzeug/ripple';\n\nimport { resolveEstimateFn } from './_utils';\nimport { requireNonNegativeNumber, requirePositiveNumber, validateOverscan } from './_validation';\nimport {\n createVirtualizer,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n type ScrollTarget,\n type ScrollToIndexOptions,\n type VirtualItem,\n type VirtualizerState,\n type VirtualizerUpdateOptions,\n type VirtualKey,\n} from './virtualizer';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface GroupSection<T> {\n items: T[];\n label: string;\n}\n\nexport interface GroupVirtualItem<T> extends VirtualItem {\n /** Item data. */\n data: T;\n /** Index within the section. */\n itemIndex: number;\n sectionIndex: number;\n}\n\nexport interface GroupVirtualHeader extends VirtualItem {\n label: string;\n sectionIndex: number;\n}\n\nexport interface GroupVirtualizerState<T> {\n readonly headers: GroupVirtualHeader[];\n readonly items: Array<GroupVirtualItem<T>>;\n readonly stickyHeader: GroupVirtualHeader | null;\n readonly totalSize: number;\n}\n\n/**\n * Options accepted by `GroupVirtualizer.update()`. Overrides are applied\n * together with the new sections on the next render cycle.\n *\n * Intentionally excludes `horizontal` (axis cannot change at runtime) and\n * `initialOffset` (one-time bootstrap).\n */\nexport interface GroupVirtualizerUpdateOptions<T> {\n estimateHeaderSize?: number | ((section: GroupSection<T>, sectionIndex: number) => number);\n estimateItemSize?: number | ((item: T, itemIndex: number, sectionIndex: number) => number);\n getItemKey?: (item: T, itemIndex: number, sectionIndex: number) => VirtualKey;\n measurementCache?: MeasurementCache;\n onChange?: ((state: GroupVirtualizerState<T>) => void) | undefined;\n onScrollEnd?: ((offset: number) => void) | undefined;\n onScrollingChange?: ((isScrolling: boolean) => void) | undefined;\n overscan?: Overscan;\n scrollEndDelay?: number;\n}\n\nexport interface GroupVirtualizerOptions<T> {\n estimateHeaderSize?: number | ((section: GroupSection<T>, sectionIndex: number) => number);\n estimateItemSize?: number | ((item: T, itemIndex: number, sectionIndex: number) => number);\n getItemKey?: (item: T, itemIndex: number, sectionIndex: number) => VirtualKey;\n horizontal?: boolean;\n measurementCache?: MeasurementCache;\n /** Called after every render cycle with the new state. Replace through `update()`. */\n onChange?: (state: GroupVirtualizerState<T>) => void;\n /** Called when scrolling settles. Replace through `update()`. */\n onScrollEnd?: (offset: number) => void;\n /** Called when the scrolling state changes. Replace through `update()`. */\n onScrollingChange?: (isScrolling: boolean) => void;\n overscan?: Overscan;\n /**\n * Debounce delay (ms) for scroll-end detection. Defaults to 150.\n * See `VirtualizerOptions.scrollEndDelay` for details.\n */\n scrollEndDelay?: number;\n sections: Array<GroupSection<T>>;\n /** Optional signal factory for reactive state. */\n toSignal?: (init: GroupVirtualizerState<T>) => Signal<GroupVirtualizerState<T>>;\n}\n\nexport interface GroupVirtualizer<T> {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n dispose: () => void;\n readonly disposed: boolean;\n invalidate: () => void;\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n readonly items: ReadonlyArray<GroupVirtualItem<T>>;\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n measureEl: (index: number, el: HTMLElement) => () => void;\n refresh: () => void;\n readonly scrollOffset: number;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToItem: (sectionIndex: number, itemIndex: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToSection: (sectionIndex: number, options?: ScrollToIndexOptions) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n update: (sections: Array<GroupSection<T>>, opts?: GroupVirtualizerUpdateOptions<T>) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Flat entry map ───────────────────────────────────────────────────────────\n\ntype FlatHeader = { isHeader: true; itemIndex: -1; label: string; sectionIndex: number };\ntype FlatItem<T> = { isHeader: false; item: T; itemIndex: number; sectionIndex: number };\ntype FlatEntry<T> = FlatHeader | FlatItem<T>;\n\nfunction buildFlatEntries<T>(sections: Array<GroupSection<T>>): FlatEntry<T>[] {\n const flat: FlatEntry<T>[] = [];\n\n for (let s = 0; s < sections.length; s++) {\n const section = sections[s]!;\n\n flat.push({ isHeader: true, itemIndex: -1, label: section.label, sectionIndex: s });\n\n for (let i = 0; i < section.items.length; i++) {\n flat.push({ isHeader: false, item: section.items[i]!, itemIndex: i, sectionIndex: s });\n }\n }\n\n return flat;\n}\n\n// ─── Implementation ───────────────────────────────────────────────────────────\n\nexport function createGroupedVirtualizer<T>(\n target: ScrollTarget,\n options: GroupVirtualizerOptions<T>,\n): GroupVirtualizer<T> {\n if (typeof options.estimateHeaderSize === 'number')\n requirePositiveNumber(options.estimateHeaderSize, 'estimateHeaderSize');\n\n if (typeof options.estimateItemSize === 'number') requirePositiveNumber(options.estimateItemSize, 'estimateItemSize');\n\n if (options.overscan !== undefined) validateOverscan(options.overscan);\n\n if (options.scrollEndDelay !== undefined) requireNonNegativeNumber(options.scrollEndDelay, 'scrollEndDelay');\n\n let sections = options.sections;\n let flat = buildFlatEntries(sections);\n\n function buildEstimateHeader(\n estimateHeaderSize: GroupVirtualizerOptions<T>['estimateHeaderSize'],\n ): (index: number) => number {\n return typeof estimateHeaderSize === 'function'\n ? (index: number) => {\n const entry = flat[index]!;\n\n return (estimateHeaderSize as (section: GroupSection<T>, sectionIndex: number) => number)(\n sections[entry.sectionIndex]!,\n entry.sectionIndex,\n );\n }\n : resolveEstimateFn(\n typeof estimateHeaderSize === 'number' ? estimateHeaderSize : undefined,\n DEFAULT_ESTIMATE_SIZE,\n );\n }\n\n let estimateHeaderFn = buildEstimateHeader(options.estimateHeaderSize);\n let estimateItemUserFn: ((item: T, itemIndex: number, sectionIndex: number) => number) | null =\n typeof options.estimateItemSize === 'function' ? options.estimateItemSize : null;\n let estimateItemFixedFn: ((index: number) => number) | null = estimateItemUserFn\n ? null\n : resolveEstimateFn(\n typeof options.estimateItemSize === 'number' ? options.estimateItemSize : undefined,\n DEFAULT_ESTIMATE_SIZE,\n );\n let userGetItemKey = options.getItemKey;\n\n function estimateFn(globalIndex: number): number {\n const entry = flat[globalIndex];\n\n if (!entry) return DEFAULT_ESTIMATE_SIZE;\n\n if (entry.isHeader) return estimateHeaderFn(globalIndex);\n\n if (estimateItemUserFn) return estimateItemUserFn(entry.item, entry.itemIndex, entry.sectionIndex);\n\n return estimateItemFixedFn!(globalIndex);\n }\n\n function getItemKey(globalIndex: number): VirtualKey {\n const entry = flat[globalIndex];\n\n if (!entry) return globalIndex;\n\n if (entry.isHeader) return `__header_${entry.sectionIndex}`;\n\n if (userGetItemKey) return userGetItemKey(entry.item, entry.itemIndex, entry.sectionIndex);\n\n return globalIndex;\n }\n\n let disposed = false;\n let lastItems: Array<GroupVirtualItem<T>> = [];\n let onChange = options.onChange;\n let onScrollEnd = options.onScrollEnd;\n let onScrollingChange = options.onScrollingChange;\n\n // Optional signal for reactive state\n let stateSignal: Signal<GroupVirtualizerState<T>> | null = null;\n if (options.toSignal) {\n const initialState: GroupVirtualizerState<T> = { headers: [], items: [], stickyHeader: null, totalSize: 0 };\n stateSignal = options.toSignal(initialState);\n }\n\n // Helper to emit state to both callback and signal\n function emitState(state: GroupVirtualizerState<T>): void {\n if (stateSignal) stateSignal.value = state;\n onChange?.(state);\n }\n\n function mapState(state: VirtualizerState): GroupVirtualizerState<T> {\n const items: Array<GroupVirtualItem<T>> = [];\n const headers: GroupVirtualHeader[] = [];\n let stickyHeader: GroupVirtualHeader | null = null;\n\n for (const vi of state.items) {\n const entry = flat[vi.index];\n\n if (!entry) continue;\n\n if (entry.isHeader) {\n headers.push({ ...vi, label: entry.label ?? '', sectionIndex: entry.sectionIndex });\n } else {\n items.push({ ...vi, data: entry.item, itemIndex: entry.itemIndex, sectionIndex: entry.sectionIndex });\n }\n }\n\n if (state.stickyItems.length > 0) {\n const svi = state.stickyItems[0]!;\n const entry = flat[svi.index];\n\n if (entry?.isHeader) {\n stickyHeader = { ...svi, label: entry.label ?? '', sectionIndex: entry.sectionIndex };\n }\n }\n\n lastItems = items;\n\n return { headers, items, stickyHeader, totalSize: state.totalSize };\n }\n\n const virtualizer = createVirtualizer(target, {\n count: flat.length,\n estimateSize: estimateFn,\n getItemKey,\n horizontal: options.horizontal,\n measurementCache: options.measurementCache,\n onChange: (state) => {\n const mapped = mapState(state);\n\n emitState(mapped);\n },\n onScrollEnd: (offset) => onScrollEnd?.(offset),\n onScrollingChange: (isScrolling) => onScrollingChange?.(isScrolling),\n overscan: options.overscan ?? DEFAULT_OVERSCAN,\n scrollEndDelay: options.scrollEndDelay,\n sticky: (i) => flat[i]?.isHeader ?? false,\n });\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (disposed) return;\n\n disposed = true;\n ac.abort();\n virtualizer.dispose();\n }\n\n // Build a flat-index lookup: [sectionIndex, itemIndex] → flat index.\n // Returns -1 when sectionIndex is out of range so callers can no-op safely.\n function flatIndexOf(sectionIndex: number, itemIndex?: number): number {\n if (sectionIndex < 0 || sectionIndex >= sections.length) return -1;\n\n let pos = 0;\n\n for (let s = 0; s < sections.length; s++) {\n if (s === sectionIndex) {\n if (itemIndex === undefined) return pos; // header\n\n return pos + 1 + itemIndex;\n }\n\n pos += 1 + sections[s]!.items.length;\n }\n\n return -1; // unreachable\n }\n\n return {\n get count() {\n return virtualizer.count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return disposed;\n },\n invalidate() {\n if (disposed) return;\n\n virtualizer.invalidate();\n },\n get isScrolling() {\n return virtualizer.isScrolling;\n },\n get items() {\n return lastItems;\n },\n measure(index: number, size: number) {\n if (disposed) return;\n\n virtualizer.measure(index, size);\n },\n measureBatch(entries: Array<{ index: number; size: number }>) {\n if (disposed) return;\n\n virtualizer.measureBatch(entries);\n },\n measureEl(index: number, el: HTMLElement) {\n if (disposed) return () => {};\n\n return virtualizer.measureEl(index, el);\n },\n refresh() {\n if (disposed) return;\n\n virtualizer.refresh();\n },\n get scrollOffset() {\n return virtualizer.scrollOffset;\n },\n scrollToBottom(opts?: { behavior?: ScrollBehavior }) {\n if (disposed) return;\n\n virtualizer.scrollToBottom(opts);\n },\n scrollToIndex(index: number, opts?: ScrollToIndexOptions) {\n if (disposed) return;\n\n virtualizer.scrollToIndex(index, opts);\n },\n scrollToItem(sectionIndex, itemIndex, opts = {}) {\n if (disposed) return;\n\n const globalIndex = flatIndexOf(sectionIndex, itemIndex);\n\n if (globalIndex < 0) return;\n\n virtualizer.scrollToIndex(globalIndex, opts);\n },\n scrollToOffset(offset: number, opts?: { behavior?: ScrollBehavior }) {\n if (disposed) return;\n\n virtualizer.scrollToOffset(offset, opts);\n },\n scrollToSection(sectionIndex, opts = {}) {\n if (disposed) return;\n\n const globalIndex = flatIndexOf(sectionIndex);\n\n if (globalIndex < 0) return;\n\n virtualizer.scrollToIndex(globalIndex, opts);\n },\n scrollToTop(opts?: { behavior?: ScrollBehavior }) {\n if (disposed) return;\n\n virtualizer.scrollToTop(opts);\n },\n get stickyItems() {\n return virtualizer.stickyItems;\n },\n [Symbol.dispose]: _dispose,\n get totalSize() {\n return virtualizer.totalSize;\n },\n /**\n * Replace all sections. Closures for estimateFn and getItemKey already\n * capture the live `flat`/`sections` references — only count is passed to\n * the underlying virtualizer to avoid discarding measured sizes on every\n * data refresh. `refresh()` rebuilds offsets while preserving the cache.\n */\n update(nextSections, opts?: GroupVirtualizerUpdateOptions<T>) {\n if (disposed) return;\n\n if (opts) {\n if (typeof opts.estimateHeaderSize === 'number')\n requirePositiveNumber(opts.estimateHeaderSize, 'estimateHeaderSize');\n\n if (typeof opts.estimateItemSize === 'number') requirePositiveNumber(opts.estimateItemSize, 'estimateItemSize');\n\n if (opts.overscan !== undefined) validateOverscan(opts.overscan);\n\n if (opts.scrollEndDelay !== undefined) requireNonNegativeNumber(opts.scrollEndDelay, 'scrollEndDelay');\n\n if (Object.hasOwn(opts, 'onChange')) onChange = opts.onChange;\n\n if (Object.hasOwn(opts, 'onScrollEnd')) onScrollEnd = opts.onScrollEnd;\n\n if (Object.hasOwn(opts, 'onScrollingChange')) onScrollingChange = opts.onScrollingChange;\n\n // Apply option overrides before rebuilding flat entries.\n if (opts.estimateHeaderSize !== undefined) {\n estimateHeaderFn = buildEstimateHeader(opts.estimateHeaderSize);\n }\n\n if (opts.estimateItemSize !== undefined) {\n estimateItemUserFn = typeof opts.estimateItemSize === 'function' ? opts.estimateItemSize : null;\n estimateItemFixedFn = estimateItemUserFn\n ? null\n : resolveEstimateFn(\n typeof opts.estimateItemSize === 'number' ? opts.estimateItemSize : undefined,\n DEFAULT_ESTIMATE_SIZE,\n );\n }\n\n if (opts.getItemKey !== undefined) userGetItemKey = opts.getItemKey;\n }\n\n sections = nextSections;\n\n const prevCount = flat.length;\n\n flat = buildFlatEntries(sections);\n\n const virtUpdateOpts: VirtualizerUpdateOptions = { count: flat.length };\n\n if (opts?.measurementCache !== undefined) virtUpdateOpts.measurementCache = opts.measurementCache;\n\n if (opts?.overscan !== undefined) virtUpdateOpts.overscan = opts.overscan;\n\n if (opts?.scrollEndDelay !== undefined) virtUpdateOpts.scrollEndDelay = opts.scrollEndDelay;\n\n if (\n flat.length !== prevCount ||\n opts?.measurementCache !== undefined ||\n opts?.overscan !== undefined ||\n opts?.scrollEndDelay !== undefined\n ) {\n virtualizer.update(virtUpdateOpts);\n } else {\n virtualizer.refresh();\n }\n },\n };\n}\n"],"mappings":"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"}
|