refactor(createVirtualizer): refactor createVirtualizerStore with modern svelte 5 patterns
This commit is contained in:
114
src/shared/lib/utils/createVirtualizer/createVirtualizer.ts
Normal file
114
src/shared/lib/utils/createVirtualizer/createVirtualizer.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import {
|
||||
createVirtualizer as coreCreateVirtualizer,
|
||||
observeElementRect,
|
||||
} from '@tanstack/svelte-virtual';
|
||||
import type { VirtualItem as CoreVirtualItem } from '@tanstack/virtual-core';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export interface VirtualItem {
|
||||
index: number;
|
||||
start: number;
|
||||
size: number;
|
||||
end: number;
|
||||
key: string | number;
|
||||
}
|
||||
|
||||
export interface VirtualizerOptions {
|
||||
/** Total number of items in the data array */
|
||||
count: number;
|
||||
/** Function to estimate the size of an item at a given index */
|
||||
estimateSize: (index: number) => number;
|
||||
/** Number of extra items to render outside viewport (default: 5) */
|
||||
overscan?: number;
|
||||
/** Function to get the key of an item at a given index (defaults to index) */
|
||||
getItemKey?: (index: number) => string | number;
|
||||
/** Optional margin in pixels for scroll calculations */
|
||||
scrollMargin?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reactive virtualizer using Svelte 5 runes and TanStack's core library.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const virtualizer = createVirtualizer(() => ({
|
||||
* count: items.length,
|
||||
* estimateSize: () => 80,
|
||||
* overscan: 5,
|
||||
* }));
|
||||
*
|
||||
* // In template:
|
||||
* // <div bind:this={virtualizer.scrollElement}>
|
||||
* // {#each virtualizer.items as item}
|
||||
* // <div style="transform: translateY({item.start}px)">
|
||||
* // {items[item.index]}
|
||||
* // </div>
|
||||
* // {/each}
|
||||
* // </div>
|
||||
* ```
|
||||
*/
|
||||
export function createVirtualizer(
|
||||
optionsGetter: () => VirtualizerOptions,
|
||||
) {
|
||||
let element = $state<HTMLElement | null>(null);
|
||||
|
||||
const internalStore = coreCreateVirtualizer({
|
||||
get count() {
|
||||
return optionsGetter().count;
|
||||
},
|
||||
get estimateSize() {
|
||||
return optionsGetter().estimateSize;
|
||||
},
|
||||
get overscan() {
|
||||
return optionsGetter().overscan ?? 5;
|
||||
},
|
||||
get scrollMargin() {
|
||||
return optionsGetter().scrollMargin;
|
||||
},
|
||||
get getItemKey() {
|
||||
return optionsGetter().getItemKey ?? (i => i);
|
||||
},
|
||||
getScrollElement: () => element,
|
||||
observeElementRect: observeElementRect,
|
||||
});
|
||||
|
||||
const state = $derived(get(internalStore));
|
||||
|
||||
const virtualItems = $derived(
|
||||
state.getVirtualItems().map((item: CoreVirtualItem): VirtualItem => ({
|
||||
index: item.index,
|
||||
start: item.start,
|
||||
size: item.size,
|
||||
end: item.end,
|
||||
key: typeof item.key === 'bigint' ? Number(item.key) : item.key,
|
||||
})),
|
||||
);
|
||||
|
||||
return {
|
||||
get items() {
|
||||
return virtualItems;
|
||||
},
|
||||
|
||||
get totalSize() {
|
||||
return state.getTotalSize();
|
||||
},
|
||||
|
||||
get scrollOffset() {
|
||||
return state.scrollOffset ?? 0;
|
||||
},
|
||||
|
||||
get scrollElement() {
|
||||
return element;
|
||||
},
|
||||
set scrollElement(el) {
|
||||
element = el;
|
||||
},
|
||||
|
||||
scrollToIndex: (idx: number, opt?: { align?: 'start' | 'center' | 'end' | 'auto' }) =>
|
||||
state.scrollToIndex(idx, opt),
|
||||
|
||||
scrollToOffset: (off: number) => state.scrollToOffset(off),
|
||||
|
||||
measureElement: (el: HTMLElement) => state.measureElement(el),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user