@humanspeak/svelte-virtual-list 0.1.6 → 0.2.1

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
@@ -3,7 +3,7 @@
3
3
  [![NPM version](https://img.shields.io/npm/v/@humanspeak/svelte-virtual-list.svg)](https://www.npmjs.com/package/@humanspeak/svelte-virtual-list)
4
4
  [![Build Status](https://github.com/humanspeak/svelte-virtual-list/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/humanspeak/svelte-virtual-list/actions/workflows/npm-publish.yml)
5
5
  [![Coverage Status](https://coveralls.io/repos/github/humanspeak/svelte-virtual-list/badge.svg?branch=main)](https://coveralls.io/github/humanspeak/svelte-virtual-list?branch=main)
6
- [![License](https://img.shields.io/npm/l/@humanspeak/svelte-virtual-list.svg)](https://github.com/humanspeak/svelte-virtual-list/blob/main/LICENSE)
6
+ [![License](https://img.shields.io/npm/l/@humanspeak/svelte-virtual-list.svg)](https://github.com/humanspeak/svelte-virtual-list/blob/main/LICENSE.md)
7
7
  [![Downloads](https://img.shields.io/npm/dm/@humanspeak/svelte-virtual-list.svg)](https://www.npmjs.com/package/@humanspeak/svelte-virtual-list)
8
8
  [![CodeQL](https://github.com/humanspeak/svelte-virtual-list/actions/workflows/codeql.yml/badge.svg)](https://github.com/humanspeak/svelte-virtual-list/actions/workflows/codeql.yml)
9
9
  [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
@@ -131,7 +131,7 @@ npm run build
131
131
 
132
132
  ## License
133
133
 
134
- [MIT](LICENSE)
134
+ [MIT](LICENSE.md)
135
135
 
136
136
  ## Key Features
137
137
 
@@ -217,21 +217,31 @@ No manual intervention is needed when item contents or dimensions change.
217
217
  }))
218
218
  </script>
219
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>
220
+ <div style="height: 500px;">
221
+ <SvelteVirtualList items={messages} mode="bottomToTop" debug>
222
+ {#snippet renderItem(message)}
223
+ <div class="message-container">
224
+ <p>{message.text}</p>
225
+ <span class="timestamp">
226
+ {message.timestamp.toLocaleString()}
227
+ </span>
228
+ </div>
229
+ {/snippet}
230
+ </SvelteVirtualList>
231
+ </div>
232
+
233
+ <style>
234
+ .message-container {
235
+ padding: 1rem;
236
+ border-radius: 0.5rem;
237
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
238
+ }
239
+
240
+ .timestamp {
241
+ font-size: 0.875rem;
242
+ color: #6b7280;
243
+ }
244
+ </style>
235
245
  ```
236
246
 
237
247
  ## Performance Considerations
@@ -46,38 +46,58 @@
46
46
  * SvelteVirtualList Implementation Journey
47
47
  *
48
48
  * Evolution & Architecture:
49
- * 1. Initial Implementation
49
+ * 1. Initial Implementation
50
50
  * - Basic virtual scrolling with fixed height items
51
51
  * - Single direction scrolling (top-to-bottom)
52
52
  * - Simple viewport calculations
53
53
  *
54
- * 2. Dynamic Height Enhancement
54
+ * 2. Dynamic Height Enhancement
55
55
  * - Added dynamic height calculation system
56
56
  * - Implemented debounced measurements
57
57
  * - Created height averaging mechanism for performance
58
58
  *
59
- * 3. Bidirectional Scrolling
59
+ * 3. Bidirectional Scrolling
60
60
  * - Added bottomToTop mode
61
61
  * - Solved complex initialization issues with flexbox
62
62
  * - Implemented careful scroll position management
63
63
  *
64
- * 4. Performance Optimizations
64
+ * 4. Performance Optimizations
65
65
  * - Added element recycling through keyed each blocks
66
66
  * - Implemented RAF for smooth animations
67
67
  * - Optimized DOM updates with transform translations
68
68
  *
69
- * 5. Stability Improvements
69
+ * 5. Stability Improvements
70
70
  * - Added ResizeObserver for responsive updates
71
71
  * - Implemented proper cleanup on component destruction
72
72
  * - Added debug mode for development assistance
73
73
  *
74
- * 6. Large Dataset Optimizations
74
+ * 6. Large Dataset Optimizations
75
75
  * - Implemented chunked processing for 10k+ items
76
76
  * - Added progressive initialization system
77
77
  * - Deferred height calculations for better initial load
78
78
  * - Optimized memory usage for large lists
79
79
  * - Added progress tracking for initialization
80
80
  *
81
+ * 7. Size Management Improvements ✓
82
+ * - Implemented height caching system for measured items
83
+ * - Added smart height estimation for unmeasured items
84
+ * - Optimized resize handling with debouncing
85
+ * - Added height recalculation on content changes
86
+ * - Implemented progressive height adjustments
87
+ *
88
+ * 8. Code Quality & Maintainability ✓
89
+ * - Extracted debug utilities for better testing
90
+ * - Improved type safety throughout
91
+ * - Added comprehensive documentation
92
+ * - Optimized debug output to reduce noise
93
+ *
94
+ * 9. Future Improvements (Planned)
95
+ * - Add horizontal scrolling support
96
+ * - Implement variable-sized item caching
97
+ * - Add keyboard navigation support
98
+ * - Support for dynamic item updates
99
+ * - Add accessibility enhancements
100
+ *
81
101
  * Technical Challenges Solved:
82
102
  * - Bottom-to-top scrolling in flexbox layouts
83
103
  * - Dynamic height calculations without layout thrashing
@@ -86,6 +106,9 @@
86
106
  * - Browser compatibility issues
87
107
  * - Performance optimization for 10k+ items
88
108
  * - Progressive initialization for large datasets
109
+ * - Debug output optimization
110
+ * - Accurate size calculations with caching
111
+ * - Responsive size adjustments
89
112
  *
90
113
  * Current Architecture:
91
114
  * - Four-layer DOM structure for optimal performance
@@ -93,11 +116,14 @@
93
116
  * - Reactive height and scroll calculations
94
117
  * - Configurable buffer zones for smooth scrolling
95
118
  * - Chunked processing system for large datasets
119
+ * - Separated debug utilities for better testing
120
+ * - Height caching and estimation system
121
+ * - Progressive size adjustment system
96
122
  */
97
123
 
98
124
  import { onMount } from 'svelte'
99
125
  import { BROWSER } from 'esm-env'
100
- import type { SvelteVirtualListDebugInfo, SvelteVirtualListProps } from './types.js'
126
+ import type { SvelteVirtualListProps } from './types.js'
101
127
  import {
102
128
  calculateScrollPosition,
103
129
  calculateVisibleRange,
@@ -107,6 +133,7 @@
107
133
  processChunked
108
134
  } from './utils/virtualList.js'
109
135
  import { rafSchedule } from './utils/raf.js'
136
+ import { shouldShowDebugInfo, createDebugInfo } from './utils/virtualListDebug.js'
110
137
 
111
138
  /**
112
139
  * Core configuration props with default values
@@ -161,6 +188,9 @@
161
188
  const chunkSize = $state(50) // Number of items to process in each chunk
162
189
  let processedItems = $state(0) // Number of items processed during initialization
163
190
 
191
+ let prevVisibleRange = $state<{ start: number; end: number } | null>(null)
192
+ let prevHeight = $state<number>(0)
193
+
164
194
  /**
165
195
  * Calculates and updates the average height of visible items with debouncing.
166
196
  *
@@ -234,6 +264,24 @@
234
264
  }
235
265
  })
236
266
 
267
+ // Add new effect to handle height changes
268
+ $effect(() => {
269
+ if (BROWSER && initialized && mode === 'bottomToTop' && viewportElement) {
270
+ const totalHeight = Math.max(0, items.length * calculatedItemHeight)
271
+ const targetScrollTop = Math.max(0, totalHeight - height)
272
+
273
+ // Only update if the difference is significant
274
+ if (Math.abs(viewportElement.scrollTop - targetScrollTop) > calculatedItemHeight) {
275
+ requestAnimationFrame(() => {
276
+ if (viewportElement) {
277
+ viewportElement.scrollTop = targetScrollTop
278
+ scrollTop = targetScrollTop
279
+ }
280
+ })
281
+ }
282
+ }
283
+ })
284
+
237
285
  // Update container height when element is mounted
238
286
  $effect(() => {
239
287
  if (BROWSER && containerElement) {
@@ -251,14 +299,24 @@
251
299
  items.length &&
252
300
  !initialized
253
301
  ) {
254
- const totalHeight = items.length * calculatedItemHeight
302
+ const totalHeight = Math.max(0, items.length * calculatedItemHeight)
303
+ const targetScrollTop = Math.max(0, totalHeight - height)
304
+
255
305
  // Add delay to ensure layout is complete
256
306
  setTimeout(() => {
257
307
  if (viewportElement) {
258
308
  // Start at the bottom for bottom-to-top mode
259
- viewportElement.scrollTop = totalHeight - height
260
- scrollTop = totalHeight - height
261
- initialized = true
309
+ viewportElement.scrollTop = targetScrollTop
310
+ scrollTop = targetScrollTop
311
+
312
+ // Double-check the scroll position after a frame
313
+ requestAnimationFrame(() => {
314
+ if (viewportElement && viewportElement.scrollTop !== targetScrollTop) {
315
+ viewportElement.scrollTop = targetScrollTop
316
+ scrollTop = targetScrollTop
317
+ }
318
+ initialized = true
319
+ })
262
320
  }
263
321
  }, 50)
264
322
  }
@@ -473,6 +531,14 @@
473
531
  }
474
532
  }
475
533
  })
534
+
535
+ // Add the effect in the script section
536
+ $effect(() => {
537
+ if (debug) {
538
+ prevVisibleRange = visibleItems()
539
+ prevHeight = calculatedItemHeight
540
+ }
541
+ })
476
542
  </script>
477
543
 
478
544
  <!--
@@ -518,15 +584,14 @@
518
584
  {#each mode === 'bottomToTop' ? items
519
585
  .slice(visibleItems().start, visibleItems().end)
520
586
  .reverse() : items.slice(visibleItems().start, visibleItems().end) as currentItem, i (currentItem?.id ?? i)}
521
- <!-- Debug output for first item if debug mode is enabled -->
522
- {#if debug && i === 0}
523
- {@const debugInfo: SvelteVirtualListDebugInfo = {
524
- visibleItemsCount: visibleItems().end - visibleItems().start,
525
- startIndex: visibleItems().start,
526
- endIndex: visibleItems().end,
527
- totalItems: items.length,
528
- processedItems
529
- }}
587
+ <!-- Only debug when visible range or average height changes -->
588
+ {#if debug && i === 0 && shouldShowDebugInfo(prevVisibleRange, visibleItems(), prevHeight, calculatedItemHeight)}
589
+ {@const debugInfo = createDebugInfo(
590
+ visibleItems(),
591
+ items.length,
592
+ processedItems,
593
+ calculatedItemHeight
594
+ )}
530
595
  {debugFunction
531
596
  ? debugFunction(debugInfo)
532
597
  : console.log('Virtual List Debug:', debugInfo)}
package/dist/types.d.ts CHANGED
@@ -54,4 +54,5 @@ export type SvelteVirtualListDebugInfo = {
54
54
  totalItems: number;
55
55
  visibleItemsCount: number;
56
56
  processedItems: number;
57
+ averageItemHeight: number;
57
58
  };
@@ -11,8 +11,10 @@
11
11
  * @returns {number} The maximum scroll position in pixels
12
12
  */
13
13
  export const calculateScrollPosition = (totalItems, itemHeight, containerHeight) => {
14
+ if (totalItems === 0)
15
+ return 0;
14
16
  const totalHeight = totalItems * itemHeight;
15
- return totalHeight - containerHeight;
17
+ return Math.max(0, totalHeight - containerHeight);
16
18
  };
17
19
  /**
18
20
  * Determines the range of items that should be rendered in the virtual list.
@@ -0,0 +1,72 @@
1
+ import type { SvelteVirtualListDebugInfo } from '../types.js';
2
+ /**
3
+ * Determines whether debug information should be displayed based on state changes in the virtual list.
4
+ *
5
+ * This function implements an intelligent change detection algorithm that prevents unnecessary debug
6
+ * output while ensuring critical state transitions are captured. It specifically tracks changes in
7
+ * the visible range boundaries and item height calculations to provide meaningful debugging insights
8
+ * without overwhelming the console.
9
+ *
10
+ * Typical usage:
11
+ * ```typescript
12
+ * if (shouldShowDebugInfo(prevRange, currentRange, prevHeight, currentHeight)) {
13
+ * console.log('Virtual list state changed significantly');
14
+ * }
15
+ * ```
16
+ *
17
+ * @param prevRange - Previous visible range state object containing start and end indices
18
+ * @param currentRange - Current visible range state object containing start and end indices
19
+ * @param prevHeight - Previous calculated item height in pixels
20
+ * @param currentHeight - Current calculated item height in pixels
21
+ * @returns {boolean} Returns true if debug information should be displayed based on state changes
22
+ *
23
+ * @example
24
+ * const shouldShow = shouldShowDebugInfo(
25
+ * { start: 0, end: 10 },
26
+ * { start: 5, end: 15 },
27
+ * 100,
28
+ * 120
29
+ * );
30
+ */
31
+ export declare function shouldShowDebugInfo(prevRange: {
32
+ start: number;
33
+ end: number;
34
+ } | null, currentRange: {
35
+ start: number;
36
+ end: number;
37
+ }, prevHeight: number, currentHeight: number): boolean;
38
+ /**
39
+ * Creates a comprehensive debug information object for virtual list state analysis.
40
+ *
41
+ * This utility function generates a structured debug object that captures the complete
42
+ * state of a virtual list at any given moment. It includes critical metrics such as
43
+ * visible item count, viewport boundaries, total items, processing progress, and
44
+ * height calculations. This information is essential for performance monitoring,
45
+ * debugging scroll behavior, and optimizing virtual list configurations.
46
+ *
47
+ * Performance considerations:
48
+ * - All calculations are O(1)
49
+ * - Memory footprint is constant regardless of list size
50
+ * - Safe for high-frequency calls during scroll events
51
+ *
52
+ * @param visibleRange - Current visible range object containing start and end indices
53
+ * @param totalItems - Total number of items in the virtual list
54
+ * @param processedItems - Number of items that have been processed/measured
55
+ * @param averageItemHeight - Current calculated average height per item in pixels
56
+ * @returns {SvelteVirtualListDebugInfo} A structured debug information object
57
+ *
58
+ * @example
59
+ * const debugInfo = createDebugInfo(
60
+ * { start: 0, end: 10 },
61
+ * 1000,
62
+ * 100,
63
+ * 50
64
+ * );
65
+ * console.log('Virtual List State:', debugInfo);
66
+ *
67
+ * @throws {Error} Will throw if end index is less than start index in visibleRange
68
+ */
69
+ export declare function createDebugInfo(visibleRange: {
70
+ start: number;
71
+ end: number;
72
+ }, totalItems: number, processedItems: number, averageItemHeight: number): SvelteVirtualListDebugInfo;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Determines whether debug information should be displayed based on state changes in the virtual list.
3
+ *
4
+ * This function implements an intelligent change detection algorithm that prevents unnecessary debug
5
+ * output while ensuring critical state transitions are captured. It specifically tracks changes in
6
+ * the visible range boundaries and item height calculations to provide meaningful debugging insights
7
+ * without overwhelming the console.
8
+ *
9
+ * Typical usage:
10
+ * ```typescript
11
+ * if (shouldShowDebugInfo(prevRange, currentRange, prevHeight, currentHeight)) {
12
+ * console.log('Virtual list state changed significantly');
13
+ * }
14
+ * ```
15
+ *
16
+ * @param prevRange - Previous visible range state object containing start and end indices
17
+ * @param currentRange - Current visible range state object containing start and end indices
18
+ * @param prevHeight - Previous calculated item height in pixels
19
+ * @param currentHeight - Current calculated item height in pixels
20
+ * @returns {boolean} Returns true if debug information should be displayed based on state changes
21
+ *
22
+ * @example
23
+ * const shouldShow = shouldShowDebugInfo(
24
+ * { start: 0, end: 10 },
25
+ * { start: 5, end: 15 },
26
+ * 100,
27
+ * 120
28
+ * );
29
+ */
30
+ export function shouldShowDebugInfo(prevRange, currentRange, prevHeight, currentHeight) {
31
+ if (!prevRange)
32
+ return true;
33
+ return (prevRange.start !== currentRange.start ||
34
+ prevRange.end !== currentRange.end ||
35
+ prevHeight !== currentHeight);
36
+ }
37
+ /**
38
+ * Creates a comprehensive debug information object for virtual list state analysis.
39
+ *
40
+ * This utility function generates a structured debug object that captures the complete
41
+ * state of a virtual list at any given moment. It includes critical metrics such as
42
+ * visible item count, viewport boundaries, total items, processing progress, and
43
+ * height calculations. This information is essential for performance monitoring,
44
+ * debugging scroll behavior, and optimizing virtual list configurations.
45
+ *
46
+ * Performance considerations:
47
+ * - All calculations are O(1)
48
+ * - Memory footprint is constant regardless of list size
49
+ * - Safe for high-frequency calls during scroll events
50
+ *
51
+ * @param visibleRange - Current visible range object containing start and end indices
52
+ * @param totalItems - Total number of items in the virtual list
53
+ * @param processedItems - Number of items that have been processed/measured
54
+ * @param averageItemHeight - Current calculated average height per item in pixels
55
+ * @returns {SvelteVirtualListDebugInfo} A structured debug information object
56
+ *
57
+ * @example
58
+ * const debugInfo = createDebugInfo(
59
+ * { start: 0, end: 10 },
60
+ * 1000,
61
+ * 100,
62
+ * 50
63
+ * );
64
+ * console.log('Virtual List State:', debugInfo);
65
+ *
66
+ * @throws {Error} Will throw if end index is less than start index in visibleRange
67
+ */
68
+ export function createDebugInfo(visibleRange, totalItems, processedItems, averageItemHeight) {
69
+ return {
70
+ visibleItemsCount: visibleRange.end - visibleRange.start,
71
+ startIndex: visibleRange.start,
72
+ endIndex: visibleRange.end,
73
+ totalItems,
74
+ processedItems,
75
+ averageItemHeight
76
+ };
77
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-virtual-list",
3
- "version": "0.1.6",
3
+ "version": "0.2.1",
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",
@@ -62,7 +62,7 @@
62
62
  "prettier-plugin-organize-imports": "^4.1.0",
63
63
  "prettier-plugin-svelte": "^3.3.2",
64
64
  "publint": "^0.2.12",
65
- "svelte": "^5.16.1",
65
+ "svelte": "^5.16.2",
66
66
  "svelte-check": "^4.1.1",
67
67
  "typescript": "^5.7.2",
68
68
  "vite": "^6.0.7",