@vielzeug/scroll 1.1.3 → 1.1.5

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.
@@ -1,2 +1,2 @@
1
- const e=require("./_utils.cjs"),t=require("./_adapter.cjs"),n=require("./_axis1d.cjs");function r(r,i){let a=!!i.horizontal,o=e=>e,s=e.toNonNegativeInt(i.count),c=e.resolveEstimateFn(i.estimateSize,36),l=e.toNonNegativeInt(i.gap??0),u=i.getItemKey??o,d=e.normalizeOverscan(i.overscan,3),f=i.sticky??null,p=i.onChange,m=i.onScrollEnd,h=i.onScrollingChange,g=typeof i.scrollEndDelay==`number`&&i.scrollEndDelay>=0?i.scrollEndDelay:150,_=!1,v=null,y=`onscrollend`in r;function b(){_&&(_=!1,h?.(!1),m?.(O))}function x(){_||(_=!0,h?.(!0)),y||(v!==null&&clearTimeout(v),v=setTimeout(b,g))}let S=i.measurementCache??new Map,C=[],w=[],T=n.createAxis1D(s,e=>S.get(u(e))??c(e),l),E=-1,D=0,O=0,k=0,A=-1,j=!1;function M(e){let t=Number.isFinite(e)?e:0,n=Math.max(0,T.totalSize-k);return Math.min(n,Math.max(0,t))}function N(){if(!f||s===0||O<=0)return[];let e=-1,t=0,n=s-1;for(;t<=n;){let r=t+n>>1;T.startAt(r)<O?(e=r,t=r+1):n=r-1}if(e<0)return[];let r=-1;for(let t=e;t>=0;t--)if(f(t)){r=t;break}if(r===-1)return[];let i=T.sizeAt(r),a=1/0;for(let e=r+1;e<s;e++)if(f(e)){a=T.startAt(e);break}let o=Math.min(O,a-i);return[{end:o+i,index:r,size:i,start:o}]}function P(){if(j)return;if(s===0||k<=0){(T.prevStart!==-1||T.prevTotalSize!==T.totalSize)&&(T.commitDedup(-1,-1),C=[],w=[],p&&p({items:C,stickyItems:w,totalSize:T.totalSize}));return}let{renderEnd:e,renderStart:t}=T.computeRange(O,O+k,d.start,d.end),n=f!==null&&O!==A;if(T.isDedupSame(t,e)&&!n)return;T.commitDedup(t,e),A=O;let r=[];for(let n=t;n<=e;n++)r.push(T.itemAt(n));C=r,w=f?N():[],p&&p({items:C,stickyItems:w,totalSize:T.totalSize})}function F(t,n){if(!Number.isFinite(t))return!1;let r=Math.floor(t);if(r<0||r>=s)return!1;let i=e.toPositiveNumber(n,-1);if(i<=0)return!1;let a=u(r);return S.get(a)===i?!1:(S.set(a,i),T.markChanged(r),!0)}function I(){if(O<=0||s===0){E=-1;return}E=T.findFirst(O),D=O-T.startAt(E)}function L(){if(E<0)return;let e=T.startAt(E)+D-O;if(E=-1,Math.abs(e)>=1){let t=M(O+e);$.writeOffset(t,`auto`),O=t}}function R(e){if(j)return;let t=!1;for(let{index:n,size:r}of e)F(n,r)&&(t=!0);t&&(T.pendingBuild||I(),T.scheduleBuild(()=>{L(),P()}))}function z(e,t){R([{index:e,size:t}])}function B(t,n){return j?()=>{}:e.observeResize(X.signal,n,e=>{z(t,a?e.contentRect.width:e.contentRect.height)})}function V(t){let n=!1,r=!1;if(t.count!==void 0){let r=e.toNonNegativeInt(t.count);r!==s&&(s=r,T.setCount(s),n=!0)}if(Object.hasOwn(t,`estimateSize`)&&(c=e.resolveEstimateFn(t.estimateSize,36),S.clear(),n=!0),t.gap!==void 0){let r=e.toNonNegativeInt(t.gap);r!==l&&(l=r,T.setGap(l),n=!0)}if(Object.hasOwn(t,`getItemKey`)){let e=t.getItemKey??o;e!==u&&(u=e,S.clear(),n=!0)}if(Object.hasOwn(t,`measurementCache`)&&t.measurementCache!==void 0&&(S=t.measurementCache,n=!0),t.overscan!==void 0){let n=e.normalizeOverscan(t.overscan,3);(n.start!==d.start||n.end!==d.end)&&(d=n,r=!0)}if(Object.hasOwn(t,`sticky`)){let e=t.sticky??null;e!==f&&(f=e,T.resetDedup()),r=!0}n&&(I(),T.rebuild(!0),L()),(r||n)&&P()}function H(){j||(S.clear(),T.rebuild(!0),P())}function U(){j||(T.rebuild(!0),P())}function W(t){if(j)return;let n=e.toNonNegativeInt(t);if(n===0)return;s+=n,T.setCount(s),T.rebuild(!0);let r=T.startAt(n),i=M(O+r);$.writeOffset(i,`auto`),O=i,P()}function G(e){j||V(e)}function K(e,t={}){if(j||s<=0)return;let n=Math.max(0,Math.min(Number.isFinite(e)?Math.floor(e):0,s-1)),i=t.align??`auto`,a=t.behavior??`auto`,o=T.startAt(n),c=T.sizeAt(n),l=o+c,u;if(i===`start`)u=o;else if(i===`end`)u=l-k;else if(i===`center`)u=o-(k-c)/2;else{let e=O,n=e+k;if(o>=e&&l<=n){t.onComplete&&queueMicrotask(t.onComplete);return}u=o<e?o:l-k}$.writeOffset(M(u),a),t.onComplete&&(a===`smooth`&&`onscrollend`in r?r.addEventListener(`scrollend`,t.onComplete,{once:!0}):queueMicrotask(t.onComplete))}function q(e,t={}){j||$.writeOffset(M(e),t.behavior??`auto`)}function J(e={}){q(0,e)}function Y(e={}){q(Math.max(0,T.totalSize-k),e)}function ee(e=0){return T.totalSize-(O+k)<=e}let X=new AbortController;function Z(){j||(j=!0,X.abort(),v!==null&&(clearTimeout(v),v=null),y&&r.removeEventListener(`scrollend`,b),Q.detach())}function te(){O=M($.readOffset()),x(),P()}function ne(){k=$.readViewportSize(),P()}let Q=t.createScrollAdapter(r,te,ne),$=a?Q.x:Q.y;return y&&r.addEventListener(`scrollend`,b,{passive:!0}),T.rebuild(!0),i.initialOffset!==void 0&&$.writeOffset(M(i.initialOffset),`auto`),O=M($.readOffset()),k=$.readViewportSize(),P(),{get count(){return s},get disposalSignal(){return X.signal},dispose:Z,get disposed(){return j},invalidate:H,isAtEnd:ee,get isScrolling(){return _},get items(){return C},measure:z,measureBatch:R,measureEl:B,prepend:W,refresh:U,get scrollOffset(){return O},scrollToBottom:Y,scrollToIndex:K,scrollToOffset:q,scrollToTop:J,get stickyItems(){return w},[Symbol.dispose]:Z,get totalSize(){return T.totalSize},update:G}}exports.DEFAULT_ESTIMATE_SIZE=e.DEFAULT_ESTIMATE_SIZE,exports.DEFAULT_OVERSCAN=e.DEFAULT_OVERSCAN,exports.createMeasurementCache=e.createMeasurementCache,exports.createVirtualizer=r;
1
+ const e=require("./_utils.cjs"),t=require("./_adapter.cjs"),n=require("./_axis1d.cjs");function r(r,i){let a=!!i.horizontal,o=e=>e,s=e.toNonNegativeInt(i.count),c=e.resolveEstimateFn(i.estimateSize,36),l=e.toNonNegativeInt(i.gap??0),u=i.getItemKey??o,d=e.normalizeOverscan(i.overscan,3),f=i.sticky??null,p=i.onChange,m=i.onScrollEnd,h=i.onScrollingChange,g=typeof i.scrollEndDelay==`number`&&i.scrollEndDelay>=0?i.scrollEndDelay:150,_=!1,v=null,y=`onscrollend`in r;function b(){_&&(_=!1,h?.(!1),m?.(O))}function x(){_||(_=!0,h?.(!0)),y||(v!==null&&clearTimeout(v),v=setTimeout(b,g))}let S=i.measurementCache??new Map,C=[],w=[],T=n.createAxis1D(s,e=>S.get(u(e))??c(e),l),E=-1,D=0,O=0,k=0,A=-1,j=!1;function M(e){let t=Number.isFinite(e)?e:0,n=Math.max(0,T.totalSize-k);return Math.min(n,Math.max(0,t))}function N(){if(!f||s===0||O<=0)return[];let e=-1,t=0,n=s-1;for(;t<=n;){let r=t+n>>1;T.startAt(r)<O?(e=r,t=r+1):n=r-1}if(e<0)return[];let r=-1;for(let t=e;t>=0;t--)if(f(t)){r=t;break}if(r===-1)return[];let i=T.sizeAt(r),a=1/0;for(let e=r+1;e<s;e++)if(f(e)){a=T.startAt(e);break}let o=Math.min(O,a-i);return[{end:o+i,index:r,size:i,start:o}]}function P(){if(j)return;if(s===0||k<=0){(T.prevStart!==-1||T.prevTotalSize!==T.totalSize)&&(T.commitDedup(-1,-1),C=[],w=[],p&&p({items:C,stickyItems:w,totalSize:T.totalSize}));return}let{renderEnd:e,renderStart:t}=T.computeRange(O,O+k,d.start,d.end),n=f!==null&&O!==A;if(T.isDedupSame(t,e)&&!n)return;T.commitDedup(t,e),A=O;let r=[];for(let n=t;n<=e;n++)r.push(T.itemAt(n));C=r,w=f?N():[],p&&p({items:C,stickyItems:w,totalSize:T.totalSize})}function F(t,n){if(!Number.isFinite(t))return!1;let r=Math.floor(t);if(r<0||r>=s)return!1;let i=e.toPositiveNumber(n,-1);if(i<=0)return!1;let a=u(r);return S.get(a)!==i&&(S.set(a,i),T.markChanged(r),!0)}function I(){if(O<=0||s===0){E=-1;return}E=T.findFirst(O),D=O-T.startAt(E)}function L(){if(E<0)return;let e=T.startAt(E)+D-O;if(E=-1,Math.abs(e)>=1){let t=M(O+e);$.writeOffset(t,`auto`),O=t}}function R(e){if(j)return;let t=!1;for(let{index:n,size:r}of e)F(n,r)&&(t=!0);t&&(T.pendingBuild||I(),T.scheduleBuild(()=>{L(),P()}))}function z(e,t){R([{index:e,size:t}])}function B(t,n){return j?()=>{}:e.observeResize(X.signal,n,e=>{z(t,a?e.contentRect.width:e.contentRect.height)})}function V(t){let n=!1,r=!1;if(t.count!==void 0){let r=e.toNonNegativeInt(t.count);r!==s&&(s=r,T.setCount(s),n=!0)}if(Object.hasOwn(t,`estimateSize`)&&(c=e.resolveEstimateFn(t.estimateSize,36),S.clear(),n=!0),t.gap!==void 0){let r=e.toNonNegativeInt(t.gap);r!==l&&(l=r,T.setGap(l),n=!0)}if(Object.hasOwn(t,`getItemKey`)){let e=t.getItemKey??o;e!==u&&(u=e,S.clear(),n=!0)}if(Object.hasOwn(t,`measurementCache`)&&t.measurementCache!==void 0&&(S=t.measurementCache,n=!0),t.overscan!==void 0){let n=e.normalizeOverscan(t.overscan,3);(n.start!==d.start||n.end!==d.end)&&(d=n,r=!0)}if(Object.hasOwn(t,`sticky`)){let e=t.sticky??null;e!==f&&(f=e,T.resetDedup()),r=!0}n&&(I(),T.rebuild(!0),L()),(r||n)&&P()}function H(){j||(S.clear(),T.rebuild(!0),P())}function U(){j||(T.rebuild(!0),P())}function W(t){if(j)return;let n=e.toNonNegativeInt(t);if(n===0)return;s+=n,T.setCount(s),T.rebuild(!0);let r=T.startAt(n),i=M(O+r);$.writeOffset(i,`auto`),O=i,P()}function G(e){j||V(e)}function K(e,t={}){if(j||s<=0)return;let n=Math.max(0,Math.min(Number.isFinite(e)?Math.floor(e):0,s-1)),i=t.align??`auto`,a=t.behavior??`auto`,o=T.startAt(n),c=T.sizeAt(n),l=o+c,u;if(i===`start`)u=o;else if(i===`end`)u=l-k;else if(i===`center`)u=o-(k-c)/2;else{let e=O,n=e+k;if(o>=e&&l<=n){t.onComplete&&queueMicrotask(t.onComplete);return}u=o<e?o:l-k}$.writeOffset(M(u),a),t.onComplete&&(a===`smooth`&&`onscrollend`in r?r.addEventListener(`scrollend`,t.onComplete,{once:!0}):queueMicrotask(t.onComplete))}function q(e,t={}){j||$.writeOffset(M(e),t.behavior??`auto`)}function J(e={}){q(0,e)}function Y(e={}){q(Math.max(0,T.totalSize-k),e)}function ee(e=0){return T.totalSize-(O+k)<=e}let X=new AbortController;function Z(){j||(j=!0,X.abort(),v!==null&&(clearTimeout(v),v=null),y&&r.removeEventListener(`scrollend`,b),Q.detach())}function te(){O=M($.readOffset()),x(),P()}function ne(){k=$.readViewportSize(),P()}let Q=t.createScrollAdapter(r,te,ne),$=a?Q.x:Q.y;return y&&r.addEventListener(`scrollend`,b,{passive:!0}),T.rebuild(!0),i.initialOffset!==void 0&&$.writeOffset(M(i.initialOffset),`auto`),O=M($.readOffset()),k=$.readViewportSize(),P(),{get count(){return s},get disposalSignal(){return X.signal},dispose:Z,get disposed(){return j},invalidate:H,isAtEnd:ee,get isScrolling(){return _},get items(){return C},measure:z,measureBatch:R,measureEl:B,prepend:W,refresh:U,get scrollOffset(){return O},scrollToBottom:Y,scrollToIndex:K,scrollToOffset:q,scrollToTop:J,get stickyItems(){return w},[Symbol.dispose]:Z,get totalSize(){return T.totalSize},update:G}}exports.DEFAULT_ESTIMATE_SIZE=e.DEFAULT_ESTIMATE_SIZE,exports.DEFAULT_OVERSCAN=e.DEFAULT_OVERSCAN,exports.createMeasurementCache=e.createMeasurementCache,exports.createVirtualizer=r;
2
2
  //# sourceMappingURL=virtualizer.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"virtualizer.cjs","names":[],"sources":["../src/virtualizer.ts"],"sourcesContent":["import { createScrollAdapter } from './_adapter';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n normalizeOverscan,\n observeResize,\n type Overscan,\n resolveEstimateFn,\n type ScrollTarget,\n toNonNegativeInt,\n toPositiveNumber,\n type VirtualKey,\n} from './_utils';\n\nexport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n type ScrollTarget,\n type VirtualItem,\n type VirtualKey,\n};\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualizerState {\n readonly items: VirtualItem[];\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n}\n\nexport interface VirtualizerOptions {\n count: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: (index: number) => VirtualKey;\n horizontal?: boolean;\n initialOffset?: number;\n /** External measurement cache for scroll restoration or SSR pre-measurement. */\n measurementCache?: MeasurementCache;\n /**\n * Called after every render cycle with the new state.\n * **Fixed at construction** — passing `onChange` to `update()` has no effect.\n * To replace the callback, dispose and re-create the virtualizer.\n */\n onChange?: (state: VirtualizerState) => void;\n /**\n * Called when scrolling has settled (no scroll events for `scrollEndDelay` ms,\n * or immediately on the native `scrollend` event when available).\n * Fixed at construction.\n */\n onScrollEnd?: (offset: number) => void;\n /**\n * Called when the scrolling state changes.\n * Fixed at construction.\n */\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),\n * `initialOffset` (one-time bootstrap value), and `onChange` (fixed at\n * construction — see `VirtualizerOptions.onChange`).\n */\nexport interface VirtualizerUpdateOptions {\n count?: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: ((index: number) => VirtualKey) | undefined;\n /** Replace the active measurement cache. Existing entries in the new cache are used immediately on the next rebuild. */\n measurementCache?: MeasurementCache;\n overscan?: Overscan;\n sticky?: ((index: number) => boolean) | undefined;\n}\n\nexport interface ScrollToIndexOptions {\n align?: 'auto' | 'center' | 'end' | 'start';\n behavior?: ScrollBehavior;\n /** Called when the scroll animation completes (instant scrolls: next microtask). */\n onComplete?: () => void;\n}\n\nexport interface Virtualizer {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n readonly disposed: boolean;\n /** Currently rendered items. Always populated. */\n readonly items: VirtualItem[];\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n readonly scrollOffset: number;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n dispose: () => void;\n invalidate: () => void;\n /**\n * `true` when scrolled to (or within `threshold` px of) the end — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Check this **before** appending\n * items to decide whether to auto-follow them (e.g. a chat \"stick to bottom\" pattern) —\n * `createDomVirtualList`'s `stickToBottom` option does exactly this. Default `threshold`: `0`.\n */\n isAtEnd: (threshold?: number) => boolean;\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` and auto-measure `index` on resize. Returns a disconnect fn. */\n measureEl: (index: number, el: HTMLElement) => () => void;\n /** Prepend `additionalCount` items at the top while keeping the viewport visually stable. */\n prepend: (additionalCount: number) => void;\n /** Rebuild the full offset table and re-emit (use when item sizes may have changed). */\n refresh: () => void;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n update: (next: VirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createVirtualizer(target: ScrollTarget, options: VirtualizerOptions): Virtualizer {\n const horizontal = !!options.horizontal;\n const defaultItemKey = (index: number): VirtualKey => index;\n\n let count = toNonNegativeInt(options.count);\n let estimateFn = resolveEstimateFn(options.estimateSize, DEFAULT_ESTIMATE_SIZE);\n let gap = toNonNegativeInt(options.gap ?? 0);\n let getItemKey = options.getItemKey ?? defaultItemKey;\n let overscan = normalizeOverscan(options.overscan, DEFAULT_OVERSCAN);\n let stickyFn: ((index: number) => boolean) | null = options.sticky ?? null;\n\n // Callbacks fixed at construction — not swappable via update().\n const onChange = options.onChange;\n const onScrollEnd = options.onScrollEnd;\n const onScrollingChange = options.onScrollingChange;\n const scrollEndDelay =\n typeof options.scrollEndDelay === 'number' && options.scrollEndDelay >= 0 ? options.scrollEndDelay : 150;\n\n let isScrolling = false;\n let scrollEndTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Detect native scrollend support (Chrome 114+, Firefox 109+)\n const hasNativeScrollEnd = 'onscrollend' in (target as EventTarget);\n\n function notifyScrollEnd(): void {\n if (!isScrolling) return;\n\n isScrolling = false;\n onScrollingChange?.(false);\n onScrollEnd?.(scrollOffset);\n }\n\n function handleScrollStart(): void {\n if (!isScrolling) {\n isScrolling = true;\n onScrollingChange?.(true);\n }\n\n if (!hasNativeScrollEnd) {\n if (scrollEndTimer !== null) clearTimeout(scrollEndTimer);\n\n scrollEndTimer = setTimeout(notifyScrollEnd, scrollEndDelay);\n }\n }\n\n // External or internal measurement cache.\n let measuredByKey: MeasurementCache = options.measurementCache ?? new Map();\n\n let items: VirtualItem[] = [];\n let stickyItems: VirtualItem[] = [];\n\n // ─── Axis setup ──────────────────────────────────────────────────────────────\n // The axis1d primitive owns offset management. The sizeAt closure captures\n // the live `measuredByKey`, `getItemKey`, and `estimateFn` by reference so\n // updates to those let-variables are picked up automatically.\n\n const ax = createAxis1D(count, (i) => measuredByKey.get(getItemKey(i)) ?? estimateFn(i), gap);\n\n // F6: Scroll anchor — prevents viewport jump when items above fold are measured.\n let anchorIndex = -1;\n let anchorDistanceFromTop = 0;\n\n let scrollOffset = 0;\n let viewportSize = 0;\n let prevScrollOffset = -1; // for sticky dedup\n let destroyed = false;\n\n // ─── Scroll clamping ─────────────────────────────────────────────────────────\n\n function clampScrollOffset(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, ax.totalSize - viewportSize);\n\n return Math.min(maxOffset, Math.max(0, safe));\n }\n\n // ─── Sticky items ─────────────────────────────────────────────────────────────\n\n function computeStickyItems(): VirtualItem[] {\n if (!stickyFn || count === 0 || scrollOffset <= 0) return [];\n\n let lastAbove = -1;\n let lo = 0;\n let hi = count - 1;\n\n while (lo <= hi) {\n const mid = (lo + hi) >> 1;\n\n if (ax.startAt(mid) < scrollOffset) {\n lastAbove = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n\n if (lastAbove < 0) return [];\n\n let activeIdx = -1;\n\n for (let i = lastAbove; i >= 0; i--) {\n if (stickyFn(i)) {\n activeIdx = i;\n break;\n }\n }\n\n if (activeIdx === -1) return [];\n\n const activeSize = ax.sizeAt(activeIdx);\n let nextStickyStart = Infinity;\n\n for (let i = activeIdx + 1; i < count; i++) {\n if (stickyFn(i)) {\n nextStickyStart = ax.startAt(i);\n break;\n }\n }\n\n const pinnedStart = Math.min(scrollOffset, nextStickyStart - activeSize);\n\n return [{ end: pinnedStart + activeSize, index: activeIdx, size: activeSize, start: pinnedStart }];\n }\n\n // ─── Compute and emit ─────────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (destroyed) return;\n\n if (count === 0 || viewportSize <= 0) {\n if (ax.prevStart !== -1 || ax.prevTotalSize !== ax.totalSize) {\n // Commit with sentinel renderStart=-1 so prevTotalSize is captured as the\n // current totalSize. Using resetDedup() would set prevTotalSize=-1, causing\n // the guard to fire on every subsequent scroll/resize (Bug 2).\n ax.commitDedup(-1, -1);\n\n items = [];\n stickyItems = [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n return;\n }\n\n const { renderEnd, renderStart } = ax.computeRange(\n scrollOffset,\n scrollOffset + viewportSize,\n overscan.start,\n overscan.end,\n );\n\n // Sticky recomputes on every scroll pixel — bypass dedup in that case.\n const stickyScrolled = stickyFn !== null && scrollOffset !== prevScrollOffset;\n const rangeChanged = !ax.isDedupSame(renderStart, renderEnd);\n\n if (!rangeChanged && !stickyScrolled) return;\n\n ax.commitDedup(renderStart, renderEnd);\n prevScrollOffset = scrollOffset;\n\n const nextItems: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n nextItems.push(ax.itemAt(i));\n }\n\n items = nextItems;\n stickyItems = stickyFn ? computeStickyItems() : [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n // ─── Measurement (R6: measure delegates to measureBatch) ─────────────────────\n\n function recordMeasurement(index: number, size: number): boolean {\n if (!Number.isFinite(index)) return false;\n\n const i = Math.floor(index);\n\n if (i < 0 || i >= count) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n const key = getItemKey(i);\n\n if (measuredByKey.get(key) === s) return false;\n\n measuredByKey.set(key, s);\n ax.markChanged(i);\n\n return true;\n }\n\n // F6: Record a scroll anchor before a batch of measurements arrives.\n function recordScrollAnchor(): void {\n if (scrollOffset <= 0 || count === 0) {\n anchorIndex = -1;\n\n return;\n }\n\n anchorIndex = ax.findFirst(scrollOffset);\n anchorDistanceFromTop = scrollOffset - ax.startAt(anchorIndex);\n }\n\n // F6: After rebuild, restore the anchor to prevent viewport jump.\n function restoreScrollAnchor(): void {\n if (anchorIndex < 0) return;\n\n const expectedOffset = ax.startAt(anchorIndex) + anchorDistanceFromTop;\n const delta = expectedOffset - scrollOffset;\n\n anchorIndex = -1;\n\n if (Math.abs(delta) >= 1) {\n const newOffset = clampScrollOffset(scrollOffset + delta);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n }\n }\n\n function measureBatch(entries: Array<{ index: number; size: number }>): void {\n if (destroyed) return;\n\n let changed = false;\n\n for (const { index, size } of entries) {\n if (recordMeasurement(index, size)) changed = true;\n }\n\n if (changed) {\n // Record anchor before the first pending build is scheduled (F6).\n if (!ax.pendingBuild) recordScrollAnchor();\n\n ax.scheduleBuild(() => {\n restoreScrollAnchor();\n computeVisible();\n });\n }\n }\n\n /** R6: Single-item convenience — delegates to measureBatch. */\n function measure(index: number, size: number): void {\n measureBatch([{ index, size }]);\n }\n\n /** Attach a ResizeObserver to `el`. Returns a disconnect function. */\n function measureEl(index: number, el: HTMLElement): () => void {\n if (destroyed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => {\n measure(index, horizontal ? entry.contentRect.width : entry.contentRect.height);\n });\n }\n\n // ─── Option updates ───────────────────────────────────────────────────────────\n\n function applyOptions(next: VirtualizerUpdateOptions): void {\n let needsRebuild = false;\n let needsCompute = false;\n\n if (next.count !== undefined) {\n const nextCount = toNonNegativeInt(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 = toNonNegativeInt(next.gap);\n\n if (nextGap !== gap) {\n gap = nextGap;\n ax.setGap(gap);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'getItemKey')) {\n const nextKey = next.getItemKey ?? defaultItemKey;\n\n if (nextKey !== getItemKey) {\n getItemKey = nextKey;\n measuredByKey.clear();\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'measurementCache') && next.measurementCache !== undefined) {\n measuredByKey = next.measurementCache;\n needsRebuild = true;\n }\n\n if (next.overscan !== undefined) {\n const nextOverscan = normalizeOverscan(next.overscan, DEFAULT_OVERSCAN);\n\n if (nextOverscan.start !== overscan.start || nextOverscan.end !== overscan.end) {\n overscan = nextOverscan;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'sticky')) {\n const nextStickyFn = next.sticky ?? null;\n\n if (nextStickyFn !== stickyFn) {\n stickyFn = nextStickyFn;\n ax.resetDedup(); // force re-emit to flush updated stickyItems\n }\n\n needsCompute = true;\n }\n\n if (needsRebuild) {\n recordScrollAnchor();\n ax.rebuild(true);\n restoreScrollAnchor();\n }\n\n if (needsCompute || needsRebuild) computeVisible();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────────\n\n function invalidate(): void {\n if (destroyed) return;\n\n measuredByKey.clear();\n ax.rebuild(true);\n computeVisible();\n }\n\n /** Full O(n) offset rebuild followed by re-emit. Also used internally for data-only re-emit when sizes unchanged. */\n function refresh(): void {\n if (destroyed) return;\n\n ax.rebuild(true);\n computeVisible();\n }\n\n function prepend(additionalCount: number): void {\n if (destroyed) return;\n\n const n = toNonNegativeInt(additionalCount);\n\n if (n === 0) return;\n\n count += n;\n ax.setCount(count);\n ax.rebuild(true);\n\n // Adjust scroll to keep the same content visible (F6-like stable prepend).\n const prependedHeight = ax.startAt(n);\n const newOffset = clampScrollOffset(scrollOffset + prependedHeight);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n\n computeVisible();\n }\n\n function update(next: VirtualizerUpdateOptions): void {\n if (destroyed) return;\n\n applyOptions(next);\n }\n\n function scrollToIndex(index: number, opts: ScrollToIndexOptions = {}): void {\n if (destroyed || count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, count - 1));\n const align = opts.align ?? 'auto';\n const behavior = opts.behavior ?? 'auto';\n const itemStart = ax.startAt(clampedIndex);\n const itemSize = ax.sizeAt(clampedIndex);\n const itemEnd = itemStart + itemSize;\n\n let targetOffset: number;\n\n if (align === 'start') {\n targetOffset = itemStart;\n } else if (align === 'end') {\n targetOffset = itemEnd - viewportSize;\n } else if (align === 'center') {\n targetOffset = itemStart - (viewportSize - itemSize) / 2;\n } else {\n const visibleStart = scrollOffset;\n const visibleEnd = visibleStart + viewportSize;\n\n if (itemStart >= visibleStart && itemEnd <= visibleEnd) {\n if (opts.onComplete) queueMicrotask(opts.onComplete);\n\n return;\n }\n\n targetOffset = itemStart < visibleStart ? itemStart : itemEnd - viewportSize;\n }\n\n domAxis.writeOffset(clampScrollOffset(targetOffset), behavior);\n\n if (opts.onComplete) {\n if (behavior === 'smooth' && 'onscrollend' in target) {\n (target as EventTarget).addEventListener('scrollend', opts.onComplete, { once: true });\n } else {\n queueMicrotask(opts.onComplete);\n }\n }\n }\n\n function scrollToOffset(offset: number, opts: { behavior?: ScrollBehavior } = {}): void {\n if (destroyed) return;\n\n domAxis.writeOffset(clampScrollOffset(offset), opts.behavior ?? 'auto');\n }\n\n function scrollToTop(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(0, opts);\n }\n\n function scrollToBottom(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(Math.max(0, ax.totalSize - viewportSize), opts);\n }\n\n function isAtEnd(threshold = 0): boolean {\n return ax.totalSize - (scrollOffset + viewportSize) <= threshold;\n }\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (destroyed) return;\n\n destroyed = true;\n ac.abort();\n\n if (scrollEndTimer !== null) {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = null;\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).removeEventListener('scrollend', notifyScrollEnd);\n }\n\n adapter.detach();\n }\n\n // ─── Bootstrap ───────────────────────────────────────────────────────────────\n\n function handleScroll(): void {\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n handleScrollStart();\n computeVisible();\n }\n\n function handleResize(): void {\n viewportSize = domAxis.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n const domAxis = horizontal ? adapter.x : adapter.y;\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).addEventListener('scrollend', notifyScrollEnd, { passive: true });\n }\n\n ax.rebuild(true);\n\n if (options.initialOffset !== undefined) {\n domAxis.writeOffset(clampScrollOffset(options.initialOffset), 'auto');\n }\n\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n viewportSize = domAxis.readViewportSize();\n\n computeVisible();\n\n return {\n get count() {\n return count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return destroyed;\n },\n invalidate,\n isAtEnd,\n get isScrolling() {\n return isScrolling;\n },\n get items() {\n return items;\n },\n measure,\n measureBatch,\n measureEl,\n prepend,\n refresh,\n get scrollOffset() {\n return scrollOffset;\n },\n scrollToBottom,\n scrollToIndex,\n scrollToOffset,\n scrollToTop,\n get stickyItems() {\n return stickyItems;\n },\n [Symbol.dispose]: _dispose,\n get totalSize() {\n return ax.totalSize;\n },\n update,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"virtualizer.cjs","names":[],"sources":["../src/virtualizer.ts"],"sourcesContent":["import { createScrollAdapter } from './_adapter';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n normalizeOverscan,\n observeResize,\n type Overscan,\n resolveEstimateFn,\n type ScrollTarget,\n toNonNegativeInt,\n toPositiveNumber,\n type VirtualKey,\n} from './_utils';\n\nexport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n type ScrollTarget,\n type VirtualItem,\n type VirtualKey,\n};\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualizerState {\n readonly items: VirtualItem[];\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n}\n\nexport interface VirtualizerOptions {\n count: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: (index: number) => VirtualKey;\n horizontal?: boolean;\n initialOffset?: number;\n /** External measurement cache for scroll restoration or SSR pre-measurement. */\n measurementCache?: MeasurementCache;\n /**\n * Called after every render cycle with the new state.\n * **Fixed at construction** — passing `onChange` to `update()` has no effect.\n * To replace the callback, dispose and re-create the virtualizer.\n */\n onChange?: (state: VirtualizerState) => void;\n /**\n * Called when scrolling has settled (no scroll events for `scrollEndDelay` ms,\n * or immediately on the native `scrollend` event when available).\n * Fixed at construction.\n */\n onScrollEnd?: (offset: number) => void;\n /**\n * Called when the scrolling state changes.\n * Fixed at construction.\n */\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),\n * `initialOffset` (one-time bootstrap value), and `onChange` (fixed at\n * construction — see `VirtualizerOptions.onChange`).\n */\nexport interface VirtualizerUpdateOptions {\n count?: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: ((index: number) => VirtualKey) | undefined;\n /** Replace the active measurement cache. Existing entries in the new cache are used immediately on the next rebuild. */\n measurementCache?: MeasurementCache;\n overscan?: Overscan;\n sticky?: ((index: number) => boolean) | undefined;\n}\n\nexport interface ScrollToIndexOptions {\n align?: 'auto' | 'center' | 'end' | 'start';\n behavior?: ScrollBehavior;\n /** Called when the scroll animation completes (instant scrolls: next microtask). */\n onComplete?: () => void;\n}\n\nexport interface Virtualizer {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n readonly disposed: boolean;\n /** Currently rendered items. Always populated. */\n readonly items: VirtualItem[];\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n readonly scrollOffset: number;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n dispose: () => void;\n invalidate: () => void;\n /**\n * `true` when scrolled to (or within `threshold` px of) the end — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Check this **before** appending\n * items to decide whether to auto-follow them (e.g. a chat \"stick to bottom\" pattern) —\n * `createDomVirtualList`'s `stickToBottom` option does exactly this. Default `threshold`: `0`.\n */\n isAtEnd: (threshold?: number) => boolean;\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` and auto-measure `index` on resize. Returns a disconnect fn. */\n measureEl: (index: number, el: HTMLElement) => () => void;\n /** Prepend `additionalCount` items at the top while keeping the viewport visually stable. */\n prepend: (additionalCount: number) => void;\n /** Rebuild the full offset table and re-emit (use when item sizes may have changed). */\n refresh: () => void;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n update: (next: VirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createVirtualizer(target: ScrollTarget, options: VirtualizerOptions): Virtualizer {\n const horizontal = !!options.horizontal;\n const defaultItemKey = (index: number): VirtualKey => index;\n\n let count = toNonNegativeInt(options.count);\n let estimateFn = resolveEstimateFn(options.estimateSize, DEFAULT_ESTIMATE_SIZE);\n let gap = toNonNegativeInt(options.gap ?? 0);\n let getItemKey = options.getItemKey ?? defaultItemKey;\n let overscan = normalizeOverscan(options.overscan, DEFAULT_OVERSCAN);\n let stickyFn: ((index: number) => boolean) | null = options.sticky ?? null;\n\n // Callbacks fixed at construction — not swappable via update().\n const onChange = options.onChange;\n const onScrollEnd = options.onScrollEnd;\n const onScrollingChange = options.onScrollingChange;\n const scrollEndDelay =\n typeof options.scrollEndDelay === 'number' && options.scrollEndDelay >= 0 ? options.scrollEndDelay : 150;\n\n let isScrolling = false;\n let scrollEndTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Detect native scrollend support (Chrome 114+, Firefox 109+)\n const hasNativeScrollEnd = 'onscrollend' in (target as EventTarget);\n\n function notifyScrollEnd(): void {\n if (!isScrolling) return;\n\n isScrolling = false;\n onScrollingChange?.(false);\n onScrollEnd?.(scrollOffset);\n }\n\n function handleScrollStart(): void {\n if (!isScrolling) {\n isScrolling = true;\n onScrollingChange?.(true);\n }\n\n if (!hasNativeScrollEnd) {\n if (scrollEndTimer !== null) clearTimeout(scrollEndTimer);\n\n scrollEndTimer = setTimeout(notifyScrollEnd, scrollEndDelay);\n }\n }\n\n // External or internal measurement cache.\n let measuredByKey: MeasurementCache = options.measurementCache ?? new Map();\n\n let items: VirtualItem[] = [];\n let stickyItems: VirtualItem[] = [];\n\n // ─── Axis setup ──────────────────────────────────────────────────────────────\n // The axis1d primitive owns offset management. The sizeAt closure captures\n // the live `measuredByKey`, `getItemKey`, and `estimateFn` by reference so\n // updates to those let-variables are picked up automatically.\n\n const ax = createAxis1D(count, (i) => measuredByKey.get(getItemKey(i)) ?? estimateFn(i), gap);\n\n // F6: Scroll anchor — prevents viewport jump when items above fold are measured.\n let anchorIndex = -1;\n let anchorDistanceFromTop = 0;\n\n let scrollOffset = 0;\n let viewportSize = 0;\n let prevScrollOffset = -1; // for sticky dedup\n let destroyed = false;\n\n // ─── Scroll clamping ─────────────────────────────────────────────────────────\n\n function clampScrollOffset(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, ax.totalSize - viewportSize);\n\n return Math.min(maxOffset, Math.max(0, safe));\n }\n\n // ─── Sticky items ─────────────────────────────────────────────────────────────\n\n function computeStickyItems(): VirtualItem[] {\n if (!stickyFn || count === 0 || scrollOffset <= 0) return [];\n\n let lastAbove = -1;\n let lo = 0;\n let hi = count - 1;\n\n while (lo <= hi) {\n const mid = (lo + hi) >> 1;\n\n if (ax.startAt(mid) < scrollOffset) {\n lastAbove = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n\n if (lastAbove < 0) return [];\n\n let activeIdx = -1;\n\n for (let i = lastAbove; i >= 0; i--) {\n if (stickyFn(i)) {\n activeIdx = i;\n break;\n }\n }\n\n if (activeIdx === -1) return [];\n\n const activeSize = ax.sizeAt(activeIdx);\n let nextStickyStart = Infinity;\n\n for (let i = activeIdx + 1; i < count; i++) {\n if (stickyFn(i)) {\n nextStickyStart = ax.startAt(i);\n break;\n }\n }\n\n const pinnedStart = Math.min(scrollOffset, nextStickyStart - activeSize);\n\n return [{ end: pinnedStart + activeSize, index: activeIdx, size: activeSize, start: pinnedStart }];\n }\n\n // ─── Compute and emit ─────────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (destroyed) return;\n\n if (count === 0 || viewportSize <= 0) {\n if (ax.prevStart !== -1 || ax.prevTotalSize !== ax.totalSize) {\n // Commit with sentinel renderStart=-1 so prevTotalSize is captured as the\n // current totalSize. Using resetDedup() would set prevTotalSize=-1, causing\n // the guard to fire on every subsequent scroll/resize (Bug 2).\n ax.commitDedup(-1, -1);\n\n items = [];\n stickyItems = [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n return;\n }\n\n const { renderEnd, renderStart } = ax.computeRange(\n scrollOffset,\n scrollOffset + viewportSize,\n overscan.start,\n overscan.end,\n );\n\n // Sticky recomputes on every scroll pixel — bypass dedup in that case.\n const stickyScrolled = stickyFn !== null && scrollOffset !== prevScrollOffset;\n const rangeChanged = !ax.isDedupSame(renderStart, renderEnd);\n\n if (!rangeChanged && !stickyScrolled) return;\n\n ax.commitDedup(renderStart, renderEnd);\n prevScrollOffset = scrollOffset;\n\n const nextItems: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n nextItems.push(ax.itemAt(i));\n }\n\n items = nextItems;\n stickyItems = stickyFn ? computeStickyItems() : [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n // ─── Measurement (R6: measure delegates to measureBatch) ─────────────────────\n\n function recordMeasurement(index: number, size: number): boolean {\n if (!Number.isFinite(index)) return false;\n\n const i = Math.floor(index);\n\n if (i < 0 || i >= count) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n const key = getItemKey(i);\n\n if (measuredByKey.get(key) === s) return false;\n\n measuredByKey.set(key, s);\n ax.markChanged(i);\n\n return true;\n }\n\n // F6: Record a scroll anchor before a batch of measurements arrives.\n function recordScrollAnchor(): void {\n if (scrollOffset <= 0 || count === 0) {\n anchorIndex = -1;\n\n return;\n }\n\n anchorIndex = ax.findFirst(scrollOffset);\n anchorDistanceFromTop = scrollOffset - ax.startAt(anchorIndex);\n }\n\n // F6: After rebuild, restore the anchor to prevent viewport jump.\n function restoreScrollAnchor(): void {\n if (anchorIndex < 0) return;\n\n const expectedOffset = ax.startAt(anchorIndex) + anchorDistanceFromTop;\n const delta = expectedOffset - scrollOffset;\n\n anchorIndex = -1;\n\n if (Math.abs(delta) >= 1) {\n const newOffset = clampScrollOffset(scrollOffset + delta);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n }\n }\n\n function measureBatch(entries: Array<{ index: number; size: number }>): void {\n if (destroyed) return;\n\n let changed = false;\n\n for (const { index, size } of entries) {\n if (recordMeasurement(index, size)) changed = true;\n }\n\n if (changed) {\n // Record anchor before the first pending build is scheduled (F6).\n if (!ax.pendingBuild) recordScrollAnchor();\n\n ax.scheduleBuild(() => {\n restoreScrollAnchor();\n computeVisible();\n });\n }\n }\n\n /** R6: Single-item convenience — delegates to measureBatch. */\n function measure(index: number, size: number): void {\n measureBatch([{ index, size }]);\n }\n\n /** Attach a ResizeObserver to `el`. Returns a disconnect function. */\n function measureEl(index: number, el: HTMLElement): () => void {\n if (destroyed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => {\n measure(index, horizontal ? entry.contentRect.width : entry.contentRect.height);\n });\n }\n\n // ─── Option updates ───────────────────────────────────────────────────────────\n\n function applyOptions(next: VirtualizerUpdateOptions): void {\n let needsRebuild = false;\n let needsCompute = false;\n\n if (next.count !== undefined) {\n const nextCount = toNonNegativeInt(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 = toNonNegativeInt(next.gap);\n\n if (nextGap !== gap) {\n gap = nextGap;\n ax.setGap(gap);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'getItemKey')) {\n const nextKey = next.getItemKey ?? defaultItemKey;\n\n if (nextKey !== getItemKey) {\n getItemKey = nextKey;\n measuredByKey.clear();\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'measurementCache') && next.measurementCache !== undefined) {\n measuredByKey = next.measurementCache;\n needsRebuild = true;\n }\n\n if (next.overscan !== undefined) {\n const nextOverscan = normalizeOverscan(next.overscan, DEFAULT_OVERSCAN);\n\n if (nextOverscan.start !== overscan.start || nextOverscan.end !== overscan.end) {\n overscan = nextOverscan;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'sticky')) {\n const nextStickyFn = next.sticky ?? null;\n\n if (nextStickyFn !== stickyFn) {\n stickyFn = nextStickyFn;\n ax.resetDedup(); // force re-emit to flush updated stickyItems\n }\n\n needsCompute = true;\n }\n\n if (needsRebuild) {\n recordScrollAnchor();\n ax.rebuild(true);\n restoreScrollAnchor();\n }\n\n if (needsCompute || needsRebuild) computeVisible();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────────\n\n function invalidate(): void {\n if (destroyed) return;\n\n measuredByKey.clear();\n ax.rebuild(true);\n computeVisible();\n }\n\n /** Full O(n) offset rebuild followed by re-emit. Also used internally for data-only re-emit when sizes unchanged. */\n function refresh(): void {\n if (destroyed) return;\n\n ax.rebuild(true);\n computeVisible();\n }\n\n function prepend(additionalCount: number): void {\n if (destroyed) return;\n\n const n = toNonNegativeInt(additionalCount);\n\n if (n === 0) return;\n\n count += n;\n ax.setCount(count);\n ax.rebuild(true);\n\n // Adjust scroll to keep the same content visible (F6-like stable prepend).\n const prependedHeight = ax.startAt(n);\n const newOffset = clampScrollOffset(scrollOffset + prependedHeight);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n\n computeVisible();\n }\n\n function update(next: VirtualizerUpdateOptions): void {\n if (destroyed) return;\n\n applyOptions(next);\n }\n\n function scrollToIndex(index: number, opts: ScrollToIndexOptions = {}): void {\n if (destroyed || count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, count - 1));\n const align = opts.align ?? 'auto';\n const behavior = opts.behavior ?? 'auto';\n const itemStart = ax.startAt(clampedIndex);\n const itemSize = ax.sizeAt(clampedIndex);\n const itemEnd = itemStart + itemSize;\n\n let targetOffset: number;\n\n if (align === 'start') {\n targetOffset = itemStart;\n } else if (align === 'end') {\n targetOffset = itemEnd - viewportSize;\n } else if (align === 'center') {\n targetOffset = itemStart - (viewportSize - itemSize) / 2;\n } else {\n const visibleStart = scrollOffset;\n const visibleEnd = visibleStart + viewportSize;\n\n if (itemStart >= visibleStart && itemEnd <= visibleEnd) {\n if (opts.onComplete) queueMicrotask(opts.onComplete);\n\n return;\n }\n\n targetOffset = itemStart < visibleStart ? itemStart : itemEnd - viewportSize;\n }\n\n domAxis.writeOffset(clampScrollOffset(targetOffset), behavior);\n\n if (opts.onComplete) {\n if (behavior === 'smooth' && 'onscrollend' in target) {\n (target as EventTarget).addEventListener('scrollend', opts.onComplete, { once: true });\n } else {\n queueMicrotask(opts.onComplete);\n }\n }\n }\n\n function scrollToOffset(offset: number, opts: { behavior?: ScrollBehavior } = {}): void {\n if (destroyed) return;\n\n domAxis.writeOffset(clampScrollOffset(offset), opts.behavior ?? 'auto');\n }\n\n function scrollToTop(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(0, opts);\n }\n\n function scrollToBottom(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(Math.max(0, ax.totalSize - viewportSize), opts);\n }\n\n function isAtEnd(threshold = 0): boolean {\n return ax.totalSize - (scrollOffset + viewportSize) <= threshold;\n }\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (destroyed) return;\n\n destroyed = true;\n ac.abort();\n\n if (scrollEndTimer !== null) {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = null;\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).removeEventListener('scrollend', notifyScrollEnd);\n }\n\n adapter.detach();\n }\n\n // ─── Bootstrap ───────────────────────────────────────────────────────────────\n\n function handleScroll(): void {\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n handleScrollStart();\n computeVisible();\n }\n\n function handleResize(): void {\n viewportSize = domAxis.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n const domAxis = horizontal ? adapter.x : adapter.y;\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).addEventListener('scrollend', notifyScrollEnd, { passive: true });\n }\n\n ax.rebuild(true);\n\n if (options.initialOffset !== undefined) {\n domAxis.writeOffset(clampScrollOffset(options.initialOffset), 'auto');\n }\n\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n viewportSize = domAxis.readViewportSize();\n\n computeVisible();\n\n return {\n get count() {\n return count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return destroyed;\n },\n invalidate,\n isAtEnd,\n get isScrolling() {\n return isScrolling;\n },\n get items() {\n return items;\n },\n measure,\n measureBatch,\n measureEl,\n prepend,\n refresh,\n get scrollOffset() {\n return scrollOffset;\n },\n scrollToBottom,\n scrollToIndex,\n scrollToOffset,\n scrollToTop,\n get stickyItems() {\n return stickyItems;\n },\n [Symbol.dispose]: _dispose,\n get totalSize() {\n return ax.totalSize;\n },\n update,\n };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{DEFAULT_ESTIMATE_SIZE as e,DEFAULT_OVERSCAN as t,createMeasurementCache as n,normalizeOverscan as r,observeResize as i,resolveEstimateFn as a,toNonNegativeInt as o,toPositiveNumber as s}from"./_utils.js";import{createScrollAdapter as c}from"./_adapter.js";import{createAxis1D as l}from"./_axis1d.js";function u(e,t){let n=!!t.horizontal,u=e=>e,d=o(t.count),f=a(t.estimateSize,36),p=o(t.gap??0),m=t.getItemKey??u,h=r(t.overscan,3),g=t.sticky??null,_=t.onChange,v=t.onScrollEnd,y=t.onScrollingChange,ee=typeof t.scrollEndDelay==`number`&&t.scrollEndDelay>=0?t.scrollEndDelay:150,b=!1,x=null,S=`onscrollend`in e;function C(){b&&(b=!1,y?.(!1),v?.(A))}function te(){b||(b=!0,y?.(!0)),S||(x!==null&&clearTimeout(x),x=setTimeout(C,ee))}let w=t.measurementCache??new Map,T=[],E=[],D=l(d,e=>w.get(m(e))??f(e),p),O=-1,k=0,A=0,j=0,M=-1,N=!1;function P(e){let t=Number.isFinite(e)?e:0,n=Math.max(0,D.totalSize-j);return Math.min(n,Math.max(0,t))}function F(){if(!g||d===0||A<=0)return[];let e=-1,t=0,n=d-1;for(;t<=n;){let r=t+n>>1;D.startAt(r)<A?(e=r,t=r+1):n=r-1}if(e<0)return[];let r=-1;for(let t=e;t>=0;t--)if(g(t)){r=t;break}if(r===-1)return[];let i=D.sizeAt(r),a=1/0;for(let e=r+1;e<d;e++)if(g(e)){a=D.startAt(e);break}let o=Math.min(A,a-i);return[{end:o+i,index:r,size:i,start:o}]}function I(){if(N)return;if(d===0||j<=0){(D.prevStart!==-1||D.prevTotalSize!==D.totalSize)&&(D.commitDedup(-1,-1),T=[],E=[],_&&_({items:T,stickyItems:E,totalSize:D.totalSize}));return}let{renderEnd:e,renderStart:t}=D.computeRange(A,A+j,h.start,h.end),n=g!==null&&A!==M;if(D.isDedupSame(t,e)&&!n)return;D.commitDedup(t,e),M=A;let r=[];for(let n=t;n<=e;n++)r.push(D.itemAt(n));T=r,E=g?F():[],_&&_({items:T,stickyItems:E,totalSize:D.totalSize})}function L(e,t){if(!Number.isFinite(e))return!1;let n=Math.floor(e);if(n<0||n>=d)return!1;let r=s(t,-1);if(r<=0)return!1;let i=m(n);return w.get(i)===r?!1:(w.set(i,r),D.markChanged(n),!0)}function R(){if(A<=0||d===0){O=-1;return}O=D.findFirst(A),k=A-D.startAt(O)}function z(){if(O<0)return;let e=D.startAt(O)+k-A;if(O=-1,Math.abs(e)>=1){let t=P(A+e);$.writeOffset(t,`auto`),A=t}}function B(e){if(N)return;let t=!1;for(let{index:n,size:r}of e)L(n,r)&&(t=!0);t&&(D.pendingBuild||R(),D.scheduleBuild(()=>{z(),I()}))}function V(e,t){B([{index:e,size:t}])}function H(e,t){return N?()=>{}:i(X.signal,t,t=>{V(e,n?t.contentRect.width:t.contentRect.height)})}function U(e){let t=!1,n=!1;if(e.count!==void 0){let n=o(e.count);n!==d&&(d=n,D.setCount(d),t=!0)}if(Object.hasOwn(e,`estimateSize`)&&(f=a(e.estimateSize,36),w.clear(),t=!0),e.gap!==void 0){let n=o(e.gap);n!==p&&(p=n,D.setGap(p),t=!0)}if(Object.hasOwn(e,`getItemKey`)){let n=e.getItemKey??u;n!==m&&(m=n,w.clear(),t=!0)}if(Object.hasOwn(e,`measurementCache`)&&e.measurementCache!==void 0&&(w=e.measurementCache,t=!0),e.overscan!==void 0){let t=r(e.overscan,3);(t.start!==h.start||t.end!==h.end)&&(h=t,n=!0)}if(Object.hasOwn(e,`sticky`)){let t=e.sticky??null;t!==g&&(g=t,D.resetDedup()),n=!0}t&&(R(),D.rebuild(!0),z()),(n||t)&&I()}function W(){N||(w.clear(),D.rebuild(!0),I())}function G(){N||(D.rebuild(!0),I())}function K(e){if(N)return;let t=o(e);if(t===0)return;d+=t,D.setCount(d),D.rebuild(!0);let n=D.startAt(t),r=P(A+n);$.writeOffset(r,`auto`),A=r,I()}function q(e){N||U(e)}function J(t,n={}){if(N||d<=0)return;let r=Math.max(0,Math.min(Number.isFinite(t)?Math.floor(t):0,d-1)),i=n.align??`auto`,a=n.behavior??`auto`,o=D.startAt(r),s=D.sizeAt(r),c=o+s,l;if(i===`start`)l=o;else if(i===`end`)l=c-j;else if(i===`center`)l=o-(j-s)/2;else{let e=A,t=e+j;if(o>=e&&c<=t){n.onComplete&&queueMicrotask(n.onComplete);return}l=o<e?o:c-j}$.writeOffset(P(l),a),n.onComplete&&(a===`smooth`&&`onscrollend`in e?e.addEventListener(`scrollend`,n.onComplete,{once:!0}):queueMicrotask(n.onComplete))}function Y(e,t={}){N||$.writeOffset(P(e),t.behavior??`auto`)}function ne(e={}){Y(0,e)}function re(e={}){Y(Math.max(0,D.totalSize-j),e)}function ie(e=0){return D.totalSize-(A+j)<=e}let X=new AbortController;function Z(){N||(N=!0,X.abort(),x!==null&&(clearTimeout(x),x=null),S&&e.removeEventListener(`scrollend`,C),Q.detach())}function ae(){A=P($.readOffset()),te(),I()}function oe(){j=$.readViewportSize(),I()}let Q=c(e,ae,oe),$=n?Q.x:Q.y;return S&&e.addEventListener(`scrollend`,C,{passive:!0}),D.rebuild(!0),t.initialOffset!==void 0&&$.writeOffset(P(t.initialOffset),`auto`),A=P($.readOffset()),j=$.readViewportSize(),I(),{get count(){return d},get disposalSignal(){return X.signal},dispose:Z,get disposed(){return N},invalidate:W,isAtEnd:ie,get isScrolling(){return b},get items(){return T},measure:V,measureBatch:B,measureEl:H,prepend:K,refresh:G,get scrollOffset(){return A},scrollToBottom:re,scrollToIndex:J,scrollToOffset:Y,scrollToTop:ne,get stickyItems(){return E},[Symbol.dispose]:Z,get totalSize(){return D.totalSize},update:q}}export{e as DEFAULT_ESTIMATE_SIZE,t as DEFAULT_OVERSCAN,n as createMeasurementCache,u as createVirtualizer};
1
+ import{DEFAULT_ESTIMATE_SIZE as e,DEFAULT_OVERSCAN as t,createMeasurementCache as n,normalizeOverscan as r,observeResize as i,resolveEstimateFn as a,toNonNegativeInt as o,toPositiveNumber as s}from"./_utils.js";import{createScrollAdapter as c}from"./_adapter.js";import{createAxis1D as l}from"./_axis1d.js";function u(e,t){let n=!!t.horizontal,u=e=>e,d=o(t.count),f=a(t.estimateSize,36),p=o(t.gap??0),m=t.getItemKey??u,h=r(t.overscan,3),g=t.sticky??null,_=t.onChange,v=t.onScrollEnd,y=t.onScrollingChange,ee=typeof t.scrollEndDelay==`number`&&t.scrollEndDelay>=0?t.scrollEndDelay:150,b=!1,x=null,S=`onscrollend`in e;function C(){b&&(b=!1,y?.(!1),v?.(A))}function te(){b||(b=!0,y?.(!0)),S||(x!==null&&clearTimeout(x),x=setTimeout(C,ee))}let w=t.measurementCache??new Map,T=[],E=[],D=l(d,e=>w.get(m(e))??f(e),p),O=-1,k=0,A=0,j=0,M=-1,N=!1;function P(e){let t=Number.isFinite(e)?e:0,n=Math.max(0,D.totalSize-j);return Math.min(n,Math.max(0,t))}function F(){if(!g||d===0||A<=0)return[];let e=-1,t=0,n=d-1;for(;t<=n;){let r=t+n>>1;D.startAt(r)<A?(e=r,t=r+1):n=r-1}if(e<0)return[];let r=-1;for(let t=e;t>=0;t--)if(g(t)){r=t;break}if(r===-1)return[];let i=D.sizeAt(r),a=1/0;for(let e=r+1;e<d;e++)if(g(e)){a=D.startAt(e);break}let o=Math.min(A,a-i);return[{end:o+i,index:r,size:i,start:o}]}function I(){if(N)return;if(d===0||j<=0){(D.prevStart!==-1||D.prevTotalSize!==D.totalSize)&&(D.commitDedup(-1,-1),T=[],E=[],_&&_({items:T,stickyItems:E,totalSize:D.totalSize}));return}let{renderEnd:e,renderStart:t}=D.computeRange(A,A+j,h.start,h.end),n=g!==null&&A!==M;if(D.isDedupSame(t,e)&&!n)return;D.commitDedup(t,e),M=A;let r=[];for(let n=t;n<=e;n++)r.push(D.itemAt(n));T=r,E=g?F():[],_&&_({items:T,stickyItems:E,totalSize:D.totalSize})}function L(e,t){if(!Number.isFinite(e))return!1;let n=Math.floor(e);if(n<0||n>=d)return!1;let r=s(t,-1);if(r<=0)return!1;let i=m(n);return w.get(i)!==r&&(w.set(i,r),D.markChanged(n),!0)}function R(){if(A<=0||d===0){O=-1;return}O=D.findFirst(A),k=A-D.startAt(O)}function z(){if(O<0)return;let e=D.startAt(O)+k-A;if(O=-1,Math.abs(e)>=1){let t=P(A+e);$.writeOffset(t,`auto`),A=t}}function B(e){if(N)return;let t=!1;for(let{index:n,size:r}of e)L(n,r)&&(t=!0);t&&(D.pendingBuild||R(),D.scheduleBuild(()=>{z(),I()}))}function V(e,t){B([{index:e,size:t}])}function H(e,t){return N?()=>{}:i(X.signal,t,t=>{V(e,n?t.contentRect.width:t.contentRect.height)})}function U(e){let t=!1,n=!1;if(e.count!==void 0){let n=o(e.count);n!==d&&(d=n,D.setCount(d),t=!0)}if(Object.hasOwn(e,`estimateSize`)&&(f=a(e.estimateSize,36),w.clear(),t=!0),e.gap!==void 0){let n=o(e.gap);n!==p&&(p=n,D.setGap(p),t=!0)}if(Object.hasOwn(e,`getItemKey`)){let n=e.getItemKey??u;n!==m&&(m=n,w.clear(),t=!0)}if(Object.hasOwn(e,`measurementCache`)&&e.measurementCache!==void 0&&(w=e.measurementCache,t=!0),e.overscan!==void 0){let t=r(e.overscan,3);(t.start!==h.start||t.end!==h.end)&&(h=t,n=!0)}if(Object.hasOwn(e,`sticky`)){let t=e.sticky??null;t!==g&&(g=t,D.resetDedup()),n=!0}t&&(R(),D.rebuild(!0),z()),(n||t)&&I()}function W(){N||(w.clear(),D.rebuild(!0),I())}function G(){N||(D.rebuild(!0),I())}function K(e){if(N)return;let t=o(e);if(t===0)return;d+=t,D.setCount(d),D.rebuild(!0);let n=D.startAt(t),r=P(A+n);$.writeOffset(r,`auto`),A=r,I()}function q(e){N||U(e)}function J(t,n={}){if(N||d<=0)return;let r=Math.max(0,Math.min(Number.isFinite(t)?Math.floor(t):0,d-1)),i=n.align??`auto`,a=n.behavior??`auto`,o=D.startAt(r),s=D.sizeAt(r),c=o+s,l;if(i===`start`)l=o;else if(i===`end`)l=c-j;else if(i===`center`)l=o-(j-s)/2;else{let e=A,t=e+j;if(o>=e&&c<=t){n.onComplete&&queueMicrotask(n.onComplete);return}l=o<e?o:c-j}$.writeOffset(P(l),a),n.onComplete&&(a===`smooth`&&`onscrollend`in e?e.addEventListener(`scrollend`,n.onComplete,{once:!0}):queueMicrotask(n.onComplete))}function Y(e,t={}){N||$.writeOffset(P(e),t.behavior??`auto`)}function ne(e={}){Y(0,e)}function re(e={}){Y(Math.max(0,D.totalSize-j),e)}function ie(e=0){return D.totalSize-(A+j)<=e}let X=new AbortController;function Z(){N||(N=!0,X.abort(),x!==null&&(clearTimeout(x),x=null),S&&e.removeEventListener(`scrollend`,C),Q.detach())}function ae(){A=P($.readOffset()),te(),I()}function oe(){j=$.readViewportSize(),I()}let Q=c(e,ae,oe),$=n?Q.x:Q.y;return S&&e.addEventListener(`scrollend`,C,{passive:!0}),D.rebuild(!0),t.initialOffset!==void 0&&$.writeOffset(P(t.initialOffset),`auto`),A=P($.readOffset()),j=$.readViewportSize(),I(),{get count(){return d},get disposalSignal(){return X.signal},dispose:Z,get disposed(){return N},invalidate:W,isAtEnd:ie,get isScrolling(){return b},get items(){return T},measure:V,measureBatch:B,measureEl:H,prepend:K,refresh:G,get scrollOffset(){return A},scrollToBottom:re,scrollToIndex:J,scrollToOffset:Y,scrollToTop:ne,get stickyItems(){return E},[Symbol.dispose]:Z,get totalSize(){return D.totalSize},update:q}}export{e as DEFAULT_ESTIMATE_SIZE,t as DEFAULT_OVERSCAN,n as createMeasurementCache,u as createVirtualizer};
2
2
  //# sourceMappingURL=virtualizer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"virtualizer.js","names":[],"sources":["../src/virtualizer.ts"],"sourcesContent":["import { createScrollAdapter } from './_adapter';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n normalizeOverscan,\n observeResize,\n type Overscan,\n resolveEstimateFn,\n type ScrollTarget,\n toNonNegativeInt,\n toPositiveNumber,\n type VirtualKey,\n} from './_utils';\n\nexport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n type ScrollTarget,\n type VirtualItem,\n type VirtualKey,\n};\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualizerState {\n readonly items: VirtualItem[];\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n}\n\nexport interface VirtualizerOptions {\n count: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: (index: number) => VirtualKey;\n horizontal?: boolean;\n initialOffset?: number;\n /** External measurement cache for scroll restoration or SSR pre-measurement. */\n measurementCache?: MeasurementCache;\n /**\n * Called after every render cycle with the new state.\n * **Fixed at construction** — passing `onChange` to `update()` has no effect.\n * To replace the callback, dispose and re-create the virtualizer.\n */\n onChange?: (state: VirtualizerState) => void;\n /**\n * Called when scrolling has settled (no scroll events for `scrollEndDelay` ms,\n * or immediately on the native `scrollend` event when available).\n * Fixed at construction.\n */\n onScrollEnd?: (offset: number) => void;\n /**\n * Called when the scrolling state changes.\n * Fixed at construction.\n */\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),\n * `initialOffset` (one-time bootstrap value), and `onChange` (fixed at\n * construction — see `VirtualizerOptions.onChange`).\n */\nexport interface VirtualizerUpdateOptions {\n count?: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: ((index: number) => VirtualKey) | undefined;\n /** Replace the active measurement cache. Existing entries in the new cache are used immediately on the next rebuild. */\n measurementCache?: MeasurementCache;\n overscan?: Overscan;\n sticky?: ((index: number) => boolean) | undefined;\n}\n\nexport interface ScrollToIndexOptions {\n align?: 'auto' | 'center' | 'end' | 'start';\n behavior?: ScrollBehavior;\n /** Called when the scroll animation completes (instant scrolls: next microtask). */\n onComplete?: () => void;\n}\n\nexport interface Virtualizer {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n readonly disposed: boolean;\n /** Currently rendered items. Always populated. */\n readonly items: VirtualItem[];\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n readonly scrollOffset: number;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n dispose: () => void;\n invalidate: () => void;\n /**\n * `true` when scrolled to (or within `threshold` px of) the end — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Check this **before** appending\n * items to decide whether to auto-follow them (e.g. a chat \"stick to bottom\" pattern) —\n * `createDomVirtualList`'s `stickToBottom` option does exactly this. Default `threshold`: `0`.\n */\n isAtEnd: (threshold?: number) => boolean;\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` and auto-measure `index` on resize. Returns a disconnect fn. */\n measureEl: (index: number, el: HTMLElement) => () => void;\n /** Prepend `additionalCount` items at the top while keeping the viewport visually stable. */\n prepend: (additionalCount: number) => void;\n /** Rebuild the full offset table and re-emit (use when item sizes may have changed). */\n refresh: () => void;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n update: (next: VirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createVirtualizer(target: ScrollTarget, options: VirtualizerOptions): Virtualizer {\n const horizontal = !!options.horizontal;\n const defaultItemKey = (index: number): VirtualKey => index;\n\n let count = toNonNegativeInt(options.count);\n let estimateFn = resolveEstimateFn(options.estimateSize, DEFAULT_ESTIMATE_SIZE);\n let gap = toNonNegativeInt(options.gap ?? 0);\n let getItemKey = options.getItemKey ?? defaultItemKey;\n let overscan = normalizeOverscan(options.overscan, DEFAULT_OVERSCAN);\n let stickyFn: ((index: number) => boolean) | null = options.sticky ?? null;\n\n // Callbacks fixed at construction — not swappable via update().\n const onChange = options.onChange;\n const onScrollEnd = options.onScrollEnd;\n const onScrollingChange = options.onScrollingChange;\n const scrollEndDelay =\n typeof options.scrollEndDelay === 'number' && options.scrollEndDelay >= 0 ? options.scrollEndDelay : 150;\n\n let isScrolling = false;\n let scrollEndTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Detect native scrollend support (Chrome 114+, Firefox 109+)\n const hasNativeScrollEnd = 'onscrollend' in (target as EventTarget);\n\n function notifyScrollEnd(): void {\n if (!isScrolling) return;\n\n isScrolling = false;\n onScrollingChange?.(false);\n onScrollEnd?.(scrollOffset);\n }\n\n function handleScrollStart(): void {\n if (!isScrolling) {\n isScrolling = true;\n onScrollingChange?.(true);\n }\n\n if (!hasNativeScrollEnd) {\n if (scrollEndTimer !== null) clearTimeout(scrollEndTimer);\n\n scrollEndTimer = setTimeout(notifyScrollEnd, scrollEndDelay);\n }\n }\n\n // External or internal measurement cache.\n let measuredByKey: MeasurementCache = options.measurementCache ?? new Map();\n\n let items: VirtualItem[] = [];\n let stickyItems: VirtualItem[] = [];\n\n // ─── Axis setup ──────────────────────────────────────────────────────────────\n // The axis1d primitive owns offset management. The sizeAt closure captures\n // the live `measuredByKey`, `getItemKey`, and `estimateFn` by reference so\n // updates to those let-variables are picked up automatically.\n\n const ax = createAxis1D(count, (i) => measuredByKey.get(getItemKey(i)) ?? estimateFn(i), gap);\n\n // F6: Scroll anchor — prevents viewport jump when items above fold are measured.\n let anchorIndex = -1;\n let anchorDistanceFromTop = 0;\n\n let scrollOffset = 0;\n let viewportSize = 0;\n let prevScrollOffset = -1; // for sticky dedup\n let destroyed = false;\n\n // ─── Scroll clamping ─────────────────────────────────────────────────────────\n\n function clampScrollOffset(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, ax.totalSize - viewportSize);\n\n return Math.min(maxOffset, Math.max(0, safe));\n }\n\n // ─── Sticky items ─────────────────────────────────────────────────────────────\n\n function computeStickyItems(): VirtualItem[] {\n if (!stickyFn || count === 0 || scrollOffset <= 0) return [];\n\n let lastAbove = -1;\n let lo = 0;\n let hi = count - 1;\n\n while (lo <= hi) {\n const mid = (lo + hi) >> 1;\n\n if (ax.startAt(mid) < scrollOffset) {\n lastAbove = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n\n if (lastAbove < 0) return [];\n\n let activeIdx = -1;\n\n for (let i = lastAbove; i >= 0; i--) {\n if (stickyFn(i)) {\n activeIdx = i;\n break;\n }\n }\n\n if (activeIdx === -1) return [];\n\n const activeSize = ax.sizeAt(activeIdx);\n let nextStickyStart = Infinity;\n\n for (let i = activeIdx + 1; i < count; i++) {\n if (stickyFn(i)) {\n nextStickyStart = ax.startAt(i);\n break;\n }\n }\n\n const pinnedStart = Math.min(scrollOffset, nextStickyStart - activeSize);\n\n return [{ end: pinnedStart + activeSize, index: activeIdx, size: activeSize, start: pinnedStart }];\n }\n\n // ─── Compute and emit ─────────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (destroyed) return;\n\n if (count === 0 || viewportSize <= 0) {\n if (ax.prevStart !== -1 || ax.prevTotalSize !== ax.totalSize) {\n // Commit with sentinel renderStart=-1 so prevTotalSize is captured as the\n // current totalSize. Using resetDedup() would set prevTotalSize=-1, causing\n // the guard to fire on every subsequent scroll/resize (Bug 2).\n ax.commitDedup(-1, -1);\n\n items = [];\n stickyItems = [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n return;\n }\n\n const { renderEnd, renderStart } = ax.computeRange(\n scrollOffset,\n scrollOffset + viewportSize,\n overscan.start,\n overscan.end,\n );\n\n // Sticky recomputes on every scroll pixel — bypass dedup in that case.\n const stickyScrolled = stickyFn !== null && scrollOffset !== prevScrollOffset;\n const rangeChanged = !ax.isDedupSame(renderStart, renderEnd);\n\n if (!rangeChanged && !stickyScrolled) return;\n\n ax.commitDedup(renderStart, renderEnd);\n prevScrollOffset = scrollOffset;\n\n const nextItems: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n nextItems.push(ax.itemAt(i));\n }\n\n items = nextItems;\n stickyItems = stickyFn ? computeStickyItems() : [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n // ─── Measurement (R6: measure delegates to measureBatch) ─────────────────────\n\n function recordMeasurement(index: number, size: number): boolean {\n if (!Number.isFinite(index)) return false;\n\n const i = Math.floor(index);\n\n if (i < 0 || i >= count) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n const key = getItemKey(i);\n\n if (measuredByKey.get(key) === s) return false;\n\n measuredByKey.set(key, s);\n ax.markChanged(i);\n\n return true;\n }\n\n // F6: Record a scroll anchor before a batch of measurements arrives.\n function recordScrollAnchor(): void {\n if (scrollOffset <= 0 || count === 0) {\n anchorIndex = -1;\n\n return;\n }\n\n anchorIndex = ax.findFirst(scrollOffset);\n anchorDistanceFromTop = scrollOffset - ax.startAt(anchorIndex);\n }\n\n // F6: After rebuild, restore the anchor to prevent viewport jump.\n function restoreScrollAnchor(): void {\n if (anchorIndex < 0) return;\n\n const expectedOffset = ax.startAt(anchorIndex) + anchorDistanceFromTop;\n const delta = expectedOffset - scrollOffset;\n\n anchorIndex = -1;\n\n if (Math.abs(delta) >= 1) {\n const newOffset = clampScrollOffset(scrollOffset + delta);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n }\n }\n\n function measureBatch(entries: Array<{ index: number; size: number }>): void {\n if (destroyed) return;\n\n let changed = false;\n\n for (const { index, size } of entries) {\n if (recordMeasurement(index, size)) changed = true;\n }\n\n if (changed) {\n // Record anchor before the first pending build is scheduled (F6).\n if (!ax.pendingBuild) recordScrollAnchor();\n\n ax.scheduleBuild(() => {\n restoreScrollAnchor();\n computeVisible();\n });\n }\n }\n\n /** R6: Single-item convenience — delegates to measureBatch. */\n function measure(index: number, size: number): void {\n measureBatch([{ index, size }]);\n }\n\n /** Attach a ResizeObserver to `el`. Returns a disconnect function. */\n function measureEl(index: number, el: HTMLElement): () => void {\n if (destroyed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => {\n measure(index, horizontal ? entry.contentRect.width : entry.contentRect.height);\n });\n }\n\n // ─── Option updates ───────────────────────────────────────────────────────────\n\n function applyOptions(next: VirtualizerUpdateOptions): void {\n let needsRebuild = false;\n let needsCompute = false;\n\n if (next.count !== undefined) {\n const nextCount = toNonNegativeInt(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 = toNonNegativeInt(next.gap);\n\n if (nextGap !== gap) {\n gap = nextGap;\n ax.setGap(gap);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'getItemKey')) {\n const nextKey = next.getItemKey ?? defaultItemKey;\n\n if (nextKey !== getItemKey) {\n getItemKey = nextKey;\n measuredByKey.clear();\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'measurementCache') && next.measurementCache !== undefined) {\n measuredByKey = next.measurementCache;\n needsRebuild = true;\n }\n\n if (next.overscan !== undefined) {\n const nextOverscan = normalizeOverscan(next.overscan, DEFAULT_OVERSCAN);\n\n if (nextOverscan.start !== overscan.start || nextOverscan.end !== overscan.end) {\n overscan = nextOverscan;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'sticky')) {\n const nextStickyFn = next.sticky ?? null;\n\n if (nextStickyFn !== stickyFn) {\n stickyFn = nextStickyFn;\n ax.resetDedup(); // force re-emit to flush updated stickyItems\n }\n\n needsCompute = true;\n }\n\n if (needsRebuild) {\n recordScrollAnchor();\n ax.rebuild(true);\n restoreScrollAnchor();\n }\n\n if (needsCompute || needsRebuild) computeVisible();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────────\n\n function invalidate(): void {\n if (destroyed) return;\n\n measuredByKey.clear();\n ax.rebuild(true);\n computeVisible();\n }\n\n /** Full O(n) offset rebuild followed by re-emit. Also used internally for data-only re-emit when sizes unchanged. */\n function refresh(): void {\n if (destroyed) return;\n\n ax.rebuild(true);\n computeVisible();\n }\n\n function prepend(additionalCount: number): void {\n if (destroyed) return;\n\n const n = toNonNegativeInt(additionalCount);\n\n if (n === 0) return;\n\n count += n;\n ax.setCount(count);\n ax.rebuild(true);\n\n // Adjust scroll to keep the same content visible (F6-like stable prepend).\n const prependedHeight = ax.startAt(n);\n const newOffset = clampScrollOffset(scrollOffset + prependedHeight);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n\n computeVisible();\n }\n\n function update(next: VirtualizerUpdateOptions): void {\n if (destroyed) return;\n\n applyOptions(next);\n }\n\n function scrollToIndex(index: number, opts: ScrollToIndexOptions = {}): void {\n if (destroyed || count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, count - 1));\n const align = opts.align ?? 'auto';\n const behavior = opts.behavior ?? 'auto';\n const itemStart = ax.startAt(clampedIndex);\n const itemSize = ax.sizeAt(clampedIndex);\n const itemEnd = itemStart + itemSize;\n\n let targetOffset: number;\n\n if (align === 'start') {\n targetOffset = itemStart;\n } else if (align === 'end') {\n targetOffset = itemEnd - viewportSize;\n } else if (align === 'center') {\n targetOffset = itemStart - (viewportSize - itemSize) / 2;\n } else {\n const visibleStart = scrollOffset;\n const visibleEnd = visibleStart + viewportSize;\n\n if (itemStart >= visibleStart && itemEnd <= visibleEnd) {\n if (opts.onComplete) queueMicrotask(opts.onComplete);\n\n return;\n }\n\n targetOffset = itemStart < visibleStart ? itemStart : itemEnd - viewportSize;\n }\n\n domAxis.writeOffset(clampScrollOffset(targetOffset), behavior);\n\n if (opts.onComplete) {\n if (behavior === 'smooth' && 'onscrollend' in target) {\n (target as EventTarget).addEventListener('scrollend', opts.onComplete, { once: true });\n } else {\n queueMicrotask(opts.onComplete);\n }\n }\n }\n\n function scrollToOffset(offset: number, opts: { behavior?: ScrollBehavior } = {}): void {\n if (destroyed) return;\n\n domAxis.writeOffset(clampScrollOffset(offset), opts.behavior ?? 'auto');\n }\n\n function scrollToTop(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(0, opts);\n }\n\n function scrollToBottom(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(Math.max(0, ax.totalSize - viewportSize), opts);\n }\n\n function isAtEnd(threshold = 0): boolean {\n return ax.totalSize - (scrollOffset + viewportSize) <= threshold;\n }\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (destroyed) return;\n\n destroyed = true;\n ac.abort();\n\n if (scrollEndTimer !== null) {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = null;\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).removeEventListener('scrollend', notifyScrollEnd);\n }\n\n adapter.detach();\n }\n\n // ─── Bootstrap ───────────────────────────────────────────────────────────────\n\n function handleScroll(): void {\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n handleScrollStart();\n computeVisible();\n }\n\n function handleResize(): void {\n viewportSize = domAxis.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n const domAxis = horizontal ? adapter.x : adapter.y;\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).addEventListener('scrollend', notifyScrollEnd, { passive: true });\n }\n\n ax.rebuild(true);\n\n if (options.initialOffset !== undefined) {\n domAxis.writeOffset(clampScrollOffset(options.initialOffset), 'auto');\n }\n\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n viewportSize = domAxis.readViewportSize();\n\n computeVisible();\n\n return {\n get count() {\n return count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return destroyed;\n },\n invalidate,\n isAtEnd,\n get isScrolling() {\n return isScrolling;\n },\n get items() {\n return items;\n },\n measure,\n measureBatch,\n measureEl,\n prepend,\n refresh,\n get scrollOffset() {\n return scrollOffset;\n },\n scrollToBottom,\n scrollToIndex,\n scrollToOffset,\n scrollToTop,\n get stickyItems() {\n return stickyItems;\n },\n [Symbol.dispose]: _dispose,\n get totalSize() {\n return ax.totalSize;\n },\n update,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"virtualizer.js","names":[],"sources":["../src/virtualizer.ts"],"sourcesContent":["import { createScrollAdapter } from './_adapter';\nimport { createAxis1D, type VirtualItem } from './_axis1d';\nimport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n normalizeOverscan,\n observeResize,\n type Overscan,\n resolveEstimateFn,\n type ScrollTarget,\n toNonNegativeInt,\n toPositiveNumber,\n type VirtualKey,\n} from './_utils';\n\nexport {\n createMeasurementCache,\n DEFAULT_ESTIMATE_SIZE,\n DEFAULT_OVERSCAN,\n type MeasurementCache,\n type Overscan,\n type ScrollTarget,\n type VirtualItem,\n type VirtualKey,\n};\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface VirtualizerState {\n readonly items: VirtualItem[];\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n}\n\nexport interface VirtualizerOptions {\n count: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: (index: number) => VirtualKey;\n horizontal?: boolean;\n initialOffset?: number;\n /** External measurement cache for scroll restoration or SSR pre-measurement. */\n measurementCache?: MeasurementCache;\n /**\n * Called after every render cycle with the new state.\n * **Fixed at construction** — passing `onChange` to `update()` has no effect.\n * To replace the callback, dispose and re-create the virtualizer.\n */\n onChange?: (state: VirtualizerState) => void;\n /**\n * Called when scrolling has settled (no scroll events for `scrollEndDelay` ms,\n * or immediately on the native `scrollend` event when available).\n * Fixed at construction.\n */\n onScrollEnd?: (offset: number) => void;\n /**\n * Called when the scrolling state changes.\n * Fixed at construction.\n */\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),\n * `initialOffset` (one-time bootstrap value), and `onChange` (fixed at\n * construction — see `VirtualizerOptions.onChange`).\n */\nexport interface VirtualizerUpdateOptions {\n count?: number;\n estimateSize?: number | ((index: number) => number);\n gap?: number;\n getItemKey?: ((index: number) => VirtualKey) | undefined;\n /** Replace the active measurement cache. Existing entries in the new cache are used immediately on the next rebuild. */\n measurementCache?: MeasurementCache;\n overscan?: Overscan;\n sticky?: ((index: number) => boolean) | undefined;\n}\n\nexport interface ScrollToIndexOptions {\n align?: 'auto' | 'center' | 'end' | 'start';\n behavior?: ScrollBehavior;\n /** Called when the scroll animation completes (instant scrolls: next microtask). */\n onComplete?: () => void;\n}\n\nexport interface Virtualizer {\n readonly count: number;\n readonly disposalSignal: AbortSignal;\n readonly disposed: boolean;\n /** Currently rendered items. Always populated. */\n readonly items: VirtualItem[];\n /** `true` while the user is actively scrolling; `false` once scroll has settled. */\n readonly isScrolling: boolean;\n readonly scrollOffset: number;\n readonly stickyItems: VirtualItem[];\n readonly totalSize: number;\n dispose: () => void;\n invalidate: () => void;\n /**\n * `true` when scrolled to (or within `threshold` px of) the end — the bottom edge in\n * vertical mode, the trailing edge in horizontal mode. Check this **before** appending\n * items to decide whether to auto-follow them (e.g. a chat \"stick to bottom\" pattern) —\n * `createDomVirtualList`'s `stickToBottom` option does exactly this. Default `threshold`: `0`.\n */\n isAtEnd: (threshold?: number) => boolean;\n measure: (index: number, size: number) => void;\n measureBatch: (entries: Array<{ index: number; size: number }>) => void;\n /** Attach a ResizeObserver to `el` and auto-measure `index` on resize. Returns a disconnect fn. */\n measureEl: (index: number, el: HTMLElement) => () => void;\n /** Prepend `additionalCount` items at the top while keeping the viewport visually stable. */\n prepend: (additionalCount: number) => void;\n /** Rebuild the full offset table and re-emit (use when item sizes may have changed). */\n refresh: () => void;\n scrollToBottom: (options?: { behavior?: ScrollBehavior }) => void;\n scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void;\n scrollToOffset: (offset: number, options?: { behavior?: ScrollBehavior }) => void;\n scrollToTop: (options?: { behavior?: ScrollBehavior }) => void;\n update: (next: VirtualizerUpdateOptions) => void;\n [Symbol.dispose]: () => void;\n}\n\n// ─── Implementation ────────────────────────────────────────────────────────────\n\nexport function createVirtualizer(target: ScrollTarget, options: VirtualizerOptions): Virtualizer {\n const horizontal = !!options.horizontal;\n const defaultItemKey = (index: number): VirtualKey => index;\n\n let count = toNonNegativeInt(options.count);\n let estimateFn = resolveEstimateFn(options.estimateSize, DEFAULT_ESTIMATE_SIZE);\n let gap = toNonNegativeInt(options.gap ?? 0);\n let getItemKey = options.getItemKey ?? defaultItemKey;\n let overscan = normalizeOverscan(options.overscan, DEFAULT_OVERSCAN);\n let stickyFn: ((index: number) => boolean) | null = options.sticky ?? null;\n\n // Callbacks fixed at construction — not swappable via update().\n const onChange = options.onChange;\n const onScrollEnd = options.onScrollEnd;\n const onScrollingChange = options.onScrollingChange;\n const scrollEndDelay =\n typeof options.scrollEndDelay === 'number' && options.scrollEndDelay >= 0 ? options.scrollEndDelay : 150;\n\n let isScrolling = false;\n let scrollEndTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Detect native scrollend support (Chrome 114+, Firefox 109+)\n const hasNativeScrollEnd = 'onscrollend' in (target as EventTarget);\n\n function notifyScrollEnd(): void {\n if (!isScrolling) return;\n\n isScrolling = false;\n onScrollingChange?.(false);\n onScrollEnd?.(scrollOffset);\n }\n\n function handleScrollStart(): void {\n if (!isScrolling) {\n isScrolling = true;\n onScrollingChange?.(true);\n }\n\n if (!hasNativeScrollEnd) {\n if (scrollEndTimer !== null) clearTimeout(scrollEndTimer);\n\n scrollEndTimer = setTimeout(notifyScrollEnd, scrollEndDelay);\n }\n }\n\n // External or internal measurement cache.\n let measuredByKey: MeasurementCache = options.measurementCache ?? new Map();\n\n let items: VirtualItem[] = [];\n let stickyItems: VirtualItem[] = [];\n\n // ─── Axis setup ──────────────────────────────────────────────────────────────\n // The axis1d primitive owns offset management. The sizeAt closure captures\n // the live `measuredByKey`, `getItemKey`, and `estimateFn` by reference so\n // updates to those let-variables are picked up automatically.\n\n const ax = createAxis1D(count, (i) => measuredByKey.get(getItemKey(i)) ?? estimateFn(i), gap);\n\n // F6: Scroll anchor — prevents viewport jump when items above fold are measured.\n let anchorIndex = -1;\n let anchorDistanceFromTop = 0;\n\n let scrollOffset = 0;\n let viewportSize = 0;\n let prevScrollOffset = -1; // for sticky dedup\n let destroyed = false;\n\n // ─── Scroll clamping ─────────────────────────────────────────────────────────\n\n function clampScrollOffset(offset: number): number {\n const safe = Number.isFinite(offset) ? offset : 0;\n const maxOffset = Math.max(0, ax.totalSize - viewportSize);\n\n return Math.min(maxOffset, Math.max(0, safe));\n }\n\n // ─── Sticky items ─────────────────────────────────────────────────────────────\n\n function computeStickyItems(): VirtualItem[] {\n if (!stickyFn || count === 0 || scrollOffset <= 0) return [];\n\n let lastAbove = -1;\n let lo = 0;\n let hi = count - 1;\n\n while (lo <= hi) {\n const mid = (lo + hi) >> 1;\n\n if (ax.startAt(mid) < scrollOffset) {\n lastAbove = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n\n if (lastAbove < 0) return [];\n\n let activeIdx = -1;\n\n for (let i = lastAbove; i >= 0; i--) {\n if (stickyFn(i)) {\n activeIdx = i;\n break;\n }\n }\n\n if (activeIdx === -1) return [];\n\n const activeSize = ax.sizeAt(activeIdx);\n let nextStickyStart = Infinity;\n\n for (let i = activeIdx + 1; i < count; i++) {\n if (stickyFn(i)) {\n nextStickyStart = ax.startAt(i);\n break;\n }\n }\n\n const pinnedStart = Math.min(scrollOffset, nextStickyStart - activeSize);\n\n return [{ end: pinnedStart + activeSize, index: activeIdx, size: activeSize, start: pinnedStart }];\n }\n\n // ─── Compute and emit ─────────────────────────────────────────────────────────\n\n function computeVisible(): void {\n if (destroyed) return;\n\n if (count === 0 || viewportSize <= 0) {\n if (ax.prevStart !== -1 || ax.prevTotalSize !== ax.totalSize) {\n // Commit with sentinel renderStart=-1 so prevTotalSize is captured as the\n // current totalSize. Using resetDedup() would set prevTotalSize=-1, causing\n // the guard to fire on every subsequent scroll/resize (Bug 2).\n ax.commitDedup(-1, -1);\n\n items = [];\n stickyItems = [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n return;\n }\n\n const { renderEnd, renderStart } = ax.computeRange(\n scrollOffset,\n scrollOffset + viewportSize,\n overscan.start,\n overscan.end,\n );\n\n // Sticky recomputes on every scroll pixel — bypass dedup in that case.\n const stickyScrolled = stickyFn !== null && scrollOffset !== prevScrollOffset;\n const rangeChanged = !ax.isDedupSame(renderStart, renderEnd);\n\n if (!rangeChanged && !stickyScrolled) return;\n\n ax.commitDedup(renderStart, renderEnd);\n prevScrollOffset = scrollOffset;\n\n const nextItems: VirtualItem[] = [];\n\n for (let i = renderStart; i <= renderEnd; i++) {\n nextItems.push(ax.itemAt(i));\n }\n\n items = nextItems;\n stickyItems = stickyFn ? computeStickyItems() : [];\n\n if (onChange) onChange({ items, stickyItems, totalSize: ax.totalSize });\n }\n\n // ─── Measurement (R6: measure delegates to measureBatch) ─────────────────────\n\n function recordMeasurement(index: number, size: number): boolean {\n if (!Number.isFinite(index)) return false;\n\n const i = Math.floor(index);\n\n if (i < 0 || i >= count) return false;\n\n const s = toPositiveNumber(size, -1);\n\n if (s <= 0) return false;\n\n const key = getItemKey(i);\n\n if (measuredByKey.get(key) === s) return false;\n\n measuredByKey.set(key, s);\n ax.markChanged(i);\n\n return true;\n }\n\n // F6: Record a scroll anchor before a batch of measurements arrives.\n function recordScrollAnchor(): void {\n if (scrollOffset <= 0 || count === 0) {\n anchorIndex = -1;\n\n return;\n }\n\n anchorIndex = ax.findFirst(scrollOffset);\n anchorDistanceFromTop = scrollOffset - ax.startAt(anchorIndex);\n }\n\n // F6: After rebuild, restore the anchor to prevent viewport jump.\n function restoreScrollAnchor(): void {\n if (anchorIndex < 0) return;\n\n const expectedOffset = ax.startAt(anchorIndex) + anchorDistanceFromTop;\n const delta = expectedOffset - scrollOffset;\n\n anchorIndex = -1;\n\n if (Math.abs(delta) >= 1) {\n const newOffset = clampScrollOffset(scrollOffset + delta);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n }\n }\n\n function measureBatch(entries: Array<{ index: number; size: number }>): void {\n if (destroyed) return;\n\n let changed = false;\n\n for (const { index, size } of entries) {\n if (recordMeasurement(index, size)) changed = true;\n }\n\n if (changed) {\n // Record anchor before the first pending build is scheduled (F6).\n if (!ax.pendingBuild) recordScrollAnchor();\n\n ax.scheduleBuild(() => {\n restoreScrollAnchor();\n computeVisible();\n });\n }\n }\n\n /** R6: Single-item convenience — delegates to measureBatch. */\n function measure(index: number, size: number): void {\n measureBatch([{ index, size }]);\n }\n\n /** Attach a ResizeObserver to `el`. Returns a disconnect function. */\n function measureEl(index: number, el: HTMLElement): () => void {\n if (destroyed) return () => {};\n\n return observeResize(ac.signal, el, (entry) => {\n measure(index, horizontal ? entry.contentRect.width : entry.contentRect.height);\n });\n }\n\n // ─── Option updates ───────────────────────────────────────────────────────────\n\n function applyOptions(next: VirtualizerUpdateOptions): void {\n let needsRebuild = false;\n let needsCompute = false;\n\n if (next.count !== undefined) {\n const nextCount = toNonNegativeInt(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 = toNonNegativeInt(next.gap);\n\n if (nextGap !== gap) {\n gap = nextGap;\n ax.setGap(gap);\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'getItemKey')) {\n const nextKey = next.getItemKey ?? defaultItemKey;\n\n if (nextKey !== getItemKey) {\n getItemKey = nextKey;\n measuredByKey.clear();\n needsRebuild = true;\n }\n }\n\n if (Object.hasOwn(next, 'measurementCache') && next.measurementCache !== undefined) {\n measuredByKey = next.measurementCache;\n needsRebuild = true;\n }\n\n if (next.overscan !== undefined) {\n const nextOverscan = normalizeOverscan(next.overscan, DEFAULT_OVERSCAN);\n\n if (nextOverscan.start !== overscan.start || nextOverscan.end !== overscan.end) {\n overscan = nextOverscan;\n needsCompute = true;\n }\n }\n\n if (Object.hasOwn(next, 'sticky')) {\n const nextStickyFn = next.sticky ?? null;\n\n if (nextStickyFn !== stickyFn) {\n stickyFn = nextStickyFn;\n ax.resetDedup(); // force re-emit to flush updated stickyItems\n }\n\n needsCompute = true;\n }\n\n if (needsRebuild) {\n recordScrollAnchor();\n ax.rebuild(true);\n restoreScrollAnchor();\n }\n\n if (needsCompute || needsRebuild) computeVisible();\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────────\n\n function invalidate(): void {\n if (destroyed) return;\n\n measuredByKey.clear();\n ax.rebuild(true);\n computeVisible();\n }\n\n /** Full O(n) offset rebuild followed by re-emit. Also used internally for data-only re-emit when sizes unchanged. */\n function refresh(): void {\n if (destroyed) return;\n\n ax.rebuild(true);\n computeVisible();\n }\n\n function prepend(additionalCount: number): void {\n if (destroyed) return;\n\n const n = toNonNegativeInt(additionalCount);\n\n if (n === 0) return;\n\n count += n;\n ax.setCount(count);\n ax.rebuild(true);\n\n // Adjust scroll to keep the same content visible (F6-like stable prepend).\n const prependedHeight = ax.startAt(n);\n const newOffset = clampScrollOffset(scrollOffset + prependedHeight);\n\n domAxis.writeOffset(newOffset, 'auto');\n scrollOffset = newOffset;\n\n computeVisible();\n }\n\n function update(next: VirtualizerUpdateOptions): void {\n if (destroyed) return;\n\n applyOptions(next);\n }\n\n function scrollToIndex(index: number, opts: ScrollToIndexOptions = {}): void {\n if (destroyed || count <= 0) return;\n\n const safeIndex = Number.isFinite(index) ? Math.floor(index) : 0;\n const clampedIndex = Math.max(0, Math.min(safeIndex, count - 1));\n const align = opts.align ?? 'auto';\n const behavior = opts.behavior ?? 'auto';\n const itemStart = ax.startAt(clampedIndex);\n const itemSize = ax.sizeAt(clampedIndex);\n const itemEnd = itemStart + itemSize;\n\n let targetOffset: number;\n\n if (align === 'start') {\n targetOffset = itemStart;\n } else if (align === 'end') {\n targetOffset = itemEnd - viewportSize;\n } else if (align === 'center') {\n targetOffset = itemStart - (viewportSize - itemSize) / 2;\n } else {\n const visibleStart = scrollOffset;\n const visibleEnd = visibleStart + viewportSize;\n\n if (itemStart >= visibleStart && itemEnd <= visibleEnd) {\n if (opts.onComplete) queueMicrotask(opts.onComplete);\n\n return;\n }\n\n targetOffset = itemStart < visibleStart ? itemStart : itemEnd - viewportSize;\n }\n\n domAxis.writeOffset(clampScrollOffset(targetOffset), behavior);\n\n if (opts.onComplete) {\n if (behavior === 'smooth' && 'onscrollend' in target) {\n (target as EventTarget).addEventListener('scrollend', opts.onComplete, { once: true });\n } else {\n queueMicrotask(opts.onComplete);\n }\n }\n }\n\n function scrollToOffset(offset: number, opts: { behavior?: ScrollBehavior } = {}): void {\n if (destroyed) return;\n\n domAxis.writeOffset(clampScrollOffset(offset), opts.behavior ?? 'auto');\n }\n\n function scrollToTop(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(0, opts);\n }\n\n function scrollToBottom(opts: { behavior?: ScrollBehavior } = {}): void {\n scrollToOffset(Math.max(0, ax.totalSize - viewportSize), opts);\n }\n\n function isAtEnd(threshold = 0): boolean {\n return ax.totalSize - (scrollOffset + viewportSize) <= threshold;\n }\n\n const ac = new AbortController();\n\n function _dispose(): void {\n if (destroyed) return;\n\n destroyed = true;\n ac.abort();\n\n if (scrollEndTimer !== null) {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = null;\n }\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).removeEventListener('scrollend', notifyScrollEnd);\n }\n\n adapter.detach();\n }\n\n // ─── Bootstrap ───────────────────────────────────────────────────────────────\n\n function handleScroll(): void {\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n handleScrollStart();\n computeVisible();\n }\n\n function handleResize(): void {\n viewportSize = domAxis.readViewportSize();\n computeVisible();\n }\n\n const adapter = createScrollAdapter(target, handleScroll, handleResize);\n const domAxis = horizontal ? adapter.x : adapter.y;\n\n if (hasNativeScrollEnd) {\n (target as EventTarget).addEventListener('scrollend', notifyScrollEnd, { passive: true });\n }\n\n ax.rebuild(true);\n\n if (options.initialOffset !== undefined) {\n domAxis.writeOffset(clampScrollOffset(options.initialOffset), 'auto');\n }\n\n scrollOffset = clampScrollOffset(domAxis.readOffset());\n viewportSize = domAxis.readViewportSize();\n\n computeVisible();\n\n return {\n get count() {\n return count;\n },\n get disposalSignal() {\n return ac.signal;\n },\n dispose: _dispose,\n get disposed() {\n return destroyed;\n },\n invalidate,\n isAtEnd,\n get isScrolling() {\n return isScrolling;\n },\n get items() {\n return items;\n },\n measure,\n measureBatch,\n measureEl,\n prepend,\n refresh,\n get scrollOffset() {\n return scrollOffset;\n },\n scrollToBottom,\n scrollToIndex,\n scrollToOffset,\n scrollToTop,\n get stickyItems() {\n return stickyItems;\n },\n [Symbol.dispose]: _dispose,\n get totalSize() {\n return ax.totalSize;\n },\n update,\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/scroll",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Virtual list engine — variable-height rows, smooth scrolling, and zero-layout-thrash rendering",
5
5
  "type": "module",
6
6
  "files": [
@@ -34,12 +34,12 @@
34
34
  "node": ">=22"
35
35
  },
36
36
  "dependencies": {
37
- "@vielzeug/ripple": "1.2.3"
37
+ "@vielzeug/ripple": "2.0.0"
38
38
  },
39
39
  "devDependencies": {
40
- "@types/node": "^26.1.0",
40
+ "@types/node": "^26.1.2",
41
41
  "typescript": "^6.0.3",
42
- "vite": "^8.1.3",
42
+ "vite": "^8.2.0",
43
43
  "vitest": "^4.1.10"
44
44
  }
45
45
  }