feature/searchbar-enhance #17
@@ -1,3 +1,84 @@
|
||||
/**
|
||||
* Represents a virtualized list item with layout information.
|
||||
*
|
||||
* Used to render visible items with absolute positioning based on computed offsets.
|
||||
*/
|
||||
export interface VirtualItem {
|
||||
/** Index of the item in the data array */
|
||||
index: number;
|
||||
/** Offset from the top of the list in pixels */
|
||||
start: number;
|
||||
/** Height/size of the item in pixels */
|
||||
size: number;
|
||||
/** End position in pixels (start + size) */
|
||||
end: number;
|
||||
/** Unique key for the item (for Svelte's {#each} keying) */
|
||||
key: string | number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options for {@link createVirtualizer}.
|
||||
*
|
||||
* Options are reactive - pass them through a function getter to enable updates.
|
||||
*/
|
||||
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.
|
||||
* Used for initial layout before actual measurements are available.
|
||||
*/
|
||||
estimateSize: (index: number) => number;
|
||||
/** Number of extra items to render outside viewport for smoother scrolling (default: 5) */
|
||||
overscan?: number;
|
||||
/**
|
||||
* Function to get the key of an item at a given index.
|
||||
* Defaults to using the index directly. Useful for stable keys when items reorder.
|
||||
*/
|
||||
getItemKey?: (index: number) => string | number;
|
||||
/**
|
||||
* Optional margin in pixels for scroll calculations.
|
||||
* Can be useful for handling sticky headers or other UI elements.
|
||||
*/
|
||||
scrollMargin?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reactive virtualizer for efficiently rendering large lists by only rendering visible items.
|
||||
*
|
||||
* Uses Svelte 5 runes ($state, $derived) for reactive state management and optimizes rendering
|
||||
* through scroll position tracking and item height measurement. Supports dynamic item heights
|
||||
* and programmatic scrolling.
|
||||
*
|
||||
* @param optionsGetter - Function that returns reactive virtualizer options
|
||||
* @returns Virtualizer instance with computed properties and action functions
|
||||
*
|
||||
* @example
|
||||
* ```svelte
|
||||
* <script lang="ts">
|
||||
* const virtualizer = createVirtualizer(() => ({
|
||||
* count: 1000,
|
||||
* estimateSize: (i) => i % 3 === 0 ? 100 : 50,
|
||||
* overscan: 5,
|
||||
* getItemKey: (i) => `item-${i}`
|
||||
* }));
|
||||
* </script>
|
||||
*
|
||||
* <div use:virtualizer.container style="height: 500px; overflow: auto;">
|
||||
* <div style="height: {virtualizer.totalSize}px;">
|
||||
* {#each virtualizer.items as item (item.key)}
|
||||
* <div
|
||||
* use:virtualizer.measureElement
|
||||
* data-index={item.index}
|
||||
* style="position: absolute; top: {item.start}px; height: {item.size}px;"
|
||||
* >
|
||||
* Item {item.index}
|
||||
* </div>
|
||||
* {/each}
|
||||
* </div>
|
||||
* </div>
|
||||
* ```
|
||||
*/
|
||||
export function createVirtualizer(optionsGetter: () => VirtualizerOptions) {
|
||||
// Reactive State
|
||||
let scrollOffset = $state(0);
|
||||
@@ -71,6 +152,15 @@ export function createVirtualizer(optionsGetter: () => VirtualizerOptions) {
|
||||
});
|
||||
|
||||
// Svelte Actions (The DOM Interface)
|
||||
|
||||
/**
|
||||
* Svelte action to attach to the scrollable container element.
|
||||
*
|
||||
* Sets up scroll tracking, container height monitoring, and cleanup on destroy.
|
||||
*
|
||||
* @param node - The DOM element to attach to (should be the scrollable container)
|
||||
* @returns Object with destroy method for cleanup
|
||||
*/
|
||||
function container(node: HTMLElement) {
|
||||
elementRef = node;
|
||||
containerHeight = node.offsetHeight;
|
||||
@@ -95,6 +185,15 @@ export function createVirtualizer(optionsGetter: () => VirtualizerOptions) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Svelte action to measure individual item elements for dynamic height support.
|
||||
*
|
||||
* Attaches a ResizeObserver to track actual element height and updates
|
||||
* measured sizes when dimensions change. Requires `data-index` attribute on the element.
|
||||
*
|
||||
* @param node - The DOM element to measure (should have `data-index` attribute)
|
||||
* @returns Object with destroy method for cleanup
|
||||
*/
|
||||
function measureElement(node: HTMLElement) {
|
||||
// Use a ResizeObserver on individual items for dynamic height support
|
||||
const resizeObserver = new ResizeObserver(([entry]) => {
|
||||
@@ -116,6 +215,18 @@ export function createVirtualizer(optionsGetter: () => VirtualizerOptions) {
|
||||
}
|
||||
|
||||
// Programmatic Scroll
|
||||
|
||||
/**
|
||||
* Scrolls the container to bring the specified item into view.
|
||||
*
|
||||
* @param index - Index of the item to scroll to
|
||||
* @param align - Scroll alignment: 'start', 'center', 'end', or 'auto' (default)
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* virtualizer.scrollToIndex(50, 'center'); // Scroll to item 50 and center it
|
||||
* ```
|
||||
*/
|
||||
function scrollToIndex(index: number, align: 'start' | 'center' | 'end' | 'auto' = 'auto') {
|
||||
if (!elementRef || index < 0 || index >= options.count) return;
|
||||
|
||||
@@ -130,42 +241,27 @@ export function createVirtualizer(optionsGetter: () => VirtualizerOptions) {
|
||||
}
|
||||
|
||||
return {
|
||||
/** Computed array of visible items to render (reactive) */
|
||||
get items() {
|
||||
return items;
|
||||
},
|
||||
/** Total height of all items in pixels (reactive) */
|
||||
get totalSize() {
|
||||
return totalSize;
|
||||
},
|
||||
/** Svelte action for the scrollable container element */
|
||||
container,
|
||||
/** Svelte action for measuring individual item elements */
|
||||
measureElement,
|
||||
/** Programmatic scroll method to scroll to a specific item */
|
||||
scrollToIndex,
|
||||
};
|
||||
}
|
||||
|
||||
export interface VirtualItem {
|
||||
/** Index of the item in the data array */
|
||||
index: number;
|
||||
/** Offset from the top of the list */
|
||||
start: number;
|
||||
/** Height of the item */
|
||||
size: number;
|
||||
/** End position (start + size) */
|
||||
end: number;
|
||||
/** Unique key for the item (for Svelte's {#each} keying) */
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtualizer instance returned by {@link createVirtualizer}.
|
||||
*
|
||||
* Provides reactive computed properties for visible items and total size,
|
||||
* along with action functions for DOM integration and element measurement.
|
||||
*/
|
||||
export type Virtualizer = ReturnType<typeof createVirtualizer>;
|
||||
|
||||
Reference in New Issue
Block a user