@vielzeug/scroll 2.1.2 → 2.3.0

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