From 1950cd4095b1a9f57806d530296495fa11344c72 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 6 Jan 2026 21:38:53 +0300 Subject: [PATCH] refactor(VirtualList): refactor VirtualList with modern svelte 5 patterns --- src/shared/ui/VirtualList.svelte | 177 ------------ src/shared/ui/VirtualList/VirtualList.svelte | 132 +++++++++ src/shared/virtual/README.md | 277 ------------------- src/shared/virtual/index.ts | 28 -- 4 files changed, 132 insertions(+), 482 deletions(-) delete mode 100644 src/shared/ui/VirtualList.svelte create mode 100644 src/shared/ui/VirtualList/VirtualList.svelte delete mode 100644 src/shared/virtual/README.md delete mode 100644 src/shared/virtual/index.ts diff --git a/src/shared/ui/VirtualList.svelte b/src/shared/ui/VirtualList.svelte deleted file mode 100644 index b5527b9..0000000 --- a/src/shared/ui/VirtualList.svelte +++ /dev/null @@ -1,177 +0,0 @@ - - - -
- -
- {#each virtualItems as item (item.key)} -
- -
- {/each} -
-
diff --git a/src/shared/ui/VirtualList/VirtualList.svelte b/src/shared/ui/VirtualList/VirtualList.svelte new file mode 100644 index 0000000..5095652 --- /dev/null +++ b/src/shared/ui/VirtualList/VirtualList.svelte @@ -0,0 +1,132 @@ + + + + +
e.target === virtual.scrollElement && focusItem(activeIndex))} +> + +
+ {#each virtual.items as row (row.key)} + +
(activeIndex = row.index)} + class="absolute top-0 left-0 w-full outline-none focus:bg-accent focus:text-accent-foreground" + style:height="{row.size}px" + style:transform="translateY({row.start}px)" + > + {@render children({ item: items[row.index], index: row.index })} +
+ {/each} +
+
diff --git a/src/shared/virtual/README.md b/src/shared/virtual/README.md deleted file mode 100644 index 4784ff8..0000000 --- a/src/shared/virtual/README.md +++ /dev/null @@ -1,277 +0,0 @@ -# Virtualization - Store Pattern Implementation - -This folder contains the virtualization layer for smooth 60FPS scrolling with large font collections. - -**Updated:** Now uses Svelte 5 rune-based store pattern instead of React-inspired hooks. - -## Files - -### Store - -- **createVirtualizerStore.ts**: Svelte 5 rune-based store for virtualized lists - -### Component - -- **VirtualList.svelte** (moved to `shared/ui/`): Generic virtualized list component - -## Why Store Pattern? - -The store pattern is more idiomatic for Svelte than React-inspired hooks: - -1. **More Svelte-native**: Stores are core to Svelte, hooks are React-specific -2. **Better reactivity**: Stores auto-derive values using `$derived`, hooks need manual updates -3. **Consistent with project patterns**: Matches `createFilterStore` and `createControlStore` -4. **More extensible**: Easy to add store methods and computed values -5. **Type-safe**: Full TypeScript generics support - -## Usage - -### Basic Usage with Store - -```svelte - - -
-
- {#each virtualItems as item (item.key)} -
- -
- {/each} -
-
-``` - -### Using VirtualList Component - -```svelte - - - - - -``` - -## API Reference - -### createVirtualizerStore - -```typescript -function createVirtualizerStore(options: VirtualizerOptions): VirtualizerStore; -``` - -**Options:** - -- `count`: Number of items (required) -- `estimateSize`: Function returning estimated height for each item (required) -- `overscan`: Number of items to render beyond viewport (default: 5) -- `getItemKey`: Function to get stable key for each item -- `scrollMargin`: Scroll offset threshold (in pixels) - -**Returns:** - -- `virtualItems`: Array of visible virtual items (reactive getter) -- `totalSize`: Total height of all items (reactive getter) -- `scrollOffset`: Current scroll offset (reactive getter) -- `scrollToIndex`: Scroll to specific item index -- `scrollToOffset`: Scroll to specific pixel offset -- `measureElement`: Manually measure item element -- `scrollElement`: Reference to scroll element (bindable) - -### VirtualList Component Props - -```typescript -interface VirtualListProps { - items: T[]; // Items to virtualize - itemHeight?: number | ((index: number) => number; // Item height (default: 80) - overscan?: number; // Overscan items (default: 5) - height?: string; // Container height class (default: "h-96") - scrollMargin?: number; // Scroll margin - class?: string; // CSS class name - getItemKey?: (item: T, index: number) => string | number; // Key function -} -``` - -**Slots:** - -- `let:item`: Current item -- `let:index`: Current item index - -## Key Features - -- Renders only visible items (50-100 max) regardless of total count -- Maintains 60FPS scrolling with 10,000+ items -- Minimal memory usage -- Smooth scrolling without jank -- ARIA roles for accessibility -- Keyboard navigation support -- Customizable overscan for smoother scrolling -- Stable keys for efficient re-rendering -- Responsive height using Tailwind CSS classes -- No pixel-based styling in `