@humanspeak/svelte-virtual-list 0.2.6 → 0.3.1-beta.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.
Files changed (32) hide show
  1. package/README.md +50 -13
  2. package/dist/SvelteVirtualList.svelte +619 -179
  3. package/dist/SvelteVirtualList.svelte.d.ts +156 -65
  4. package/dist/reactive-height-manager/INTEGRATION_EXAMPLE.md +136 -0
  5. package/dist/reactive-height-manager/README.md +324 -0
  6. package/dist/reactive-height-manager/ReactiveHeightManager.svelte.d.ts +116 -0
  7. package/dist/reactive-height-manager/ReactiveHeightManager.svelte.js +200 -0
  8. package/dist/reactive-height-manager/benchmark.d.ts +5 -0
  9. package/dist/reactive-height-manager/benchmark.js +25 -0
  10. package/dist/reactive-height-manager/index.d.ts +50 -0
  11. package/dist/reactive-height-manager/index.js +55 -0
  12. package/dist/reactive-height-manager/test/TestComponent.svelte +78 -0
  13. package/dist/reactive-height-manager/test/TestComponent.svelte.d.ts +23 -0
  14. package/dist/reactive-height-manager/types.d.ts +41 -0
  15. package/dist/reactive-height-manager/types.js +1 -0
  16. package/dist/types.d.ts +24 -5
  17. package/dist/utils/heightCalculation.d.ts +18 -8
  18. package/dist/utils/heightCalculation.js +18 -11
  19. package/dist/utils/heightChangeDetection.d.ts +12 -0
  20. package/dist/utils/heightChangeDetection.js +20 -0
  21. package/dist/utils/resizeObserver.d.ts +89 -0
  22. package/dist/utils/resizeObserver.js +119 -0
  23. package/dist/utils/scrollCalculation.d.ts +47 -0
  24. package/dist/utils/scrollCalculation.js +167 -0
  25. package/dist/utils/throttle.d.ts +95 -0
  26. package/dist/utils/throttle.js +155 -0
  27. package/dist/utils/types.d.ts +0 -6
  28. package/dist/utils/virtualList.d.ts +20 -23
  29. package/dist/utils/virtualList.js +153 -61
  30. package/dist/utils/virtualListDebug.d.ts +12 -7
  31. package/dist/utils/virtualListDebug.js +19 -9
  32. package/package.json +33 -31
@@ -47,7 +47,14 @@
47
47
  * - Added comprehensive documentation
48
48
  * - Optimized debug output to reduce noise
49
49
  *
50
- * 9. Future Improvements (Planned)
50
+ * 9. Architecture Refactoring
51
+ * - Extracted scroll calculation logic to scrollCalculation.ts utility
52
+ * - Extracted ResizeObserver utilities to resizeObserver.ts
53
+ * - Added comprehensive test coverage for extracted utilities
54
+ * - Improved separation of concerns and maintainability
55
+ * - Simplified initialization (removed unnecessary chunked processing)
56
+ *
57
+ * 10. Future Improvements (Planned)
51
58
  * - Add horizontal scrolling support
52
59
  * - Implement variable-sized item caching
53
60
  * - Add keyboard navigation support
@@ -65,18 +72,158 @@
65
72
  * - Debug output optimization
66
73
  * - Accurate size calculations with caching
67
74
  * - Responsive size adjustments
75
+ * - Modular architecture with testable utility functions
68
76
  *
69
77
  * Current Architecture:
70
78
  * - Four-layer DOM structure for optimal performance
71
79
  * - State management using Svelte 5's $state
72
80
  * - Reactive height and scroll calculations
73
81
  * - Configurable buffer zones for smooth scrolling
74
- * - Chunked processing system for large datasets
75
- * - Separated debug utilities for better testing
82
+ * - Modular utility system with dedicated helper files:
83
+ * * scrollCalculation.ts: Complex scroll positioning logic
84
+ * * resizeObserver.ts: ResizeObserver management utilities
85
+ * * heightCalculation.ts: Debounced height measurement
86
+ * * virtualList.ts: Core virtual list calculations
87
+ * * virtualListDebug.ts: Debug information utilities
76
88
  * - Height caching and estimation system
77
89
  * - Progressive size adjustment system
78
90
  */
79
91
  import { type SvelteVirtualListProps, type SvelteVirtualListScrollOptions } from './types.js';
92
+ declare function $$render<TItem = any>(): {
93
+ props: SvelteVirtualListProps<TItem>;
94
+ exports: {
95
+ /**
96
+ * Scrolls the virtual list to the item at the given index.
97
+ *
98
+ * @deprecated This function is deprecated and will be removed in a future version.
99
+ * Use the new scroll method from the component instance instead.
100
+ *
101
+ * @function scrollToIndex
102
+ * @param index The index of the item to scroll to.
103
+ * @param smoothScroll (default: true) Whether to use smooth scrolling.
104
+ * @param shouldThrowOnBounds (default: true) Whether to throw an error if the index is out of bounds.
105
+ *
106
+ * @example
107
+ * // Svelte usage:
108
+ * // In your <script> block:
109
+ * import SvelteVirtualList from '@humanspeak/svelte-virtual-list';
110
+ * let virtualList;
111
+ * const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
112
+ *
113
+ * // In your markup:
114
+ * <button onclick={() => virtualList.scrollToIndex(5000)}>
115
+ * Scroll to 5000
116
+ * </button>
117
+ * <SvelteVirtualList {items} bind:this={virtualList}>
118
+ * {#snippet renderItem(item)}
119
+ * <div>{item.text}</div>
120
+ * {/snippet}
121
+ * </SvelteVirtualList>
122
+ *
123
+ * @returns {void}
124
+ * @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
125
+ */ scrollToIndex: (index: number, smoothScroll?: boolean, shouldThrowOnBounds?: boolean) => void;
126
+ /**
127
+ * Scrolls the virtual list to the item at the given index using a type-based options approach.
128
+ *
129
+ * @function scroll
130
+ * @param options Configuration options for scrolling behavior.
131
+ *
132
+ * @example
133
+ * // Svelte usage:
134
+ * // In your <script> block:
135
+ * import SvelteVirtualList from './index.js';
136
+ * let virtualList;
137
+ * const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
138
+ *
139
+ * <button onclick={() => virtualList.scroll({ index: 5000 })}>
140
+ * Scroll to 5000
141
+ * </button>
142
+ * <SvelteVirtualList {items} bind:this={virtualList}>
143
+ * {#snippet renderItem(item)}
144
+ * <div>{item.text}</div>
145
+ * {/snippet}
146
+ * </SvelteVirtualList>
147
+ *
148
+ * @returns {Promise<void>} Promise that resolves when scrolling is complete
149
+ * @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
150
+ */ scroll: (options: SvelteVirtualListScrollOptions) => Promise<void>;
151
+ };
152
+ bindings: "";
153
+ slots: {};
154
+ events: {};
155
+ };
156
+ declare class __sveltets_Render<TItem = any> {
157
+ props(): ReturnType<typeof $$render<TItem>>['props'];
158
+ events(): ReturnType<typeof $$render<TItem>>['events'];
159
+ slots(): ReturnType<typeof $$render<TItem>>['slots'];
160
+ bindings(): "";
161
+ exports(): {
162
+ /**
163
+ * Scrolls the virtual list to the item at the given index.
164
+ *
165
+ * @deprecated This function is deprecated and will be removed in a future version.
166
+ * Use the new scroll method from the component instance instead.
167
+ *
168
+ * @function scrollToIndex
169
+ * @param index The index of the item to scroll to.
170
+ * @param smoothScroll (default: true) Whether to use smooth scrolling.
171
+ * @param shouldThrowOnBounds (default: true) Whether to throw an error if the index is out of bounds.
172
+ *
173
+ * @example
174
+ * // Svelte usage:
175
+ * // In your <script> block:
176
+ * import SvelteVirtualList from '@humanspeak/svelte-virtual-list';
177
+ * let virtualList;
178
+ * const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
179
+ *
180
+ * // In your markup:
181
+ * <button onclick={() => virtualList.scrollToIndex(5000)}>
182
+ * Scroll to 5000
183
+ * </button>
184
+ * <SvelteVirtualList {items} bind:this={virtualList}>
185
+ * {#snippet renderItem(item)}
186
+ * <div>{item.text}</div>
187
+ * {/snippet}
188
+ * </SvelteVirtualList>
189
+ *
190
+ * @returns {void}
191
+ * @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
192
+ */ scrollToIndex: (index: number, smoothScroll?: boolean, shouldThrowOnBounds?: boolean) => void;
193
+ /**
194
+ * Scrolls the virtual list to the item at the given index using a type-based options approach.
195
+ *
196
+ * @function scroll
197
+ * @param options Configuration options for scrolling behavior.
198
+ *
199
+ * @example
200
+ * // Svelte usage:
201
+ * // In your <script> block:
202
+ * import SvelteVirtualList from './index.js';
203
+ * let virtualList;
204
+ * const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
205
+ *
206
+ * <button onclick={() => virtualList.scroll({ index: 5000 })}>
207
+ * Scroll to 5000
208
+ * </button>
209
+ * <SvelteVirtualList {items} bind:this={virtualList}>
210
+ * {#snippet renderItem(item)}
211
+ * <div>{item.text}</div>
212
+ * {/snippet}
213
+ * </SvelteVirtualList>
214
+ *
215
+ * @returns {Promise<void>} Promise that resolves when scrolling is complete
216
+ * @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
217
+ */ scroll: (options: SvelteVirtualListScrollOptions) => Promise<void>;
218
+ };
219
+ }
220
+ interface $$IsomorphicComponent {
221
+ new <TItem = any>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TItem>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TItem>['props']>, ReturnType<__sveltets_Render<TItem>['events']>, ReturnType<__sveltets_Render<TItem>['slots']>> & {
222
+ $$bindings?: ReturnType<__sveltets_Render<TItem>['bindings']>;
223
+ } & ReturnType<__sveltets_Render<TItem>['exports']>;
224
+ <TItem = any>(internal: unknown, props: ReturnType<__sveltets_Render<TItem>['props']> & {}): ReturnType<__sveltets_Render<TItem>['exports']>;
225
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
226
+ }
80
227
  /**
81
228
  * SvelteVirtualList
82
229
  *
@@ -120,15 +267,16 @@ import { type SvelteVirtualListProps, type SvelteVirtualListScrollOptions } from
120
267
  * - Only visible items + buffer are mounted in the DOM
121
268
  * - Height caching and estimation for dynamic content
122
269
  * - Handles resize events and dynamic content changes
123
- * - Supports chunked initialization for very large lists
124
- * - All scrolling logic is centralized in the scroll() method
270
+ * - Optimized for very large lists through virtualization
271
+ * - Modular architecture with extracted utility functions
125
272
  * - Bi-directional support: mode="topToBottom" or "bottomToTop"
126
273
  * - Designed for extensibility and easy debugging
127
274
  *
128
275
  * =============================
129
276
  * == For Contributors ==
130
277
  * =============================
131
- * - Please keep all scrolling logic in the scroll() method
278
+ * - Complex logic is extracted to dedicated utility files in src/lib/utils/
279
+ * - Scroll positioning logic is in scrollCalculation.ts (well-tested)
132
280
  * - Add new features behind feature flags or as optional props
133
281
  * - Write tests for all new features (see /test and /tests/scroll)
134
282
  * - Use TypeScript and Svelte 5 runes for all new code
@@ -138,63 +286,6 @@ import { type SvelteVirtualListProps, type SvelteVirtualListScrollOptions } from
138
286
  *
139
287
  * MIT License © Humanspeak, Inc.
140
288
  */
141
- declare const SvelteVirtualList: import("svelte").Component<SvelteVirtualListProps, {
142
- /**
143
- * Scrolls the virtual list to the item at the given index.
144
- *
145
- * @deprecated This function is deprecated and will be removed in a future version.
146
- * Use the new scroll method from the component instance instead.
147
- *
148
- * @function scrollToIndex
149
- * @param index The index of the item to scroll to.
150
- * @param smoothScroll (default: true) Whether to use smooth scrolling.
151
- * @param shouldThrowOnBounds (default: true) Whether to throw an error if the index is out of bounds.
152
- *
153
- * @example
154
- * // Svelte usage:
155
- * // In your <script> block:
156
- * import SvelteVirtualList from '@humanspeak/svelte-virtual-list';
157
- * let virtualList;
158
- * const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
159
- *
160
- * // In your markup:
161
- * <button onclick={() => virtualList.scrollToIndex(5000)}>
162
- * Scroll to 5000
163
- * </button>
164
- * <SvelteVirtualList {items} bind:this={virtualList}>
165
- * {#snippet renderItem(item)}
166
- * <div>{item.text}</div>
167
- * {/snippet}
168
- * </SvelteVirtualList>
169
- *
170
- * @returns {void}
171
- * @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
172
- */ scrollToIndex: (index: number, smoothScroll?: boolean, shouldThrowOnBounds?: boolean) => void;
173
- /**
174
- * Scrolls the virtual list to the item at the given index using a type-based options approach.
175
- *
176
- * @function scroll
177
- * @param options Configuration options for scrolling behavior.
178
- *
179
- * @example
180
- * // Svelte usage:
181
- * // In your <script> block:
182
- * import SvelteVirtualList from './index.js';
183
- * let virtualList;
184
- * const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
185
- *
186
- * <button onclick={() => virtualList.scroll({ index: 5000 })}>
187
- * Scroll to 5000
188
- * </button>
189
- * <SvelteVirtualList {items} bind:this={virtualList}>
190
- * {#snippet renderItem(item)}
191
- * <div>{item.text}</div>
192
- * {/snippet}
193
- * </SvelteVirtualList>
194
- *
195
- * @returns {void}
196
- * @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
197
- */ scroll: (options: SvelteVirtualListScrollOptions) => void;
198
- }, "">;
199
- type SvelteVirtualList = ReturnType<typeof SvelteVirtualList>;
289
+ declare const SvelteVirtualList: $$IsomorphicComponent;
290
+ type SvelteVirtualList<TItem = any> = InstanceType<typeof SvelteVirtualList<TItem>>;
200
291
  export default SvelteVirtualList;
@@ -0,0 +1,136 @@
1
+ # ReactiveHeightManager Integration Examples
2
+
3
+ > Detailed integration patterns for common use cases
4
+
5
+ **📖 For full documentation, see [README.md](./README.md)**
6
+
7
+ ## SvelteVirtualList Integration
8
+
9
+ ### 1. Import the ReactiveHeightManager
10
+
11
+ ```typescript
12
+ import { ReactiveHeightManager } from '$lib/reactive-height-manager'
13
+ ```
14
+
15
+ ### 2. Create the manager instance
16
+
17
+ ```typescript
18
+ // Create reactive height manager
19
+ const heightManager = new ReactiveHeightManager({
20
+ itemLength: items.length,
21
+ estimatedHeight: defaultEstimatedItemHeight
22
+ })
23
+
24
+ // Update when items change
25
+ $effect(() => {
26
+ heightManager.updateItemLength(items.length)
27
+ })
28
+
29
+ // Update calculated height when it changes
30
+ $effect(() => {
31
+ heightManager.calculatedItemHeight = calculatedItemHeight
32
+ })
33
+ ```
34
+
35
+ ### 3. Modify the calculateAverageHeightDebounced callback
36
+
37
+ ```typescript
38
+ const updateHeight = () => {
39
+ heightUpdateTimeout = calculateAverageHeightDebounced(
40
+ // ... existing params
41
+ (result) => {
42
+ // Critical updates first (synchronous)
43
+ calculatedItemHeight = result.newHeight
44
+ lastMeasuredIndex = result.newLastMeasuredIndex
45
+ heightCache = result.updatedHeightCache
46
+
47
+ // Process dirty heights incrementally - O(dirty items)!
48
+ // If result.heightChanges already has compatible format, pass directly:
49
+ heightManager.processDirtyHeights(result.heightChanges)
50
+
51
+ // Or convert if needed:
52
+ // const heightChanges = result.heightChanges.map((change) => ({
53
+ // index: change.index,
54
+ // oldHeight: change.oldHeight,
55
+ // newHeight: change.newHeight
56
+ // }))
57
+ // heightManager.processDirtyHeights(heightChanges)
58
+
59
+ // Handle scroll correction (bottomToTop mode)
60
+ if (result.heightChanges.length > 0 && mode === 'bottomToTop') {
61
+ handleHeightChangesScrollCorrection(result.heightChanges)
62
+ }
63
+
64
+ // ... rest of callback logic
65
+ }
66
+ )
67
+ }
68
+ ```
69
+
70
+ ### 4. Replace totalHeight derived with reactive manager
71
+
72
+ ```typescript
73
+ // OLD: O(n) calculation every time
74
+ // let totalHeight = $derived(() => {
75
+ // let total = 0
76
+ // for (let i = 0; i < items.length; i++) {
77
+ // total += heightCache[i] || calculatedItemHeight
78
+ // }
79
+ // return total
80
+ // })
81
+
82
+ // NEW: O(1) reactive calculation 🚀
83
+ let totalHeight = $derived(() => heightManager.totalHeight)
84
+ ```
85
+
86
+ ## Performance Benefits
87
+
88
+ - **Before**: O(n) calculation on every height change
89
+ - **After**: O(dirty items) incremental updates + O(1) derived calculation
90
+ - **Result**: ~90%+ performance improvement for large lists (10k+ items)
91
+
92
+ ## Type Compatibility
93
+
94
+ The `ReactiveHeightManager` uses its own types but is designed to be compatible:
95
+
96
+ ```typescript
97
+ // If SvelteVirtualList heightChanges are compatible, use directly:
98
+ heightManager.processDirtyHeights(result.heightChanges)
99
+
100
+ // Or convert if different interface:
101
+ const heightChanges: HeightChange[] = result.heightChanges.map((change) => ({
102
+ index: change.index,
103
+ oldHeight: change.oldHeight,
104
+ newHeight: change.newHeight
105
+ }))
106
+ ```
107
+
108
+ ## Debug and Monitoring
109
+
110
+ ```typescript
111
+ // Performance monitoring
112
+ const debugInfo = heightManager.getDebugInfo()
113
+ console.log(`Coverage: ${debugInfo.coveragePercent.toFixed(1)}%`)
114
+ console.log(`Measured: ${debugInfo.measuredCount}/${debugInfo.itemLength}`)
115
+
116
+ // Check if we have sufficient measurements
117
+ if (heightManager.hasSufficientMeasurements(20)) {
118
+ console.log('Height calculations are highly accurate')
119
+ }
120
+ ```
121
+
122
+ ## Testing
123
+
124
+ The ReactiveHeightManager comes with comprehensive performance tests:
125
+
126
+ ```bash
127
+ # Run specific tests
128
+ npm run test -- ReactiveHeightManager.test.ts --reporter=verbose
129
+
130
+ # Performance benchmarking
131
+ import { benchmarkHeightManager } from '$lib/reactive-height-manager'
132
+
133
+ const results = benchmarkHeightManager(10000, 1000, 100)
134
+ console.log(`Average time: ${results.avgTime.toFixed(2)}ms`)
135
+ console.log(`Operations/sec: ${results.opsPerSecond.toFixed(0)}`)
136
+ ```