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) {
|
export function createVirtualizer(optionsGetter: () => VirtualizerOptions) {
|
||||||
// Reactive State
|
// Reactive State
|
||||||
let scrollOffset = $state(0);
|
let scrollOffset = $state(0);
|
||||||
@@ -71,6 +152,15 @@ export function createVirtualizer(optionsGetter: () => VirtualizerOptions) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Svelte Actions (The DOM Interface)
|
// 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) {
|
function container(node: HTMLElement) {
|
||||||
elementRef = node;
|
elementRef = node;
|
||||||
containerHeight = node.offsetHeight;
|
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) {
|
function measureElement(node: HTMLElement) {
|
||||||
// Use a ResizeObserver on individual items for dynamic height support
|
// Use a ResizeObserver on individual items for dynamic height support
|
||||||
const resizeObserver = new ResizeObserver(([entry]) => {
|
const resizeObserver = new ResizeObserver(([entry]) => {
|
||||||
@@ -116,6 +215,18 @@ export function createVirtualizer(optionsGetter: () => VirtualizerOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Programmatic Scroll
|
// 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') {
|
function scrollToIndex(index: number, align: 'start' | 'center' | 'end' | 'auto' = 'auto') {
|
||||||
if (!elementRef || index < 0 || index >= options.count) return;
|
if (!elementRef || index < 0 || index >= options.count) return;
|
||||||
|
|
||||||
@@ -130,42 +241,27 @@ export function createVirtualizer(optionsGetter: () => VirtualizerOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
/** Computed array of visible items to render (reactive) */
|
||||||
get items() {
|
get items() {
|
||||||
return items;
|
return items;
|
||||||
},
|
},
|
||||||
|
/** Total height of all items in pixels (reactive) */
|
||||||
get totalSize() {
|
get totalSize() {
|
||||||
return totalSize;
|
return totalSize;
|
||||||
},
|
},
|
||||||
|
/** Svelte action for the scrollable container element */
|
||||||
container,
|
container,
|
||||||
|
/** Svelte action for measuring individual item elements */
|
||||||
measureElement,
|
measureElement,
|
||||||
|
/** Programmatic scroll method to scroll to a specific item */
|
||||||
scrollToIndex,
|
scrollToIndex,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VirtualItem {
|
/**
|
||||||
/** Index of the item in the data array */
|
* Virtualizer instance returned by {@link createVirtualizer}.
|
||||||
index: number;
|
*
|
||||||
/** Offset from the top of the list */
|
* Provides reactive computed properties for visible items and total size,
|
||||||
start: number;
|
* along with action functions for DOM integration and element measurement.
|
||||||
/** 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Virtualizer = ReturnType<typeof createVirtualizer>;
|
export type Virtualizer = ReturnType<typeof createVirtualizer>;
|
||||||
|
|||||||
Reference in New Issue
Block a user