@vielzeug/scroll 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_adapter.cjs.map +1 -1
- package/dist/_adapter.d.ts.map +1 -1
- package/dist/_adapter.js.map +1 -1
- package/dist/dom-virtual-list.cjs +1 -1
- package/dist/dom-virtual-list.cjs.map +1 -1
- package/dist/dom-virtual-list.js +1 -1
- package/dist/grid-virtualizer.cjs.map +1 -1
- package/dist/grid-virtualizer.d.ts +9 -9
- package/dist/grid-virtualizer.d.ts.map +1 -1
- package/dist/grid-virtualizer.js.map +1 -1
- package/dist/grouped-virtualizer.cjs.map +1 -1
- package/dist/grouped-virtualizer.d.ts +5 -5
- package/dist/grouped-virtualizer.d.ts.map +1 -1
- package/dist/grouped-virtualizer.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/reactive.cjs.map +1 -1
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js.map +1 -1
- package/dist/scroll.cjs +1 -1
- package/dist/scroll.cjs.map +1 -1
- package/dist/scroll.iife.js +1 -1
- package/dist/scroll.iife.js.map +1 -1
- package/dist/scroll.js +1 -1
- package/dist/scroll.js.map +1 -1
- package/dist/virtualizer.cjs.map +1 -1
- package/dist/virtualizer.d.ts +8 -8
- package/dist/virtualizer.d.ts.map +1 -1
- package/dist/virtualizer.js.map +1 -1
- package/package.json +17 -17
package/dist/virtualizer.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtualizer.cjs","names":[],"sources":["../src/virtualizer.ts"],"sourcesContent":["import { createScrollAdapter } from './_adapter';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n normalizeOverscan,\n observeResize,\n type Overscan,\n resolveEstimateFn,\n type ScrollTarget,\n toNonNegativeInt,\n toPositiveNumber,\n type VirtualKey,\n} from './_utils';\nimport {\n requireNonNegativeInteger,\n requireNonNegativeNumber,\n requirePositiveNumber,\n validateOverscan,\n} from './_validation';\n\nexport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n type ScrollTarget,\n type VirtualItem,\n type VirtualKey,\n};\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualizerState {\n readonly items: VirtualItem[];\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n}\n\nexport interface VirtualizerOptions {\n count: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: (index: number) => VirtualKey;\n horizontal?: boolean;\n initialOffset?: number;\n /** External measurement cache for scroll restoration or SSR pre-measurement. */\n measurementCache?: MeasurementCache;\n /** Called after every render cycle with the new state. Replace through `update()`. */\n onChange?: (state: VirtualizerState) => void;\n /** Called when scrolling has settled. Replace through `update()`. */\n onScrollEnd?: (offset: number) => void;\n /** Called when the scrolling state changes. Replace through `update()`. */\n onScrollingChange?: (isScrolling: boolean) => void;\n overscan?: Overscan;\n /**\n * Debounce delay (ms) used to detect scroll end when the native `scrollend`\n * event is unavailable. Defaults to 150.\n */\n scrollEndDelay?: number;\n sticky?: (index: number) => boolean;\n}\n\n/**\n * Options accepted by `update()`. Explicit interface instead of Omit<...>\n * for better IDE hover and autocomplete (R7).\n *\n * Intentionally excludes `horizontal` (axis cannot change at runtime) and\n * `initialOffset` (one-time bootstrap value).\n */\nexport interface VirtualizerUpdateOptions {\n count?: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: ((index: number) => VirtualKey) | undefined;\n /** Replace the active measurement cache. Existing entries in the new cache are used immediately on the next rebuild. */\n measurementCache?: MeasurementCache;\n onChange?: ((state: VirtualizerState) => void) | undefined;\n onScrollEnd?: ((offset: number) => void) | undefined;\n onScrollingChange?: ((isScrolling: boolean) => void) | undefined;\n overscan?: Overscan;\n scrollEndDelay?: number;\n sticky?: ((index: number) => boolean) | undefined;\n}\n\nexport interface ScrollToIndexOptions {\n align?: 'auto' | 'center' | 'end' | 'start';\n behavior?: ScrollBehavior;\n /** Called when the scroll animation completes (instant scrolls: next microtask). */\n onComplete?: () => void;\n}\n\nexport interface Virtualizer {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n readonly disposed: boolean;\n /** Currently rendered items. Always populated. */\n readonly items: VirtualItem[];\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n readonly scrollOffset: number;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n dispose: () => void;\n invalidate: () => void;\n /**\n * `true` when scrolled to (or within `threshold` px of) the end — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Check this **before** appending\n * items to decide whether to auto-follow them (e.g. a chat \"stick to bottom\" pattern) —\n * `createDomVirtualList`'s `stickToBottom` option does exactly this. Default `threshold`: `0`.\n */\n isAtEnd: (threshold?: number) => boolean;\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` and auto-measure `index` on resize. Returns a disconnect fn. */\n measureEl: (index: number, el: HTMLElement) => () => void;\n /** Prepend `additionalCount` items at the top while keeping the viewport visually stable. */\n prepend: (additionalCount: number) => void;\n /** Rebuild the full offset table and re-emit (use when item sizes may have changed). */\n refresh: () => void;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n update: (next: VirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createVirtualizer(target: ScrollTarget, options: VirtualizerOptions): Virtualizer {\n if (typeof options.estimateSize === 'number') requirePositiveNumber(options.estimateSize, 'estimateSize');\n\n if (options.gap !== undefined) requireNonNegativeInteger(options.gap, 'gap');\n\n if (options.initialOffset !== undefined) requireNonNegativeNumber(options.initialOffset, 'initialOffset');\n\n if (options.overscan !== undefined) validateOverscan(options.overscan);\n\n if (options.scrollEndDelay !== undefined) requireNonNegativeNumber(options.scrollEndDelay, 'scrollEndDelay');\n\n const horizontal = !!options.horizontal;\n const defaultItemKey = (index: number): VirtualKey => index;\n\n let count = requireNonNegativeInteger(options.count, 'count');\n let estimateFn = resolveEstimateFn(options.estimateSize, DEFAULT_ESTIMATE_SIZE);\n let gap = toNonNegativeInt(options.gap ?? 0);\n let getItemKey = options.getItemKey ?? defaultItemKey;\n let overscan = normalizeOverscan(options.overscan, DEFAULT_OVERSCAN);\n let stickyFn: ((index: number) => boolean) | null = options.sticky ?? null;\n\n let onChange = options.onChange;\n let onScrollEnd = options.onScrollEnd;\n let onScrollingChange = options.onScrollingChange;\n let scrollEndDelay = options.scrollEndDelay ?? 150;\n\n let isScrolling = false;\n let scrollEndTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Detect native scrollend support (Chrome 114+, Firefox 109+)\n const hasNativeScrollEnd = 'onscrollend' in (target as EventTarget);\n\n function notifyScrollEnd(): void {\n if (!isScrolling) return;\n\n isScrolling = false;\n onScrollingChange?.(false);\n onScrollEnd?.(scrollOffset);\n }\n\n function handleScrollStart(): void {\n if (!isScrolling) {\n isScrolling = true;\n onScrollingChange?.(true);\n }\n\n if (!hasNativeScrollEnd) {\n if (scrollEndTimer !== null) clearTimeout(scrollEndTimer);\n\n scrollEndTimer = setTimeout(notifyScrollEnd, scrollEndDelay);\n }\n }\n\n // External or internal measurement cache.\n let measuredByKey: MeasurementCache = options.measurementCache ?? new Map();\n\n let items: VirtualItem[] = [];\n let stickyItems: VirtualItem[] = [];\n\n // ─── Axis setup ──────────────────────────────────────────────────────────────\n // The axis1d primitive owns offset management. The sizeAt closure captures\n // the live `measuredByKey`, `getItemKey`, and `estimateFn` by reference so\n // updates to those let-variables are picked up automatically.\n\n const ax = createAxis1D(count, (i) => measuredByKey.get(getItemKey(i)) ?? estimateFn(i), gap);\n\n // F6: Scroll anchor — prevents viewport jump when items above fold are measured.\n let anchorIndex = -1;\n let anchorDistanceFromTop = 0;\n\n let scrollOffset = 0;\n let viewportSize = 0;\n let prevScrollOffset = -1; // for sticky dedup\n let destroyed = false;\n\n // ─── Scroll clamping ─────────────────────────────────────────────────────────\n\n function clampScrollOffset(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, ax.totalSize - viewportSize);\n\n return Math.min(maxOffset, Math.max(0, safe));\n }\n\n // ─── Sticky items ─────────────────────────────────────────────────────────────\n\n function computeStickyItems(): VirtualItem[] {\n if (!stickyFn || count === 0 || scrollOffset <= 0) return [];\n\n let lastAbove = -1;\n let lo = 0;\n let hi = count - 1;\n\n while (lo <= hi) {\n const mid = (lo + hi) >> 1;\n\n if (ax.startAt(mid) < scrollOffset) {\n lastAbove = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n\n if (lastAbove < 0) return [];\n\n let activeIdx = -1;\n\n for (let i = lastAbove; i >= 0; i--) {\n if (stickyFn(i)) {\n activeIdx = i;\n break;\n }\n }\n\n if (activeIdx === -1) return [];\n\n const activeSize = ax.sizeAt(activeIdx);\n let nextStickyStart = Infinity;\n\n for (let i = activeIdx + 1; i < count; i++) {\n if (stickyFn(i)) {\n nextStickyStart = ax.startAt(i);\n break;\n }\n }\n\n const pinnedStart = Math.min(scrollOffset, nextStickyStart - activeSize);\n\n return [{ end: pinnedStart + activeSize, index: activeIdx, size: activeSize, start: pinnedStart }];\n }\n\n // ─── Compute and emit ─────────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (destroyed) return;\n\n if (count === 0 || viewportSize <= 0) {\n if (ax.prevStart !== -1 || ax.prevTotalSize !== ax.totalSize) {\n // Commit with sentinel renderStart=-1 so prevTotalSize is captured as the\n // current totalSize. Using resetDedup() would set prevTotalSize=-1, causing\n // the guard to fire on every subsequent scroll/resize (Bug 2).\n ax.commitDedup(-1, -1);\n\n items = [];\n stickyItems = [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n return;\n }\n\n const { renderEnd, renderStart } = ax.computeRange(\n scrollOffset,\n scrollOffset + viewportSize,\n overscan.start,\n overscan.end,\n );\n\n // Sticky recomputes on every scroll pixel — bypass dedup in that case.\n const stickyScrolled = stickyFn !== null && scrollOffset !== prevScrollOffset;\n const rangeChanged = !ax.isDedupSame(renderStart, renderEnd);\n\n if (!rangeChanged && !stickyScrolled) return;\n\n ax.commitDedup(renderStart, renderEnd);\n prevScrollOffset = scrollOffset;\n\n const nextItems: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n nextItems.push(ax.itemAt(i));\n }\n\n items = nextItems;\n stickyItems = stickyFn ? computeStickyItems() : [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n // ─── Measurement (R6: measure delegates to measureBatch) ─────────────────────\n\n function recordMeasurement(index: number, size: number): boolean {\n if (!Number.isFinite(index)) return false;\n\n const i = Math.floor(index);\n\n if (i < 0 || i >= count) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n const key = getItemKey(i);\n\n if (measuredByKey.get(key) === s) return false;\n\n measuredByKey.set(key, s);\n ax.markChanged(i);\n\n return true;\n }\n\n // F6: Record a scroll anchor before a batch of measurements arrives.\n function recordScrollAnchor(): void {\n if (scrollOffset <= 0 || count === 0) {\n anchorIndex = -1;\n\n return;\n }\n\n anchorIndex = ax.findFirst(scrollOffset);\n anchorDistanceFromTop = scrollOffset - ax.startAt(anchorIndex);\n }\n\n // F6: After rebuild, restore the anchor to prevent viewport jump.\n function restoreScrollAnchor(): void {\n if (anchorIndex < 0) return;\n\n const expectedOffset = ax.startAt(anchorIndex) + anchorDistanceFromTop;\n const delta = expectedOffset - scrollOffset;\n\n anchorIndex = -1;\n\n if (Math.abs(delta) >= 1) {\n const newOffset = clampScrollOffset(scrollOffset + delta);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n }\n }\n\n function measureBatch(entries: Array<{ index: number; size: number }>): void {\n if (destroyed) return;\n\n let changed = false;\n\n for (const { index, size } of entries) {\n if (recordMeasurement(index, size)) changed = true;\n }\n\n if (changed) {\n // Record anchor before the first pending build is scheduled (F6).\n if (!ax.pendingBuild) recordScrollAnchor();\n\n ax.scheduleBuild(() => {\n restoreScrollAnchor();\n computeVisible();\n });\n }\n }\n\n /** R6: Single-item convenience — delegates to measureBatch. */\n function measure(index: number, size: number): void {\n measureBatch([{ index, size }]);\n }\n\n /** Attach a ResizeObserver to `el`. Returns a disconnect function. */\n function measureEl(index: number, el: HTMLElement): () => void {\n if (destroyed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => {\n measure(index, horizontal ? entry.contentRect.width : entry.contentRect.height);\n });\n }\n\n // ─── Option updates ───────────────────────────────────────────────────────────\n\n function applyOptions(next: VirtualizerUpdateOptions): void {\n if (next.count !== undefined) requireNonNegativeInteger(next.count, 'count');\n\n if (typeof next.estimateSize === 'number') requirePositiveNumber(next.estimateSize, 'estimateSize');\n\n if (next.gap !== undefined) requireNonNegativeInteger(next.gap, 'gap');\n\n if (next.overscan !== undefined) validateOverscan(next.overscan);\n\n if (next.scrollEndDelay !== undefined) requireNonNegativeNumber(next.scrollEndDelay, 'scrollEndDelay');\n\n let needsRebuild = false;\n let needsCompute = false;\n\n if (Object.hasOwn(next, 'onChange')) onChange = next.onChange;\n\n if (Object.hasOwn(next, 'onScrollEnd')) onScrollEnd = next.onScrollEnd;\n\n if (Object.hasOwn(next, 'onScrollingChange')) onScrollingChange = next.onScrollingChange;\n\n if (next.scrollEndDelay !== undefined) {\n scrollEndDelay = next.scrollEndDelay;\n }\n\n if (next.count !== undefined) {\n const nextCount = next.count;\n\n if (nextCount !== count) {\n count = nextCount;\n ax.setCount(count);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'estimateSize')) {\n estimateFn = resolveEstimateFn(next.estimateSize, DEFAULT_ESTIMATE_SIZE);\n measuredByKey.clear();\n needsRebuild = true;\n }\n\n if (next.gap !== undefined) {\n const nextGap = next.gap;\n\n if (nextGap !== gap) {\n gap = nextGap;\n ax.setGap(gap);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'getItemKey')) {\n const nextKey = next.getItemKey ?? defaultItemKey;\n\n if (nextKey !== getItemKey) {\n getItemKey = nextKey;\n measuredByKey.clear();\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'measurementCache') && next.measurementCache !== undefined) {\n measuredByKey = next.measurementCache;\n needsRebuild = true;\n }\n\n if (next.overscan !== undefined) {\n const nextOverscan = normalizeOverscan(next.overscan, DEFAULT_OVERSCAN);\n\n if (nextOverscan.start !== overscan.start || nextOverscan.end !== overscan.end) {\n overscan = nextOverscan;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'sticky')) {\n const nextStickyFn = next.sticky ?? null;\n\n if (nextStickyFn !== stickyFn) {\n stickyFn = nextStickyFn;\n ax.resetDedup(); // force re-emit to flush updated stickyItems\n }\n\n needsCompute = true;\n }\n\n if (needsRebuild) {\n recordScrollAnchor();\n ax.rebuild(true);\n restoreScrollAnchor();\n }\n\n if (needsCompute || needsRebuild) computeVisible();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────────\n\n function invalidate(): void {\n if (destroyed) return;\n\n measuredByKey.clear();\n ax.rebuild(true);\n computeVisible();\n }\n\n /** Full O(n) offset rebuild followed by re-emit. Also used internally for data-only re-emit when sizes unchanged. */\n function refresh(): void {\n if (destroyed) return;\n\n ax.rebuild(true);\n computeVisible();\n }\n\n function prepend(additionalCount: number): void {\n if (destroyed) return;\n\n const n = toNonNegativeInt(additionalCount);\n\n if (n === 0) return;\n\n count += n;\n ax.setCount(count);\n ax.rebuild(true);\n\n // Adjust scroll to keep the same content visible (F6-like stable prepend).\n const prependedHeight = ax.startAt(n);\n const newOffset = clampScrollOffset(scrollOffset + prependedHeight);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n\n computeVisible();\n }\n\n function update(next: VirtualizerUpdateOptions): void {\n if (destroyed) return;\n\n applyOptions(next);\n }\n\n function scrollToIndex(index: number, opts: ScrollToIndexOptions = {}): void {\n if (destroyed || count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, count - 1));\n const align = opts.align ?? 'auto';\n const behavior = opts.behavior ?? 'auto';\n const itemStart = ax.startAt(clampedIndex);\n const itemSize = ax.sizeAt(clampedIndex);\n const itemEnd = itemStart + itemSize;\n\n let targetOffset: number;\n\n if (align === 'start') {\n targetOffset = itemStart;\n } else if (align === 'end') {\n targetOffset = itemEnd - viewportSize;\n } else if (align === 'center') {\n targetOffset = itemStart - (viewportSize - itemSize) / 2;\n } else {\n const visibleStart = scrollOffset;\n const visibleEnd = visibleStart + viewportSize;\n\n if (itemStart >= visibleStart && itemEnd <= visibleEnd) {\n if (opts.onComplete) queueMicrotask(opts.onComplete);\n\n return;\n }\n\n targetOffset = itemStart < visibleStart ? itemStart : itemEnd - viewportSize;\n }\n\n domAxis.writeOffset(clampScrollOffset(targetOffset), behavior);\n\n if (opts.onComplete) {\n if (behavior === 'smooth' && 'onscrollend' in target) {\n (target as EventTarget).addEventListener('scrollend', opts.onComplete, { once: true });\n } else {\n queueMicrotask(opts.onComplete);\n }\n }\n }\n\n function scrollToOffset(offset: number, opts: { behavior?: ScrollBehavior } = {}): void {\n if (destroyed) return;\n\n domAxis.writeOffset(clampScrollOffset(offset), opts.behavior ?? 'auto');\n }\n\n function scrollToTop(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(0, opts);\n }\n\n function scrollToBottom(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(Math.max(0, ax.totalSize - viewportSize), opts);\n }\n\n function isAtEnd(threshold = 0): boolean {\n return ax.totalSize - (scrollOffset + viewportSize) <= threshold;\n }\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (destroyed) return;\n\n destroyed = true;\n ac.abort();\n\n if (scrollEndTimer !== null) {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = null;\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).removeEventListener('scrollend', notifyScrollEnd);\n }\n\n adapter.detach();\n }\n\n // ─── Bootstrap ───────────────────────────────────────────────────────────────\n\n function handleScroll(): void {\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n handleScrollStart();\n computeVisible();\n }\n\n function handleResize(): void {\n viewportSize = domAxis.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n const domAxis = horizontal ? adapter.x : adapter.y;\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).addEventListener('scrollend', notifyScrollEnd, { passive: true });\n }\n\n ax.rebuild(true);\n\n if (options.initialOffset !== undefined) {\n domAxis.writeOffset(clampScrollOffset(options.initialOffset), 'auto');\n }\n\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n viewportSize = domAxis.readViewportSize();\n\n computeVisible();\n\n return {\n get count() {\n return count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return destroyed;\n },\n invalidate,\n isAtEnd,\n get isScrolling() {\n return isScrolling;\n },\n get items() {\n return items;\n },\n measure,\n measureBatch,\n measureEl,\n prepend,\n refresh,\n get scrollOffset() {\n return scrollOffset;\n },\n scrollToBottom,\n scrollToIndex,\n scrollToOffset,\n scrollToTop,\n get stickyItems() {\n return stickyItems;\n },\n [Symbol.dispose]: _dispose,\n get totalSize() {\n return ax.totalSize;\n },\n update,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"virtualizer.cjs","names":[],"sources":["../src/virtualizer.ts"],"sourcesContent":["import { createScrollAdapter } from './_adapter';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n normalizeOverscan,\n type Overscan,\n observeResize,\n resolveEstimateFn,\n type ScrollTarget,\n toNonNegativeInt,\n toPositiveNumber,\n type VirtualKey,\n} from './_utils';\nimport {\n requireNonNegativeInteger,\n requireNonNegativeNumber,\n requirePositiveNumber,\n validateOverscan,\n} from './_validation';\n\nexport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n type ScrollTarget,\n type VirtualItem,\n type VirtualKey,\n};\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualizerState {\n readonly items: VirtualItem[];\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n}\n\nexport interface VirtualizerOptions {\n count: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: (index: number) => VirtualKey;\n horizontal?: boolean;\n initialOffset?: number;\n /** External measurement cache for scroll restoration or SSR pre-measurement. */\n measurementCache?: MeasurementCache;\n /** Called after every render cycle with the new state. Replace through `update()`. */\n onChange?: (state: VirtualizerState) => void;\n /** Called when scrolling has settled. Replace through `update()`. */\n onScrollEnd?: (offset: number) => void;\n /** Called when the scrolling state changes. Replace through `update()`. */\n onScrollingChange?: (isScrolling: boolean) => void;\n overscan?: Overscan;\n /**\n * Debounce delay (ms) used to detect scroll end when the native `scrollend`\n * event is unavailable. Defaults to 150.\n */\n scrollEndDelay?: number;\n sticky?: (index: number) => boolean;\n}\n\n/**\n * Options accepted by `update()`. Explicit interface instead of Omit<...>\n * for better IDE hover and autocomplete (R7).\n *\n * Intentionally excludes `horizontal` (axis cannot change at runtime) and\n * `initialOffset` (one-time bootstrap value).\n */\nexport interface VirtualizerUpdateOptions {\n count?: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: ((index: number) => VirtualKey) | undefined;\n /** Replace the active measurement cache. Existing entries in the new cache are used immediately on the next rebuild. */\n measurementCache?: MeasurementCache;\n onChange?: ((state: VirtualizerState) => void) | undefined;\n onScrollEnd?: ((offset: number) => void) | undefined;\n onScrollingChange?: ((isScrolling: boolean) => void) | undefined;\n overscan?: Overscan;\n scrollEndDelay?: number;\n sticky?: ((index: number) => boolean) | undefined;\n}\n\nexport interface ScrollToIndexOptions {\n align?: 'auto' | 'center' | 'end' | 'start';\n behavior?: ScrollBehavior;\n /** Called when the scroll animation completes (instant scrolls: next microtask). */\n onComplete?: () => void;\n}\n\nexport interface Virtualizer {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n dispose: () => void;\n readonly disposed: boolean;\n invalidate: () => void;\n /**\n * `true` when scrolled to (or within `threshold` px of) the end — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Check this **before** appending\n * items to decide whether to auto-follow them (e.g. a chat \"stick to bottom\" pattern) —\n * `createDomVirtualList`'s `stickToBottom` option does exactly this. Default `threshold`: `0`.\n */\n isAtEnd: (threshold?: number) => boolean;\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n /** Currently rendered items. Always populated. */\n readonly items: VirtualItem[];\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` and auto-measure `index` on resize. Returns a disconnect fn. */\n measureEl: (index: number, el: HTMLElement) => () => void;\n /** Prepend `additionalCount` items at the top while keeping the viewport visually stable. */\n prepend: (additionalCount: number) => void;\n /** Rebuild the full offset table and re-emit (use when item sizes may have changed). */\n refresh: () => void;\n readonly scrollOffset: number;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n update: (next: VirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createVirtualizer(target: ScrollTarget, options: VirtualizerOptions): Virtualizer {\n if (typeof options.estimateSize === 'number') requirePositiveNumber(options.estimateSize, 'estimateSize');\n\n if (options.gap !== undefined) requireNonNegativeInteger(options.gap, 'gap');\n\n if (options.initialOffset !== undefined) requireNonNegativeNumber(options.initialOffset, 'initialOffset');\n\n if (options.overscan !== undefined) validateOverscan(options.overscan);\n\n if (options.scrollEndDelay !== undefined) requireNonNegativeNumber(options.scrollEndDelay, 'scrollEndDelay');\n\n const horizontal = !!options.horizontal;\n const defaultItemKey = (index: number): VirtualKey => index;\n\n let count = requireNonNegativeInteger(options.count, 'count');\n let estimateFn = resolveEstimateFn(options.estimateSize, DEFAULT_ESTIMATE_SIZE);\n let gap = toNonNegativeInt(options.gap ?? 0);\n let getItemKey = options.getItemKey ?? defaultItemKey;\n let overscan = normalizeOverscan(options.overscan, DEFAULT_OVERSCAN);\n let stickyFn: ((index: number) => boolean) | null = options.sticky ?? null;\n\n let onChange = options.onChange;\n let onScrollEnd = options.onScrollEnd;\n let onScrollingChange = options.onScrollingChange;\n let scrollEndDelay = options.scrollEndDelay ?? 150;\n\n let isScrolling = false;\n let scrollEndTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Detect native scrollend support (Chrome 114+, Firefox 109+)\n const hasNativeScrollEnd = 'onscrollend' in (target as EventTarget);\n\n function notifyScrollEnd(): void {\n if (!isScrolling) return;\n\n isScrolling = false;\n onScrollingChange?.(false);\n onScrollEnd?.(scrollOffset);\n }\n\n function handleScrollStart(): void {\n if (!isScrolling) {\n isScrolling = true;\n onScrollingChange?.(true);\n }\n\n if (!hasNativeScrollEnd) {\n if (scrollEndTimer !== null) clearTimeout(scrollEndTimer);\n\n scrollEndTimer = setTimeout(notifyScrollEnd, scrollEndDelay);\n }\n }\n\n // External or internal measurement cache.\n let measuredByKey: MeasurementCache = options.measurementCache ?? new Map();\n\n let items: VirtualItem[] = [];\n let stickyItems: VirtualItem[] = [];\n\n // ─── Axis setup ──────────────────────────────────────────────────────────────\n // The axis1d primitive owns offset management. The sizeAt closure captures\n // the live `measuredByKey`, `getItemKey`, and `estimateFn` by reference so\n // updates to those let-variables are picked up automatically.\n\n const ax = createAxis1D(count, (i) => measuredByKey.get(getItemKey(i)) ?? estimateFn(i), gap);\n\n // F6: Scroll anchor — prevents viewport jump when items above fold are measured.\n let anchorIndex = -1;\n let anchorDistanceFromTop = 0;\n\n let scrollOffset = 0;\n let viewportSize = 0;\n let prevScrollOffset = -1; // for sticky dedup\n let destroyed = false;\n\n // ─── Scroll clamping ─────────────────────────────────────────────────────────\n\n function clampScrollOffset(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, ax.totalSize - viewportSize);\n\n return Math.min(maxOffset, Math.max(0, safe));\n }\n\n // ─── Sticky items ─────────────────────────────────────────────────────────────\n\n function computeStickyItems(): VirtualItem[] {\n if (!stickyFn || count === 0 || scrollOffset <= 0) return [];\n\n let lastAbove = -1;\n let lo = 0;\n let hi = count - 1;\n\n while (lo <= hi) {\n const mid = (lo + hi) >> 1;\n\n if (ax.startAt(mid) < scrollOffset) {\n lastAbove = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n\n if (lastAbove < 0) return [];\n\n let activeIdx = -1;\n\n for (let i = lastAbove; i >= 0; i--) {\n if (stickyFn(i)) {\n activeIdx = i;\n break;\n }\n }\n\n if (activeIdx === -1) return [];\n\n const activeSize = ax.sizeAt(activeIdx);\n let nextStickyStart = Infinity;\n\n for (let i = activeIdx + 1; i < count; i++) {\n if (stickyFn(i)) {\n nextStickyStart = ax.startAt(i);\n break;\n }\n }\n\n const pinnedStart = Math.min(scrollOffset, nextStickyStart - activeSize);\n\n return [{ end: pinnedStart + activeSize, index: activeIdx, size: activeSize, start: pinnedStart }];\n }\n\n // ─── Compute and emit ─────────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (destroyed) return;\n\n if (count === 0 || viewportSize <= 0) {\n if (ax.prevStart !== -1 || ax.prevTotalSize !== ax.totalSize) {\n // Commit with sentinel renderStart=-1 so prevTotalSize is captured as the\n // current totalSize. Using resetDedup() would set prevTotalSize=-1, causing\n // the guard to fire on every subsequent scroll/resize (Bug 2).\n ax.commitDedup(-1, -1);\n\n items = [];\n stickyItems = [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n return;\n }\n\n const { renderEnd, renderStart } = ax.computeRange(\n scrollOffset,\n scrollOffset + viewportSize,\n overscan.start,\n overscan.end,\n );\n\n // Sticky recomputes on every scroll pixel — bypass dedup in that case.\n const stickyScrolled = stickyFn !== null && scrollOffset !== prevScrollOffset;\n const rangeChanged = !ax.isDedupSame(renderStart, renderEnd);\n\n if (!rangeChanged && !stickyScrolled) return;\n\n ax.commitDedup(renderStart, renderEnd);\n prevScrollOffset = scrollOffset;\n\n const nextItems: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n nextItems.push(ax.itemAt(i));\n }\n\n items = nextItems;\n stickyItems = stickyFn ? computeStickyItems() : [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n // ─── Measurement (R6: measure delegates to measureBatch) ─────────────────────\n\n function recordMeasurement(index: number, size: number): boolean {\n if (!Number.isFinite(index)) return false;\n\n const i = Math.floor(index);\n\n if (i < 0 || i >= count) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n const key = getItemKey(i);\n\n if (measuredByKey.get(key) === s) return false;\n\n measuredByKey.set(key, s);\n ax.markChanged(i);\n\n return true;\n }\n\n // F6: Record a scroll anchor before a batch of measurements arrives.\n function recordScrollAnchor(): void {\n if (scrollOffset <= 0 || count === 0) {\n anchorIndex = -1;\n\n return;\n }\n\n anchorIndex = ax.findFirst(scrollOffset);\n anchorDistanceFromTop = scrollOffset - ax.startAt(anchorIndex);\n }\n\n // F6: After rebuild, restore the anchor to prevent viewport jump.\n function restoreScrollAnchor(): void {\n if (anchorIndex < 0) return;\n\n const expectedOffset = ax.startAt(anchorIndex) + anchorDistanceFromTop;\n const delta = expectedOffset - scrollOffset;\n\n anchorIndex = -1;\n\n if (Math.abs(delta) >= 1) {\n const newOffset = clampScrollOffset(scrollOffset + delta);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n }\n }\n\n function measureBatch(entries: Array<{ index: number; size: number }>): void {\n if (destroyed) return;\n\n let changed = false;\n\n for (const { index, size } of entries) {\n if (recordMeasurement(index, size)) changed = true;\n }\n\n if (changed) {\n // Record anchor before the first pending build is scheduled (F6).\n if (!ax.pendingBuild) recordScrollAnchor();\n\n ax.scheduleBuild(() => {\n restoreScrollAnchor();\n computeVisible();\n });\n }\n }\n\n /** R6: Single-item convenience — delegates to measureBatch. */\n function measure(index: number, size: number): void {\n measureBatch([{ index, size }]);\n }\n\n /** Attach a ResizeObserver to `el`. Returns a disconnect function. */\n function measureEl(index: number, el: HTMLElement): () => void {\n if (destroyed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => {\n measure(index, horizontal ? entry.contentRect.width : entry.contentRect.height);\n });\n }\n\n // ─── Option updates ───────────────────────────────────────────────────────────\n\n function applyOptions(next: VirtualizerUpdateOptions): void {\n if (next.count !== undefined) requireNonNegativeInteger(next.count, 'count');\n\n if (typeof next.estimateSize === 'number') requirePositiveNumber(next.estimateSize, 'estimateSize');\n\n if (next.gap !== undefined) requireNonNegativeInteger(next.gap, 'gap');\n\n if (next.overscan !== undefined) validateOverscan(next.overscan);\n\n if (next.scrollEndDelay !== undefined) requireNonNegativeNumber(next.scrollEndDelay, 'scrollEndDelay');\n\n let needsRebuild = false;\n let needsCompute = false;\n\n if (Object.hasOwn(next, 'onChange')) onChange = next.onChange;\n\n if (Object.hasOwn(next, 'onScrollEnd')) onScrollEnd = next.onScrollEnd;\n\n if (Object.hasOwn(next, 'onScrollingChange')) onScrollingChange = next.onScrollingChange;\n\n if (next.scrollEndDelay !== undefined) {\n scrollEndDelay = next.scrollEndDelay;\n }\n\n if (next.count !== undefined) {\n const nextCount = next.count;\n\n if (nextCount !== count) {\n count = nextCount;\n ax.setCount(count);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'estimateSize')) {\n estimateFn = resolveEstimateFn(next.estimateSize, DEFAULT_ESTIMATE_SIZE);\n measuredByKey.clear();\n needsRebuild = true;\n }\n\n if (next.gap !== undefined) {\n const nextGap = next.gap;\n\n if (nextGap !== gap) {\n gap = nextGap;\n ax.setGap(gap);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'getItemKey')) {\n const nextKey = next.getItemKey ?? defaultItemKey;\n\n if (nextKey !== getItemKey) {\n getItemKey = nextKey;\n measuredByKey.clear();\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'measurementCache') && next.measurementCache !== undefined) {\n measuredByKey = next.measurementCache;\n needsRebuild = true;\n }\n\n if (next.overscan !== undefined) {\n const nextOverscan = normalizeOverscan(next.overscan, DEFAULT_OVERSCAN);\n\n if (nextOverscan.start !== overscan.start || nextOverscan.end !== overscan.end) {\n overscan = nextOverscan;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'sticky')) {\n const nextStickyFn = next.sticky ?? null;\n\n if (nextStickyFn !== stickyFn) {\n stickyFn = nextStickyFn;\n ax.resetDedup(); // force re-emit to flush updated stickyItems\n }\n\n needsCompute = true;\n }\n\n if (needsRebuild) {\n recordScrollAnchor();\n ax.rebuild(true);\n restoreScrollAnchor();\n }\n\n if (needsCompute || needsRebuild) computeVisible();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────────\n\n function invalidate(): void {\n if (destroyed) return;\n\n measuredByKey.clear();\n ax.rebuild(true);\n computeVisible();\n }\n\n /** Full O(n) offset rebuild followed by re-emit. Also used internally for data-only re-emit when sizes unchanged. */\n function refresh(): void {\n if (destroyed) return;\n\n ax.rebuild(true);\n computeVisible();\n }\n\n function prepend(additionalCount: number): void {\n if (destroyed) return;\n\n const n = toNonNegativeInt(additionalCount);\n\n if (n === 0) return;\n\n count += n;\n ax.setCount(count);\n ax.rebuild(true);\n\n // Adjust scroll to keep the same content visible (F6-like stable prepend).\n const prependedHeight = ax.startAt(n);\n const newOffset = clampScrollOffset(scrollOffset + prependedHeight);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n\n computeVisible();\n }\n\n function update(next: VirtualizerUpdateOptions): void {\n if (destroyed) return;\n\n applyOptions(next);\n }\n\n function scrollToIndex(index: number, opts: ScrollToIndexOptions = {}): void {\n if (destroyed || count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, count - 1));\n const align = opts.align ?? 'auto';\n const behavior = opts.behavior ?? 'auto';\n const itemStart = ax.startAt(clampedIndex);\n const itemSize = ax.sizeAt(clampedIndex);\n const itemEnd = itemStart + itemSize;\n\n let targetOffset: number;\n\n if (align === 'start') {\n targetOffset = itemStart;\n } else if (align === 'end') {\n targetOffset = itemEnd - viewportSize;\n } else if (align === 'center') {\n targetOffset = itemStart - (viewportSize - itemSize) / 2;\n } else {\n const visibleStart = scrollOffset;\n const visibleEnd = visibleStart + viewportSize;\n\n if (itemStart >= visibleStart && itemEnd <= visibleEnd) {\n if (opts.onComplete) queueMicrotask(opts.onComplete);\n\n return;\n }\n\n targetOffset = itemStart < visibleStart ? itemStart : itemEnd - viewportSize;\n }\n\n domAxis.writeOffset(clampScrollOffset(targetOffset), behavior);\n\n if (opts.onComplete) {\n if (behavior === 'smooth' && 'onscrollend' in target) {\n (target as EventTarget).addEventListener('scrollend', opts.onComplete, { once: true });\n } else {\n queueMicrotask(opts.onComplete);\n }\n }\n }\n\n function scrollToOffset(offset: number, opts: { behavior?: ScrollBehavior } = {}): void {\n if (destroyed) return;\n\n domAxis.writeOffset(clampScrollOffset(offset), opts.behavior ?? 'auto');\n }\n\n function scrollToTop(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(0, opts);\n }\n\n function scrollToBottom(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(Math.max(0, ax.totalSize - viewportSize), opts);\n }\n\n function isAtEnd(threshold = 0): boolean {\n return ax.totalSize - (scrollOffset + viewportSize) <= threshold;\n }\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (destroyed) return;\n\n destroyed = true;\n ac.abort();\n\n if (scrollEndTimer !== null) {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = null;\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).removeEventListener('scrollend', notifyScrollEnd);\n }\n\n adapter.detach();\n }\n\n // ─── Bootstrap ───────────────────────────────────────────────────────────────\n\n function handleScroll(): void {\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n handleScrollStart();\n computeVisible();\n }\n\n function handleResize(): void {\n viewportSize = domAxis.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n const domAxis = horizontal ? adapter.x : adapter.y;\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).addEventListener('scrollend', notifyScrollEnd, { passive: true });\n }\n\n ax.rebuild(true);\n\n if (options.initialOffset !== undefined) {\n domAxis.writeOffset(clampScrollOffset(options.initialOffset), 'auto');\n }\n\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n viewportSize = domAxis.readViewportSize();\n\n computeVisible();\n\n return {\n get count() {\n return count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return destroyed;\n },\n invalidate,\n isAtEnd,\n get isScrolling() {\n return isScrolling;\n },\n get items() {\n return items;\n },\n measure,\n measureBatch,\n measureEl,\n prepend,\n refresh,\n get scrollOffset() {\n return scrollOffset;\n },\n scrollToBottom,\n scrollToIndex,\n scrollToOffset,\n scrollToTop,\n get stickyItems() {\n return stickyItems;\n },\n [Symbol.dispose]: _dispose,\n get totalSize() {\n return ax.totalSize;\n },\n update,\n };\n}\n"],"mappings":"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"}
|
package/dist/virtualizer.d.ts
CHANGED
|
@@ -59,15 +59,8 @@ export interface ScrollToIndexOptions {
|
|
|
59
59
|
export interface Virtualizer {
|
|
60
60
|
readonly count: number;
|
|
61
61
|
readonly disposalSignal: AbortSignal;
|
|
62
|
-
readonly disposed: boolean;
|
|
63
|
-
/** Currently rendered items. Always populated. */
|
|
64
|
-
readonly items: VirtualItem[];
|
|
65
|
-
/** `true` while the user is actively scrolling; `false` once scroll has settled. */
|
|
66
|
-
readonly isScrolling: boolean;
|
|
67
|
-
readonly scrollOffset: number;
|
|
68
|
-
readonly stickyItems: VirtualItem[];
|
|
69
|
-
readonly totalSize: number;
|
|
70
62
|
dispose: () => void;
|
|
63
|
+
readonly disposed: boolean;
|
|
71
64
|
invalidate: () => void;
|
|
72
65
|
/**
|
|
73
66
|
* `true` when scrolled to (or within `threshold` px of) the end — the bottom edge in
|
|
@@ -76,6 +69,10 @@ export interface Virtualizer {
|
|
|
76
69
|
* `createDomVirtualList`'s `stickToBottom` option does exactly this. Default `threshold`: `0`.
|
|
77
70
|
*/
|
|
78
71
|
isAtEnd: (threshold?: number) => boolean;
|
|
72
|
+
/** `true` while the user is actively scrolling; `false` once scroll has settled. */
|
|
73
|
+
readonly isScrolling: boolean;
|
|
74
|
+
/** Currently rendered items. Always populated. */
|
|
75
|
+
readonly items: VirtualItem[];
|
|
79
76
|
measure: (index: number, size: number) => void;
|
|
80
77
|
measureBatch: (entries: Array<{
|
|
81
78
|
index: number;
|
|
@@ -87,6 +84,7 @@ export interface Virtualizer {
|
|
|
87
84
|
prepend: (additionalCount: number) => void;
|
|
88
85
|
/** Rebuild the full offset table and re-emit (use when item sizes may have changed). */
|
|
89
86
|
refresh: () => void;
|
|
87
|
+
readonly scrollOffset: number;
|
|
90
88
|
scrollToBottom: (options?: {
|
|
91
89
|
behavior?: ScrollBehavior;
|
|
92
90
|
}) => void;
|
|
@@ -97,6 +95,8 @@ export interface Virtualizer {
|
|
|
97
95
|
scrollToTop: (options?: {
|
|
98
96
|
behavior?: ScrollBehavior;
|
|
99
97
|
}) => void;
|
|
98
|
+
readonly stickyItems: VirtualItem[];
|
|
99
|
+
readonly totalSize: number;
|
|
100
100
|
update: (next: VirtualizerUpdateOptions) => void;
|
|
101
101
|
[Symbol.dispose]: () => void;
|
|
102
102
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtualizer.d.ts","sourceRoot":"","sources":["../src/virtualizer.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,gBAAgB,
|
|
1
|
+
{"version":3,"file":"virtualizer.d.ts","sourceRoot":"","sources":["../src/virtualizer.ts"],"names":[],"mappings":"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"}
|
package/dist/virtualizer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtualizer.js","names":[],"sources":["../src/virtualizer.ts"],"sourcesContent":["import { createScrollAdapter } from './_adapter';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n normalizeOverscan,\n observeResize,\n type Overscan,\n resolveEstimateFn,\n type ScrollTarget,\n toNonNegativeInt,\n toPositiveNumber,\n type VirtualKey,\n} from './_utils';\nimport {\n requireNonNegativeInteger,\n requireNonNegativeNumber,\n requirePositiveNumber,\n validateOverscan,\n} from './_validation';\n\nexport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n type ScrollTarget,\n type VirtualItem,\n type VirtualKey,\n};\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualizerState {\n readonly items: VirtualItem[];\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n}\n\nexport interface VirtualizerOptions {\n count: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: (index: number) => VirtualKey;\n horizontal?: boolean;\n initialOffset?: number;\n /** External measurement cache for scroll restoration or SSR pre-measurement. */\n measurementCache?: MeasurementCache;\n /** Called after every render cycle with the new state. Replace through `update()`. */\n onChange?: (state: VirtualizerState) => void;\n /** Called when scrolling has settled. Replace through `update()`. */\n onScrollEnd?: (offset: number) => void;\n /** Called when the scrolling state changes. Replace through `update()`. */\n onScrollingChange?: (isScrolling: boolean) => void;\n overscan?: Overscan;\n /**\n * Debounce delay (ms) used to detect scroll end when the native `scrollend`\n * event is unavailable. Defaults to 150.\n */\n scrollEndDelay?: number;\n sticky?: (index: number) => boolean;\n}\n\n/**\n * Options accepted by `update()`. Explicit interface instead of Omit<...>\n * for better IDE hover and autocomplete (R7).\n *\n * Intentionally excludes `horizontal` (axis cannot change at runtime) and\n * `initialOffset` (one-time bootstrap value).\n */\nexport interface VirtualizerUpdateOptions {\n count?: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: ((index: number) => VirtualKey) | undefined;\n /** Replace the active measurement cache. Existing entries in the new cache are used immediately on the next rebuild. */\n measurementCache?: MeasurementCache;\n onChange?: ((state: VirtualizerState) => void) | undefined;\n onScrollEnd?: ((offset: number) => void) | undefined;\n onScrollingChange?: ((isScrolling: boolean) => void) | undefined;\n overscan?: Overscan;\n scrollEndDelay?: number;\n sticky?: ((index: number) => boolean) | undefined;\n}\n\nexport interface ScrollToIndexOptions {\n align?: 'auto' | 'center' | 'end' | 'start';\n behavior?: ScrollBehavior;\n /** Called when the scroll animation completes (instant scrolls: next microtask). */\n onComplete?: () => void;\n}\n\nexport interface Virtualizer {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n readonly disposed: boolean;\n /** Currently rendered items. Always populated. */\n readonly items: VirtualItem[];\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n readonly scrollOffset: number;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n dispose: () => void;\n invalidate: () => void;\n /**\n * `true` when scrolled to (or within `threshold` px of) the end — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Check this **before** appending\n * items to decide whether to auto-follow them (e.g. a chat \"stick to bottom\" pattern) —\n * `createDomVirtualList`'s `stickToBottom` option does exactly this. Default `threshold`: `0`.\n */\n isAtEnd: (threshold?: number) => boolean;\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` and auto-measure `index` on resize. Returns a disconnect fn. */\n measureEl: (index: number, el: HTMLElement) => () => void;\n /** Prepend `additionalCount` items at the top while keeping the viewport visually stable. */\n prepend: (additionalCount: number) => void;\n /** Rebuild the full offset table and re-emit (use when item sizes may have changed). */\n refresh: () => void;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n update: (next: VirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createVirtualizer(target: ScrollTarget, options: VirtualizerOptions): Virtualizer {\n if (typeof options.estimateSize === 'number') requirePositiveNumber(options.estimateSize, 'estimateSize');\n\n if (options.gap !== undefined) requireNonNegativeInteger(options.gap, 'gap');\n\n if (options.initialOffset !== undefined) requireNonNegativeNumber(options.initialOffset, 'initialOffset');\n\n if (options.overscan !== undefined) validateOverscan(options.overscan);\n\n if (options.scrollEndDelay !== undefined) requireNonNegativeNumber(options.scrollEndDelay, 'scrollEndDelay');\n\n const horizontal = !!options.horizontal;\n const defaultItemKey = (index: number): VirtualKey => index;\n\n let count = requireNonNegativeInteger(options.count, 'count');\n let estimateFn = resolveEstimateFn(options.estimateSize, DEFAULT_ESTIMATE_SIZE);\n let gap = toNonNegativeInt(options.gap ?? 0);\n let getItemKey = options.getItemKey ?? defaultItemKey;\n let overscan = normalizeOverscan(options.overscan, DEFAULT_OVERSCAN);\n let stickyFn: ((index: number) => boolean) | null = options.sticky ?? null;\n\n let onChange = options.onChange;\n let onScrollEnd = options.onScrollEnd;\n let onScrollingChange = options.onScrollingChange;\n let scrollEndDelay = options.scrollEndDelay ?? 150;\n\n let isScrolling = false;\n let scrollEndTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Detect native scrollend support (Chrome 114+, Firefox 109+)\n const hasNativeScrollEnd = 'onscrollend' in (target as EventTarget);\n\n function notifyScrollEnd(): void {\n if (!isScrolling) return;\n\n isScrolling = false;\n onScrollingChange?.(false);\n onScrollEnd?.(scrollOffset);\n }\n\n function handleScrollStart(): void {\n if (!isScrolling) {\n isScrolling = true;\n onScrollingChange?.(true);\n }\n\n if (!hasNativeScrollEnd) {\n if (scrollEndTimer !== null) clearTimeout(scrollEndTimer);\n\n scrollEndTimer = setTimeout(notifyScrollEnd, scrollEndDelay);\n }\n }\n\n // External or internal measurement cache.\n let measuredByKey: MeasurementCache = options.measurementCache ?? new Map();\n\n let items: VirtualItem[] = [];\n let stickyItems: VirtualItem[] = [];\n\n // ─── Axis setup ──────────────────────────────────────────────────────────────\n // The axis1d primitive owns offset management. The sizeAt closure captures\n // the live `measuredByKey`, `getItemKey`, and `estimateFn` by reference so\n // updates to those let-variables are picked up automatically.\n\n const ax = createAxis1D(count, (i) => measuredByKey.get(getItemKey(i)) ?? estimateFn(i), gap);\n\n // F6: Scroll anchor — prevents viewport jump when items above fold are measured.\n let anchorIndex = -1;\n let anchorDistanceFromTop = 0;\n\n let scrollOffset = 0;\n let viewportSize = 0;\n let prevScrollOffset = -1; // for sticky dedup\n let destroyed = false;\n\n // ─── Scroll clamping ─────────────────────────────────────────────────────────\n\n function clampScrollOffset(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, ax.totalSize - viewportSize);\n\n return Math.min(maxOffset, Math.max(0, safe));\n }\n\n // ─── Sticky items ─────────────────────────────────────────────────────────────\n\n function computeStickyItems(): VirtualItem[] {\n if (!stickyFn || count === 0 || scrollOffset <= 0) return [];\n\n let lastAbove = -1;\n let lo = 0;\n let hi = count - 1;\n\n while (lo <= hi) {\n const mid = (lo + hi) >> 1;\n\n if (ax.startAt(mid) < scrollOffset) {\n lastAbove = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n\n if (lastAbove < 0) return [];\n\n let activeIdx = -1;\n\n for (let i = lastAbove; i >= 0; i--) {\n if (stickyFn(i)) {\n activeIdx = i;\n break;\n }\n }\n\n if (activeIdx === -1) return [];\n\n const activeSize = ax.sizeAt(activeIdx);\n let nextStickyStart = Infinity;\n\n for (let i = activeIdx + 1; i < count; i++) {\n if (stickyFn(i)) {\n nextStickyStart = ax.startAt(i);\n break;\n }\n }\n\n const pinnedStart = Math.min(scrollOffset, nextStickyStart - activeSize);\n\n return [{ end: pinnedStart + activeSize, index: activeIdx, size: activeSize, start: pinnedStart }];\n }\n\n // ─── Compute and emit ─────────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (destroyed) return;\n\n if (count === 0 || viewportSize <= 0) {\n if (ax.prevStart !== -1 || ax.prevTotalSize !== ax.totalSize) {\n // Commit with sentinel renderStart=-1 so prevTotalSize is captured as the\n // current totalSize. Using resetDedup() would set prevTotalSize=-1, causing\n // the guard to fire on every subsequent scroll/resize (Bug 2).\n ax.commitDedup(-1, -1);\n\n items = [];\n stickyItems = [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n return;\n }\n\n const { renderEnd, renderStart } = ax.computeRange(\n scrollOffset,\n scrollOffset + viewportSize,\n overscan.start,\n overscan.end,\n );\n\n // Sticky recomputes on every scroll pixel — bypass dedup in that case.\n const stickyScrolled = stickyFn !== null && scrollOffset !== prevScrollOffset;\n const rangeChanged = !ax.isDedupSame(renderStart, renderEnd);\n\n if (!rangeChanged && !stickyScrolled) return;\n\n ax.commitDedup(renderStart, renderEnd);\n prevScrollOffset = scrollOffset;\n\n const nextItems: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n nextItems.push(ax.itemAt(i));\n }\n\n items = nextItems;\n stickyItems = stickyFn ? computeStickyItems() : [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n // ─── Measurement (R6: measure delegates to measureBatch) ─────────────────────\n\n function recordMeasurement(index: number, size: number): boolean {\n if (!Number.isFinite(index)) return false;\n\n const i = Math.floor(index);\n\n if (i < 0 || i >= count) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n const key = getItemKey(i);\n\n if (measuredByKey.get(key) === s) return false;\n\n measuredByKey.set(key, s);\n ax.markChanged(i);\n\n return true;\n }\n\n // F6: Record a scroll anchor before a batch of measurements arrives.\n function recordScrollAnchor(): void {\n if (scrollOffset <= 0 || count === 0) {\n anchorIndex = -1;\n\n return;\n }\n\n anchorIndex = ax.findFirst(scrollOffset);\n anchorDistanceFromTop = scrollOffset - ax.startAt(anchorIndex);\n }\n\n // F6: After rebuild, restore the anchor to prevent viewport jump.\n function restoreScrollAnchor(): void {\n if (anchorIndex < 0) return;\n\n const expectedOffset = ax.startAt(anchorIndex) + anchorDistanceFromTop;\n const delta = expectedOffset - scrollOffset;\n\n anchorIndex = -1;\n\n if (Math.abs(delta) >= 1) {\n const newOffset = clampScrollOffset(scrollOffset + delta);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n }\n }\n\n function measureBatch(entries: Array<{ index: number; size: number }>): void {\n if (destroyed) return;\n\n let changed = false;\n\n for (const { index, size } of entries) {\n if (recordMeasurement(index, size)) changed = true;\n }\n\n if (changed) {\n // Record anchor before the first pending build is scheduled (F6).\n if (!ax.pendingBuild) recordScrollAnchor();\n\n ax.scheduleBuild(() => {\n restoreScrollAnchor();\n computeVisible();\n });\n }\n }\n\n /** R6: Single-item convenience — delegates to measureBatch. */\n function measure(index: number, size: number): void {\n measureBatch([{ index, size }]);\n }\n\n /** Attach a ResizeObserver to `el`. Returns a disconnect function. */\n function measureEl(index: number, el: HTMLElement): () => void {\n if (destroyed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => {\n measure(index, horizontal ? entry.contentRect.width : entry.contentRect.height);\n });\n }\n\n // ─── Option updates ───────────────────────────────────────────────────────────\n\n function applyOptions(next: VirtualizerUpdateOptions): void {\n if (next.count !== undefined) requireNonNegativeInteger(next.count, 'count');\n\n if (typeof next.estimateSize === 'number') requirePositiveNumber(next.estimateSize, 'estimateSize');\n\n if (next.gap !== undefined) requireNonNegativeInteger(next.gap, 'gap');\n\n if (next.overscan !== undefined) validateOverscan(next.overscan);\n\n if (next.scrollEndDelay !== undefined) requireNonNegativeNumber(next.scrollEndDelay, 'scrollEndDelay');\n\n let needsRebuild = false;\n let needsCompute = false;\n\n if (Object.hasOwn(next, 'onChange')) onChange = next.onChange;\n\n if (Object.hasOwn(next, 'onScrollEnd')) onScrollEnd = next.onScrollEnd;\n\n if (Object.hasOwn(next, 'onScrollingChange')) onScrollingChange = next.onScrollingChange;\n\n if (next.scrollEndDelay !== undefined) {\n scrollEndDelay = next.scrollEndDelay;\n }\n\n if (next.count !== undefined) {\n const nextCount = next.count;\n\n if (nextCount !== count) {\n count = nextCount;\n ax.setCount(count);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'estimateSize')) {\n estimateFn = resolveEstimateFn(next.estimateSize, DEFAULT_ESTIMATE_SIZE);\n measuredByKey.clear();\n needsRebuild = true;\n }\n\n if (next.gap !== undefined) {\n const nextGap = next.gap;\n\n if (nextGap !== gap) {\n gap = nextGap;\n ax.setGap(gap);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'getItemKey')) {\n const nextKey = next.getItemKey ?? defaultItemKey;\n\n if (nextKey !== getItemKey) {\n getItemKey = nextKey;\n measuredByKey.clear();\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'measurementCache') && next.measurementCache !== undefined) {\n measuredByKey = next.measurementCache;\n needsRebuild = true;\n }\n\n if (next.overscan !== undefined) {\n const nextOverscan = normalizeOverscan(next.overscan, DEFAULT_OVERSCAN);\n\n if (nextOverscan.start !== overscan.start || nextOverscan.end !== overscan.end) {\n overscan = nextOverscan;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'sticky')) {\n const nextStickyFn = next.sticky ?? null;\n\n if (nextStickyFn !== stickyFn) {\n stickyFn = nextStickyFn;\n ax.resetDedup(); // force re-emit to flush updated stickyItems\n }\n\n needsCompute = true;\n }\n\n if (needsRebuild) {\n recordScrollAnchor();\n ax.rebuild(true);\n restoreScrollAnchor();\n }\n\n if (needsCompute || needsRebuild) computeVisible();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────────\n\n function invalidate(): void {\n if (destroyed) return;\n\n measuredByKey.clear();\n ax.rebuild(true);\n computeVisible();\n }\n\n /** Full O(n) offset rebuild followed by re-emit. Also used internally for data-only re-emit when sizes unchanged. */\n function refresh(): void {\n if (destroyed) return;\n\n ax.rebuild(true);\n computeVisible();\n }\n\n function prepend(additionalCount: number): void {\n if (destroyed) return;\n\n const n = toNonNegativeInt(additionalCount);\n\n if (n === 0) return;\n\n count += n;\n ax.setCount(count);\n ax.rebuild(true);\n\n // Adjust scroll to keep the same content visible (F6-like stable prepend).\n const prependedHeight = ax.startAt(n);\n const newOffset = clampScrollOffset(scrollOffset + prependedHeight);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n\n computeVisible();\n }\n\n function update(next: VirtualizerUpdateOptions): void {\n if (destroyed) return;\n\n applyOptions(next);\n }\n\n function scrollToIndex(index: number, opts: ScrollToIndexOptions = {}): void {\n if (destroyed || count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, count - 1));\n const align = opts.align ?? 'auto';\n const behavior = opts.behavior ?? 'auto';\n const itemStart = ax.startAt(clampedIndex);\n const itemSize = ax.sizeAt(clampedIndex);\n const itemEnd = itemStart + itemSize;\n\n let targetOffset: number;\n\n if (align === 'start') {\n targetOffset = itemStart;\n } else if (align === 'end') {\n targetOffset = itemEnd - viewportSize;\n } else if (align === 'center') {\n targetOffset = itemStart - (viewportSize - itemSize) / 2;\n } else {\n const visibleStart = scrollOffset;\n const visibleEnd = visibleStart + viewportSize;\n\n if (itemStart >= visibleStart && itemEnd <= visibleEnd) {\n if (opts.onComplete) queueMicrotask(opts.onComplete);\n\n return;\n }\n\n targetOffset = itemStart < visibleStart ? itemStart : itemEnd - viewportSize;\n }\n\n domAxis.writeOffset(clampScrollOffset(targetOffset), behavior);\n\n if (opts.onComplete) {\n if (behavior === 'smooth' && 'onscrollend' in target) {\n (target as EventTarget).addEventListener('scrollend', opts.onComplete, { once: true });\n } else {\n queueMicrotask(opts.onComplete);\n }\n }\n }\n\n function scrollToOffset(offset: number, opts: { behavior?: ScrollBehavior } = {}): void {\n if (destroyed) return;\n\n domAxis.writeOffset(clampScrollOffset(offset), opts.behavior ?? 'auto');\n }\n\n function scrollToTop(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(0, opts);\n }\n\n function scrollToBottom(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(Math.max(0, ax.totalSize - viewportSize), opts);\n }\n\n function isAtEnd(threshold = 0): boolean {\n return ax.totalSize - (scrollOffset + viewportSize) <= threshold;\n }\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (destroyed) return;\n\n destroyed = true;\n ac.abort();\n\n if (scrollEndTimer !== null) {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = null;\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).removeEventListener('scrollend', notifyScrollEnd);\n }\n\n adapter.detach();\n }\n\n // ─── Bootstrap ───────────────────────────────────────────────────────────────\n\n function handleScroll(): void {\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n handleScrollStart();\n computeVisible();\n }\n\n function handleResize(): void {\n viewportSize = domAxis.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n const domAxis = horizontal ? adapter.x : adapter.y;\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).addEventListener('scrollend', notifyScrollEnd, { passive: true });\n }\n\n ax.rebuild(true);\n\n if (options.initialOffset !== undefined) {\n domAxis.writeOffset(clampScrollOffset(options.initialOffset), 'auto');\n }\n\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n viewportSize = domAxis.readViewportSize();\n\n computeVisible();\n\n return {\n get count() {\n return count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return destroyed;\n },\n invalidate,\n isAtEnd,\n get isScrolling() {\n return isScrolling;\n },\n get items() {\n return items;\n },\n measure,\n measureBatch,\n measureEl,\n prepend,\n refresh,\n get scrollOffset() {\n return scrollOffset;\n },\n scrollToBottom,\n scrollToIndex,\n scrollToOffset,\n scrollToTop,\n get stickyItems() {\n return stickyItems;\n },\n [Symbol.dispose]: _dispose,\n get totalSize() {\n return ax.totalSize;\n },\n update,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"virtualizer.js","names":[],"sources":["../src/virtualizer.ts"],"sourcesContent":["import { createScrollAdapter } from './_adapter';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n normalizeOverscan,\n type Overscan,\n observeResize,\n resolveEstimateFn,\n type ScrollTarget,\n toNonNegativeInt,\n toPositiveNumber,\n type VirtualKey,\n} from './_utils';\nimport {\n requireNonNegativeInteger,\n requireNonNegativeNumber,\n requirePositiveNumber,\n validateOverscan,\n} from './_validation';\n\nexport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n type ScrollTarget,\n type VirtualItem,\n type VirtualKey,\n};\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualizerState {\n readonly items: VirtualItem[];\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n}\n\nexport interface VirtualizerOptions {\n count: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: (index: number) => VirtualKey;\n horizontal?: boolean;\n initialOffset?: number;\n /** External measurement cache for scroll restoration or SSR pre-measurement. */\n measurementCache?: MeasurementCache;\n /** Called after every render cycle with the new state. Replace through `update()`. */\n onChange?: (state: VirtualizerState) => void;\n /** Called when scrolling has settled. Replace through `update()`. */\n onScrollEnd?: (offset: number) => void;\n /** Called when the scrolling state changes. Replace through `update()`. */\n onScrollingChange?: (isScrolling: boolean) => void;\n overscan?: Overscan;\n /**\n * Debounce delay (ms) used to detect scroll end when the native `scrollend`\n * event is unavailable. Defaults to 150.\n */\n scrollEndDelay?: number;\n sticky?: (index: number) => boolean;\n}\n\n/**\n * Options accepted by `update()`. Explicit interface instead of Omit<...>\n * for better IDE hover and autocomplete (R7).\n *\n * Intentionally excludes `horizontal` (axis cannot change at runtime) and\n * `initialOffset` (one-time bootstrap value).\n */\nexport interface VirtualizerUpdateOptions {\n count?: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: ((index: number) => VirtualKey) | undefined;\n /** Replace the active measurement cache. Existing entries in the new cache are used immediately on the next rebuild. */\n measurementCache?: MeasurementCache;\n onChange?: ((state: VirtualizerState) => void) | undefined;\n onScrollEnd?: ((offset: number) => void) | undefined;\n onScrollingChange?: ((isScrolling: boolean) => void) | undefined;\n overscan?: Overscan;\n scrollEndDelay?: number;\n sticky?: ((index: number) => boolean) | undefined;\n}\n\nexport interface ScrollToIndexOptions {\n align?: 'auto' | 'center' | 'end' | 'start';\n behavior?: ScrollBehavior;\n /** Called when the scroll animation completes (instant scrolls: next microtask). */\n onComplete?: () => void;\n}\n\nexport interface Virtualizer {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n dispose: () => void;\n readonly disposed: boolean;\n invalidate: () => void;\n /**\n * `true` when scrolled to (or within `threshold` px of) the end — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Check this **before** appending\n * items to decide whether to auto-follow them (e.g. a chat \"stick to bottom\" pattern) —\n * `createDomVirtualList`'s `stickToBottom` option does exactly this. Default `threshold`: `0`.\n */\n isAtEnd: (threshold?: number) => boolean;\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n /** Currently rendered items. Always populated. */\n readonly items: VirtualItem[];\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` and auto-measure `index` on resize. Returns a disconnect fn. */\n measureEl: (index: number, el: HTMLElement) => () => void;\n /** Prepend `additionalCount` items at the top while keeping the viewport visually stable. */\n prepend: (additionalCount: number) => void;\n /** Rebuild the full offset table and re-emit (use when item sizes may have changed). */\n refresh: () => void;\n readonly scrollOffset: number;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n update: (next: VirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createVirtualizer(target: ScrollTarget, options: VirtualizerOptions): Virtualizer {\n if (typeof options.estimateSize === 'number') requirePositiveNumber(options.estimateSize, 'estimateSize');\n\n if (options.gap !== undefined) requireNonNegativeInteger(options.gap, 'gap');\n\n if (options.initialOffset !== undefined) requireNonNegativeNumber(options.initialOffset, 'initialOffset');\n\n if (options.overscan !== undefined) validateOverscan(options.overscan);\n\n if (options.scrollEndDelay !== undefined) requireNonNegativeNumber(options.scrollEndDelay, 'scrollEndDelay');\n\n const horizontal = !!options.horizontal;\n const defaultItemKey = (index: number): VirtualKey => index;\n\n let count = requireNonNegativeInteger(options.count, 'count');\n let estimateFn = resolveEstimateFn(options.estimateSize, DEFAULT_ESTIMATE_SIZE);\n let gap = toNonNegativeInt(options.gap ?? 0);\n let getItemKey = options.getItemKey ?? defaultItemKey;\n let overscan = normalizeOverscan(options.overscan, DEFAULT_OVERSCAN);\n let stickyFn: ((index: number) => boolean) | null = options.sticky ?? null;\n\n let onChange = options.onChange;\n let onScrollEnd = options.onScrollEnd;\n let onScrollingChange = options.onScrollingChange;\n let scrollEndDelay = options.scrollEndDelay ?? 150;\n\n let isScrolling = false;\n let scrollEndTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Detect native scrollend support (Chrome 114+, Firefox 109+)\n const hasNativeScrollEnd = 'onscrollend' in (target as EventTarget);\n\n function notifyScrollEnd(): void {\n if (!isScrolling) return;\n\n isScrolling = false;\n onScrollingChange?.(false);\n onScrollEnd?.(scrollOffset);\n }\n\n function handleScrollStart(): void {\n if (!isScrolling) {\n isScrolling = true;\n onScrollingChange?.(true);\n }\n\n if (!hasNativeScrollEnd) {\n if (scrollEndTimer !== null) clearTimeout(scrollEndTimer);\n\n scrollEndTimer = setTimeout(notifyScrollEnd, scrollEndDelay);\n }\n }\n\n // External or internal measurement cache.\n let measuredByKey: MeasurementCache = options.measurementCache ?? new Map();\n\n let items: VirtualItem[] = [];\n let stickyItems: VirtualItem[] = [];\n\n // ─── Axis setup ──────────────────────────────────────────────────────────────\n // The axis1d primitive owns offset management. The sizeAt closure captures\n // the live `measuredByKey`, `getItemKey`, and `estimateFn` by reference so\n // updates to those let-variables are picked up automatically.\n\n const ax = createAxis1D(count, (i) => measuredByKey.get(getItemKey(i)) ?? estimateFn(i), gap);\n\n // F6: Scroll anchor — prevents viewport jump when items above fold are measured.\n let anchorIndex = -1;\n let anchorDistanceFromTop = 0;\n\n let scrollOffset = 0;\n let viewportSize = 0;\n let prevScrollOffset = -1; // for sticky dedup\n let destroyed = false;\n\n // ─── Scroll clamping ─────────────────────────────────────────────────────────\n\n function clampScrollOffset(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, ax.totalSize - viewportSize);\n\n return Math.min(maxOffset, Math.max(0, safe));\n }\n\n // ─── Sticky items ─────────────────────────────────────────────────────────────\n\n function computeStickyItems(): VirtualItem[] {\n if (!stickyFn || count === 0 || scrollOffset <= 0) return [];\n\n let lastAbove = -1;\n let lo = 0;\n let hi = count - 1;\n\n while (lo <= hi) {\n const mid = (lo + hi) >> 1;\n\n if (ax.startAt(mid) < scrollOffset) {\n lastAbove = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n\n if (lastAbove < 0) return [];\n\n let activeIdx = -1;\n\n for (let i = lastAbove; i >= 0; i--) {\n if (stickyFn(i)) {\n activeIdx = i;\n break;\n }\n }\n\n if (activeIdx === -1) return [];\n\n const activeSize = ax.sizeAt(activeIdx);\n let nextStickyStart = Infinity;\n\n for (let i = activeIdx + 1; i < count; i++) {\n if (stickyFn(i)) {\n nextStickyStart = ax.startAt(i);\n break;\n }\n }\n\n const pinnedStart = Math.min(scrollOffset, nextStickyStart - activeSize);\n\n return [{ end: pinnedStart + activeSize, index: activeIdx, size: activeSize, start: pinnedStart }];\n }\n\n // ─── Compute and emit ─────────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (destroyed) return;\n\n if (count === 0 || viewportSize <= 0) {\n if (ax.prevStart !== -1 || ax.prevTotalSize !== ax.totalSize) {\n // Commit with sentinel renderStart=-1 so prevTotalSize is captured as the\n // current totalSize. Using resetDedup() would set prevTotalSize=-1, causing\n // the guard to fire on every subsequent scroll/resize (Bug 2).\n ax.commitDedup(-1, -1);\n\n items = [];\n stickyItems = [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n return;\n }\n\n const { renderEnd, renderStart } = ax.computeRange(\n scrollOffset,\n scrollOffset + viewportSize,\n overscan.start,\n overscan.end,\n );\n\n // Sticky recomputes on every scroll pixel — bypass dedup in that case.\n const stickyScrolled = stickyFn !== null && scrollOffset !== prevScrollOffset;\n const rangeChanged = !ax.isDedupSame(renderStart, renderEnd);\n\n if (!rangeChanged && !stickyScrolled) return;\n\n ax.commitDedup(renderStart, renderEnd);\n prevScrollOffset = scrollOffset;\n\n const nextItems: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n nextItems.push(ax.itemAt(i));\n }\n\n items = nextItems;\n stickyItems = stickyFn ? computeStickyItems() : [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n // ─── Measurement (R6: measure delegates to measureBatch) ─────────────────────\n\n function recordMeasurement(index: number, size: number): boolean {\n if (!Number.isFinite(index)) return false;\n\n const i = Math.floor(index);\n\n if (i < 0 || i >= count) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n const key = getItemKey(i);\n\n if (measuredByKey.get(key) === s) return false;\n\n measuredByKey.set(key, s);\n ax.markChanged(i);\n\n return true;\n }\n\n // F6: Record a scroll anchor before a batch of measurements arrives.\n function recordScrollAnchor(): void {\n if (scrollOffset <= 0 || count === 0) {\n anchorIndex = -1;\n\n return;\n }\n\n anchorIndex = ax.findFirst(scrollOffset);\n anchorDistanceFromTop = scrollOffset - ax.startAt(anchorIndex);\n }\n\n // F6: After rebuild, restore the anchor to prevent viewport jump.\n function restoreScrollAnchor(): void {\n if (anchorIndex < 0) return;\n\n const expectedOffset = ax.startAt(anchorIndex) + anchorDistanceFromTop;\n const delta = expectedOffset - scrollOffset;\n\n anchorIndex = -1;\n\n if (Math.abs(delta) >= 1) {\n const newOffset = clampScrollOffset(scrollOffset + delta);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n }\n }\n\n function measureBatch(entries: Array<{ index: number; size: number }>): void {\n if (destroyed) return;\n\n let changed = false;\n\n for (const { index, size } of entries) {\n if (recordMeasurement(index, size)) changed = true;\n }\n\n if (changed) {\n // Record anchor before the first pending build is scheduled (F6).\n if (!ax.pendingBuild) recordScrollAnchor();\n\n ax.scheduleBuild(() => {\n restoreScrollAnchor();\n computeVisible();\n });\n }\n }\n\n /** R6: Single-item convenience — delegates to measureBatch. */\n function measure(index: number, size: number): void {\n measureBatch([{ index, size }]);\n }\n\n /** Attach a ResizeObserver to `el`. Returns a disconnect function. */\n function measureEl(index: number, el: HTMLElement): () => void {\n if (destroyed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => {\n measure(index, horizontal ? entry.contentRect.width : entry.contentRect.height);\n });\n }\n\n // ─── Option updates ───────────────────────────────────────────────────────────\n\n function applyOptions(next: VirtualizerUpdateOptions): void {\n if (next.count !== undefined) requireNonNegativeInteger(next.count, 'count');\n\n if (typeof next.estimateSize === 'number') requirePositiveNumber(next.estimateSize, 'estimateSize');\n\n if (next.gap !== undefined) requireNonNegativeInteger(next.gap, 'gap');\n\n if (next.overscan !== undefined) validateOverscan(next.overscan);\n\n if (next.scrollEndDelay !== undefined) requireNonNegativeNumber(next.scrollEndDelay, 'scrollEndDelay');\n\n let needsRebuild = false;\n let needsCompute = false;\n\n if (Object.hasOwn(next, 'onChange')) onChange = next.onChange;\n\n if (Object.hasOwn(next, 'onScrollEnd')) onScrollEnd = next.onScrollEnd;\n\n if (Object.hasOwn(next, 'onScrollingChange')) onScrollingChange = next.onScrollingChange;\n\n if (next.scrollEndDelay !== undefined) {\n scrollEndDelay = next.scrollEndDelay;\n }\n\n if (next.count !== undefined) {\n const nextCount = next.count;\n\n if (nextCount !== count) {\n count = nextCount;\n ax.setCount(count);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'estimateSize')) {\n estimateFn = resolveEstimateFn(next.estimateSize, DEFAULT_ESTIMATE_SIZE);\n measuredByKey.clear();\n needsRebuild = true;\n }\n\n if (next.gap !== undefined) {\n const nextGap = next.gap;\n\n if (nextGap !== gap) {\n gap = nextGap;\n ax.setGap(gap);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'getItemKey')) {\n const nextKey = next.getItemKey ?? defaultItemKey;\n\n if (nextKey !== getItemKey) {\n getItemKey = nextKey;\n measuredByKey.clear();\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'measurementCache') && next.measurementCache !== undefined) {\n measuredByKey = next.measurementCache;\n needsRebuild = true;\n }\n\n if (next.overscan !== undefined) {\n const nextOverscan = normalizeOverscan(next.overscan, DEFAULT_OVERSCAN);\n\n if (nextOverscan.start !== overscan.start || nextOverscan.end !== overscan.end) {\n overscan = nextOverscan;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'sticky')) {\n const nextStickyFn = next.sticky ?? null;\n\n if (nextStickyFn !== stickyFn) {\n stickyFn = nextStickyFn;\n ax.resetDedup(); // force re-emit to flush updated stickyItems\n }\n\n needsCompute = true;\n }\n\n if (needsRebuild) {\n recordScrollAnchor();\n ax.rebuild(true);\n restoreScrollAnchor();\n }\n\n if (needsCompute || needsRebuild) computeVisible();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────────\n\n function invalidate(): void {\n if (destroyed) return;\n\n measuredByKey.clear();\n ax.rebuild(true);\n computeVisible();\n }\n\n /** Full O(n) offset rebuild followed by re-emit. Also used internally for data-only re-emit when sizes unchanged. */\n function refresh(): void {\n if (destroyed) return;\n\n ax.rebuild(true);\n computeVisible();\n }\n\n function prepend(additionalCount: number): void {\n if (destroyed) return;\n\n const n = toNonNegativeInt(additionalCount);\n\n if (n === 0) return;\n\n count += n;\n ax.setCount(count);\n ax.rebuild(true);\n\n // Adjust scroll to keep the same content visible (F6-like stable prepend).\n const prependedHeight = ax.startAt(n);\n const newOffset = clampScrollOffset(scrollOffset + prependedHeight);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n\n computeVisible();\n }\n\n function update(next: VirtualizerUpdateOptions): void {\n if (destroyed) return;\n\n applyOptions(next);\n }\n\n function scrollToIndex(index: number, opts: ScrollToIndexOptions = {}): void {\n if (destroyed || count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, count - 1));\n const align = opts.align ?? 'auto';\n const behavior = opts.behavior ?? 'auto';\n const itemStart = ax.startAt(clampedIndex);\n const itemSize = ax.sizeAt(clampedIndex);\n const itemEnd = itemStart + itemSize;\n\n let targetOffset: number;\n\n if (align === 'start') {\n targetOffset = itemStart;\n } else if (align === 'end') {\n targetOffset = itemEnd - viewportSize;\n } else if (align === 'center') {\n targetOffset = itemStart - (viewportSize - itemSize) / 2;\n } else {\n const visibleStart = scrollOffset;\n const visibleEnd = visibleStart + viewportSize;\n\n if (itemStart >= visibleStart && itemEnd <= visibleEnd) {\n if (opts.onComplete) queueMicrotask(opts.onComplete);\n\n return;\n }\n\n targetOffset = itemStart < visibleStart ? itemStart : itemEnd - viewportSize;\n }\n\n domAxis.writeOffset(clampScrollOffset(targetOffset), behavior);\n\n if (opts.onComplete) {\n if (behavior === 'smooth' && 'onscrollend' in target) {\n (target as EventTarget).addEventListener('scrollend', opts.onComplete, { once: true });\n } else {\n queueMicrotask(opts.onComplete);\n }\n }\n }\n\n function scrollToOffset(offset: number, opts: { behavior?: ScrollBehavior } = {}): void {\n if (destroyed) return;\n\n domAxis.writeOffset(clampScrollOffset(offset), opts.behavior ?? 'auto');\n }\n\n function scrollToTop(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(0, opts);\n }\n\n function scrollToBottom(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(Math.max(0, ax.totalSize - viewportSize), opts);\n }\n\n function isAtEnd(threshold = 0): boolean {\n return ax.totalSize - (scrollOffset + viewportSize) <= threshold;\n }\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (destroyed) return;\n\n destroyed = true;\n ac.abort();\n\n if (scrollEndTimer !== null) {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = null;\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).removeEventListener('scrollend', notifyScrollEnd);\n }\n\n adapter.detach();\n }\n\n // ─── Bootstrap ───────────────────────────────────────────────────────────────\n\n function handleScroll(): void {\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n handleScrollStart();\n computeVisible();\n }\n\n function handleResize(): void {\n viewportSize = domAxis.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n const domAxis = horizontal ? adapter.x : adapter.y;\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).addEventListener('scrollend', notifyScrollEnd, { passive: true });\n }\n\n ax.rebuild(true);\n\n if (options.initialOffset !== undefined) {\n domAxis.writeOffset(clampScrollOffset(options.initialOffset), 'auto');\n }\n\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n viewportSize = domAxis.readViewportSize();\n\n computeVisible();\n\n return {\n get count() {\n return count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return destroyed;\n },\n invalidate,\n isAtEnd,\n get isScrolling() {\n return isScrolling;\n },\n get items() {\n return items;\n },\n measure,\n measureBatch,\n measureEl,\n prepend,\n refresh,\n get scrollOffset() {\n return scrollOffset;\n },\n scrollToBottom,\n scrollToIndex,\n scrollToOffset,\n scrollToTop,\n get stickyItems() {\n return stickyItems;\n },\n [Symbol.dispose]: _dispose,\n get totalSize() {\n return ax.totalSize;\n },\n update,\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vielzeug/scroll",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Virtual list engine — variable-height rows, smooth scrolling, and zero-layout-thrash rendering",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22"
|
|
8
|
+
},
|
|
6
9
|
"files": [
|
|
7
10
|
"dist"
|
|
8
11
|
],
|
|
@@ -11,34 +14,31 @@
|
|
|
11
14
|
"types": "dist/index.d.ts",
|
|
12
15
|
"exports": {
|
|
13
16
|
".": {
|
|
14
|
-
"types": "./dist/index.d.ts",
|
|
15
17
|
"import": "./dist/index.js",
|
|
16
|
-
"require": "./dist/index.cjs"
|
|
18
|
+
"require": "./dist/index.cjs",
|
|
19
|
+
"types": "./dist/index.d.ts"
|
|
17
20
|
}
|
|
18
21
|
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "vite build && pnpm run build:bundle && pnpm run build:types",
|
|
21
|
-
"build:types": "tsc -p tsconfig.declarations.json",
|
|
22
|
-
"fix": "eslint --fix src",
|
|
23
|
-
"lint": "eslint src",
|
|
24
|
-
"prepublishOnly": "pnpm run build",
|
|
25
|
-
"test": "vitest",
|
|
26
|
-
"build:bundle": "vite build --config vite.bundle.config.ts"
|
|
27
|
-
},
|
|
28
22
|
"publishConfig": {
|
|
29
23
|
"access": "public",
|
|
30
24
|
"registry": "https://registry.npmjs.org/"
|
|
31
25
|
},
|
|
32
|
-
"
|
|
33
|
-
"
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "vite build && pnpm run build:bundle && pnpm run build:types",
|
|
28
|
+
"build:bundle": "vite build --config vite.bundle.config.ts",
|
|
29
|
+
"build:types": "tsc -p tsconfig.declarations.json",
|
|
30
|
+
"fix": "biome check --write src",
|
|
31
|
+
"lint": "biome ci src",
|
|
32
|
+
"prepublishOnly": "pnpm run build",
|
|
33
|
+
"test": "vitest"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@vielzeug/ripple": "2.0
|
|
36
|
+
"@vielzeug/ripple": "2.2.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@types/node": "^26.
|
|
39
|
+
"@types/node": "^26.2.0",
|
|
40
40
|
"typescript": "^6.0.3",
|
|
41
|
-
"vite": "^8.2.
|
|
41
|
+
"vite": "^8.2.1",
|
|
42
42
|
"vitest": "^4.1.10"
|
|
43
43
|
}
|
|
44
44
|
}
|