@vielzeug/scroll 2.3.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -320
- package/dist/_utils.cjs +1 -1
- package/dist/_utils.cjs.map +1 -1
- package/dist/_utils.d.ts +6 -0
- package/dist/_utils.d.ts.map +1 -1
- package/dist/_utils.js +1 -1
- package/dist/_utils.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.d.ts +0 -3
- package/dist/dom-virtual-list.d.ts.map +1 -1
- package/dist/dom-virtual-list.js +1 -1
- package/dist/dom-virtual-list.js.map +1 -1
- package/dist/grid-virtualizer.cjs +1 -1
- package/dist/grid-virtualizer.cjs.map +1 -1
- package/dist/grid-virtualizer.d.ts +2 -4
- package/dist/grid-virtualizer.d.ts.map +1 -1
- package/dist/grid-virtualizer.js +1 -1
- package/dist/grid-virtualizer.js.map +1 -1
- package/dist/grouped-virtualizer.cjs +1 -1
- package/dist/grouped-virtualizer.cjs.map +1 -1
- package/dist/grouped-virtualizer.d.ts +2 -5
- package/dist/grouped-virtualizer.d.ts.map +1 -1
- package/dist/grouped-virtualizer.js +1 -1
- package/dist/grouped-virtualizer.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/scroll.cjs +1 -1
- package/dist/scroll.cjs.map +1 -1
- package/dist/scroll.iife.js +1 -1
- package/dist/scroll.iife.js.map +1 -1
- package/dist/scroll.js +1 -1
- package/dist/scroll.js.map +1 -1
- package/dist/virtualizer.cjs +1 -1
- package/dist/virtualizer.cjs.map +1 -1
- package/dist/virtualizer.d.ts +5 -4
- package/dist/virtualizer.d.ts.map +1 -1
- package/dist/virtualizer.js +1 -1
- package/dist/virtualizer.js.map +1 -1
- package/package.json +6 -9
package/dist/virtualizer.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./_utils.cjs"),t=require("./_validation.cjs"),n=require("./_adapter.cjs"),r=require("./_axis1d.cjs"),i=require("./_sticky.cjs");function a(a,o){typeof o.estimateSize==`number`&&t.requirePositiveNumber(o.estimateSize,`estimateSize`),o.gap!==void 0&&t.requireNonNegativeInteger(o.gap,`gap`),o.initialOffset!==void 0&&t.requireNonNegativeNumber(o.initialOffset,`initialOffset`),o.overscan!==void 0&&t.validateOverscan(o.overscan),o.scrollEndDelay!==void 0&&t.requireNonNegativeNumber(o.scrollEndDelay,`scrollEndDelay`);let s=!!o.horizontal,c=e=>e,l=o.keyboardScroll??!1,u=t.requireNonNegativeInteger(o.count,`count`),d=e.resolveEstimateFn(o.estimateSize,36),f=e.toNonNegativeInt(o.gap??0),p=o.getItemKey??c,m=e.normalizeOverscan(o.overscan,3),h=o.sticky??null,g=l,_=o.autoMeasure??!1,v=o.onChange,y=o.onScrollEnd,b=o.onScrollingChange,x=o.scrollEndDelay??150
|
|
1
|
+
const e=require("./_utils.cjs"),t=require("./_validation.cjs"),n=require("./_adapter.cjs"),r=require("./_axis1d.cjs"),i=require("./_sticky.cjs");function a(a,o){typeof o.estimateSize==`number`&&t.requirePositiveNumber(o.estimateSize,`estimateSize`),o.gap!==void 0&&t.requireNonNegativeInteger(o.gap,`gap`),o.initialOffset!==void 0&&t.requireNonNegativeNumber(o.initialOffset,`initialOffset`),o.overscan!==void 0&&t.validateOverscan(o.overscan),o.scrollEndDelay!==void 0&&t.requireNonNegativeNumber(o.scrollEndDelay,`scrollEndDelay`);let s=!!o.horizontal,c=e=>e,l=o.keyboardScroll??!1,u=t.requireNonNegativeInteger(o.count,`count`),d=e.resolveEstimateFn(o.estimateSize,36),f=e.toNonNegativeInt(o.gap??0),p=o.getItemKey??c,m=e.normalizeOverscan(o.overscan,3),h=o.sticky??null,g=l,_=o.autoMeasure??!1,v=o.onChange,y=o.onScrollEnd,b=o.onScrollingChange,x=o.scrollEndDelay??150;function S(e){A.publish(e),v?.(e)}let C=null,w=`onscrollend`in a;function T(){R&&(R=!1,b?.(!1),y?.(P))}function E(){R||(R=!0,b?.(!0)),w||(C!==null&&clearTimeout(C),C=setTimeout(T,x))}let D=o.measurementCache??new Map,O=[],k=[],A=e.createSnapshotStore({items:O,stickyItems:k,totalSize:0}),j=r.createAxis1D(u,e=>D.get(p(e))??d(e),f),M=-1,N=0,P=0,F=0,I=-1,L=!1,R=!1;function z(e){let t=Number.isFinite(e)?e:0,n=Math.max(0,j.totalSize-F);return Math.min(n,Math.max(0,t))}function B(){if(L)return;if(u===0||F<=0){(j.prevStart!==-1||j.prevTotalSize!==j.totalSize)&&(j.commitDedup(-1,-1),O=[],k=[],S({items:O,stickyItems:k,totalSize:j.totalSize}));return}let{renderEnd:e,renderStart:t}=j.computeRange(P,P+F,m.start,m.end),n=h!==null&&P!==I;if(j.isDedupSame(t,e)&&!n)return;j.commitDedup(t,e),I=P;let r=[];for(let n=t;n<=e;n++)r.push(j.itemAt(n));O=r,k=h?i.computeStickyItems({count:u,scrollOffset:P,sizeAt:j.sizeAt,startAt:j.startAt,stickyFn:h}):[],S({items:O,stickyItems:k,totalSize:j.totalSize}),_&&O.length>0&&queueMicrotask(()=>{if(!(L||a instanceof Window))for(let e of O){let t=p(e.index),n=a?.querySelector?.(`[data-vz-key="${t}"]`);n instanceof HTMLElement&&K(e.index,n)}})}function V(t,n){if(!Number.isFinite(t))return!1;let r=Math.floor(t);if(r<0||r>=u)return!1;let i=e.toPositiveNumber(n,-1);if(i<=0)return!1;let a=p(r);return D.get(a)!==i&&(D.set(a,i),j.markChanged(r),!0)}function H(){if(P<=0||u===0){M=-1;return}M=j.findFirst(P),N=P-j.startAt(M)}function U(){if(M<0)return;let e=j.startAt(M)+N-P;if(M=-1,Math.abs(e)>=1){let t=z(P+e);$.writeOffset(t,`auto`),P=t}}function W(e){if(L)return;let t=!1;for(let{index:n,size:r}of e)V(n,r)&&(t=!0);t&&(j.pendingBuild||H(),j.scheduleBuild(()=>{U(),B()}))}function G(e,t){W([{index:e,size:t}])}function K(t,n){return L?()=>{}:e.observeResize(X.signal,n,e=>{G(t,s?e.contentRect.width:e.contentRect.height)})}function q(n){n.count!==void 0&&t.requireNonNegativeInteger(n.count,`count`),typeof n.estimateSize==`number`&&t.requirePositiveNumber(n.estimateSize,`estimateSize`),n.gap!==void 0&&t.requireNonNegativeInteger(n.gap,`gap`),n.overscan!==void 0&&t.validateOverscan(n.overscan),n.scrollEndDelay!==void 0&&t.requireNonNegativeNumber(n.scrollEndDelay,`scrollEndDelay`);let r=!1,i=!1;if(Object.hasOwn(n,`onChange`)&&(v=n.onChange),Object.hasOwn(n,`onScrollEnd`)&&(y=n.onScrollEnd),Object.hasOwn(n,`onScrollingChange`)&&(b=n.onScrollingChange),n.scrollEndDelay!==void 0&&(x=n.scrollEndDelay),n.count!==void 0){let e=n.count;e!==u&&(u=e,j.setCount(u),r=!0)}if(Object.hasOwn(n,`estimateSize`)&&(d=e.resolveEstimateFn(n.estimateSize,36),D.clear(),r=!0),n.gap!==void 0){let e=n.gap;e!==f&&(f=e,j.setGap(f),r=!0)}if(Object.hasOwn(n,`getItemKey`)){let e=n.getItemKey??c;e!==p&&(p=e,D.clear(),r=!0)}if(Object.hasOwn(n,`measurementCache`)&&n.measurementCache!==void 0&&(D=n.measurementCache,r=!0),n.overscan!==void 0){let t=e.normalizeOverscan(n.overscan,3);(t.start!==m.start||t.end!==m.end)&&(m=t,i=!0)}if(Object.hasOwn(n,`sticky`)){let e=n.sticky??null;e!==h&&(h=e,j.resetDedup()),i=!0}Object.hasOwn(n,`keyboardScroll`)&&(g=n.keyboardScroll??!1),Object.hasOwn(n,`autoMeasure`)&&(_=n.autoMeasure??!1),r&&(H(),j.rebuild(!0),U()),(i||r)&&B()}function J(){L||(D.clear(),j.rebuild(!0),B())}function ee(){L||(j.rebuild(!0),B())}function te(t){if(L)return;let n=e.toNonNegativeInt(t);if(n===0)return;u+=n,j.setCount(u),j.rebuild(!0);let r=j.startAt(n),i=z(P+r);$.writeOffset(i,`auto`),P=i,B()}function ne(e){L||q(e)}function re(e,t={}){if(L||u<=0)return;let n=Math.max(0,Math.min(Number.isFinite(e)?Math.floor(e):0,u-1)),r=t.align??`auto`,i=t.behavior??`auto`,o=j.startAt(n),s=j.sizeAt(n),c=o+s,l;if(r===`start`)l=o;else if(r===`end`)l=c-F;else if(r===`center`)l=o-(F-s)/2;else{let e=P,n=e+F;if(o>=e&&c<=n){t.onComplete&&queueMicrotask(t.onComplete);return}l=o<e?o:c-F}$.writeOffset(z(l),i),t.onComplete&&(i===`smooth`&&`onscrollend`in a?a.addEventListener(`scrollend`,t.onComplete,{once:!0}):queueMicrotask(t.onComplete))}function Y(e,t={}){L||$.writeOffset(z(e),t.behavior??`auto`)}function ie(e={}){Y(0,e)}function ae(e={}){Y(Math.max(0,j.totalSize-F),e)}function oe(e=0){return j.totalSize-(P+F)<=e}let X=new AbortController;function Z(){L||(L=!0,A.dispose(),X.abort(),C!==null&&(clearTimeout(C),C=null),w&&a.removeEventListener(`scrollend`,T),Q.detach())}function se(){P=z($.readOffset()),E(),B()}function ce(){F=$.readViewportSize(),B()}let Q=n.createScrollAdapter(a,se,ce),$=s?Q.x:Q.y;if(g){let e=e=>{if(L||!(e instanceof KeyboardEvent)||u===0)return;let t=!1,n=P,r=Math.max(F*.8,F-100);switch(e.key){case`ArrowUp`:case`ArrowLeft`:{let e=typeof d(0)==`number`?d(0):36;n=Math.max(0,P-(e+f)),t=!0;break}case`ArrowDown`:case`ArrowRight`:{let e=typeof d(0)==`number`?d(0):36;n=Math.min(j.totalSize-F,P+(e+f)),t=!0;break}case`PageUp`:n=Math.max(0,P-r),t=!0;break;case`PageDown`:n=Math.min(j.totalSize-F,P+r),t=!0;break;case`Home`:n=0,t=!0;break;case`End`:n=Math.max(0,j.totalSize-F),t=!0}t&&(e.preventDefault(),$.writeOffset(n,`auto`),P=z(n),B())};typeof a==`object`&&a&&`addEventListener`in a&&(a.addEventListener(`keydown`,e),X.signal.addEventListener(`abort`,()=>{a.removeEventListener(`keydown`,e)}))}return w&&a.addEventListener(`scrollend`,T,{passive:!0}),j.rebuild(!0),o.initialOffset!==void 0&&$.writeOffset(z(o.initialOffset),`auto`),P=z($.readOffset()),F=$.readViewportSize(),B(),{get count(){return u},get disposalSignal(){return X.signal},dispose:Z,get disposed(){return L},getSnapshot:A.getSnapshot,invalidate:J,isAtEnd:oe,get isScrolling(){return R},get items(){return O},measure:G,measureBatch:W,measureEl:K,prepend:te,refresh:ee,get scrollOffset(){return P},scrollToBottom:ae,scrollToIndex:re,scrollToOffset:Y,scrollToTop:ie,get stickyItems(){return k},subscribe:A.subscribe,[Symbol.dispose]:Z,get totalSize(){return j.totalSize},update:ne}}exports.DEFAULT_ESTIMATE_SIZE=e.DEFAULT_ESTIMATE_SIZE,exports.DEFAULT_OVERSCAN=e.DEFAULT_OVERSCAN,exports.createMeasurementCache=e.createMeasurementCache,exports.createVirtualizer=a;
|
|
2
2
|
//# sourceMappingURL=virtualizer.cjs.map
|
package/dist/virtualizer.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtualizer.cjs","names":[],"sources":["../src/virtualizer.ts"],"sourcesContent":["import type { Signal } from '@vielzeug/ripple';\n\nimport { createScrollAdapter } from './_adapter';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport { computeStickyItems } from './_sticky';\nimport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n normalizeOverscan,\n type Overscan,\n observeResize,\n resolveEstimateFn,\n type ScrollTarget,\n toNonNegativeInt,\n toPositiveNumber,\n type VirtualKey,\n} from './_utils';\nimport {\n requireNonNegativeInteger,\n requireNonNegativeNumber,\n requirePositiveNumber,\n validateOverscan,\n} from './_validation';\n\nexport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n type ScrollTarget,\n type VirtualItem,\n type VirtualKey,\n};\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualizerState {\n readonly items: VirtualItem[];\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n}\n\nexport interface VirtualizerOptions {\n /**\n * Auto-attach ResizeObserver to visible items.\n * Elements must have a `data-vz-key` attribute set to the item key\n * so the virtualizer can locate and measure them after render.\n */\n autoMeasure?: boolean;\n count: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: (index: number) => VirtualKey;\n horizontal?: boolean;\n initialOffset?: number;\n /** Enable keyboard scroll (arrow keys, Page Up/Down, Home, End). */\n keyboardScroll?: boolean;\n /** External measurement cache for scroll restoration or SSR pre-measurement. */\n measurementCache?: MeasurementCache;\n /** Called after every render cycle with the new state. Replace through `update()`. */\n onChange?: (state: VirtualizerState) => void;\n /** Called when scrolling has settled. Replace through `update()`. */\n onScrollEnd?: (offset: number) => void;\n /** Called when the scrolling state changes. Replace through `update()`. */\n onScrollingChange?: (isScrolling: boolean) => void;\n overscan?: Overscan;\n /**\n * Debounce delay (ms) used to detect scroll end when the native `scrollend`\n * event is unavailable. Defaults to 150.\n */\n scrollEndDelay?: number;\n sticky?: (index: number) => boolean;\n /** Optional signal factory for reactive state. */\n toSignal?: (init: VirtualizerState) => Signal<VirtualizerState>;\n}\n\n/**\n * Options accepted by `update()`. Explicit interface instead of Omit<...>\n * for better IDE hover and autocomplete (R7).\n *\n * Intentionally excludes `horizontal` (axis cannot change at runtime) and\n * `initialOffset` (one-time bootstrap value).\n */\nexport interface VirtualizerUpdateOptions {\n /**\n * Auto-attach ResizeObserver to visible items.\n * Elements must have a `data-vz-key` attribute set to the item key\n * so the virtualizer can locate and measure them after render.\n */\n autoMeasure?: boolean;\n count?: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: ((index: number) => VirtualKey) | undefined;\n keyboardScroll?: boolean;\n /** Replace the active measurement cache. Existing entries in the new cache are used immediately on the next rebuild. */\n measurementCache?: MeasurementCache;\n onChange?: ((state: VirtualizerState) => void) | undefined;\n onScrollEnd?: ((offset: number) => void) | undefined;\n onScrollingChange?: ((isScrolling: boolean) => void) | undefined;\n overscan?: Overscan;\n scrollEndDelay?: number;\n sticky?: ((index: number) => boolean) | undefined;\n}\n\nexport interface ScrollToIndexOptions {\n align?: 'auto' | 'center' | 'end' | 'start';\n behavior?: ScrollBehavior;\n /** Called when the scroll animation completes (instant scrolls: next microtask). */\n onComplete?: () => void;\n}\n\nexport interface Virtualizer {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n dispose: () => void;\n readonly disposed: boolean;\n invalidate: () => void;\n /**\n * `true` when scrolled to (or within `threshold` px of) the end — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Check this **before** appending\n * items to decide whether to auto-follow them (e.g. a chat \"stick to bottom\" pattern) —\n * `createDomVirtualList`'s `stickToBottom` option does exactly this. Default `threshold`: `0`.\n */\n isAtEnd: (threshold?: number) => boolean;\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n /** Currently rendered items. Always populated. */\n readonly items: VirtualItem[];\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` and auto-measure `index` on resize. Returns a disconnect fn. */\n measureEl: (index: number, el: HTMLElement) => () => void;\n /** Prepend `additionalCount` items at the top while keeping the viewport visually stable. */\n prepend: (additionalCount: number) => void;\n /** Rebuild the full offset table and re-emit (use when item sizes may have changed). */\n refresh: () => void;\n readonly scrollOffset: number;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n update: (next: VirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createVirtualizer(target: ScrollTarget, options: VirtualizerOptions): Virtualizer {\n if (typeof options.estimateSize === 'number') requirePositiveNumber(options.estimateSize, 'estimateSize');\n\n if (options.gap !== undefined) requireNonNegativeInteger(options.gap, 'gap');\n\n if (options.initialOffset !== undefined) requireNonNegativeNumber(options.initialOffset, 'initialOffset');\n\n if (options.overscan !== undefined) validateOverscan(options.overscan);\n\n if (options.scrollEndDelay !== undefined) requireNonNegativeNumber(options.scrollEndDelay, 'scrollEndDelay');\n\n const horizontal = !!options.horizontal;\n const defaultItemKey = (index: number): VirtualKey => index;\n const enableKeyboardScroll = options.keyboardScroll ?? false;\n\n let count = requireNonNegativeInteger(options.count, 'count');\n let estimateFn = resolveEstimateFn(options.estimateSize, DEFAULT_ESTIMATE_SIZE);\n let gap = toNonNegativeInt(options.gap ?? 0);\n let getItemKey = options.getItemKey ?? defaultItemKey;\n let overscan = normalizeOverscan(options.overscan, DEFAULT_OVERSCAN);\n let stickyFn: ((index: number) => boolean) | null = options.sticky ?? null;\n let keyboardScrollEnabled = enableKeyboardScroll;\n let autoMeasureEnabled = options.autoMeasure ?? false;\n\n let onChange = options.onChange;\n let onScrollEnd = options.onScrollEnd;\n let onScrollingChange = options.onScrollingChange;\n let scrollEndDelay = options.scrollEndDelay ?? 150;\n\n // Optional signal for reactive state\n let stateSignal: Signal<VirtualizerState> | null = null;\n if (options.toSignal) {\n const initialState: VirtualizerState = { items: [], stickyItems: [], totalSize: 0 };\n stateSignal = options.toSignal(initialState);\n }\n\n // Helper to emit state to both callback and signal\n function emitState(state: VirtualizerState): void {\n if (stateSignal) stateSignal.value = state;\n onChange?.(state);\n }\n let scrollEndTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Detect native scrollend support (Chrome 114+, Firefox 109+)\n const hasNativeScrollEnd = 'onscrollend' in (target as EventTarget);\n\n function notifyScrollEnd(): void {\n if (!isScrolling) return;\n\n isScrolling = false;\n onScrollingChange?.(false);\n onScrollEnd?.(scrollOffset);\n }\n\n function handleScrollStart(): void {\n if (!isScrolling) {\n isScrolling = true;\n onScrollingChange?.(true);\n }\n\n if (!hasNativeScrollEnd) {\n if (scrollEndTimer !== null) clearTimeout(scrollEndTimer);\n\n scrollEndTimer = setTimeout(notifyScrollEnd, scrollEndDelay);\n }\n }\n\n // External or internal measurement cache.\n let measuredByKey: MeasurementCache = options.measurementCache ?? new Map();\n\n let items: VirtualItem[] = [];\n let stickyItems: VirtualItem[] = [];\n\n // ─── Axis setup ──────────────────────────────────────────────────────────────\n // The axis1d primitive owns offset management. The sizeAt closure captures\n // the live `measuredByKey`, `getItemKey`, and `estimateFn` by reference so\n // updates to those let-variables are picked up automatically.\n\n const ax = createAxis1D(count, (i) => measuredByKey.get(getItemKey(i)) ?? estimateFn(i), gap);\n\n // F6: Scroll anchor — prevents viewport jump when items above fold are measured.\n let anchorIndex = -1;\n let anchorDistanceFromTop = 0;\n\n let scrollOffset = 0;\n let viewportSize = 0;\n let prevScrollOffset = -1; // for sticky dedup\n let disposed = false;\n let isScrolling = false;\n\n // ─── Scroll clamping ─────────────────────────────────────────────────────────\n\n function clampScrollOffset(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, ax.totalSize - viewportSize);\n\n return Math.min(maxOffset, Math.max(0, safe));\n }\n\n // ─── Compute and emit ─────────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (disposed) return;\n\n if (count === 0 || viewportSize <= 0) {\n if (ax.prevStart !== -1 || ax.prevTotalSize !== ax.totalSize) {\n // Commit with sentinel renderStart=-1 so prevTotalSize is captured as the\n // current totalSize. Using resetDedup() would set prevTotalSize=-1, causing\n // the guard to fire on every subsequent scroll/resize (Bug 2).\n ax.commitDedup(-1, -1);\n\n items = [];\n stickyItems = [];\n\n emitState({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n return;\n }\n\n const { renderEnd, renderStart } = ax.computeRange(\n scrollOffset,\n scrollOffset + viewportSize,\n overscan.start,\n overscan.end,\n );\n\n // Sticky recomputes on every scroll pixel — bypass dedup in that case.\n const stickyScrolled = stickyFn !== null && scrollOffset !== prevScrollOffset;\n const rangeChanged = !ax.isDedupSame(renderStart, renderEnd);\n\n if (!rangeChanged && !stickyScrolled) return;\n\n ax.commitDedup(renderStart, renderEnd);\n prevScrollOffset = scrollOffset;\n\n const nextItems: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n nextItems.push(ax.itemAt(i));\n }\n\n items = nextItems;\n stickyItems = stickyFn\n ? computeStickyItems({ count, scrollOffset, sizeAt: ax.sizeAt, startAt: ax.startAt, stickyFn })\n : [];\n\n emitState({ items, stickyItems, totalSize: ax.totalSize });\n\n // Auto-measure visible items if enabled\n if (autoMeasureEnabled && items.length > 0) {\n // Queue microtask to allow DOM to render first\n queueMicrotask(() => {\n if (disposed || target instanceof Window) return;\n\n for (const item of items) {\n const key = getItemKey(item.index);\n const el = (target as HTMLElement)?.querySelector?.(`[data-vz-key=\"${key}\"]`);\n\n if (el instanceof HTMLElement) {\n measureEl(item.index, el);\n }\n }\n });\n }\n }\n\n // ─── Measurement (R6: measure delegates to measureBatch) ─────────────────────\n\n function recordMeasurement(index: number, size: number): boolean {\n if (!Number.isFinite(index)) return false;\n\n const i = Math.floor(index);\n\n if (i < 0 || i >= count) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n const key = getItemKey(i);\n\n if (measuredByKey.get(key) === s) return false;\n\n measuredByKey.set(key, s);\n ax.markChanged(i);\n\n return true;\n }\n\n // F6: Record a scroll anchor before a batch of measurements arrives.\n function recordScrollAnchor(): void {\n if (scrollOffset <= 0 || count === 0) {\n anchorIndex = -1;\n\n return;\n }\n\n anchorIndex = ax.findFirst(scrollOffset);\n anchorDistanceFromTop = scrollOffset - ax.startAt(anchorIndex);\n }\n\n // F6: After rebuild, restore the anchor to prevent viewport jump.\n function restoreScrollAnchor(): void {\n if (anchorIndex < 0) return;\n\n const expectedOffset = ax.startAt(anchorIndex) + anchorDistanceFromTop;\n const delta = expectedOffset - scrollOffset;\n\n anchorIndex = -1;\n\n if (Math.abs(delta) >= 1) {\n const newOffset = clampScrollOffset(scrollOffset + delta);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n }\n }\n\n function measureBatch(entries: Array<{ index: number; size: number }>): void {\n if (disposed) return;\n\n let changed = false;\n\n for (const { index, size } of entries) {\n if (recordMeasurement(index, size)) changed = true;\n }\n\n if (changed) {\n // Record anchor before the first pending build is scheduled (F6).\n if (!ax.pendingBuild) recordScrollAnchor();\n\n ax.scheduleBuild(() => {\n restoreScrollAnchor();\n computeVisible();\n });\n }\n }\n\n /** R6: Single-item convenience — delegates to measureBatch. */\n function measure(index: number, size: number): void {\n measureBatch([{ index, size }]);\n }\n\n /** Attach a ResizeObserver to `el`. Returns a disconnect function. */\n function measureEl(index: number, el: HTMLElement): () => void {\n if (disposed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => {\n measure(index, horizontal ? entry.contentRect.width : entry.contentRect.height);\n });\n }\n\n // ─── Option updates ───────────────────────────────────────────────────────────\n\n function applyOptions(next: VirtualizerUpdateOptions): void {\n if (next.count !== undefined) requireNonNegativeInteger(next.count, 'count');\n\n if (typeof next.estimateSize === 'number') requirePositiveNumber(next.estimateSize, 'estimateSize');\n\n if (next.gap !== undefined) requireNonNegativeInteger(next.gap, 'gap');\n\n if (next.overscan !== undefined) validateOverscan(next.overscan);\n\n if (next.scrollEndDelay !== undefined) requireNonNegativeNumber(next.scrollEndDelay, 'scrollEndDelay');\n\n let needsRebuild = false;\n let needsCompute = false;\n\n if (Object.hasOwn(next, 'onChange')) onChange = next.onChange;\n\n if (Object.hasOwn(next, 'onScrollEnd')) onScrollEnd = next.onScrollEnd;\n\n if (Object.hasOwn(next, 'onScrollingChange')) onScrollingChange = next.onScrollingChange;\n\n if (next.scrollEndDelay !== undefined) {\n scrollEndDelay = next.scrollEndDelay;\n }\n\n if (next.count !== undefined) {\n const nextCount = next.count;\n\n if (nextCount !== count) {\n count = nextCount;\n ax.setCount(count);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'estimateSize')) {\n estimateFn = resolveEstimateFn(next.estimateSize, DEFAULT_ESTIMATE_SIZE);\n measuredByKey.clear();\n needsRebuild = true;\n }\n\n if (next.gap !== undefined) {\n const nextGap = next.gap;\n\n if (nextGap !== gap) {\n gap = nextGap;\n ax.setGap(gap);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'getItemKey')) {\n const nextKey = next.getItemKey ?? defaultItemKey;\n\n if (nextKey !== getItemKey) {\n getItemKey = nextKey;\n measuredByKey.clear();\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'measurementCache') && next.measurementCache !== undefined) {\n measuredByKey = next.measurementCache;\n needsRebuild = true;\n }\n\n if (next.overscan !== undefined) {\n const nextOverscan = normalizeOverscan(next.overscan, DEFAULT_OVERSCAN);\n\n if (nextOverscan.start !== overscan.start || nextOverscan.end !== overscan.end) {\n overscan = nextOverscan;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'sticky')) {\n const nextStickyFn = next.sticky ?? null;\n\n if (nextStickyFn !== stickyFn) {\n stickyFn = nextStickyFn;\n ax.resetDedup(); // force re-emit to flush updated stickyItems\n }\n\n needsCompute = true;\n }\n\n if (Object.hasOwn(next, 'keyboardScroll')) {\n keyboardScrollEnabled = next.keyboardScroll ?? false;\n }\n\n if (Object.hasOwn(next, 'autoMeasure')) {\n autoMeasureEnabled = next.autoMeasure ?? false;\n }\n\n if (needsRebuild) {\n recordScrollAnchor();\n ax.rebuild(true);\n restoreScrollAnchor();\n }\n\n if (needsCompute || needsRebuild) computeVisible();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────────\n\n function invalidate(): void {\n if (disposed) return;\n\n measuredByKey.clear();\n ax.rebuild(true);\n computeVisible();\n }\n\n /** Full O(n) offset rebuild followed by re-emit. Also used internally for data-only re-emit when sizes unchanged. */\n function refresh(): void {\n if (disposed) return;\n\n ax.rebuild(true);\n computeVisible();\n }\n\n function prepend(additionalCount: number): void {\n if (disposed) return;\n\n const n = toNonNegativeInt(additionalCount);\n\n if (n === 0) return;\n\n count += n;\n ax.setCount(count);\n ax.rebuild(true);\n\n // Adjust scroll to keep the same content visible (F6-like stable prepend).\n const prependedHeight = ax.startAt(n);\n const newOffset = clampScrollOffset(scrollOffset + prependedHeight);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n\n computeVisible();\n }\n\n function update(next: VirtualizerUpdateOptions): void {\n if (disposed) return;\n\n applyOptions(next);\n }\n\n function scrollToIndex(index: number, opts: ScrollToIndexOptions = {}): void {\n if (disposed || count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, count - 1));\n const align = opts.align ?? 'auto';\n const behavior = opts.behavior ?? 'auto';\n const itemStart = ax.startAt(clampedIndex);\n const itemSize = ax.sizeAt(clampedIndex);\n const itemEnd = itemStart + itemSize;\n\n let targetOffset: number;\n\n if (align === 'start') {\n targetOffset = itemStart;\n } else if (align === 'end') {\n targetOffset = itemEnd - viewportSize;\n } else if (align === 'center') {\n targetOffset = itemStart - (viewportSize - itemSize) / 2;\n } else {\n const visibleStart = scrollOffset;\n const visibleEnd = visibleStart + viewportSize;\n\n if (itemStart >= visibleStart && itemEnd <= visibleEnd) {\n if (opts.onComplete) queueMicrotask(opts.onComplete);\n\n return;\n }\n\n targetOffset = itemStart < visibleStart ? itemStart : itemEnd - viewportSize;\n }\n\n domAxis.writeOffset(clampScrollOffset(targetOffset), behavior);\n\n if (opts.onComplete) {\n if (behavior === 'smooth' && 'onscrollend' in target) {\n (target as EventTarget).addEventListener('scrollend', opts.onComplete, { once: true });\n } else {\n queueMicrotask(opts.onComplete);\n }\n }\n }\n\n function scrollToOffset(offset: number, opts: { behavior?: ScrollBehavior } = {}): void {\n if (disposed) return;\n\n domAxis.writeOffset(clampScrollOffset(offset), opts.behavior ?? 'auto');\n }\n\n function scrollToTop(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(0, opts);\n }\n\n function scrollToBottom(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(Math.max(0, ax.totalSize - viewportSize), opts);\n }\n\n function isAtEnd(threshold = 0): boolean {\n return ax.totalSize - (scrollOffset + viewportSize) <= threshold;\n }\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (disposed) return;\n\n disposed = true;\n ac.abort();\n\n if (scrollEndTimer !== null) {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = null;\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).removeEventListener('scrollend', notifyScrollEnd);\n }\n\n adapter.detach();\n }\n\n // ─── Bootstrap ───────────────────────────────────────────────────────────────\n\n function handleScroll(): void {\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n handleScrollStart();\n computeVisible();\n }\n\n function handleResize(): void {\n viewportSize = domAxis.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n const domAxis = horizontal ? adapter.x : adapter.y;\n\n // ─── Keyboard scroll support ──────────────────────────────────────────────────\n\n if (keyboardScrollEnabled) {\n const handleKeyDown = (e: Event): void => {\n if (disposed) return;\n if (!(e instanceof KeyboardEvent)) return;\n if (count === 0) return;\n\n let handled = false;\n let newOffset = scrollOffset;\n const pageScrollSize = Math.max(viewportSize * 0.8, viewportSize - 100);\n\n switch (e.key) {\n case 'ArrowUp':\n case 'ArrowLeft': {\n const itemSize = typeof estimateFn(0) === 'number' ? estimateFn(0) : DEFAULT_ESTIMATE_SIZE;\n newOffset = Math.max(0, scrollOffset - (itemSize + gap));\n handled = true;\n break;\n }\n case 'ArrowDown':\n case 'ArrowRight': {\n const itemSize = typeof estimateFn(0) === 'number' ? estimateFn(0) : DEFAULT_ESTIMATE_SIZE;\n newOffset = Math.min(ax.totalSize - viewportSize, scrollOffset + (itemSize + gap));\n handled = true;\n break;\n }\n case 'PageUp':\n newOffset = Math.max(0, scrollOffset - pageScrollSize);\n handled = true;\n break;\n case 'PageDown':\n newOffset = Math.min(ax.totalSize - viewportSize, scrollOffset + pageScrollSize);\n handled = true;\n break;\n case 'Home':\n newOffset = 0;\n handled = true;\n break;\n case 'End':\n newOffset = Math.max(0, ax.totalSize - viewportSize);\n handled = true;\n break;\n }\n\n if (handled) {\n e.preventDefault();\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = clampScrollOffset(newOffset);\n computeVisible();\n }\n };\n\n if (typeof target === 'object' && target !== null && 'addEventListener' in target) {\n (target as EventTarget).addEventListener('keydown', handleKeyDown);\n ac.signal.addEventListener('abort', () => {\n (target as EventTarget).removeEventListener('keydown', handleKeyDown);\n });\n }\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).addEventListener('scrollend', notifyScrollEnd, { passive: true });\n }\n\n ax.rebuild(true);\n\n if (options.initialOffset !== undefined) {\n domAxis.writeOffset(clampScrollOffset(options.initialOffset), 'auto');\n }\n\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n viewportSize = domAxis.readViewportSize();\n\n computeVisible();\n\n return {\n get count() {\n return count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return disposed;\n },\n invalidate,\n isAtEnd,\n get isScrolling() {\n return isScrolling;\n },\n get items() {\n return items;\n },\n measure,\n measureBatch,\n measureEl,\n prepend,\n refresh,\n get scrollOffset() {\n return scrollOffset;\n },\n scrollToBottom,\n scrollToIndex,\n scrollToOffset,\n scrollToTop,\n get stickyItems() {\n return stickyItems;\n },\n [Symbol.dispose]: _dispose,\n get totalSize() {\n return ax.totalSize;\n },\n update,\n };\n}\n"],"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 { computeStickyItems } from './_sticky';\nimport {\n createMeasurementCache,\n createSnapshotStore,\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 ScrollStore<State> {\n getSnapshot: () => State;\n subscribe: (listener: () => void) => () => void;\n}\n\nexport interface VirtualizerOptions {\n /**\n * Auto-attach ResizeObserver to visible items.\n * Elements must have a `data-vz-key` attribute set to the item key\n * so the virtualizer can locate and measure them after render.\n */\n autoMeasure?: boolean;\n count: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: (index: number) => VirtualKey;\n horizontal?: boolean;\n initialOffset?: number;\n /** Enable keyboard scroll (arrow keys, Page Up/Down, Home, End). */\n keyboardScroll?: boolean;\n /** External measurement cache for scroll restoration or SSR pre-measurement. */\n measurementCache?: MeasurementCache;\n /** Called after every render cycle with the new state. Replace through `update()`. */\n onChange?: (state: VirtualizerState) => void;\n /** Called when scrolling has settled. Replace through `update()`. */\n onScrollEnd?: (offset: number) => void;\n /** Called when the scrolling state changes. Replace through `update()`. */\n onScrollingChange?: (isScrolling: boolean) => void;\n overscan?: Overscan;\n /**\n * Debounce delay (ms) used to detect scroll end when the native `scrollend`\n * event is unavailable. Defaults to 150.\n */\n scrollEndDelay?: number;\n sticky?: (index: number) => boolean;\n}\n\n/**\n * Options accepted by `update()`. Explicit interface instead of Omit<...>\n * for better IDE hover and autocomplete (R7).\n *\n * Intentionally excludes `horizontal` (axis cannot change at runtime) and\n * `initialOffset` (one-time bootstrap value).\n */\nexport interface VirtualizerUpdateOptions {\n /**\n * Auto-attach ResizeObserver to visible items.\n * Elements must have a `data-vz-key` attribute set to the item key\n * so the virtualizer can locate and measure them after render.\n */\n autoMeasure?: boolean;\n count?: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: ((index: number) => VirtualKey) | undefined;\n keyboardScroll?: boolean;\n /** Replace the active measurement cache. Existing entries in the new cache are used immediately on the next rebuild. */\n measurementCache?: MeasurementCache;\n onChange?: ((state: VirtualizerState) => void) | undefined;\n onScrollEnd?: ((offset: number) => void) | undefined;\n onScrollingChange?: ((isScrolling: boolean) => void) | undefined;\n overscan?: Overscan;\n scrollEndDelay?: number;\n sticky?: ((index: number) => boolean) | undefined;\n}\n\nexport interface ScrollToIndexOptions {\n align?: 'auto' | 'center' | 'end' | 'start';\n behavior?: ScrollBehavior;\n /** Called when the scroll animation completes (instant scrolls: next microtask). */\n onComplete?: () => void;\n}\n\nexport interface Virtualizer extends ScrollStore<VirtualizerState> {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n dispose: () => void;\n readonly disposed: boolean;\n invalidate: () => void;\n /**\n * `true` when scrolled to (or within `threshold` px of) the end — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Check this **before** appending\n * items to decide whether to auto-follow them (e.g. a chat \"stick to bottom\" pattern) —\n * `createDomVirtualList`'s `stickToBottom` option does exactly this. Default `threshold`: `0`.\n */\n isAtEnd: (threshold?: number) => boolean;\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n /** Currently rendered items. Always populated. */\n readonly items: VirtualItem[];\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` and auto-measure `index` on resize. Returns a disconnect fn. */\n measureEl: (index: number, el: HTMLElement) => () => void;\n /** Prepend `additionalCount` items at the top while keeping the viewport visually stable. */\n prepend: (additionalCount: number) => void;\n /** Rebuild the full offset table and re-emit (use when item sizes may have changed). */\n refresh: () => void;\n readonly scrollOffset: number;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n update: (next: VirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createVirtualizer(target: ScrollTarget, options: VirtualizerOptions): Virtualizer {\n if (typeof options.estimateSize === 'number') requirePositiveNumber(options.estimateSize, 'estimateSize');\n\n if (options.gap !== undefined) requireNonNegativeInteger(options.gap, 'gap');\n\n if (options.initialOffset !== undefined) requireNonNegativeNumber(options.initialOffset, 'initialOffset');\n\n if (options.overscan !== undefined) validateOverscan(options.overscan);\n\n if (options.scrollEndDelay !== undefined) requireNonNegativeNumber(options.scrollEndDelay, 'scrollEndDelay');\n\n const horizontal = !!options.horizontal;\n const defaultItemKey = (index: number): VirtualKey => index;\n const enableKeyboardScroll = options.keyboardScroll ?? false;\n\n let count = requireNonNegativeInteger(options.count, 'count');\n let estimateFn = resolveEstimateFn(options.estimateSize, DEFAULT_ESTIMATE_SIZE);\n let gap = toNonNegativeInt(options.gap ?? 0);\n let getItemKey = options.getItemKey ?? defaultItemKey;\n let overscan = normalizeOverscan(options.overscan, DEFAULT_OVERSCAN);\n let stickyFn: ((index: number) => boolean) | null = options.sticky ?? null;\n let keyboardScrollEnabled = enableKeyboardScroll;\n let autoMeasureEnabled = options.autoMeasure ?? false;\n\n let onChange = options.onChange;\n let onScrollEnd = options.onScrollEnd;\n let onScrollingChange = options.onScrollingChange;\n let scrollEndDelay = options.scrollEndDelay ?? 150;\n\n // Publish state before invoking the render callback.\n function emitState(state: VirtualizerState): void {\n stateStore.publish(state);\n onChange?.(state);\n }\n let scrollEndTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Detect native scrollend support (Chrome 114+, Firefox 109+)\n const hasNativeScrollEnd = 'onscrollend' in (target as EventTarget);\n\n function notifyScrollEnd(): void {\n if (!isScrolling) return;\n\n isScrolling = false;\n onScrollingChange?.(false);\n onScrollEnd?.(scrollOffset);\n }\n\n function handleScrollStart(): void {\n if (!isScrolling) {\n isScrolling = true;\n onScrollingChange?.(true);\n }\n\n if (!hasNativeScrollEnd) {\n if (scrollEndTimer !== null) clearTimeout(scrollEndTimer);\n\n scrollEndTimer = setTimeout(notifyScrollEnd, scrollEndDelay);\n }\n }\n\n // External or internal measurement cache.\n let measuredByKey: MeasurementCache = options.measurementCache ?? new Map();\n\n let items: VirtualItem[] = [];\n let stickyItems: VirtualItem[] = [];\n const stateStore = createSnapshotStore<VirtualizerState>({ items, stickyItems, totalSize: 0 });\n\n // ─── Axis setup ──────────────────────────────────────────────────────────────\n // The axis1d primitive owns offset management. The sizeAt closure captures\n // the live `measuredByKey`, `getItemKey`, and `estimateFn` by reference so\n // updates to those let-variables are picked up automatically.\n\n const ax = createAxis1D(count, (i) => measuredByKey.get(getItemKey(i)) ?? estimateFn(i), gap);\n\n // F6: Scroll anchor — prevents viewport jump when items above fold are measured.\n let anchorIndex = -1;\n let anchorDistanceFromTop = 0;\n\n let scrollOffset = 0;\n let viewportSize = 0;\n let prevScrollOffset = -1; // for sticky dedup\n let disposed = false;\n let isScrolling = false;\n\n // ─── Scroll clamping ─────────────────────────────────────────────────────────\n\n function clampScrollOffset(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, ax.totalSize - viewportSize);\n\n return Math.min(maxOffset, Math.max(0, safe));\n }\n\n // ─── Compute and emit ─────────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (disposed) return;\n\n if (count === 0 || viewportSize <= 0) {\n if (ax.prevStart !== -1 || ax.prevTotalSize !== ax.totalSize) {\n // Commit with sentinel renderStart=-1 so prevTotalSize is captured as the\n // current totalSize. Using resetDedup() would set prevTotalSize=-1, causing\n // the guard to fire on every subsequent scroll/resize (Bug 2).\n ax.commitDedup(-1, -1);\n\n items = [];\n stickyItems = [];\n\n emitState({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n return;\n }\n\n const { renderEnd, renderStart } = ax.computeRange(\n scrollOffset,\n scrollOffset + viewportSize,\n overscan.start,\n overscan.end,\n );\n\n // Sticky recomputes on every scroll pixel — bypass dedup in that case.\n const stickyScrolled = stickyFn !== null && scrollOffset !== prevScrollOffset;\n const rangeChanged = !ax.isDedupSame(renderStart, renderEnd);\n\n if (!rangeChanged && !stickyScrolled) return;\n\n ax.commitDedup(renderStart, renderEnd);\n prevScrollOffset = scrollOffset;\n\n const nextItems: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n nextItems.push(ax.itemAt(i));\n }\n\n items = nextItems;\n stickyItems = stickyFn\n ? computeStickyItems({ count, scrollOffset, sizeAt: ax.sizeAt, startAt: ax.startAt, stickyFn })\n : [];\n\n emitState({ items, stickyItems, totalSize: ax.totalSize });\n\n // Auto-measure visible items if enabled\n if (autoMeasureEnabled && items.length > 0) {\n // Queue microtask to allow DOM to render first\n queueMicrotask(() => {\n if (disposed || target instanceof Window) return;\n\n for (const item of items) {\n const key = getItemKey(item.index);\n const el = (target as HTMLElement)?.querySelector?.(`[data-vz-key=\"${key}\"]`);\n\n if (el instanceof HTMLElement) {\n measureEl(item.index, el);\n }\n }\n });\n }\n }\n\n // ─── Measurement (R6: measure delegates to measureBatch) ─────────────────────\n\n function recordMeasurement(index: number, size: number): boolean {\n if (!Number.isFinite(index)) return false;\n\n const i = Math.floor(index);\n\n if (i < 0 || i >= count) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n const key = getItemKey(i);\n\n if (measuredByKey.get(key) === s) return false;\n\n measuredByKey.set(key, s);\n ax.markChanged(i);\n\n return true;\n }\n\n // F6: Record a scroll anchor before a batch of measurements arrives.\n function recordScrollAnchor(): void {\n if (scrollOffset <= 0 || count === 0) {\n anchorIndex = -1;\n\n return;\n }\n\n anchorIndex = ax.findFirst(scrollOffset);\n anchorDistanceFromTop = scrollOffset - ax.startAt(anchorIndex);\n }\n\n // F6: After rebuild, restore the anchor to prevent viewport jump.\n function restoreScrollAnchor(): void {\n if (anchorIndex < 0) return;\n\n const expectedOffset = ax.startAt(anchorIndex) + anchorDistanceFromTop;\n const delta = expectedOffset - scrollOffset;\n\n anchorIndex = -1;\n\n if (Math.abs(delta) >= 1) {\n const newOffset = clampScrollOffset(scrollOffset + delta);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n }\n }\n\n function measureBatch(entries: Array<{ index: number; size: number }>): void {\n if (disposed) return;\n\n let changed = false;\n\n for (const { index, size } of entries) {\n if (recordMeasurement(index, size)) changed = true;\n }\n\n if (changed) {\n // Record anchor before the first pending build is scheduled (F6).\n if (!ax.pendingBuild) recordScrollAnchor();\n\n ax.scheduleBuild(() => {\n restoreScrollAnchor();\n computeVisible();\n });\n }\n }\n\n /** R6: Single-item convenience — delegates to measureBatch. */\n function measure(index: number, size: number): void {\n measureBatch([{ index, size }]);\n }\n\n /** Attach a ResizeObserver to `el`. Returns a disconnect function. */\n function measureEl(index: number, el: HTMLElement): () => void {\n if (disposed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => {\n measure(index, horizontal ? entry.contentRect.width : entry.contentRect.height);\n });\n }\n\n // ─── Option updates ───────────────────────────────────────────────────────────\n\n function applyOptions(next: VirtualizerUpdateOptions): void {\n if (next.count !== undefined) requireNonNegativeInteger(next.count, 'count');\n\n if (typeof next.estimateSize === 'number') requirePositiveNumber(next.estimateSize, 'estimateSize');\n\n if (next.gap !== undefined) requireNonNegativeInteger(next.gap, 'gap');\n\n if (next.overscan !== undefined) validateOverscan(next.overscan);\n\n if (next.scrollEndDelay !== undefined) requireNonNegativeNumber(next.scrollEndDelay, 'scrollEndDelay');\n\n let needsRebuild = false;\n let needsCompute = false;\n\n if (Object.hasOwn(next, 'onChange')) onChange = next.onChange;\n\n if (Object.hasOwn(next, 'onScrollEnd')) onScrollEnd = next.onScrollEnd;\n\n if (Object.hasOwn(next, 'onScrollingChange')) onScrollingChange = next.onScrollingChange;\n\n if (next.scrollEndDelay !== undefined) {\n scrollEndDelay = next.scrollEndDelay;\n }\n\n if (next.count !== undefined) {\n const nextCount = next.count;\n\n if (nextCount !== count) {\n count = nextCount;\n ax.setCount(count);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'estimateSize')) {\n estimateFn = resolveEstimateFn(next.estimateSize, DEFAULT_ESTIMATE_SIZE);\n measuredByKey.clear();\n needsRebuild = true;\n }\n\n if (next.gap !== undefined) {\n const nextGap = next.gap;\n\n if (nextGap !== gap) {\n gap = nextGap;\n ax.setGap(gap);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'getItemKey')) {\n const nextKey = next.getItemKey ?? defaultItemKey;\n\n if (nextKey !== getItemKey) {\n getItemKey = nextKey;\n measuredByKey.clear();\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'measurementCache') && next.measurementCache !== undefined) {\n measuredByKey = next.measurementCache;\n needsRebuild = true;\n }\n\n if (next.overscan !== undefined) {\n const nextOverscan = normalizeOverscan(next.overscan, DEFAULT_OVERSCAN);\n\n if (nextOverscan.start !== overscan.start || nextOverscan.end !== overscan.end) {\n overscan = nextOverscan;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'sticky')) {\n const nextStickyFn = next.sticky ?? null;\n\n if (nextStickyFn !== stickyFn) {\n stickyFn = nextStickyFn;\n ax.resetDedup(); // force re-emit to flush updated stickyItems\n }\n\n needsCompute = true;\n }\n\n if (Object.hasOwn(next, 'keyboardScroll')) {\n keyboardScrollEnabled = next.keyboardScroll ?? false;\n }\n\n if (Object.hasOwn(next, 'autoMeasure')) {\n autoMeasureEnabled = next.autoMeasure ?? false;\n }\n\n if (needsRebuild) {\n recordScrollAnchor();\n ax.rebuild(true);\n restoreScrollAnchor();\n }\n\n if (needsCompute || needsRebuild) computeVisible();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────────\n\n function invalidate(): void {\n if (disposed) return;\n\n measuredByKey.clear();\n ax.rebuild(true);\n computeVisible();\n }\n\n /** Full O(n) offset rebuild followed by re-emit. Also used internally for data-only re-emit when sizes unchanged. */\n function refresh(): void {\n if (disposed) return;\n\n ax.rebuild(true);\n computeVisible();\n }\n\n function prepend(additionalCount: number): void {\n if (disposed) return;\n\n const n = toNonNegativeInt(additionalCount);\n\n if (n === 0) return;\n\n count += n;\n ax.setCount(count);\n ax.rebuild(true);\n\n // Adjust scroll to keep the same content visible (F6-like stable prepend).\n const prependedHeight = ax.startAt(n);\n const newOffset = clampScrollOffset(scrollOffset + prependedHeight);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n\n computeVisible();\n }\n\n function update(next: VirtualizerUpdateOptions): void {\n if (disposed) return;\n\n applyOptions(next);\n }\n\n function scrollToIndex(index: number, opts: ScrollToIndexOptions = {}): void {\n if (disposed || count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, count - 1));\n const align = opts.align ?? 'auto';\n const behavior = opts.behavior ?? 'auto';\n const itemStart = ax.startAt(clampedIndex);\n const itemSize = ax.sizeAt(clampedIndex);\n const itemEnd = itemStart + itemSize;\n\n let targetOffset: number;\n\n if (align === 'start') {\n targetOffset = itemStart;\n } else if (align === 'end') {\n targetOffset = itemEnd - viewportSize;\n } else if (align === 'center') {\n targetOffset = itemStart - (viewportSize - itemSize) / 2;\n } else {\n const visibleStart = scrollOffset;\n const visibleEnd = visibleStart + viewportSize;\n\n if (itemStart >= visibleStart && itemEnd <= visibleEnd) {\n if (opts.onComplete) queueMicrotask(opts.onComplete);\n\n return;\n }\n\n targetOffset = itemStart < visibleStart ? itemStart : itemEnd - viewportSize;\n }\n\n domAxis.writeOffset(clampScrollOffset(targetOffset), behavior);\n\n if (opts.onComplete) {\n if (behavior === 'smooth' && 'onscrollend' in target) {\n (target as EventTarget).addEventListener('scrollend', opts.onComplete, { once: true });\n } else {\n queueMicrotask(opts.onComplete);\n }\n }\n }\n\n function scrollToOffset(offset: number, opts: { behavior?: ScrollBehavior } = {}): void {\n if (disposed) return;\n\n domAxis.writeOffset(clampScrollOffset(offset), opts.behavior ?? 'auto');\n }\n\n function scrollToTop(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(0, opts);\n }\n\n function scrollToBottom(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(Math.max(0, ax.totalSize - viewportSize), opts);\n }\n\n function isAtEnd(threshold = 0): boolean {\n return ax.totalSize - (scrollOffset + viewportSize) <= threshold;\n }\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (disposed) return;\n\n disposed = true;\n stateStore.dispose();\n ac.abort();\n\n if (scrollEndTimer !== null) {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = null;\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).removeEventListener('scrollend', notifyScrollEnd);\n }\n\n adapter.detach();\n }\n\n // ─── Bootstrap ───────────────────────────────────────────────────────────────\n\n function handleScroll(): void {\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n handleScrollStart();\n computeVisible();\n }\n\n function handleResize(): void {\n viewportSize = domAxis.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n const domAxis = horizontal ? adapter.x : adapter.y;\n\n // ─── Keyboard scroll support ──────────────────────────────────────────────────\n\n if (keyboardScrollEnabled) {\n const handleKeyDown = (e: Event): void => {\n if (disposed) return;\n if (!(e instanceof KeyboardEvent)) return;\n if (count === 0) return;\n\n let handled = false;\n let newOffset = scrollOffset;\n const pageScrollSize = Math.max(viewportSize * 0.8, viewportSize - 100);\n\n switch (e.key) {\n case 'ArrowUp':\n case 'ArrowLeft': {\n const itemSize = typeof estimateFn(0) === 'number' ? estimateFn(0) : DEFAULT_ESTIMATE_SIZE;\n newOffset = Math.max(0, scrollOffset - (itemSize + gap));\n handled = true;\n break;\n }\n case 'ArrowDown':\n case 'ArrowRight': {\n const itemSize = typeof estimateFn(0) === 'number' ? estimateFn(0) : DEFAULT_ESTIMATE_SIZE;\n newOffset = Math.min(ax.totalSize - viewportSize, scrollOffset + (itemSize + gap));\n handled = true;\n break;\n }\n case 'PageUp':\n newOffset = Math.max(0, scrollOffset - pageScrollSize);\n handled = true;\n break;\n case 'PageDown':\n newOffset = Math.min(ax.totalSize - viewportSize, scrollOffset + pageScrollSize);\n handled = true;\n break;\n case 'Home':\n newOffset = 0;\n handled = true;\n break;\n case 'End':\n newOffset = Math.max(0, ax.totalSize - viewportSize);\n handled = true;\n break;\n }\n\n if (handled) {\n e.preventDefault();\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = clampScrollOffset(newOffset);\n computeVisible();\n }\n };\n\n if (typeof target === 'object' && target !== null && 'addEventListener' in target) {\n (target as EventTarget).addEventListener('keydown', handleKeyDown);\n ac.signal.addEventListener('abort', () => {\n (target as EventTarget).removeEventListener('keydown', handleKeyDown);\n });\n }\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).addEventListener('scrollend', notifyScrollEnd, { passive: true });\n }\n\n ax.rebuild(true);\n\n if (options.initialOffset !== undefined) {\n domAxis.writeOffset(clampScrollOffset(options.initialOffset), 'auto');\n }\n\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n viewportSize = domAxis.readViewportSize();\n\n computeVisible();\n\n return {\n get count() {\n return count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return disposed;\n },\n getSnapshot: stateStore.getSnapshot,\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 subscribe: stateStore.subscribe,\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
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Signal } from '@vielzeug/ripple';
|
|
2
1
|
import { type VirtualItem } from './_axis1d';
|
|
3
2
|
import { createMeasurementCache, DEFAULT_ESTIMATE_SIZE, DEFAULT_OVERSCAN, type MeasurementCache, type Overscan, type ScrollTarget, type VirtualKey } from './_utils';
|
|
4
3
|
export { createMeasurementCache, DEFAULT_ESTIMATE_SIZE, DEFAULT_OVERSCAN, type MeasurementCache, type Overscan, type ScrollTarget, type VirtualItem, type VirtualKey, };
|
|
@@ -7,6 +6,10 @@ export interface VirtualizerState {
|
|
|
7
6
|
readonly stickyItems: VirtualItem[];
|
|
8
7
|
readonly totalSize: number;
|
|
9
8
|
}
|
|
9
|
+
export interface ScrollStore<State> {
|
|
10
|
+
getSnapshot: () => State;
|
|
11
|
+
subscribe: (listener: () => void) => () => void;
|
|
12
|
+
}
|
|
10
13
|
export interface VirtualizerOptions {
|
|
11
14
|
/**
|
|
12
15
|
* Auto-attach ResizeObserver to visible items.
|
|
@@ -37,8 +40,6 @@ export interface VirtualizerOptions {
|
|
|
37
40
|
*/
|
|
38
41
|
scrollEndDelay?: number;
|
|
39
42
|
sticky?: (index: number) => boolean;
|
|
40
|
-
/** Optional signal factory for reactive state. */
|
|
41
|
-
toSignal?: (init: VirtualizerState) => Signal<VirtualizerState>;
|
|
42
43
|
}
|
|
43
44
|
/**
|
|
44
45
|
* Options accepted by `update()`. Explicit interface instead of Omit<...>
|
|
@@ -74,7 +75,7 @@ export interface ScrollToIndexOptions {
|
|
|
74
75
|
/** Called when the scroll animation completes (instant scrolls: next microtask). */
|
|
75
76
|
onComplete?: () => void;
|
|
76
77
|
}
|
|
77
|
-
export interface Virtualizer {
|
|
78
|
+
export interface Virtualizer extends ScrollStore<VirtualizerState> {
|
|
78
79
|
readonly count: number;
|
|
79
80
|
readonly disposalSignal: AbortSignal;
|
|
80
81
|
dispose: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtualizer.d.ts","sourceRoot":"","sources":["../src/virtualizer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"virtualizer.d.ts","sourceRoot":"","sources":["../src/virtualizer.ts"],"names":[],"mappings":"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"}
|
package/dist/virtualizer.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{DEFAULT_ESTIMATE_SIZE as e,DEFAULT_OVERSCAN as t,createMeasurementCache as n,
|
|
1
|
+
import{DEFAULT_ESTIMATE_SIZE as e,DEFAULT_OVERSCAN as t,createMeasurementCache as n,createSnapshotStore as r,normalizeOverscan as i,observeResize as a,resolveEstimateFn as o,toNonNegativeInt as s,toPositiveNumber as c}from"./_utils.js";import{requireNonNegativeInteger as l,requireNonNegativeNumber as u,requirePositiveNumber as d,validateOverscan as f}from"./_validation.js";import{createScrollAdapter as ee}from"./_adapter.js";import{createAxis1D as te}from"./_axis1d.js";import{computeStickyItems as ne}from"./_sticky.js";function p(e,t){typeof t.estimateSize==`number`&&d(t.estimateSize,`estimateSize`),t.gap!==void 0&&l(t.gap,`gap`),t.initialOffset!==void 0&&u(t.initialOffset,`initialOffset`),t.overscan!==void 0&&f(t.overscan),t.scrollEndDelay!==void 0&&u(t.scrollEndDelay,`scrollEndDelay`);let n=!!t.horizontal,p=e=>e,re=t.keyboardScroll??!1,m=l(t.count,`count`),h=o(t.estimateSize,36),g=s(t.gap??0),_=t.getItemKey??p,v=i(t.overscan,3),y=t.sticky??null,b=re,x=t.autoMeasure??!1,S=t.onChange,C=t.onScrollEnd,w=t.onScrollingChange,T=t.scrollEndDelay??150;function E(e){N.publish(e),S?.(e)}let D=null,O=`onscrollend`in e;function k(){V&&(V=!1,w?.(!1),C?.(L))}function ie(){V||(V=!0,w?.(!0)),O||(D!==null&&clearTimeout(D),D=setTimeout(k,T))}let A=t.measurementCache??new Map,j=[],M=[],N=r({items:j,stickyItems:M,totalSize:0}),P=te(m,e=>A.get(_(e))??h(e),g),F=-1,I=0,L=0,R=0,z=-1,B=!1,V=!1;function H(e){let t=Number.isFinite(e)?e:0,n=Math.max(0,P.totalSize-R);return Math.min(n,Math.max(0,t))}function U(){if(B)return;if(m===0||R<=0){(P.prevStart!==-1||P.prevTotalSize!==P.totalSize)&&(P.commitDedup(-1,-1),j=[],M=[],E({items:j,stickyItems:M,totalSize:P.totalSize}));return}let{renderEnd:t,renderStart:n}=P.computeRange(L,L+R,v.start,v.end),r=y!==null&&L!==z;if(P.isDedupSame(n,t)&&!r)return;P.commitDedup(n,t),z=L;let i=[];for(let e=n;e<=t;e++)i.push(P.itemAt(e));j=i,M=y?ne({count:m,scrollOffset:L,sizeAt:P.sizeAt,startAt:P.startAt,stickyFn:y}):[],E({items:j,stickyItems:M,totalSize:P.totalSize}),x&&j.length>0&&queueMicrotask(()=>{if(!(B||e instanceof Window))for(let t of j){let n=_(t.index),r=e?.querySelector?.(`[data-vz-key="${n}"]`);r instanceof HTMLElement&&J(t.index,r)}})}function ae(e,t){if(!Number.isFinite(e))return!1;let n=Math.floor(e);if(n<0||n>=m)return!1;let r=c(t,-1);if(r<=0)return!1;let i=_(n);return A.get(i)!==r&&(A.set(i,r),P.markChanged(n),!0)}function W(){if(L<=0||m===0){F=-1;return}F=P.findFirst(L),I=L-P.startAt(F)}function G(){if(F<0)return;let e=P.startAt(F)+I-L;if(F=-1,Math.abs(e)>=1){let t=H(L+e);$.writeOffset(t,`auto`),L=t}}function K(e){if(B)return;let t=!1;for(let{index:n,size:r}of e)ae(n,r)&&(t=!0);t&&(P.pendingBuild||W(),P.scheduleBuild(()=>{G(),U()}))}function q(e,t){K([{index:e,size:t}])}function J(e,t){return B?()=>{}:a(X.signal,t,t=>{q(e,n?t.contentRect.width:t.contentRect.height)})}function oe(e){e.count!==void 0&&l(e.count,`count`),typeof e.estimateSize==`number`&&d(e.estimateSize,`estimateSize`),e.gap!==void 0&&l(e.gap,`gap`),e.overscan!==void 0&&f(e.overscan),e.scrollEndDelay!==void 0&&u(e.scrollEndDelay,`scrollEndDelay`);let t=!1,n=!1;if(Object.hasOwn(e,`onChange`)&&(S=e.onChange),Object.hasOwn(e,`onScrollEnd`)&&(C=e.onScrollEnd),Object.hasOwn(e,`onScrollingChange`)&&(w=e.onScrollingChange),e.scrollEndDelay!==void 0&&(T=e.scrollEndDelay),e.count!==void 0){let n=e.count;n!==m&&(m=n,P.setCount(m),t=!0)}if(Object.hasOwn(e,`estimateSize`)&&(h=o(e.estimateSize,36),A.clear(),t=!0),e.gap!==void 0){let n=e.gap;n!==g&&(g=n,P.setGap(g),t=!0)}if(Object.hasOwn(e,`getItemKey`)){let n=e.getItemKey??p;n!==_&&(_=n,A.clear(),t=!0)}if(Object.hasOwn(e,`measurementCache`)&&e.measurementCache!==void 0&&(A=e.measurementCache,t=!0),e.overscan!==void 0){let t=i(e.overscan,3);(t.start!==v.start||t.end!==v.end)&&(v=t,n=!0)}if(Object.hasOwn(e,`sticky`)){let t=e.sticky??null;t!==y&&(y=t,P.resetDedup()),n=!0}Object.hasOwn(e,`keyboardScroll`)&&(b=e.keyboardScroll??!1),Object.hasOwn(e,`autoMeasure`)&&(x=e.autoMeasure??!1),t&&(W(),P.rebuild(!0),G()),(n||t)&&U()}function se(){B||(A.clear(),P.rebuild(!0),U())}function ce(){B||(P.rebuild(!0),U())}function le(e){if(B)return;let t=s(e);if(t===0)return;m+=t,P.setCount(m),P.rebuild(!0);let n=P.startAt(t),r=H(L+n);$.writeOffset(r,`auto`),L=r,U()}function ue(e){B||oe(e)}function de(t,n={}){if(B||m<=0)return;let r=Math.max(0,Math.min(Number.isFinite(t)?Math.floor(t):0,m-1)),i=n.align??`auto`,a=n.behavior??`auto`,o=P.startAt(r),s=P.sizeAt(r),c=o+s,l;if(i===`start`)l=o;else if(i===`end`)l=c-R;else if(i===`center`)l=o-(R-s)/2;else{let e=L,t=e+R;if(o>=e&&c<=t){n.onComplete&&queueMicrotask(n.onComplete);return}l=o<e?o:c-R}$.writeOffset(H(l),a),n.onComplete&&(a===`smooth`&&`onscrollend`in e?e.addEventListener(`scrollend`,n.onComplete,{once:!0}):queueMicrotask(n.onComplete))}function Y(e,t={}){B||$.writeOffset(H(e),t.behavior??`auto`)}function fe(e={}){Y(0,e)}function pe(e={}){Y(Math.max(0,P.totalSize-R),e)}function me(e=0){return P.totalSize-(L+R)<=e}let X=new AbortController;function Z(){B||(B=!0,N.dispose(),X.abort(),D!==null&&(clearTimeout(D),D=null),O&&e.removeEventListener(`scrollend`,k),Q.detach())}function he(){L=H($.readOffset()),ie(),U()}function ge(){R=$.readViewportSize(),U()}let Q=ee(e,he,ge),$=n?Q.x:Q.y;if(b){let t=e=>{if(B||!(e instanceof KeyboardEvent)||m===0)return;let t=!1,n=L,r=Math.max(R*.8,R-100);switch(e.key){case`ArrowUp`:case`ArrowLeft`:{let e=typeof h(0)==`number`?h(0):36;n=Math.max(0,L-(e+g)),t=!0;break}case`ArrowDown`:case`ArrowRight`:{let e=typeof h(0)==`number`?h(0):36;n=Math.min(P.totalSize-R,L+(e+g)),t=!0;break}case`PageUp`:n=Math.max(0,L-r),t=!0;break;case`PageDown`:n=Math.min(P.totalSize-R,L+r),t=!0;break;case`Home`:n=0,t=!0;break;case`End`:n=Math.max(0,P.totalSize-R),t=!0}t&&(e.preventDefault(),$.writeOffset(n,`auto`),L=H(n),U())};typeof e==`object`&&e&&`addEventListener`in e&&(e.addEventListener(`keydown`,t),X.signal.addEventListener(`abort`,()=>{e.removeEventListener(`keydown`,t)}))}return O&&e.addEventListener(`scrollend`,k,{passive:!0}),P.rebuild(!0),t.initialOffset!==void 0&&$.writeOffset(H(t.initialOffset),`auto`),L=H($.readOffset()),R=$.readViewportSize(),U(),{get count(){return m},get disposalSignal(){return X.signal},dispose:Z,get disposed(){return B},getSnapshot:N.getSnapshot,invalidate:se,isAtEnd:me,get isScrolling(){return V},get items(){return j},measure:q,measureBatch:K,measureEl:J,prepend:le,refresh:ce,get scrollOffset(){return L},scrollToBottom:pe,scrollToIndex:de,scrollToOffset:Y,scrollToTop:fe,get stickyItems(){return M},subscribe:N.subscribe,[Symbol.dispose]:Z,get totalSize(){return P.totalSize},update:ue}}export{e as DEFAULT_ESTIMATE_SIZE,t as DEFAULT_OVERSCAN,n as createMeasurementCache,p as createVirtualizer};
|
|
2
2
|
//# sourceMappingURL=virtualizer.js.map
|
package/dist/virtualizer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtualizer.js","names":[],"sources":["../src/virtualizer.ts"],"sourcesContent":["import type { Signal } from '@vielzeug/ripple';\n\nimport { createScrollAdapter } from './_adapter';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport { computeStickyItems } from './_sticky';\nimport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n normalizeOverscan,\n type Overscan,\n observeResize,\n resolveEstimateFn,\n type ScrollTarget,\n toNonNegativeInt,\n toPositiveNumber,\n type VirtualKey,\n} from './_utils';\nimport {\n requireNonNegativeInteger,\n requireNonNegativeNumber,\n requirePositiveNumber,\n validateOverscan,\n} from './_validation';\n\nexport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n type ScrollTarget,\n type VirtualItem,\n type VirtualKey,\n};\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualizerState {\n readonly items: VirtualItem[];\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n}\n\nexport interface VirtualizerOptions {\n /**\n * Auto-attach ResizeObserver to visible items.\n * Elements must have a `data-vz-key` attribute set to the item key\n * so the virtualizer can locate and measure them after render.\n */\n autoMeasure?: boolean;\n count: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: (index: number) => VirtualKey;\n horizontal?: boolean;\n initialOffset?: number;\n /** Enable keyboard scroll (arrow keys, Page Up/Down, Home, End). */\n keyboardScroll?: boolean;\n /** External measurement cache for scroll restoration or SSR pre-measurement. */\n measurementCache?: MeasurementCache;\n /** Called after every render cycle with the new state. Replace through `update()`. */\n onChange?: (state: VirtualizerState) => void;\n /** Called when scrolling has settled. Replace through `update()`. */\n onScrollEnd?: (offset: number) => void;\n /** Called when the scrolling state changes. Replace through `update()`. */\n onScrollingChange?: (isScrolling: boolean) => void;\n overscan?: Overscan;\n /**\n * Debounce delay (ms) used to detect scroll end when the native `scrollend`\n * event is unavailable. Defaults to 150.\n */\n scrollEndDelay?: number;\n sticky?: (index: number) => boolean;\n /** Optional signal factory for reactive state. */\n toSignal?: (init: VirtualizerState) => Signal<VirtualizerState>;\n}\n\n/**\n * Options accepted by `update()`. Explicit interface instead of Omit<...>\n * for better IDE hover and autocomplete (R7).\n *\n * Intentionally excludes `horizontal` (axis cannot change at runtime) and\n * `initialOffset` (one-time bootstrap value).\n */\nexport interface VirtualizerUpdateOptions {\n /**\n * Auto-attach ResizeObserver to visible items.\n * Elements must have a `data-vz-key` attribute set to the item key\n * so the virtualizer can locate and measure them after render.\n */\n autoMeasure?: boolean;\n count?: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: ((index: number) => VirtualKey) | undefined;\n keyboardScroll?: boolean;\n /** Replace the active measurement cache. Existing entries in the new cache are used immediately on the next rebuild. */\n measurementCache?: MeasurementCache;\n onChange?: ((state: VirtualizerState) => void) | undefined;\n onScrollEnd?: ((offset: number) => void) | undefined;\n onScrollingChange?: ((isScrolling: boolean) => void) | undefined;\n overscan?: Overscan;\n scrollEndDelay?: number;\n sticky?: ((index: number) => boolean) | undefined;\n}\n\nexport interface ScrollToIndexOptions {\n align?: 'auto' | 'center' | 'end' | 'start';\n behavior?: ScrollBehavior;\n /** Called when the scroll animation completes (instant scrolls: next microtask). */\n onComplete?: () => void;\n}\n\nexport interface Virtualizer {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n dispose: () => void;\n readonly disposed: boolean;\n invalidate: () => void;\n /**\n * `true` when scrolled to (or within `threshold` px of) the end — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Check this **before** appending\n * items to decide whether to auto-follow them (e.g. a chat \"stick to bottom\" pattern) —\n * `createDomVirtualList`'s `stickToBottom` option does exactly this. Default `threshold`: `0`.\n */\n isAtEnd: (threshold?: number) => boolean;\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n /** Currently rendered items. Always populated. */\n readonly items: VirtualItem[];\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` and auto-measure `index` on resize. Returns a disconnect fn. */\n measureEl: (index: number, el: HTMLElement) => () => void;\n /** Prepend `additionalCount` items at the top while keeping the viewport visually stable. */\n prepend: (additionalCount: number) => void;\n /** Rebuild the full offset table and re-emit (use when item sizes may have changed). */\n refresh: () => void;\n readonly scrollOffset: number;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n update: (next: VirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createVirtualizer(target: ScrollTarget, options: VirtualizerOptions): Virtualizer {\n if (typeof options.estimateSize === 'number') requirePositiveNumber(options.estimateSize, 'estimateSize');\n\n if (options.gap !== undefined) requireNonNegativeInteger(options.gap, 'gap');\n\n if (options.initialOffset !== undefined) requireNonNegativeNumber(options.initialOffset, 'initialOffset');\n\n if (options.overscan !== undefined) validateOverscan(options.overscan);\n\n if (options.scrollEndDelay !== undefined) requireNonNegativeNumber(options.scrollEndDelay, 'scrollEndDelay');\n\n const horizontal = !!options.horizontal;\n const defaultItemKey = (index: number): VirtualKey => index;\n const enableKeyboardScroll = options.keyboardScroll ?? false;\n\n let count = requireNonNegativeInteger(options.count, 'count');\n let estimateFn = resolveEstimateFn(options.estimateSize, DEFAULT_ESTIMATE_SIZE);\n let gap = toNonNegativeInt(options.gap ?? 0);\n let getItemKey = options.getItemKey ?? defaultItemKey;\n let overscan = normalizeOverscan(options.overscan, DEFAULT_OVERSCAN);\n let stickyFn: ((index: number) => boolean) | null = options.sticky ?? null;\n let keyboardScrollEnabled = enableKeyboardScroll;\n let autoMeasureEnabled = options.autoMeasure ?? false;\n\n let onChange = options.onChange;\n let onScrollEnd = options.onScrollEnd;\n let onScrollingChange = options.onScrollingChange;\n let scrollEndDelay = options.scrollEndDelay ?? 150;\n\n // Optional signal for reactive state\n let stateSignal: Signal<VirtualizerState> | null = null;\n if (options.toSignal) {\n const initialState: VirtualizerState = { items: [], stickyItems: [], totalSize: 0 };\n stateSignal = options.toSignal(initialState);\n }\n\n // Helper to emit state to both callback and signal\n function emitState(state: VirtualizerState): void {\n if (stateSignal) stateSignal.value = state;\n onChange?.(state);\n }\n let scrollEndTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Detect native scrollend support (Chrome 114+, Firefox 109+)\n const hasNativeScrollEnd = 'onscrollend' in (target as EventTarget);\n\n function notifyScrollEnd(): void {\n if (!isScrolling) return;\n\n isScrolling = false;\n onScrollingChange?.(false);\n onScrollEnd?.(scrollOffset);\n }\n\n function handleScrollStart(): void {\n if (!isScrolling) {\n isScrolling = true;\n onScrollingChange?.(true);\n }\n\n if (!hasNativeScrollEnd) {\n if (scrollEndTimer !== null) clearTimeout(scrollEndTimer);\n\n scrollEndTimer = setTimeout(notifyScrollEnd, scrollEndDelay);\n }\n }\n\n // External or internal measurement cache.\n let measuredByKey: MeasurementCache = options.measurementCache ?? new Map();\n\n let items: VirtualItem[] = [];\n let stickyItems: VirtualItem[] = [];\n\n // ─── Axis setup ──────────────────────────────────────────────────────────────\n // The axis1d primitive owns offset management. The sizeAt closure captures\n // the live `measuredByKey`, `getItemKey`, and `estimateFn` by reference so\n // updates to those let-variables are picked up automatically.\n\n const ax = createAxis1D(count, (i) => measuredByKey.get(getItemKey(i)) ?? estimateFn(i), gap);\n\n // F6: Scroll anchor — prevents viewport jump when items above fold are measured.\n let anchorIndex = -1;\n let anchorDistanceFromTop = 0;\n\n let scrollOffset = 0;\n let viewportSize = 0;\n let prevScrollOffset = -1; // for sticky dedup\n let disposed = false;\n let isScrolling = false;\n\n // ─── Scroll clamping ─────────────────────────────────────────────────────────\n\n function clampScrollOffset(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, ax.totalSize - viewportSize);\n\n return Math.min(maxOffset, Math.max(0, safe));\n }\n\n // ─── Compute and emit ─────────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (disposed) return;\n\n if (count === 0 || viewportSize <= 0) {\n if (ax.prevStart !== -1 || ax.prevTotalSize !== ax.totalSize) {\n // Commit with sentinel renderStart=-1 so prevTotalSize is captured as the\n // current totalSize. Using resetDedup() would set prevTotalSize=-1, causing\n // the guard to fire on every subsequent scroll/resize (Bug 2).\n ax.commitDedup(-1, -1);\n\n items = [];\n stickyItems = [];\n\n emitState({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n return;\n }\n\n const { renderEnd, renderStart } = ax.computeRange(\n scrollOffset,\n scrollOffset + viewportSize,\n overscan.start,\n overscan.end,\n );\n\n // Sticky recomputes on every scroll pixel — bypass dedup in that case.\n const stickyScrolled = stickyFn !== null && scrollOffset !== prevScrollOffset;\n const rangeChanged = !ax.isDedupSame(renderStart, renderEnd);\n\n if (!rangeChanged && !stickyScrolled) return;\n\n ax.commitDedup(renderStart, renderEnd);\n prevScrollOffset = scrollOffset;\n\n const nextItems: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n nextItems.push(ax.itemAt(i));\n }\n\n items = nextItems;\n stickyItems = stickyFn\n ? computeStickyItems({ count, scrollOffset, sizeAt: ax.sizeAt, startAt: ax.startAt, stickyFn })\n : [];\n\n emitState({ items, stickyItems, totalSize: ax.totalSize });\n\n // Auto-measure visible items if enabled\n if (autoMeasureEnabled && items.length > 0) {\n // Queue microtask to allow DOM to render first\n queueMicrotask(() => {\n if (disposed || target instanceof Window) return;\n\n for (const item of items) {\n const key = getItemKey(item.index);\n const el = (target as HTMLElement)?.querySelector?.(`[data-vz-key=\"${key}\"]`);\n\n if (el instanceof HTMLElement) {\n measureEl(item.index, el);\n }\n }\n });\n }\n }\n\n // ─── Measurement (R6: measure delegates to measureBatch) ─────────────────────\n\n function recordMeasurement(index: number, size: number): boolean {\n if (!Number.isFinite(index)) return false;\n\n const i = Math.floor(index);\n\n if (i < 0 || i >= count) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n const key = getItemKey(i);\n\n if (measuredByKey.get(key) === s) return false;\n\n measuredByKey.set(key, s);\n ax.markChanged(i);\n\n return true;\n }\n\n // F6: Record a scroll anchor before a batch of measurements arrives.\n function recordScrollAnchor(): void {\n if (scrollOffset <= 0 || count === 0) {\n anchorIndex = -1;\n\n return;\n }\n\n anchorIndex = ax.findFirst(scrollOffset);\n anchorDistanceFromTop = scrollOffset - ax.startAt(anchorIndex);\n }\n\n // F6: After rebuild, restore the anchor to prevent viewport jump.\n function restoreScrollAnchor(): void {\n if (anchorIndex < 0) return;\n\n const expectedOffset = ax.startAt(anchorIndex) + anchorDistanceFromTop;\n const delta = expectedOffset - scrollOffset;\n\n anchorIndex = -1;\n\n if (Math.abs(delta) >= 1) {\n const newOffset = clampScrollOffset(scrollOffset + delta);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n }\n }\n\n function measureBatch(entries: Array<{ index: number; size: number }>): void {\n if (disposed) return;\n\n let changed = false;\n\n for (const { index, size } of entries) {\n if (recordMeasurement(index, size)) changed = true;\n }\n\n if (changed) {\n // Record anchor before the first pending build is scheduled (F6).\n if (!ax.pendingBuild) recordScrollAnchor();\n\n ax.scheduleBuild(() => {\n restoreScrollAnchor();\n computeVisible();\n });\n }\n }\n\n /** R6: Single-item convenience — delegates to measureBatch. */\n function measure(index: number, size: number): void {\n measureBatch([{ index, size }]);\n }\n\n /** Attach a ResizeObserver to `el`. Returns a disconnect function. */\n function measureEl(index: number, el: HTMLElement): () => void {\n if (disposed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => {\n measure(index, horizontal ? entry.contentRect.width : entry.contentRect.height);\n });\n }\n\n // ─── Option updates ───────────────────────────────────────────────────────────\n\n function applyOptions(next: VirtualizerUpdateOptions): void {\n if (next.count !== undefined) requireNonNegativeInteger(next.count, 'count');\n\n if (typeof next.estimateSize === 'number') requirePositiveNumber(next.estimateSize, 'estimateSize');\n\n if (next.gap !== undefined) requireNonNegativeInteger(next.gap, 'gap');\n\n if (next.overscan !== undefined) validateOverscan(next.overscan);\n\n if (next.scrollEndDelay !== undefined) requireNonNegativeNumber(next.scrollEndDelay, 'scrollEndDelay');\n\n let needsRebuild = false;\n let needsCompute = false;\n\n if (Object.hasOwn(next, 'onChange')) onChange = next.onChange;\n\n if (Object.hasOwn(next, 'onScrollEnd')) onScrollEnd = next.onScrollEnd;\n\n if (Object.hasOwn(next, 'onScrollingChange')) onScrollingChange = next.onScrollingChange;\n\n if (next.scrollEndDelay !== undefined) {\n scrollEndDelay = next.scrollEndDelay;\n }\n\n if (next.count !== undefined) {\n const nextCount = next.count;\n\n if (nextCount !== count) {\n count = nextCount;\n ax.setCount(count);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'estimateSize')) {\n estimateFn = resolveEstimateFn(next.estimateSize, DEFAULT_ESTIMATE_SIZE);\n measuredByKey.clear();\n needsRebuild = true;\n }\n\n if (next.gap !== undefined) {\n const nextGap = next.gap;\n\n if (nextGap !== gap) {\n gap = nextGap;\n ax.setGap(gap);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'getItemKey')) {\n const nextKey = next.getItemKey ?? defaultItemKey;\n\n if (nextKey !== getItemKey) {\n getItemKey = nextKey;\n measuredByKey.clear();\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'measurementCache') && next.measurementCache !== undefined) {\n measuredByKey = next.measurementCache;\n needsRebuild = true;\n }\n\n if (next.overscan !== undefined) {\n const nextOverscan = normalizeOverscan(next.overscan, DEFAULT_OVERSCAN);\n\n if (nextOverscan.start !== overscan.start || nextOverscan.end !== overscan.end) {\n overscan = nextOverscan;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'sticky')) {\n const nextStickyFn = next.sticky ?? null;\n\n if (nextStickyFn !== stickyFn) {\n stickyFn = nextStickyFn;\n ax.resetDedup(); // force re-emit to flush updated stickyItems\n }\n\n needsCompute = true;\n }\n\n if (Object.hasOwn(next, 'keyboardScroll')) {\n keyboardScrollEnabled = next.keyboardScroll ?? false;\n }\n\n if (Object.hasOwn(next, 'autoMeasure')) {\n autoMeasureEnabled = next.autoMeasure ?? false;\n }\n\n if (needsRebuild) {\n recordScrollAnchor();\n ax.rebuild(true);\n restoreScrollAnchor();\n }\n\n if (needsCompute || needsRebuild) computeVisible();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────────\n\n function invalidate(): void {\n if (disposed) return;\n\n measuredByKey.clear();\n ax.rebuild(true);\n computeVisible();\n }\n\n /** Full O(n) offset rebuild followed by re-emit. Also used internally for data-only re-emit when sizes unchanged. */\n function refresh(): void {\n if (disposed) return;\n\n ax.rebuild(true);\n computeVisible();\n }\n\n function prepend(additionalCount: number): void {\n if (disposed) return;\n\n const n = toNonNegativeInt(additionalCount);\n\n if (n === 0) return;\n\n count += n;\n ax.setCount(count);\n ax.rebuild(true);\n\n // Adjust scroll to keep the same content visible (F6-like stable prepend).\n const prependedHeight = ax.startAt(n);\n const newOffset = clampScrollOffset(scrollOffset + prependedHeight);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n\n computeVisible();\n }\n\n function update(next: VirtualizerUpdateOptions): void {\n if (disposed) return;\n\n applyOptions(next);\n }\n\n function scrollToIndex(index: number, opts: ScrollToIndexOptions = {}): void {\n if (disposed || count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, count - 1));\n const align = opts.align ?? 'auto';\n const behavior = opts.behavior ?? 'auto';\n const itemStart = ax.startAt(clampedIndex);\n const itemSize = ax.sizeAt(clampedIndex);\n const itemEnd = itemStart + itemSize;\n\n let targetOffset: number;\n\n if (align === 'start') {\n targetOffset = itemStart;\n } else if (align === 'end') {\n targetOffset = itemEnd - viewportSize;\n } else if (align === 'center') {\n targetOffset = itemStart - (viewportSize - itemSize) / 2;\n } else {\n const visibleStart = scrollOffset;\n const visibleEnd = visibleStart + viewportSize;\n\n if (itemStart >= visibleStart && itemEnd <= visibleEnd) {\n if (opts.onComplete) queueMicrotask(opts.onComplete);\n\n return;\n }\n\n targetOffset = itemStart < visibleStart ? itemStart : itemEnd - viewportSize;\n }\n\n domAxis.writeOffset(clampScrollOffset(targetOffset), behavior);\n\n if (opts.onComplete) {\n if (behavior === 'smooth' && 'onscrollend' in target) {\n (target as EventTarget).addEventListener('scrollend', opts.onComplete, { once: true });\n } else {\n queueMicrotask(opts.onComplete);\n }\n }\n }\n\n function scrollToOffset(offset: number, opts: { behavior?: ScrollBehavior } = {}): void {\n if (disposed) return;\n\n domAxis.writeOffset(clampScrollOffset(offset), opts.behavior ?? 'auto');\n }\n\n function scrollToTop(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(0, opts);\n }\n\n function scrollToBottom(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(Math.max(0, ax.totalSize - viewportSize), opts);\n }\n\n function isAtEnd(threshold = 0): boolean {\n return ax.totalSize - (scrollOffset + viewportSize) <= threshold;\n }\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (disposed) return;\n\n disposed = true;\n ac.abort();\n\n if (scrollEndTimer !== null) {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = null;\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).removeEventListener('scrollend', notifyScrollEnd);\n }\n\n adapter.detach();\n }\n\n // ─── Bootstrap ───────────────────────────────────────────────────────────────\n\n function handleScroll(): void {\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n handleScrollStart();\n computeVisible();\n }\n\n function handleResize(): void {\n viewportSize = domAxis.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n const domAxis = horizontal ? adapter.x : adapter.y;\n\n // ─── Keyboard scroll support ──────────────────────────────────────────────────\n\n if (keyboardScrollEnabled) {\n const handleKeyDown = (e: Event): void => {\n if (disposed) return;\n if (!(e instanceof KeyboardEvent)) return;\n if (count === 0) return;\n\n let handled = false;\n let newOffset = scrollOffset;\n const pageScrollSize = Math.max(viewportSize * 0.8, viewportSize - 100);\n\n switch (e.key) {\n case 'ArrowUp':\n case 'ArrowLeft': {\n const itemSize = typeof estimateFn(0) === 'number' ? estimateFn(0) : DEFAULT_ESTIMATE_SIZE;\n newOffset = Math.max(0, scrollOffset - (itemSize + gap));\n handled = true;\n break;\n }\n case 'ArrowDown':\n case 'ArrowRight': {\n const itemSize = typeof estimateFn(0) === 'number' ? estimateFn(0) : DEFAULT_ESTIMATE_SIZE;\n newOffset = Math.min(ax.totalSize - viewportSize, scrollOffset + (itemSize + gap));\n handled = true;\n break;\n }\n case 'PageUp':\n newOffset = Math.max(0, scrollOffset - pageScrollSize);\n handled = true;\n break;\n case 'PageDown':\n newOffset = Math.min(ax.totalSize - viewportSize, scrollOffset + pageScrollSize);\n handled = true;\n break;\n case 'Home':\n newOffset = 0;\n handled = true;\n break;\n case 'End':\n newOffset = Math.max(0, ax.totalSize - viewportSize);\n handled = true;\n break;\n }\n\n if (handled) {\n e.preventDefault();\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = clampScrollOffset(newOffset);\n computeVisible();\n }\n };\n\n if (typeof target === 'object' && target !== null && 'addEventListener' in target) {\n (target as EventTarget).addEventListener('keydown', handleKeyDown);\n ac.signal.addEventListener('abort', () => {\n (target as EventTarget).removeEventListener('keydown', handleKeyDown);\n });\n }\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).addEventListener('scrollend', notifyScrollEnd, { passive: true });\n }\n\n ax.rebuild(true);\n\n if (options.initialOffset !== undefined) {\n domAxis.writeOffset(clampScrollOffset(options.initialOffset), 'auto');\n }\n\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n viewportSize = domAxis.readViewportSize();\n\n computeVisible();\n\n return {\n get count() {\n return count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return disposed;\n },\n invalidate,\n isAtEnd,\n get isScrolling() {\n return isScrolling;\n },\n get items() {\n return items;\n },\n measure,\n measureBatch,\n measureEl,\n prepend,\n refresh,\n get scrollOffset() {\n return scrollOffset;\n },\n scrollToBottom,\n scrollToIndex,\n scrollToOffset,\n scrollToTop,\n get stickyItems() {\n return stickyItems;\n },\n [Symbol.dispose]: _dispose,\n get totalSize() {\n return ax.totalSize;\n },\n update,\n };\n}\n"],"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 { computeStickyItems } from './_sticky';\nimport {\n createMeasurementCache,\n createSnapshotStore,\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 ScrollStore<State> {\n getSnapshot: () => State;\n subscribe: (listener: () => void) => () => void;\n}\n\nexport interface VirtualizerOptions {\n /**\n * Auto-attach ResizeObserver to visible items.\n * Elements must have a `data-vz-key` attribute set to the item key\n * so the virtualizer can locate and measure them after render.\n */\n autoMeasure?: boolean;\n count: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: (index: number) => VirtualKey;\n horizontal?: boolean;\n initialOffset?: number;\n /** Enable keyboard scroll (arrow keys, Page Up/Down, Home, End). */\n keyboardScroll?: boolean;\n /** External measurement cache for scroll restoration or SSR pre-measurement. */\n measurementCache?: MeasurementCache;\n /** Called after every render cycle with the new state. Replace through `update()`. */\n onChange?: (state: VirtualizerState) => void;\n /** Called when scrolling has settled. Replace through `update()`. */\n onScrollEnd?: (offset: number) => void;\n /** Called when the scrolling state changes. Replace through `update()`. */\n onScrollingChange?: (isScrolling: boolean) => void;\n overscan?: Overscan;\n /**\n * Debounce delay (ms) used to detect scroll end when the native `scrollend`\n * event is unavailable. Defaults to 150.\n */\n scrollEndDelay?: number;\n sticky?: (index: number) => boolean;\n}\n\n/**\n * Options accepted by `update()`. Explicit interface instead of Omit<...>\n * for better IDE hover and autocomplete (R7).\n *\n * Intentionally excludes `horizontal` (axis cannot change at runtime) and\n * `initialOffset` (one-time bootstrap value).\n */\nexport interface VirtualizerUpdateOptions {\n /**\n * Auto-attach ResizeObserver to visible items.\n * Elements must have a `data-vz-key` attribute set to the item key\n * so the virtualizer can locate and measure them after render.\n */\n autoMeasure?: boolean;\n count?: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: ((index: number) => VirtualKey) | undefined;\n keyboardScroll?: boolean;\n /** Replace the active measurement cache. Existing entries in the new cache are used immediately on the next rebuild. */\n measurementCache?: MeasurementCache;\n onChange?: ((state: VirtualizerState) => void) | undefined;\n onScrollEnd?: ((offset: number) => void) | undefined;\n onScrollingChange?: ((isScrolling: boolean) => void) | undefined;\n overscan?: Overscan;\n scrollEndDelay?: number;\n sticky?: ((index: number) => boolean) | undefined;\n}\n\nexport interface ScrollToIndexOptions {\n align?: 'auto' | 'center' | 'end' | 'start';\n behavior?: ScrollBehavior;\n /** Called when the scroll animation completes (instant scrolls: next microtask). */\n onComplete?: () => void;\n}\n\nexport interface Virtualizer extends ScrollStore<VirtualizerState> {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n dispose: () => void;\n readonly disposed: boolean;\n invalidate: () => void;\n /**\n * `true` when scrolled to (or within `threshold` px of) the end — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Check this **before** appending\n * items to decide whether to auto-follow them (e.g. a chat \"stick to bottom\" pattern) —\n * `createDomVirtualList`'s `stickToBottom` option does exactly this. Default `threshold`: `0`.\n */\n isAtEnd: (threshold?: number) => boolean;\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n /** Currently rendered items. Always populated. */\n readonly items: VirtualItem[];\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` and auto-measure `index` on resize. Returns a disconnect fn. */\n measureEl: (index: number, el: HTMLElement) => () => void;\n /** Prepend `additionalCount` items at the top while keeping the viewport visually stable. */\n prepend: (additionalCount: number) => void;\n /** Rebuild the full offset table and re-emit (use when item sizes may have changed). */\n refresh: () => void;\n readonly scrollOffset: number;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n update: (next: VirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createVirtualizer(target: ScrollTarget, options: VirtualizerOptions): Virtualizer {\n if (typeof options.estimateSize === 'number') requirePositiveNumber(options.estimateSize, 'estimateSize');\n\n if (options.gap !== undefined) requireNonNegativeInteger(options.gap, 'gap');\n\n if (options.initialOffset !== undefined) requireNonNegativeNumber(options.initialOffset, 'initialOffset');\n\n if (options.overscan !== undefined) validateOverscan(options.overscan);\n\n if (options.scrollEndDelay !== undefined) requireNonNegativeNumber(options.scrollEndDelay, 'scrollEndDelay');\n\n const horizontal = !!options.horizontal;\n const defaultItemKey = (index: number): VirtualKey => index;\n const enableKeyboardScroll = options.keyboardScroll ?? false;\n\n let count = requireNonNegativeInteger(options.count, 'count');\n let estimateFn = resolveEstimateFn(options.estimateSize, DEFAULT_ESTIMATE_SIZE);\n let gap = toNonNegativeInt(options.gap ?? 0);\n let getItemKey = options.getItemKey ?? defaultItemKey;\n let overscan = normalizeOverscan(options.overscan, DEFAULT_OVERSCAN);\n let stickyFn: ((index: number) => boolean) | null = options.sticky ?? null;\n let keyboardScrollEnabled = enableKeyboardScroll;\n let autoMeasureEnabled = options.autoMeasure ?? false;\n\n let onChange = options.onChange;\n let onScrollEnd = options.onScrollEnd;\n let onScrollingChange = options.onScrollingChange;\n let scrollEndDelay = options.scrollEndDelay ?? 150;\n\n // Publish state before invoking the render callback.\n function emitState(state: VirtualizerState): void {\n stateStore.publish(state);\n onChange?.(state);\n }\n let scrollEndTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Detect native scrollend support (Chrome 114+, Firefox 109+)\n const hasNativeScrollEnd = 'onscrollend' in (target as EventTarget);\n\n function notifyScrollEnd(): void {\n if (!isScrolling) return;\n\n isScrolling = false;\n onScrollingChange?.(false);\n onScrollEnd?.(scrollOffset);\n }\n\n function handleScrollStart(): void {\n if (!isScrolling) {\n isScrolling = true;\n onScrollingChange?.(true);\n }\n\n if (!hasNativeScrollEnd) {\n if (scrollEndTimer !== null) clearTimeout(scrollEndTimer);\n\n scrollEndTimer = setTimeout(notifyScrollEnd, scrollEndDelay);\n }\n }\n\n // External or internal measurement cache.\n let measuredByKey: MeasurementCache = options.measurementCache ?? new Map();\n\n let items: VirtualItem[] = [];\n let stickyItems: VirtualItem[] = [];\n const stateStore = createSnapshotStore<VirtualizerState>({ items, stickyItems, totalSize: 0 });\n\n // ─── Axis setup ──────────────────────────────────────────────────────────────\n // The axis1d primitive owns offset management. The sizeAt closure captures\n // the live `measuredByKey`, `getItemKey`, and `estimateFn` by reference so\n // updates to those let-variables are picked up automatically.\n\n const ax = createAxis1D(count, (i) => measuredByKey.get(getItemKey(i)) ?? estimateFn(i), gap);\n\n // F6: Scroll anchor — prevents viewport jump when items above fold are measured.\n let anchorIndex = -1;\n let anchorDistanceFromTop = 0;\n\n let scrollOffset = 0;\n let viewportSize = 0;\n let prevScrollOffset = -1; // for sticky dedup\n let disposed = false;\n let isScrolling = false;\n\n // ─── Scroll clamping ─────────────────────────────────────────────────────────\n\n function clampScrollOffset(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, ax.totalSize - viewportSize);\n\n return Math.min(maxOffset, Math.max(0, safe));\n }\n\n // ─── Compute and emit ─────────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (disposed) return;\n\n if (count === 0 || viewportSize <= 0) {\n if (ax.prevStart !== -1 || ax.prevTotalSize !== ax.totalSize) {\n // Commit with sentinel renderStart=-1 so prevTotalSize is captured as the\n // current totalSize. Using resetDedup() would set prevTotalSize=-1, causing\n // the guard to fire on every subsequent scroll/resize (Bug 2).\n ax.commitDedup(-1, -1);\n\n items = [];\n stickyItems = [];\n\n emitState({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n return;\n }\n\n const { renderEnd, renderStart } = ax.computeRange(\n scrollOffset,\n scrollOffset + viewportSize,\n overscan.start,\n overscan.end,\n );\n\n // Sticky recomputes on every scroll pixel — bypass dedup in that case.\n const stickyScrolled = stickyFn !== null && scrollOffset !== prevScrollOffset;\n const rangeChanged = !ax.isDedupSame(renderStart, renderEnd);\n\n if (!rangeChanged && !stickyScrolled) return;\n\n ax.commitDedup(renderStart, renderEnd);\n prevScrollOffset = scrollOffset;\n\n const nextItems: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n nextItems.push(ax.itemAt(i));\n }\n\n items = nextItems;\n stickyItems = stickyFn\n ? computeStickyItems({ count, scrollOffset, sizeAt: ax.sizeAt, startAt: ax.startAt, stickyFn })\n : [];\n\n emitState({ items, stickyItems, totalSize: ax.totalSize });\n\n // Auto-measure visible items if enabled\n if (autoMeasureEnabled && items.length > 0) {\n // Queue microtask to allow DOM to render first\n queueMicrotask(() => {\n if (disposed || target instanceof Window) return;\n\n for (const item of items) {\n const key = getItemKey(item.index);\n const el = (target as HTMLElement)?.querySelector?.(`[data-vz-key=\"${key}\"]`);\n\n if (el instanceof HTMLElement) {\n measureEl(item.index, el);\n }\n }\n });\n }\n }\n\n // ─── Measurement (R6: measure delegates to measureBatch) ─────────────────────\n\n function recordMeasurement(index: number, size: number): boolean {\n if (!Number.isFinite(index)) return false;\n\n const i = Math.floor(index);\n\n if (i < 0 || i >= count) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n const key = getItemKey(i);\n\n if (measuredByKey.get(key) === s) return false;\n\n measuredByKey.set(key, s);\n ax.markChanged(i);\n\n return true;\n }\n\n // F6: Record a scroll anchor before a batch of measurements arrives.\n function recordScrollAnchor(): void {\n if (scrollOffset <= 0 || count === 0) {\n anchorIndex = -1;\n\n return;\n }\n\n anchorIndex = ax.findFirst(scrollOffset);\n anchorDistanceFromTop = scrollOffset - ax.startAt(anchorIndex);\n }\n\n // F6: After rebuild, restore the anchor to prevent viewport jump.\n function restoreScrollAnchor(): void {\n if (anchorIndex < 0) return;\n\n const expectedOffset = ax.startAt(anchorIndex) + anchorDistanceFromTop;\n const delta = expectedOffset - scrollOffset;\n\n anchorIndex = -1;\n\n if (Math.abs(delta) >= 1) {\n const newOffset = clampScrollOffset(scrollOffset + delta);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n }\n }\n\n function measureBatch(entries: Array<{ index: number; size: number }>): void {\n if (disposed) return;\n\n let changed = false;\n\n for (const { index, size } of entries) {\n if (recordMeasurement(index, size)) changed = true;\n }\n\n if (changed) {\n // Record anchor before the first pending build is scheduled (F6).\n if (!ax.pendingBuild) recordScrollAnchor();\n\n ax.scheduleBuild(() => {\n restoreScrollAnchor();\n computeVisible();\n });\n }\n }\n\n /** R6: Single-item convenience — delegates to measureBatch. */\n function measure(index: number, size: number): void {\n measureBatch([{ index, size }]);\n }\n\n /** Attach a ResizeObserver to `el`. Returns a disconnect function. */\n function measureEl(index: number, el: HTMLElement): () => void {\n if (disposed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => {\n measure(index, horizontal ? entry.contentRect.width : entry.contentRect.height);\n });\n }\n\n // ─── Option updates ───────────────────────────────────────────────────────────\n\n function applyOptions(next: VirtualizerUpdateOptions): void {\n if (next.count !== undefined) requireNonNegativeInteger(next.count, 'count');\n\n if (typeof next.estimateSize === 'number') requirePositiveNumber(next.estimateSize, 'estimateSize');\n\n if (next.gap !== undefined) requireNonNegativeInteger(next.gap, 'gap');\n\n if (next.overscan !== undefined) validateOverscan(next.overscan);\n\n if (next.scrollEndDelay !== undefined) requireNonNegativeNumber(next.scrollEndDelay, 'scrollEndDelay');\n\n let needsRebuild = false;\n let needsCompute = false;\n\n if (Object.hasOwn(next, 'onChange')) onChange = next.onChange;\n\n if (Object.hasOwn(next, 'onScrollEnd')) onScrollEnd = next.onScrollEnd;\n\n if (Object.hasOwn(next, 'onScrollingChange')) onScrollingChange = next.onScrollingChange;\n\n if (next.scrollEndDelay !== undefined) {\n scrollEndDelay = next.scrollEndDelay;\n }\n\n if (next.count !== undefined) {\n const nextCount = next.count;\n\n if (nextCount !== count) {\n count = nextCount;\n ax.setCount(count);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'estimateSize')) {\n estimateFn = resolveEstimateFn(next.estimateSize, DEFAULT_ESTIMATE_SIZE);\n measuredByKey.clear();\n needsRebuild = true;\n }\n\n if (next.gap !== undefined) {\n const nextGap = next.gap;\n\n if (nextGap !== gap) {\n gap = nextGap;\n ax.setGap(gap);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'getItemKey')) {\n const nextKey = next.getItemKey ?? defaultItemKey;\n\n if (nextKey !== getItemKey) {\n getItemKey = nextKey;\n measuredByKey.clear();\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'measurementCache') && next.measurementCache !== undefined) {\n measuredByKey = next.measurementCache;\n needsRebuild = true;\n }\n\n if (next.overscan !== undefined) {\n const nextOverscan = normalizeOverscan(next.overscan, DEFAULT_OVERSCAN);\n\n if (nextOverscan.start !== overscan.start || nextOverscan.end !== overscan.end) {\n overscan = nextOverscan;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'sticky')) {\n const nextStickyFn = next.sticky ?? null;\n\n if (nextStickyFn !== stickyFn) {\n stickyFn = nextStickyFn;\n ax.resetDedup(); // force re-emit to flush updated stickyItems\n }\n\n needsCompute = true;\n }\n\n if (Object.hasOwn(next, 'keyboardScroll')) {\n keyboardScrollEnabled = next.keyboardScroll ?? false;\n }\n\n if (Object.hasOwn(next, 'autoMeasure')) {\n autoMeasureEnabled = next.autoMeasure ?? false;\n }\n\n if (needsRebuild) {\n recordScrollAnchor();\n ax.rebuild(true);\n restoreScrollAnchor();\n }\n\n if (needsCompute || needsRebuild) computeVisible();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────────\n\n function invalidate(): void {\n if (disposed) return;\n\n measuredByKey.clear();\n ax.rebuild(true);\n computeVisible();\n }\n\n /** Full O(n) offset rebuild followed by re-emit. Also used internally for data-only re-emit when sizes unchanged. */\n function refresh(): void {\n if (disposed) return;\n\n ax.rebuild(true);\n computeVisible();\n }\n\n function prepend(additionalCount: number): void {\n if (disposed) return;\n\n const n = toNonNegativeInt(additionalCount);\n\n if (n === 0) return;\n\n count += n;\n ax.setCount(count);\n ax.rebuild(true);\n\n // Adjust scroll to keep the same content visible (F6-like stable prepend).\n const prependedHeight = ax.startAt(n);\n const newOffset = clampScrollOffset(scrollOffset + prependedHeight);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n\n computeVisible();\n }\n\n function update(next: VirtualizerUpdateOptions): void {\n if (disposed) return;\n\n applyOptions(next);\n }\n\n function scrollToIndex(index: number, opts: ScrollToIndexOptions = {}): void {\n if (disposed || count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, count - 1));\n const align = opts.align ?? 'auto';\n const behavior = opts.behavior ?? 'auto';\n const itemStart = ax.startAt(clampedIndex);\n const itemSize = ax.sizeAt(clampedIndex);\n const itemEnd = itemStart + itemSize;\n\n let targetOffset: number;\n\n if (align === 'start') {\n targetOffset = itemStart;\n } else if (align === 'end') {\n targetOffset = itemEnd - viewportSize;\n } else if (align === 'center') {\n targetOffset = itemStart - (viewportSize - itemSize) / 2;\n } else {\n const visibleStart = scrollOffset;\n const visibleEnd = visibleStart + viewportSize;\n\n if (itemStart >= visibleStart && itemEnd <= visibleEnd) {\n if (opts.onComplete) queueMicrotask(opts.onComplete);\n\n return;\n }\n\n targetOffset = itemStart < visibleStart ? itemStart : itemEnd - viewportSize;\n }\n\n domAxis.writeOffset(clampScrollOffset(targetOffset), behavior);\n\n if (opts.onComplete) {\n if (behavior === 'smooth' && 'onscrollend' in target) {\n (target as EventTarget).addEventListener('scrollend', opts.onComplete, { once: true });\n } else {\n queueMicrotask(opts.onComplete);\n }\n }\n }\n\n function scrollToOffset(offset: number, opts: { behavior?: ScrollBehavior } = {}): void {\n if (disposed) return;\n\n domAxis.writeOffset(clampScrollOffset(offset), opts.behavior ?? 'auto');\n }\n\n function scrollToTop(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(0, opts);\n }\n\n function scrollToBottom(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(Math.max(0, ax.totalSize - viewportSize), opts);\n }\n\n function isAtEnd(threshold = 0): boolean {\n return ax.totalSize - (scrollOffset + viewportSize) <= threshold;\n }\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (disposed) return;\n\n disposed = true;\n stateStore.dispose();\n ac.abort();\n\n if (scrollEndTimer !== null) {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = null;\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).removeEventListener('scrollend', notifyScrollEnd);\n }\n\n adapter.detach();\n }\n\n // ─── Bootstrap ───────────────────────────────────────────────────────────────\n\n function handleScroll(): void {\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n handleScrollStart();\n computeVisible();\n }\n\n function handleResize(): void {\n viewportSize = domAxis.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n const domAxis = horizontal ? adapter.x : adapter.y;\n\n // ─── Keyboard scroll support ──────────────────────────────────────────────────\n\n if (keyboardScrollEnabled) {\n const handleKeyDown = (e: Event): void => {\n if (disposed) return;\n if (!(e instanceof KeyboardEvent)) return;\n if (count === 0) return;\n\n let handled = false;\n let newOffset = scrollOffset;\n const pageScrollSize = Math.max(viewportSize * 0.8, viewportSize - 100);\n\n switch (e.key) {\n case 'ArrowUp':\n case 'ArrowLeft': {\n const itemSize = typeof estimateFn(0) === 'number' ? estimateFn(0) : DEFAULT_ESTIMATE_SIZE;\n newOffset = Math.max(0, scrollOffset - (itemSize + gap));\n handled = true;\n break;\n }\n case 'ArrowDown':\n case 'ArrowRight': {\n const itemSize = typeof estimateFn(0) === 'number' ? estimateFn(0) : DEFAULT_ESTIMATE_SIZE;\n newOffset = Math.min(ax.totalSize - viewportSize, scrollOffset + (itemSize + gap));\n handled = true;\n break;\n }\n case 'PageUp':\n newOffset = Math.max(0, scrollOffset - pageScrollSize);\n handled = true;\n break;\n case 'PageDown':\n newOffset = Math.min(ax.totalSize - viewportSize, scrollOffset + pageScrollSize);\n handled = true;\n break;\n case 'Home':\n newOffset = 0;\n handled = true;\n break;\n case 'End':\n newOffset = Math.max(0, ax.totalSize - viewportSize);\n handled = true;\n break;\n }\n\n if (handled) {\n e.preventDefault();\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = clampScrollOffset(newOffset);\n computeVisible();\n }\n };\n\n if (typeof target === 'object' && target !== null && 'addEventListener' in target) {\n (target as EventTarget).addEventListener('keydown', handleKeyDown);\n ac.signal.addEventListener('abort', () => {\n (target as EventTarget).removeEventListener('keydown', handleKeyDown);\n });\n }\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).addEventListener('scrollend', notifyScrollEnd, { passive: true });\n }\n\n ax.rebuild(true);\n\n if (options.initialOffset !== undefined) {\n domAxis.writeOffset(clampScrollOffset(options.initialOffset), 'auto');\n }\n\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n viewportSize = domAxis.readViewportSize();\n\n computeVisible();\n\n return {\n get count() {\n return count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return disposed;\n },\n getSnapshot: stateStore.getSnapshot,\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 subscribe: stateStore.subscribe,\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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vielzeug/scroll",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Virtual list engine — variable-height rows, smooth scrolling, and zero-layout-thrash rendering",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,21 +30,18 @@
|
|
|
30
30
|
"registry": "https://registry.npmjs.org/"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
|
-
"build": "vite build && pnpm run build:bundle && pnpm run build:types",
|
|
33
|
+
"build": "vite build && pnpm --silent run build:bundle && pnpm --silent run build:types",
|
|
34
34
|
"build:bundle": "vite build --config vite.bundle.config.ts",
|
|
35
35
|
"build:types": "tsc -p tsconfig.declarations.json",
|
|
36
36
|
"fix": "biome check --write src",
|
|
37
37
|
"lint": "biome ci src",
|
|
38
|
-
"prepublishOnly": "pnpm run build",
|
|
38
|
+
"prepublishOnly": "pnpm --silent run build",
|
|
39
39
|
"test": "vitest"
|
|
40
40
|
},
|
|
41
|
-
"dependencies": {
|
|
42
|
-
"@vielzeug/ripple": "2.3.0"
|
|
43
|
-
},
|
|
44
41
|
"devDependencies": {
|
|
45
|
-
"@types/node": "^26.
|
|
42
|
+
"@types/node": "^26.4.1",
|
|
46
43
|
"typescript": "^6.0.3",
|
|
47
|
-
"vite": "^8.2.
|
|
48
|
-
"vitest": "^
|
|
44
|
+
"vite": "^8.2.2",
|
|
45
|
+
"vitest": "^5.0.0"
|
|
49
46
|
}
|
|
50
47
|
}
|