@humanspeak/svelte-virtual-list 0.1.5 → 0.2.0
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 +25 -15
- package/dist/SvelteVirtualList.svelte +85 -20
- package/dist/types.d.ts +1 -0
- package/dist/utils/virtualList.js +3 -1
- package/dist/utils/virtualListDebug.d.ts +72 -0
- package/dist/utils/virtualListDebug.js +77 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -217,21 +217,31 @@ No manual intervention is needed when item contents or dimensions change.
|
|
|
217
217
|
}))
|
|
218
218
|
</script>
|
|
219
219
|
|
|
220
|
-
<
|
|
221
|
-
items={messages}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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 {
|
|
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 =
|
|
260
|
-
scrollTop =
|
|
261
|
-
|
|
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
|
-
<!--
|
|
522
|
-
{#if debug && i === 0}
|
|
523
|
-
{@const debugInfo
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
@@ -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.
|
|
3
|
+
"version": "0.2.0",
|
|
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.
|
|
65
|
+
"svelte": "^5.16.2",
|
|
66
66
|
"svelte-check": "^4.1.1",
|
|
67
67
|
"typescript": "^5.7.2",
|
|
68
68
|
"vite": "^6.0.7",
|