@vielzeug/scroll 26.10.15 → 26.10.17
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/LICENSE +21 -0
- package/dist/_adapter.cjs.map +1 -1
- package/dist/_adapter.js.map +1 -1
- package/dist/_alignment.cjs.map +1 -1
- package/dist/_alignment.js.map +1 -1
- package/dist/_axis1d.cjs.map +1 -1
- package/dist/_axis1d.js.map +1 -1
- package/dist/_dev.cjs.map +1 -1
- package/dist/_dev.js.map +1 -1
- package/dist/_sticky.cjs.map +1 -1
- package/dist/_sticky.js.map +1 -1
- package/dist/_utils.cjs.map +1 -1
- package/dist/_utils.js +1 -1
- package/dist/_utils.js.map +1 -1
- package/dist/_validation.cjs +1 -1
- package/dist/_validation.cjs.map +1 -1
- package/dist/_validation.js +1 -1
- package/dist/_validation.js.map +1 -1
- package/dist/dom-virtual-list.cjs.map +1 -1
- package/dist/dom-virtual-list.js +1 -1
- package/dist/dom-virtual-list.js.map +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/grid-virtualizer.cjs.map +1 -1
- package/dist/grid-virtualizer.js +1 -1
- package/dist/grid-virtualizer.js.map +1 -1
- package/dist/grouped-virtualizer.cjs.map +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 +1153 -1
- package/dist/scroll.js.map +1 -1
- package/dist/virtualizer.cjs +1 -1
- package/dist/virtualizer.cjs.map +1 -1
- package/dist/virtualizer.js +1 -1
- package/dist/virtualizer.js.map +1 -1
- package/package.json +5 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom-virtual-list.js","names":[],"sources":["../src/dom-virtual-list.ts"],"sourcesContent":["import {\n createSnapshotStore,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n} from './_utils';\nimport {\n requireNonNegativeInteger,\n requireNonNegativeNumber,\n requirePositiveNumber,\n validateOverscan,\n} from './_validation';\nimport { ScrollConfigError, 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 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};\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 ScrollConfigError('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 const stateStore = createSnapshotStore<VirtualizerState>({ items: [], stickyItems: [], totalSize: 0 });\n\n // Publish state before invoking the render callback.\n function emitState(state: VirtualizerState): void {\n stateStore.publish(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 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 stateStore.dispose();\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 getSnapshot: stateStore.getSnapshot,\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 remeasure() {\n if (disposed) return;\n\n virtualizer?.remeasure();\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 stateStore.publish({ items: [], stickyItems: [], totalSize: 0 });\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 subscribe: stateStore.subscribe,\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dom-virtual-list.js","names":["createNodePool","live","nextLive","stale","inCycle","create","existing","key","node","createDomVirtualList","options","requirePositiveNumber","requireNonNegativeInteger","validateOverscan","ScrollConfigError","requireNonNegativeNumber","currentItems","disposed","ac","listEl","stateStore","createSnapshotStore","emitState","state","handleChange","pool","virtualizer","resolveKey","item","index","resolveEstimate","applyListSize","totalSize","toRenderItem","data","vi","ScrollRangeError","clearAndReset","resolveStickToBottom","opt","spawnVirtualizer","v","createVirtualizer","_dispose","threshold","size","entries","el","scrollOptions","offset","stick","wasAtEnd","items","countChanged","createVirtualScroller","scrollEl","container","ctrl","innerDispose"],"sources":["../src/dom-virtual-list.ts"],"sourcesContent":["import {\n createSnapshotStore,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n} from './_utils';\nimport {\n requireNonNegativeInteger,\n requireNonNegativeNumber,\n requirePositiveNumber,\n validateOverscan,\n} from './_validation';\nimport { ScrollConfigError, 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 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};\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 ScrollConfigError('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 const stateStore = createSnapshotStore<VirtualizerState>({ items: [], stickyItems: [], totalSize: 0 });\n\n // Publish state before invoking the render callback.\n function emitState(state: VirtualizerState): void {\n stateStore.publish(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 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 stateStore.dispose();\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 getSnapshot: stateStore.getSnapshot,\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 remeasure() {\n if (disposed) return;\n\n virtualizer?.remeasure();\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 stateStore.publish({ items: [], stickyItems: [], totalSize: 0 });\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 subscribe: stateStore.subscribe,\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"],"mappings":"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"}
|
package/dist/errors.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** 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 ScrollConfigError extends ScrollError {}\n\n/** Thrown when a virtual-list index is out of bounds. */\nexport class ScrollRangeError extends ScrollError {}\n"],"mappings":"AACA,
|
|
1
|
+
{"version":3,"file":"errors.cjs","names":["ScrollError","message","opts","ScrollConfigError","ScrollRangeError"],"sources":["../src/errors.ts"],"sourcesContent":["/** 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 ScrollConfigError extends ScrollError {}\n\n/** Thrown when a virtual-list index is out of bounds. */\nexport class ScrollRangeError extends ScrollError {}\n"],"mappings":"AACA,IAAaA,EAAb,cAAiC,KAAM,CACrC,YAAY,EAAiB,EAAqB,CAChD,MAAMC,EAASC,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGaC,EAAb,cAAuCH,CAAY,CAAC,EAGvCI,EAAb,cAAsCJ,CAAY,CAAC,E"}
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** 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 ScrollConfigError extends ScrollError {}\n\n/** Thrown when a virtual-list index is out of bounds. */\nexport class ScrollRangeError extends ScrollError {}\n"],"mappings":"AACA,
|
|
1
|
+
{"version":3,"file":"errors.js","names":["ScrollError","message","opts","ScrollConfigError","ScrollRangeError"],"sources":["../src/errors.ts"],"sourcesContent":["/** 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 ScrollConfigError extends ScrollError {}\n\n/** Thrown when a virtual-list index is out of bounds. */\nexport class ScrollRangeError extends ScrollError {}\n"],"mappings":"AACA,IAAaA,EAAb,cAAiC,KAAM,CACrC,YAAY,EAAiB,EAAqB,CAChD,MAAMC,EAASC,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGaC,EAAb,cAAuCH,CAAY,CAAC,EAGvCI,EAAb,cAAsCJ,CAAY,CAAC,E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grid-virtualizer.cjs","names":[],"sources":["../src/grid-virtualizer.ts"],"sourcesContent":["import { createScrollAdapter } from './_adapter';\nimport { alignOffset } from './_alignment';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport {\n createSnapshotStore,\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';\nimport type { ScrollStore } from './virtualizer';\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 /** 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}\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 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 extends ScrollStore<GridVirtualizerState> {\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\n let onChange = options.onChange;\n let onRangeChange = options.onRangeChange;\n\n // Publish state before invoking the render callback.\n function emitState(state: GridVirtualizerState): void {\n stateStore.publish(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 const stateStore = createSnapshotStore<GridVirtualizerState>({ cols, rows, totalHeight: 0, totalWidth: 0 });\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 (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 stateStore.dispose();\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 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 getSnapshot: stateStore.getSnapshot,\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 subscribe: stateStore.subscribe,\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"grid-virtualizer.cjs","names":["requireNonNegativeInteger","resolveEstimateFn","normalizeOverscan","createSnapshotStore","createAxis1D","toNonNegativeInt","toPositiveNumber","observeResize","alignOffset","createScrollAdapter","require__utils","require__validation","require__adapter","require__axis1d","require__alignment","createGridVirtualizer","options","rowCount","colCount","estimateRowFn","estimateColFn","overscanY","overscanX","onChange","onRangeChange","emitState","stateStore","state","measuredRows","measuredCols","scrollTop","scrollLeft","viewportHeight","viewportWidth","rows","cols","disposed","rowAx","r","colAx","c","pendingBatchBuild","clampTop","safe","offset","clampLeft","computeVisible","totalHeight","totalWidth","renderLastRow","renderFirstRow","renderLastCol","renderFirstCol","rowSame","colSame","nextRows","nextCols","recordRowMeasurement","row","s","size","recordColMeasurement","col","scheduleFlush","fromRow","fromCol","measureRow","measureColumn","measureBatch","changed","rowEntries","index","colEntries","measureRowEl","ac","el","entry","measureColEl","invalidate","refresh","prependRows","additionalRowCount","prependedHeight","newScrollTop","adapter","scrollToRow","safeRow","behavior","opts","rowStart","rowSize","targetTop","scrollToColumn","safeCol","colStart","colSize","targetLeft","scrollToCell","update","next","rebuildRows","rebuildCols","needsCompute","n","disposeImpl","handleScroll","handleResize","target"],"sources":["../src/grid-virtualizer.ts"],"sourcesContent":["import { createScrollAdapter } from './_adapter';\nimport { alignOffset } from './_alignment';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport {\n createSnapshotStore,\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';\nimport type { ScrollStore } from './virtualizer';\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 /** 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}\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 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 extends ScrollStore<GridVirtualizerState> {\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\n let onChange = options.onChange;\n let onRangeChange = options.onRangeChange;\n\n // Publish state before invoking the render callback.\n function emitState(state: GridVirtualizerState): void {\n stateStore.publish(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 const stateStore = createSnapshotStore<GridVirtualizerState>({ cols, rows, totalHeight: 0, totalWidth: 0 });\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 (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 stateStore.dispose();\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 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 getSnapshot: stateStore.getSnapshot,\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 subscribe: stateStore.subscribe,\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"],"mappings":"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"}
|
package/dist/grid-virtualizer.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createSnapshotStore as e,normalizeOverscan as t,observeResize as n,resolveEstimateFn as r,toNonNegativeInt as i,toPositiveNumber as a}from"./_utils.js";import{requireNonNegativeInteger as o,requireNonNegativeNumber as s,requirePositiveNumber as c,validateOverscan as l}from"./_validation.js";import{createScrollAdapter as u}from"./_adapter.js";import{createAxis1D as d}from"./_axis1d.js";import{alignOffset as f}from"./_alignment.js";function p(p,m){typeof m.estimateRowSize==`number`&&c(m.estimateRowSize,`estimateRowSize`),typeof m.estimateColSize==`number`&&c(m.estimateColSize,`estimateColSize`),m.initialScrollTop!==void 0&&s(m.initialScrollTop,`initialScrollTop`),m.initialScrollLeft!==void 0&&s(m.initialScrollLeft,`initialScrollLeft`),m.rowGap!==void 0&&o(m.rowGap,`rowGap`),m.colGap!==void 0&&o(m.colGap,`colGap`),m.overscanY!==void 0&&l(m.overscanY,`overscanY`),m.overscanX!==void 0&&l(m.overscanX,`overscanX`);let h=o(m.rowCount,`rowCount`),g=o(m.colCount,`colCount`),_=r(m.estimateRowSize,36),v=r(m.estimateColSize,36),y=t(m.overscanY,3),b=t(m.overscanX,3),x=m.onChange,S=m.onRangeChange;function C(e){M.publish(e),x?.(e)}let w=m.rowMeasurementCache
|
|
1
|
+
import{createSnapshotStore as e,normalizeOverscan as t,observeResize as n,resolveEstimateFn as r,toNonNegativeInt as i,toPositiveNumber as a}from"./_utils.js";import{requireNonNegativeInteger as o,requireNonNegativeNumber as s,requirePositiveNumber as c,validateOverscan as l}from"./_validation.js";import{createScrollAdapter as u}from"./_adapter.js";import{createAxis1D as d}from"./_axis1d.js";import{alignOffset as f}from"./_alignment.js";function p(p,m){typeof m.estimateRowSize==`number`&&c(m.estimateRowSize,`estimateRowSize`),typeof m.estimateColSize==`number`&&c(m.estimateColSize,`estimateColSize`),m.initialScrollTop!==void 0&&s(m.initialScrollTop,`initialScrollTop`),m.initialScrollLeft!==void 0&&s(m.initialScrollLeft,`initialScrollLeft`),m.rowGap!==void 0&&o(m.rowGap,`rowGap`),m.colGap!==void 0&&o(m.colGap,`colGap`),m.overscanY!==void 0&&l(m.overscanY,`overscanY`),m.overscanX!==void 0&&l(m.overscanX,`overscanX`);let h=o(m.rowCount,`rowCount`),g=o(m.colCount,`colCount`),_=r(m.estimateRowSize,36),v=r(m.estimateColSize,36),y=t(m.overscanY,3),b=t(m.overscanX,3),x=m.onChange,S=m.onRangeChange;function C(e){M.publish(e),x?.(e)}let w=m.rowMeasurementCache??/* @__PURE__ */ new Map,T=m.colMeasurementCache??/* @__PURE__ */ new Map,E=0,D=0,O=0,k=0,A=[],j=[],M=e({cols:j,rows:A,totalHeight:0,totalWidth:0}),N=!1,P=d(h,e=>w.get(e)??_(e),i(m.rowGap??0)),F=d(g,e=>T.get(e)??v(e),i(m.colGap??0)),I=!1;function L(e){let t=Number.isFinite(e)?e:0;return Math.min(Math.max(0,P.totalSize-O),Math.max(0,t))}function R(e){let t=Number.isFinite(e)?e:0;return Math.min(Math.max(0,F.totalSize-k),Math.max(0,t))}function z(){if(N)return;let e=P.totalSize,t=F.totalSize;if(h===0||g===0||O<=0||k<=0){(P.prevStart!==-1||P.prevTotalSize!==e||F.prevTotalSize!==t)&&(P.commitDedup(-1,-1),F.commitDedup(-1,-1),A=[],j=[],C({cols:[],rows:[],totalHeight:e,totalWidth:t}));return}let{renderEnd:n,renderStart:r}=P.computeRange(E,E+O,y.start,y.end),{renderEnd:i,renderStart:a}=F.computeRange(D,D+k,b.start,b.end),o=P.isDedupSame(r,n),s=F.isDedupSame(a,i);if(o&&s)return;P.commitDedup(r,n),F.commitDedup(a,i),(!o||!s)&&S&&S({firstCol:a,firstRow:r,lastCol:i,lastRow:n});let c=[];for(let e=r;e<=n;e++)c.push(P.itemAt(e));let l=[];for(let e=a;e<=i;e++)l.push(F.itemAt(e));A=c,j=l,C({cols:j,rows:A,totalHeight:e,totalWidth:t})}function B(e,t){if(!Number.isFinite(e))return!1;let n=Math.floor(e);if(n<0||n>=h)return!1;let r=a(t,-1);return r<=0||w.get(n)===r?!1:(w.set(n,r),P.markChanged(n),!0)}function V(e,t){if(!Number.isFinite(e))return!1;let n=Math.floor(e);if(n<0||n>=g)return!1;let r=a(t,-1);return r<=0||T.get(n)===r?!1:(T.set(n,r),F.markChanged(n),!0)}function H(){I||(I=!0,queueMicrotask(()=>{I=!1;let e=P.consumeMinChangedIndex();e!==1/0&&P.rebuildFrom(e);let t=F.consumeMinChangedIndex();t!==1/0&&F.rebuildFrom(t),z()}))}function U(e,t){N||B(e,t)&&H()}function W(e,t){N||V(e,t)&&H()}function G(e,t){if(N)return;let n=!1;for(let{index:t,size:r}of e)B(t,r)&&(n=!0);for(let{index:e,size:r}of t)V(e,r)&&(n=!0);n&&H()}function K(e,t){return N?()=>{}:n(Z.signal,t,t=>U(e,t.contentRect.height))}function q(e,t){return N?()=>{}:n(Z.signal,t,t=>W(e,t.contentRect.width))}function J(){N||(w.clear(),T.clear(),P.rebuild(!0),F.rebuild(!0),z())}function Y(){N||(P.rebuild(!0),F.rebuild(!0),z())}function X(e){if(N)return;let t=i(e);if(t===0)return;h+=t,P.setCount(h),P.rebuild(!0);let n=P.startAt(t),r=L(E+n);$.scrollTo(D,r,`auto`),E=r,z()}function ee(e,t={}){if(N||h===0)return;let n=Math.max(0,Math.min(Math.floor(Number.isFinite(e)?e:0),h-1)),r=t.behavior??`auto`,i=P.startAt(n),a=P.sizeAt(n),o=f(i,i+a,a,E,O,t.rowAlign??`auto`);o!==null&&$.scrollTo(D,L(o),r)}function te(e,t={}){if(N||g===0)return;let n=Math.max(0,Math.min(Math.floor(Number.isFinite(e)?e:0),g-1)),r=t.behavior??`auto`,i=F.startAt(n),a=F.sizeAt(n),o=f(i,i+a,a,D,k,t.colAlign??`auto`);o!==null&&$.scrollTo(R(o),E,r)}function ne(e,t,n={}){if(N||h===0||g===0)return;let r=Math.max(0,Math.min(Math.floor(Number.isFinite(e)?e:0),h-1)),i=Math.max(0,Math.min(Math.floor(Number.isFinite(t)?t:0),g-1)),a=n.behavior??`auto`,o=P.startAt(r),s=P.sizeAt(r),c=F.startAt(i),l=F.sizeAt(i),u=f(o,o+s,s,E,O,n.rowAlign??`auto`),d=f(c,c+l,l,D,k,n.colAlign??`auto`);(u!==null||d!==null)&&$.scrollTo(d===null?D:R(d),u===null?E:L(u),a)}function re(e){if(N)return;e.rowCount!==void 0&&o(e.rowCount,`rowCount`),e.colCount!==void 0&&o(e.colCount,`colCount`),typeof e.estimateRowSize==`number`&&c(e.estimateRowSize,`estimateRowSize`),typeof e.estimateColSize==`number`&&c(e.estimateColSize,`estimateColSize`),e.rowGap!==void 0&&o(e.rowGap,`rowGap`),e.colGap!==void 0&&o(e.colGap,`colGap`),e.overscanY!==void 0&&l(e.overscanY,`overscanY`),e.overscanX!==void 0&&l(e.overscanX,`overscanX`);let n=!1,i=!1,a=!1;if(Object.hasOwn(e,`onChange`)&&(x=e.onChange),Object.hasOwn(e,`onRangeChange`)&&(S=e.onRangeChange),e.rowCount!==void 0){let t=e.rowCount;t!==h&&(h=t,P.setCount(h),n=!0)}if(e.colCount!==void 0){let t=e.colCount;t!==g&&(g=t,F.setCount(g),i=!0)}if(e.rowGap!==void 0){let t=e.rowGap;t!==P.gap&&(P.setGap(t),n=!0)}if(e.colGap!==void 0){let t=e.colGap;t!==F.gap&&(F.setGap(t),i=!0)}if(`estimateRowSize`in e&&(_=r(e.estimateRowSize,36),w.clear(),n=!0),`estimateColSize`in e&&(v=r(e.estimateColSize,36),T.clear(),i=!0),e.overscanY!==void 0){let n=t(e.overscanY,3);(n.start!==y.start||n.end!==y.end)&&(y=n,a=!0)}if(e.overscanX!==void 0){let n=t(e.overscanX,3);(n.start!==b.start||n.end!==b.end)&&(b=n,a=!0)}n&&P.rebuild(!0),i&&F.rebuild(!0),(n||i||a)&&z()}let Z=new AbortController;function Q(){N||(N=!0,M.dispose(),Z.abort(),$.detach())}function ie(){E=L($.y.readOffset()),D=R($.x.readOffset()),z()}function ae(){O=$.y.readViewportSize(),k=$.x.readViewportSize(),z()}let $=u(p,ie,ae);return P.rebuild(!0),F.rebuild(!0),O=$.y.readViewportSize(),k=$.x.readViewportSize(),m.initialScrollTop===void 0?m.initialScrollLeft!==void 0&&$.scrollTo(R(m.initialScrollLeft),0,`auto`):$.scrollTo(m.initialScrollLeft??0,L(m.initialScrollTop),`auto`),E=L($.y.readOffset()),D=R($.x.readOffset()),z(),{get cols(){return j},get disposalSignal(){return Z.signal},dispose:Q,get disposed(){return N},getSnapshot:M.getSnapshot,invalidate:J,measureBatch:G,measureColEl:q,measureColumn:W,measureRow:U,measureRowEl:K,prependRows:X,refresh:Y,get rows(){return A},get scrollLeft(){return D},scrollToCell:ne,scrollToColumn:te,get scrollTop(){return E},scrollToRow:ee,subscribe:M.subscribe,[Symbol.dispose]:Q,get totalHeight(){return P.totalSize},get totalWidth(){return F.totalSize},update:re}}export{p as createGridVirtualizer};
|
|
2
2
|
//# sourceMappingURL=grid-virtualizer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grid-virtualizer.js","names":[],"sources":["../src/grid-virtualizer.ts"],"sourcesContent":["import { createScrollAdapter } from './_adapter';\nimport { alignOffset } from './_alignment';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport {\n createSnapshotStore,\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';\nimport type { ScrollStore } from './virtualizer';\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 /** 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}\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 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 extends ScrollStore<GridVirtualizerState> {\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\n let onChange = options.onChange;\n let onRangeChange = options.onRangeChange;\n\n // Publish state before invoking the render callback.\n function emitState(state: GridVirtualizerState): void {\n stateStore.publish(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 const stateStore = createSnapshotStore<GridVirtualizerState>({ cols, rows, totalHeight: 0, totalWidth: 0 });\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 (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 stateStore.dispose();\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 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 getSnapshot: stateStore.getSnapshot,\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 subscribe: stateStore.subscribe,\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"grid-virtualizer.js","names":["createGridVirtualizer","options","requirePositiveNumber","requireNonNegativeNumber","requireNonNegativeInteger","validateOverscan","rowCount","colCount","estimateRowFn","resolveEstimateFn","estimateColFn","overscanY","normalizeOverscan","overscanX","onChange","onRangeChange","emitState","stateStore","state","measuredRows","measuredCols","scrollTop","scrollLeft","viewportHeight","viewportWidth","rows","cols","createSnapshotStore","disposed","rowAx","createAxis1D","r","toNonNegativeInt","colAx","c","pendingBatchBuild","clampTop","safe","offset","clampLeft","computeVisible","totalHeight","totalWidth","renderLastRow","renderFirstRow","renderLastCol","renderFirstCol","rowSame","colSame","nextRows","nextCols","recordRowMeasurement","row","s","toPositiveNumber","size","recordColMeasurement","col","scheduleFlush","fromRow","fromCol","measureRow","measureColumn","measureBatch","changed","rowEntries","index","colEntries","measureRowEl","observeResize","ac","el","entry","measureColEl","invalidate","refresh","prependRows","n","additionalRowCount","prependedHeight","newScrollTop","adapter","scrollToRow","safeRow","behavior","opts","rowStart","rowSize","targetTop","alignOffset","scrollToColumn","safeCol","colStart","colSize","targetLeft","scrollToCell","update","next","rebuildRows","rebuildCols","needsCompute","disposeImpl","handleScroll","handleResize","createScrollAdapter","target"],"sources":["../src/grid-virtualizer.ts"],"sourcesContent":["import { createScrollAdapter } from './_adapter';\nimport { alignOffset } from './_alignment';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport {\n createSnapshotStore,\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';\nimport type { ScrollStore } from './virtualizer';\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 /** 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}\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 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 extends ScrollStore<GridVirtualizerState> {\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\n let onChange = options.onChange;\n let onRangeChange = options.onRangeChange;\n\n // Publish state before invoking the render callback.\n function emitState(state: GridVirtualizerState): void {\n stateStore.publish(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 const stateStore = createSnapshotStore<GridVirtualizerState>({ cols, rows, totalHeight: 0, totalWidth: 0 });\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 (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 stateStore.dispose();\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 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 getSnapshot: stateStore.getSnapshot,\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 subscribe: stateStore.subscribe,\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"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grouped-virtualizer.cjs","names":[],"sources":["../src/grouped-virtualizer.ts"],"sourcesContent":["import { createSnapshotStore, 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 ScrollStore,\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}\n\nexport interface GroupVirtualizer<T> extends ScrollStore<GroupVirtualizerState<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 const stateStore = createSnapshotStore<GroupVirtualizerState<T>>({\n headers: [],\n items: [],\n stickyHeader: null,\n totalSize: 0,\n });\n let onChange = options.onChange;\n let onScrollEnd = options.onScrollEnd;\n let onScrollingChange = options.onScrollingChange;\n\n // Publish state before invoking the render callback.\n function emitState(state: GroupVirtualizerState<T>): void {\n stateStore.publish(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 stateStore.dispose();\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 getSnapshot: stateStore.getSnapshot,\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 subscribe: stateStore.subscribe,\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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"}
|
|
1
|
+
{"version":3,"file":"grouped-virtualizer.cjs","names":["resolveEstimateFn","createSnapshotStore","createVirtualizer","require__utils","require__validation","require_virtualizer","buildFlatEntries","flat","s","sections","section","i","createGroupedVirtualizer","options","buildEstimateHeader","estimateHeaderSize","entry","index","estimateHeaderFn","estimateItemUserFn","estimateItemFixedFn","userGetItemKey","estimateFn","globalIndex","getItemKey","disposed","lastItems","stateStore","onChange","onScrollEnd","onScrollingChange","emitState","state","mapState","items","headers","stickyHeader","vi","svi","virtualizer","target","offset","isScrolling","ac","_dispose","flatIndexOf","sectionIndex","pos","itemIndex","size","entries","el","opts","nextSections","prevCount","virtUpdateOpts"],"sources":["../src/grouped-virtualizer.ts"],"sourcesContent":["import { createSnapshotStore, 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 ScrollStore,\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}\n\nexport interface GroupVirtualizer<T> extends ScrollStore<GroupVirtualizerState<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 const stateStore = createSnapshotStore<GroupVirtualizerState<T>>({\n headers: [],\n items: [],\n stickyHeader: null,\n totalSize: 0,\n });\n let onChange = options.onChange;\n let onScrollEnd = options.onScrollEnd;\n let onScrollingChange = options.onScrollingChange;\n\n // Publish state before invoking the render callback.\n function emitState(state: GroupVirtualizerState<T>): void {\n stateStore.publish(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 stateStore.dispose();\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 getSnapshot: stateStore.getSnapshot,\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 subscribe: stateStore.subscribe,\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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"}
|