@humanspeak/svelte-virtual-list 0.1.1 → 0.1.3

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.
package/README.md CHANGED
@@ -12,12 +12,57 @@
12
12
 
13
13
  A virtual list component for Svelte applications. Built for Svelte 5 with TypeScript support.
14
14
 
15
+ ## Features
16
+
17
+ - Efficient rendering of large lists with dynamic heights
18
+ - Bi-directional scrolling (top-to-bottom and bottom-to-top)
19
+ - Automatic resize handling and content updates
20
+ - TypeScript support with full type safety
21
+ - SSR compatible with hydration support
22
+ - Svelte 5 runes and snippets support
23
+ - Customizable styling with class props
24
+ - Debug mode for development
25
+ - Smooth scrolling with configurable buffer zones
26
+
15
27
  ## Installation
16
28
 
17
29
  ```bash
18
30
  npm install @humanspeak/svelte-virtual-list
19
31
  ```
20
32
 
33
+ ## Basic Usage
34
+
35
+ ```svelte
36
+ <script lang="ts">
37
+ import SvelteVirtualList from '@humanspeak/svelte-virtual-list'
38
+
39
+ const items = Array.from({ length: 1000 }, (_, i) => ({
40
+ id: i,
41
+ text: `Item ${i}`
42
+ }))
43
+ </script>
44
+
45
+ <SvelteVirtualList {items}>
46
+ {#snippet renderItem(item)}
47
+ <div>{item.text}</div>
48
+ {/snippet}
49
+ </SvelteVirtualList>
50
+ ```
51
+
52
+ ## Props
53
+
54
+ | Prop | Type | Default | Description |
55
+ | ------------------- | -------------------------------- | --------------- | ------------------------------------------- |
56
+ | `items` | `T[]` | Required | Array of items to render |
57
+ | `defaultItemHeight` | `number` | `40` | Initial height for items before measurement |
58
+ | `mode` | `'topToBottom' \| 'bottomToTop'` | `'topToBottom'` | Scroll direction |
59
+ | `bufferSize` | `number` | `20` | Number of items to render outside viewport |
60
+ | `debug` | `boolean` | `false` | Enable debug logging and visualizations |
61
+ | `containerClass` | `string` | `''` | Class for outer container |
62
+ | `viewportClass` | `string` | `''` | Class for scrollable viewport |
63
+ | `contentClass` | `string` | `''` | Class for content wrapper |
64
+ | `itemsClass` | `string` | `''` | Class for items container |
65
+
21
66
  ## Dependencies
22
67
 
23
68
  - [esm-env](https://github.com/benmccann/esm-env) - svelte5 suggested environment detecting
@@ -65,43 +110,6 @@ npm install @humanspeak/svelte-virtual-list
65
110
  </div>
66
111
  ```
67
112
 
68
- ## Props
69
-
70
- The VirtualList component accepts the following props:
71
-
72
- - `items` - Array of items to render
73
- - `defaultItemHeight` - Initial height of each item in pixels (optional, defaults to 40)
74
- - `mode` - Scroll direction ('topToBottom' or 'bottomToTop')
75
- - `bufferSize` - Number of items to render outside the visible area (optional, defaults to 20)
76
- - `debug` - Enable debug mode (optional)
77
- - `containerClass` - Custom class for container element (optional)
78
- - `viewportClass` - Custom class for viewport element (optional)
79
- - `contentClass` - Custom class for content element (optional)
80
- - `itemsClass` - Custom class for items wrapper (optional)
81
- - `renderItem` - Snippet function to render each item
82
-
83
- Note: The component will automatically calculate the average item height based on rendered items, using `defaultItemHeight` only as an initial value until real measurements are available.
84
-
85
- ### Buffer Size
86
-
87
- The `bufferSize` prop determines how many additional items are rendered outside the visible area. A larger buffer:
88
-
89
- - Reduces the chance of seeing blank spaces during fast scrolling
90
- - Provides smoother scrolling experience
91
- - Increases memory usage (as more items are rendered)
92
-
93
- Default value is 20 items, which provides a good balance between performance and smoothness.
94
-
95
- ## Features
96
-
97
- - Efficient rendering of large lists
98
- - TypeScript support
99
- - Customizable styling
100
- - Debug mode for development
101
- - Smooth scrolling with buffer zones
102
- - SSR compatible
103
- - Svelte 5 runes support
104
-
105
113
  ## Development
106
114
 
107
115
  ```bash
@@ -188,6 +196,52 @@ The component automatically handles:
188
196
 
189
197
  No manual intervention is needed when item contents or dimensions change.
190
198
 
199
+ ## Advanced Usage
200
+
201
+ ### Chat Application Example
202
+
203
+ ```svelte
204
+ <script lang="ts">
205
+ import SvelteVirtualList from '@humanspeak/svelte-virtual-list'
206
+
207
+ type Message = {
208
+ id: number
209
+ text: string
210
+ timestamp: Date
211
+ }
212
+
213
+ const messages: Message[] = Array.from({ length: 100 }, (_, i) => ({
214
+ id: i,
215
+ text: `Message ${i}`,
216
+ timestamp: new Date()
217
+ }))
218
+ </script>
219
+
220
+ <SvelteVirtualList
221
+ items={messages}
222
+ mode="bottomToTop"
223
+ containerClass="h-screen"
224
+ viewportClass="bg-gray-100"
225
+ >
226
+ {#snippet renderItem(message)}
227
+ <div class="p-4 rounded-lg shadow-sm">
228
+ <p>{message.text}</p>
229
+ <span class="text-sm text-gray-500">
230
+ {message.timestamp.toLocaleString()}
231
+ </span>
232
+ </div>
233
+ {/snippet}
234
+ </SvelteVirtualList>
235
+ ```
236
+
237
+ ## Performance Considerations
238
+
239
+ - The `bufferSize` prop affects memory usage and scroll smoothness
240
+ - Items are measured and cached for optimal performance
241
+ - Dynamic height calculations happen automatically
242
+ - Resize observers handle container/content changes
243
+ - Virtual DOM updates are batched for efficiency
244
+
191
245
  ## Related
192
246
 
193
247
  - [Svelte](https://svelte.dev) - JavaScript front-end framework
@@ -1,7 +1,8 @@
1
1
  <!--
2
2
  @component
3
3
  A high-performance virtualized list component that efficiently renders large datasets
4
- by only mounting DOM nodes for visible items and a small buffer.
4
+ by only mounting DOM nodes for visible items and a small buffer. Optimized for handling
5
+ lists of 10k+ items through chunked processing and progressive initialization.
5
6
 
6
7
  Props:
7
8
  - `items` - Array of items to render
@@ -34,6 +35,10 @@
34
35
  - Configurable buffer size
35
36
  - Debug mode
36
37
  - Custom styling
38
+ - Progressive initialization for large datasets
39
+ - Memory-optimized for 10k+ items
40
+ - Chunked processing for smooth performance
41
+ - Progress tracking during initialization
37
42
  -->
38
43
 
39
44
  <script lang="ts">
@@ -66,18 +71,28 @@
66
71
  * - Implemented proper cleanup on component destruction
67
72
  * - Added debug mode for development assistance
68
73
  *
74
+ * 6. Large Dataset Optimizations
75
+ * - Implemented chunked processing for 10k+ items
76
+ * - Added progressive initialization system
77
+ * - Deferred height calculations for better initial load
78
+ * - Optimized memory usage for large lists
79
+ * - Added progress tracking for initialization
80
+ *
69
81
  * Technical Challenges Solved:
70
82
  * - Bottom-to-top scrolling in flexbox layouts
71
83
  * - Dynamic height calculations without layout thrashing
72
84
  * - Smooth scrolling on various devices
73
85
  * - Memory management for large lists
74
86
  * - Browser compatibility issues
87
+ * - Performance optimization for 10k+ items
88
+ * - Progressive initialization for large datasets
75
89
  *
76
90
  * Current Architecture:
77
91
  * - Four-layer DOM structure for optimal performance
78
92
  * - State management using Svelte 5's $state
79
93
  * - Reactive height and scroll calculations
80
94
  * - Configurable buffer zones for smooth scrolling
95
+ * - Chunked processing system for large datasets
81
96
  */
82
97
 
83
98
  import { onMount } from 'svelte'
@@ -87,42 +102,98 @@
87
102
  calculateScrollPosition,
88
103
  calculateVisibleRange,
89
104
  calculateTransformY,
90
- updateHeightAndScroll as utilsUpdateHeightAndScroll
105
+ updateHeightAndScroll as utilsUpdateHeightAndScroll,
106
+ calculateAverageHeight,
107
+ processChunked
91
108
  } from './utils/virtualList.js'
109
+ import { rafSchedule } from './utils/raf.js'
92
110
 
93
- // Core configuration props with default values
111
+ /**
112
+ * Core configuration props with default values
113
+ * @type {SvelteVirtualListProps}
114
+ */
94
115
  const {
95
- items = [],
96
- defaultEstimatedItemHeight = 40,
97
- debug = false,
98
- renderItem,
99
- containerClass,
100
- viewportClass,
101
- contentClass,
102
- itemsClass,
103
- debugFunction,
104
- mode = 'topToBottom',
105
- bufferSize = 20
116
+ items = [], // Array of items to be rendered in the virtual list
117
+ defaultEstimatedItemHeight = 40, // Initial height estimate for items before measurement
118
+ debug = false, // Enable debug logging
119
+ renderItem, // Function to render each item
120
+ containerClass, // Custom class for the container element
121
+ viewportClass, // Custom class for the viewport element
122
+ contentClass, // Custom class for the content wrapper
123
+ itemsClass, // Custom class for the items wrapper
124
+ debugFunction, // Custom debug logging function
125
+ mode = 'topToBottom', // Scroll direction mode
126
+ bufferSize = 20 // Number of items to render outside visible area
106
127
  }: SvelteVirtualListProps = $props()
107
128
 
108
- // DOM references and state management
109
- let containerElement: HTMLElement
110
- let viewportElement: HTMLElement
111
- const itemElements = $state<HTMLElement[]>([]) // Tracks rendered item elements for height calculations
129
+ /**
130
+ * DOM References and Core State
131
+ */
132
+ let containerElement: HTMLElement // Reference to the main container element
133
+ let viewportElement: HTMLElement // Reference to the scrollable viewport element
134
+ const itemElements = $state<HTMLElement[]>([]) // Array of rendered item element references
135
+
136
+ /**
137
+ * Scroll and Height Management
138
+ */
112
139
  let scrollTop = $state(0) // Current scroll position
113
- let initialized = $state(false) // Tracks if initial setup is complete
114
140
  let height = $state(0) // Container height
115
141
  let calculatedItemHeight = $state(defaultEstimatedItemHeight) // Current average item height
142
+
143
+ /**
144
+ * State Flags and Control
145
+ */
146
+ let initialized = $state(false) // Tracks if initial setup is complete
116
147
  let isCalculatingHeight = $state(false) // Prevents concurrent height calculations
117
- let lastMeasuredIndex = $state(-1) // Tracks last measured item for optimization
118
- let heightUpdateTimeout: ReturnType<typeof setTimeout> | null = null
119
- let resizeObserver: ResizeObserver | null = null
148
+ let isScrolling = $state(false) // Tracks active scrolling state
149
+ let lastMeasuredIndex = $state(-1) // Index of last measured item
120
150
 
121
151
  /**
122
- * Calculates the average height of visible items to improve accuracy of virtual scrolling
123
- * Uses debouncing to prevent excessive calculations
152
+ * Timers and Observers
124
153
  */
125
- const calculateAverageHeight = () => {
154
+ let heightUpdateTimeout: ReturnType<typeof setTimeout> | null = null // Debounce timer for height updates
155
+ let resizeObserver: ResizeObserver | null = null // Watches for container size changes
156
+
157
+ /**
158
+ * Performance Optimization State
159
+ */
160
+ let heightCache = $state<Record<number, number>>({}) // Cache of measured item heights
161
+ const chunkSize = $state(50) // Number of items to process in each chunk
162
+ let processedItems = $state(0) // Number of items processed during initialization
163
+
164
+ /**
165
+ * Calculates and updates the average height of visible items with debouncing.
166
+ *
167
+ * This function optimizes performance by:
168
+ * - Debouncing calculations to prevent excessive DOM reads
169
+ * - Caching item heights to minimize recalculations
170
+ * - Only updating when significant changes are detected
171
+ *
172
+ * Implementation details:
173
+ * - Uses a 200ms debounce timeout
174
+ * - Tracks calculation state to prevent concurrent updates
175
+ * - Caches heights in heightCache for reuse
176
+ * - Only updates if height difference > 1px
177
+ *
178
+ * State interactions:
179
+ * - Updates calculatedItemHeight
180
+ * - Updates lastMeasuredIndex
181
+ * - Modifies heightCache
182
+ * - Uses/sets isCalculatingHeight flag
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * // Automatically called when items are rendered
187
+ * $effect(() => {
188
+ * if (BROWSER && itemElements.length > 0 && !isCalculatingHeight) {
189
+ * calculateAverageHeightDebounced()
190
+ * }
191
+ * })
192
+ * ```
193
+ *
194
+ * @returns {void}
195
+ */
196
+ const calculateAverageHeightDebounced = () => {
126
197
  if (!BROWSER || isCalculatingHeight || heightUpdateTimeout) return
127
198
  isCalculatingHeight = true
128
199
 
@@ -134,35 +205,32 @@
134
205
  const visibleRange = visibleItems()
135
206
  const currentIndex = visibleRange.start
136
207
 
137
- // Only recalculate if we're looking at different items
138
208
  if (currentIndex !== lastMeasuredIndex) {
139
- const validElements = itemElements.filter((el) => el)
140
- if (validElements.length > 0) {
141
- // Calculate average height from actual rendered elements
142
- const heights = validElements.map((el) => el.getBoundingClientRect().height)
143
- const averageHeight = heights.reduce((sum, h) => sum + h, 0) / heights.length
144
-
145
- // Update only if there's a significant change
146
- if (
147
- averageHeight > 0 &&
148
- !isNaN(averageHeight) &&
149
- Math.abs(averageHeight - calculatedItemHeight) > 1
150
- ) {
151
- calculatedItemHeight = averageHeight
152
- lastMeasuredIndex = currentIndex
153
- }
209
+ const { newHeight, newLastMeasuredIndex, updatedHeightCache } =
210
+ calculateAverageHeight(
211
+ itemElements,
212
+ visibleRange,
213
+ heightCache,
214
+ lastMeasuredIndex,
215
+ calculatedItemHeight
216
+ )
217
+
218
+ if (Math.abs(newHeight - calculatedItemHeight) > 1) {
219
+ calculatedItemHeight = newHeight
220
+ lastMeasuredIndex = newLastMeasuredIndex
221
+ heightCache = updatedHeightCache
154
222
  }
155
223
  }
156
224
 
157
225
  isCalculatingHeight = false
158
226
  heightUpdateTimeout = null
159
- }, 200) // Debounce for 200ms
227
+ }, 200)
160
228
  }
161
229
 
162
230
  // Trigger height calculation when items are rendered
163
231
  $effect(() => {
164
232
  if (BROWSER && itemElements.length > 0 && !isCalculatingHeight) {
165
- calculateAverageHeight()
233
+ calculateAverageHeightDebounced()
166
234
  }
167
235
  })
168
236
 
@@ -196,7 +264,25 @@
196
264
  }
197
265
  })
198
266
 
199
- // Calculate which items should be visible based on current scroll position
267
+ /**
268
+ * Calculates the range of items that should be rendered based on current scroll position.
269
+ *
270
+ * This derived calculation determines which items should be visible in the viewport,
271
+ * including the buffer zone. It takes into account:
272
+ * - Current scroll position
273
+ * - Viewport height
274
+ * - Item height estimates
275
+ * - Buffer size
276
+ * - Scroll direction mode
277
+ *
278
+ * @example
279
+ * ```typescript
280
+ * const range = visibleItems()
281
+ * console.log(`Rendering items from ${range.start} to ${range.end}`)
282
+ * ```
283
+ *
284
+ * @returns {{ start: number, end: number }} Object containing start and end indices of visible items
285
+ */
200
286
  const visibleItems = $derived(() => {
201
287
  if (!items.length) return { start: 0, end: 0 }
202
288
  const viewportHeight = height || 0
@@ -211,10 +297,37 @@
211
297
  )
212
298
  })
213
299
 
214
- // Update scroll position when user scrolls
300
+ /**
301
+ * Handles scroll events in the viewport using requestAnimationFrame for performance.
302
+ *
303
+ * This function debounces scroll events and updates the scrollTop state only when
304
+ * necessary to prevent excessive re-renders. It uses RAF scheduling to ensure
305
+ * smooth scrolling performance.
306
+ *
307
+ * Implementation details:
308
+ * - Uses isScrolling flag to prevent multiple RAF calls
309
+ * - Updates scrollTop state only when scrolling has settled
310
+ * - Browser-only functionality
311
+ *
312
+ * @example
313
+ * ```svelte
314
+ * <div onscroll={handleScroll}>
315
+ * <!-- scrollable content -->
316
+ * </div>
317
+ * ```
318
+ *
319
+ * @returns {void}
320
+ */
215
321
  const handleScroll = () => {
216
322
  if (!BROWSER || !viewportElement) return
217
- scrollTop = viewportElement.scrollTop
323
+
324
+ if (!isScrolling) {
325
+ isScrolling = true
326
+ rafSchedule(() => {
327
+ scrollTop = viewportElement.scrollTop
328
+ isScrolling = false
329
+ })
330
+ }
218
331
  }
219
332
 
220
333
  /**
@@ -290,6 +403,53 @@
290
403
  )
291
404
  }
292
405
 
406
+ /**
407
+ * Initializes large datasets in chunks to prevent UI blocking.
408
+ *
409
+ * This function processes items in smaller chunks using setTimeout to yield
410
+ * to the main thread, allowing other UI operations to remain responsive.
411
+ * Progress is tracked and reported through the processedItems state.
412
+ *
413
+ * For datasets larger than 1000 items, this method is automatically used
414
+ * instead of immediate initialization. The chunk size is controlled by the
415
+ * component's chunkSize state (default: 50).
416
+ *
417
+ * @async
418
+ * @example
419
+ * ```typescript
420
+ * // Component initialization
421
+ * $effect(() => {
422
+ * if (BROWSER && items.length > 1000) {
423
+ * initializeChunked()
424
+ * } else {
425
+ * initialized = true
426
+ * }
427
+ * })
428
+ * ```
429
+ *
430
+ * @throws {Error} If processChunked fails to complete initialization
431
+ * @returns {Promise<void>} Resolves when all chunks have been processed
432
+ */
433
+ const initializeChunked = async () => {
434
+ if (!items.length) return
435
+
436
+ await processChunked(
437
+ items,
438
+ chunkSize,
439
+ (processed) => (processedItems = processed),
440
+ () => (initialized = true)
441
+ )
442
+ }
443
+
444
+ // Modify the mount effect to use chunked initialization
445
+ $effect(() => {
446
+ if (BROWSER && items.length > 1000) {
447
+ initializeChunked()
448
+ } else {
449
+ initialized = true
450
+ }
451
+ })
452
+
293
453
  // Setup and cleanup
294
454
  onMount(() => {
295
455
  if (BROWSER) {
@@ -364,7 +524,8 @@
364
524
  visibleItemsCount: visibleItems().end - visibleItems().start,
365
525
  startIndex: visibleItems().start,
366
526
  endIndex: visibleItems().end,
367
- totalItems: items.length
527
+ totalItems: items.length,
528
+ processedItems
368
529
  }}
369
530
  {debugFunction
370
531
  ? debugFunction(debugInfo)
@@ -1,7 +1,8 @@
1
1
  import type { SvelteVirtualListProps } from './types.js';
2
2
  /**
3
3
  * A high-performance virtualized list component that efficiently renders large datasets
4
- * by only mounting DOM nodes for visible items and a small buffer.
4
+ * by only mounting DOM nodes for visible items and a small buffer. Optimized for handling
5
+ * lists of 10k+ items through chunked processing and progressive initialization.
5
6
  *
6
7
  * Props:
7
8
  * - `items` - Array of items to render
@@ -34,6 +35,10 @@ import type { SvelteVirtualListProps } from './types.js';
34
35
  * - Configurable buffer size
35
36
  * - Debug mode
36
37
  * - Custom styling
38
+ * - Progressive initialization for large datasets
39
+ * - Memory-optimized for 10k+ items
40
+ * - Chunked processing for smooth performance
41
+ * - Progress tracking during initialization
37
42
  */
38
43
  declare const SvelteVirtualList: import("svelte").Component<SvelteVirtualListProps, {}, "">;
39
44
  type SvelteVirtualList = ReturnType<typeof SvelteVirtualList>;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import SvelteVirtualList from './SvelteVirtualList.svelte';
2
2
  import type { SvelteVirtualListDebugInfo, SvelteVirtualListMode, SvelteVirtualListProps } from './types.js';
3
3
  export default SvelteVirtualList;
4
- export type { SvelteVirtualListDebugInfo as DebugInfo, SvelteVirtualListMode as Mode, SvelteVirtualListProps as Props };
4
+ export type { SvelteVirtualListDebugInfo, SvelteVirtualListMode, SvelteVirtualListProps };
package/dist/types.d.ts CHANGED
@@ -46,10 +46,12 @@ export type SvelteVirtualListProps = {
46
46
  * @property {number} startIndex - Index of the first rendered item in the viewport.
47
47
  * @property {number} totalItems - Total number of items in the list.
48
48
  * @property {number} visibleItemsCount - Number of items currently visible in the viewport.
49
+ * @property {number} processedItems - Number of items processed in the viewport.
49
50
  */
50
51
  export type SvelteVirtualListDebugInfo = {
51
52
  endIndex: number;
52
53
  startIndex: number;
53
54
  totalItems: number;
54
55
  visibleItemsCount: number;
56
+ processedItems: number;
55
57
  };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Schedules a function to be executed on the next animation frame.
3
+ * If a function is already scheduled, the new function will replace it.
4
+ * This helps prevent multiple RAF calls and ensures smooth animations.
5
+ *
6
+ * @param fn - The function to be executed on the next animation frame
7
+ */
8
+ export declare const rafSchedule: (fn: () => void) => void;
@@ -0,0 +1,22 @@
1
+ let scheduled = false;
2
+ let callback = null;
3
+ /**
4
+ * Schedules a function to be executed on the next animation frame.
5
+ * If a function is already scheduled, the new function will replace it.
6
+ * This helps prevent multiple RAF calls and ensures smooth animations.
7
+ *
8
+ * @param fn - The function to be executed on the next animation frame
9
+ */
10
+ export const rafSchedule = (fn) => {
11
+ callback = fn;
12
+ if (!scheduled) {
13
+ scheduled = true;
14
+ requestAnimationFrame(() => {
15
+ scheduled = false;
16
+ if (callback) {
17
+ callback();
18
+ callback = null;
19
+ }
20
+ });
21
+ }
22
+ };
@@ -60,3 +60,64 @@ export declare const calculateTransformY: (mode: SvelteVirtualListMode, totalIte
60
60
  * @param {boolean} immediate - Whether to perform the update immediately
61
61
  */
62
62
  export declare const updateHeightAndScroll: (state: VirtualListState, setters: VirtualListSetters, immediate?: boolean) => void;
63
+ /**
64
+ * Calculates the average height of visible items in a virtual list.
65
+ *
66
+ * This function optimizes performance by:
67
+ * 1. Using a height cache to store measured item heights
68
+ * 2. Only measuring new items not in the cache
69
+ * 3. Calculating a running average of all measured heights
70
+ *
71
+ * @param {HTMLElement[]} itemElements - Array of currently rendered item elements
72
+ * @param {{ start: number }} visibleRange - Object containing the start index of visible items
73
+ * @param {Record<number, number>} heightCache - Cache of previously measured item heights
74
+ * @param {number} lastMeasuredIndex - Index of the last measured item
75
+ * @param {number} currentItemHeight - Current average item height being used
76
+ *
77
+ * @returns {{
78
+ * newHeight: number,
79
+ * newLastMeasuredIndex: number,
80
+ * updatedHeightCache: Record<number, number>
81
+ * }} Object containing new calculated height, last measured index, and updated cache
82
+ *
83
+ * @example
84
+ * const result = calculateAverageHeight(
85
+ * itemElements,
86
+ * { start: 0 },
87
+ * {},
88
+ * -1,
89
+ * 40
90
+ * )
91
+ */
92
+ export declare const calculateAverageHeight: (itemElements: HTMLElement[], visibleRange: {
93
+ start: number;
94
+ }, heightCache: Record<number, number>, lastMeasuredIndex: number, currentItemHeight: number) => {
95
+ newHeight: number;
96
+ newLastMeasuredIndex: number;
97
+ updatedHeightCache: Record<number, number>;
98
+ };
99
+ /**
100
+ * Processes large arrays in chunks to prevent UI blocking.
101
+ *
102
+ * This function implements a progressive processing strategy that:
103
+ * 1. Breaks down large arrays into manageable chunks
104
+ * 2. Processes each chunk asynchronously
105
+ * 3. Reports progress after each chunk
106
+ * 4. Yields to the main thread between chunks
107
+ *
108
+ * @param {any[]} items - Array of items to process
109
+ * @param {number} chunkSize - Number of items to process in each chunk
110
+ * @param {(processed: number) => void} onProgress - Callback for progress updates
111
+ * @param {() => void} onComplete - Callback when all processing is complete
112
+ *
113
+ * @returns {Promise<void>} Resolves when all chunks have been processed
114
+ *
115
+ * @example
116
+ * await processChunked(
117
+ * largeArray,
118
+ * 50,
119
+ * (processed) => console.log(`Processed ${processed} items`),
120
+ * () => console.log('All items processed')
121
+ * )
122
+ */
123
+ export declare const processChunked: (items: any[], chunkSize: number, onProgress: (processed: number) => void, onComplete: () => void) => Promise<void>;
@@ -95,3 +95,101 @@ export const updateHeightAndScroll = (state, setters, immediate = false) => {
95
95
  }
96
96
  }
97
97
  };
98
+ /**
99
+ * Calculates the average height of visible items in a virtual list.
100
+ *
101
+ * This function optimizes performance by:
102
+ * 1. Using a height cache to store measured item heights
103
+ * 2. Only measuring new items not in the cache
104
+ * 3. Calculating a running average of all measured heights
105
+ *
106
+ * @param {HTMLElement[]} itemElements - Array of currently rendered item elements
107
+ * @param {{ start: number }} visibleRange - Object containing the start index of visible items
108
+ * @param {Record<number, number>} heightCache - Cache of previously measured item heights
109
+ * @param {number} lastMeasuredIndex - Index of the last measured item
110
+ * @param {number} currentItemHeight - Current average item height being used
111
+ *
112
+ * @returns {{
113
+ * newHeight: number,
114
+ * newLastMeasuredIndex: number,
115
+ * updatedHeightCache: Record<number, number>
116
+ * }} Object containing new calculated height, last measured index, and updated cache
117
+ *
118
+ * @example
119
+ * const result = calculateAverageHeight(
120
+ * itemElements,
121
+ * { start: 0 },
122
+ * {},
123
+ * -1,
124
+ * 40
125
+ * )
126
+ */
127
+ export const calculateAverageHeight = (itemElements, visibleRange, heightCache, lastMeasuredIndex, currentItemHeight) => {
128
+ const validElements = itemElements.filter((el) => el);
129
+ if (validElements.length === 0) {
130
+ return {
131
+ newHeight: currentItemHeight,
132
+ newLastMeasuredIndex: lastMeasuredIndex,
133
+ updatedHeightCache: heightCache
134
+ };
135
+ }
136
+ const newHeightCache = { ...heightCache };
137
+ // Cache heights for new items
138
+ validElements.forEach((el, i) => {
139
+ const itemIndex = visibleRange.start + i;
140
+ if (!newHeightCache[itemIndex]) {
141
+ newHeightCache[itemIndex] = el.getBoundingClientRect().height;
142
+ }
143
+ });
144
+ // Calculate average from cached heights
145
+ const heights = Object.values(newHeightCache);
146
+ const averageHeight = heights.reduce((sum, h) => sum + h, 0) / heights.length;
147
+ return {
148
+ newHeight: averageHeight > 0 && !isNaN(averageHeight) ? averageHeight : currentItemHeight,
149
+ newLastMeasuredIndex: visibleRange.start,
150
+ updatedHeightCache: newHeightCache
151
+ };
152
+ };
153
+ /**
154
+ * Processes large arrays in chunks to prevent UI blocking.
155
+ *
156
+ * This function implements a progressive processing strategy that:
157
+ * 1. Breaks down large arrays into manageable chunks
158
+ * 2. Processes each chunk asynchronously
159
+ * 3. Reports progress after each chunk
160
+ * 4. Yields to the main thread between chunks
161
+ *
162
+ * @param {any[]} items - Array of items to process
163
+ * @param {number} chunkSize - Number of items to process in each chunk
164
+ * @param {(processed: number) => void} onProgress - Callback for progress updates
165
+ * @param {() => void} onComplete - Callback when all processing is complete
166
+ *
167
+ * @returns {Promise<void>} Resolves when all chunks have been processed
168
+ *
169
+ * @example
170
+ * await processChunked(
171
+ * largeArray,
172
+ * 50,
173
+ * (processed) => console.log(`Processed ${processed} items`),
174
+ * () => console.log('All items processed')
175
+ * )
176
+ */
177
+ export const processChunked = async (items, // eslint-disable-line @typescript-eslint/no-explicit-any
178
+ chunkSize, onProgress, // eslint-disable-line no-unused-vars
179
+ onComplete) => {
180
+ if (!items.length) {
181
+ onComplete();
182
+ return;
183
+ }
184
+ const processChunk = async (startIdx) => {
185
+ const endIdx = Math.min(startIdx + chunkSize, items.length);
186
+ onProgress(endIdx);
187
+ if (endIdx < items.length) {
188
+ setTimeout(() => processChunk(endIdx), 0);
189
+ }
190
+ else {
191
+ onComplete();
192
+ }
193
+ };
194
+ await processChunk(0);
195
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-virtual-list",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "A lightweight, high-performance virtual list component for Svelte 5 that renders large datasets with minimal memory usage. Features include dynamic height support, smooth scrolling, TypeScript support, and efficient DOM recycling. Ideal for infinite scrolling lists, data tables, chat interfaces, and any application requiring the rendering of thousands of items without compromising performance. Zero dependencies and fully customizable.",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",