@humanspeak/svelte-virtual-list 0.2.6-beta.5 → 0.2.6-beta.7

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.
@@ -205,9 +205,9 @@ declare const SvelteVirtualList: import("svelte").Component<SvelteVirtualListPro
205
205
  * {/snippet}
206
206
  * </SvelteVirtualList>
207
207
  *
208
- * @returns {void}
208
+ * @returns {Promise<void>} Promise that resolves when scrolling is complete
209
209
  * @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
210
- */ scroll: (options: SvelteVirtualListScrollOptions) => void;
210
+ */ scroll: (options: SvelteVirtualListScrollOptions) => Promise<void>;
211
211
  }, "">;
212
212
  type SvelteVirtualList = ReturnType<typeof SvelteVirtualList>;
213
213
  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
+ ```
@@ -0,0 +1,324 @@
1
+ # ReactiveHeightManager
2
+
3
+ > A standalone, high-performance reactive height calculation system for virtualized lists
4
+
5
+ <!-- [![Tests](https://img.shields.io/badge/tests-13%20passing-brightgreen)](./ReactiveHeightManager.test.ts)
6
+ [![Performance](https://img.shields.io/badge/performance-1000%20updates%20%3C1ms-blue)](#performance)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/) -->
8
+
9
+ ## 🚀 Features
10
+
11
+ - **Incremental Processing**: O(dirty items) instead of O(all items)
12
+ - **Reactive State**: Built with Svelte 5 runes for automatic updates
13
+ - **Framework Agnostic**: Standalone types, no external dependencies
14
+ - **High Performance**: 1000+ height updates processed in <1ms
15
+ - **Memory Efficient**: Tracks only measured vs estimated items
16
+ - **Comprehensive Testing**: 13 test cases including performance benchmarks
17
+
18
+ ## 🎯 Problem It Solves
19
+
20
+ Traditional virtual list height calculations loop through **all items** on every change:
21
+
22
+ ```typescript
23
+ // ❌ O(n) - Slow for large lists
24
+ let totalHeight = 0
25
+ for (let i = 0; i < items.length; i++) {
26
+ totalHeight += heightCache[i] || estimatedHeight
27
+ }
28
+ ```
29
+
30
+ ReactiveHeightManager processes only **dirty/changed items**:
31
+
32
+ ```typescript
33
+ // ✅ O(dirty items) - Fast and reactive
34
+ manager.processDirtyHeights(changedItems)
35
+ const totalHeight = manager.getDerivedTotalHeight()
36
+ ```
37
+
38
+ ## 📦 Installation
39
+
40
+ ```bash
41
+ # If using within svelte-virtual-list project
42
+ import { ReactiveHeightManager } from '$lib/reactive-height-manager'
43
+
44
+ # For standalone usage (copy the module)
45
+ cp -r src/lib/reactive-height-manager your-project/src/lib/
46
+ ```
47
+
48
+ ## 🚀 Quick Start
49
+
50
+ ```typescript
51
+ import { ReactiveHeightManager } from './reactive-height-manager'
52
+
53
+ // Create manager
54
+ const manager = new ReactiveHeightManager({
55
+ itemLength: 10000,
56
+ itemHeight: 40
57
+ })
58
+
59
+ // Process height changes
60
+ const heightChanges = [
61
+ { index: 0, oldHeight: undefined, newHeight: 45 },
62
+ { index: 1, oldHeight: 40, newHeight: 50 }
63
+ ]
64
+ manager.processDirtyHeights(heightChanges)
65
+
66
+ // Update calculated item height
67
+ manager.calculatedItemHeight = 42
68
+
69
+ // Get reactive total height (automatically updates)
70
+ const totalHeight = manager.totalHeight
71
+ console.log(`Total height: ${totalHeight}px`)
72
+ ```
73
+
74
+ ## 📖 API Documentation
75
+
76
+ ### Constructor
77
+
78
+ ```typescript
79
+ new ReactiveHeightManager(config: HeightManagerConfig)
80
+ ```
81
+
82
+ **Parameters:**
83
+
84
+ - `config.itemLength` - Total number of items
85
+ - `config.estimatedHeight` - Default height for unmeasured items
86
+
87
+ ### Core Methods
88
+
89
+ #### `processDirtyHeights(changes: HeightChange[])`
90
+
91
+ Process height changes incrementally. This is the performance-critical method.
92
+
93
+ ```typescript
94
+ const changes = [{ index: 0, oldHeight: undefined, newHeight: 45 }]
95
+ manager.processDirtyHeights(changes)
96
+ ```
97
+
98
+ #### `totalHeight` (getter)
99
+
100
+ Get total height of all items (measured + estimated). This property is reactive and automatically updates when dependencies change.
101
+
102
+ ```typescript
103
+ const totalHeight = manager.totalHeight // Automatically reactive
104
+ ```
105
+
106
+ #### `calculatedItemHeight` (getter/setter)
107
+
108
+ Get or set the calculated average item height, which affects total height calculations.
109
+
110
+ ```typescript
111
+ manager.calculatedItemHeight = 42 // Updates totalHeight automatically
112
+ const currentHeight = manager.calculatedItemHeight
113
+ ```
114
+
115
+ ### State Management
116
+
117
+ #### `updateItemLength(newLength: number)`
118
+
119
+ Update when items array changes.
120
+
121
+ #### `updateEstimatedHeight(newHeight: number)`
122
+
123
+ Update estimated height for unmeasured items.
124
+
125
+ #### `reset()`
126
+
127
+ Reset all state to initial values.
128
+
129
+ ### Utilities
130
+
131
+ #### `getDebugInfo(): HeightManagerDebugInfo`
132
+
133
+ Get comprehensive debug information.
134
+
135
+ ```typescript
136
+ const debug = manager.getDebugInfo()
137
+ console.log(`Coverage: ${debug.coveragePercent}%`)
138
+ console.log(`Measured: ${debug.measuredCount}/${debug.itemLength}`)
139
+ ```
140
+
141
+ #### `getMeasurementCoverage(): number`
142
+
143
+ Get percentage of items measured (0-100).
144
+
145
+ #### `hasSufficientMeasurements(threshold?: number): boolean`
146
+
147
+ Check if manager has sufficient measurement data.
148
+
149
+ ## 🎨 Integration Examples
150
+
151
+ ### With SvelteVirtualList
152
+
153
+ ```typescript
154
+ // Create manager
155
+ const heightManager = new ReactiveHeightManager({
156
+ itemLength: items.length,
157
+ estimatedHeight: defaultEstimatedItemHeight
158
+ })
159
+
160
+ // Update on items change
161
+ $effect(() => {
162
+ heightManager.updateItemLength(items.length)
163
+ })
164
+
165
+ // Process in callback
166
+ const updateHeight = () => {
167
+ heightUpdateTimeout = calculateAverageHeightDebounced(
168
+ // ... params
169
+ (result) => {
170
+ // Convert types if needed (or pass directly if compatible)
171
+ const heightChanges = result.heightChanges
172
+
173
+ // Process incrementally
174
+ heightManager.processDirtyHeights(heightChanges)
175
+ }
176
+ )
177
+ }
178
+
179
+ // Update calculated height when needed
180
+ $effect(() => {
181
+ heightManager.calculatedItemHeight = calculatedItemHeight
182
+ })
183
+
184
+ // Reactive total height (automatically updates)
185
+ let totalHeight = $derived(() => heightManager.totalHeight)
186
+ ```
187
+
188
+ ### Standalone Usage
189
+
190
+ ```typescript
191
+ import { ReactiveHeightManager, benchmarkHeightManager } from './reactive-height-manager'
192
+
193
+ const manager = new ReactiveHeightManager({ itemLength: 1000, estimatedHeight: 50 })
194
+
195
+ // Performance monitoring
196
+ const results = benchmarkHeightManager(10000, 1000, 100)
197
+ console.log(`Average time: ${results.avgTime.toFixed(2)}ms`)
198
+ console.log(`Operations/sec: ${results.opsPerSecond.toFixed(0)}`)
199
+
200
+ // Test reactive totalHeight
201
+ manager.calculatedItemHeight = 50 // Triggers reactive update
202
+ console.log(`New total height: ${manager.totalHeight}`)
203
+ ```
204
+
205
+ ## ⚡ Performance
206
+
207
+ ### Benchmarks
208
+
209
+ | Operation | Items | Time | Performance |
210
+ | -------------------- | ------ | ------ | ------------ |
211
+ | 1,000 dirty updates | 10,000 | < 1ms | 🚀 Excellent |
212
+ | 10,000 dirty updates | 10,000 | < 10ms | 🚀 Excellent |
213
+ | Complex scenarios | 5,000 | < 25ms | ✅ Good |
214
+
215
+ ### Memory Usage
216
+
217
+ - **Measured Items**: Tracked incrementally
218
+ - **Unmeasured Items**: Single multiplier calculation
219
+ - **State**: Only 4 reactive variables total
220
+
221
+ ### Compared to O(n) Loop
222
+
223
+ ```typescript
224
+ // Before: O(n) calculation every time
225
+ for (let i = 0; i < 100000; i++) {
226
+ /* ... */
227
+ } // ~10-50ms
228
+
229
+ // After: O(1) reactive access
230
+ manager.totalHeight // ~0.01ms
231
+ ```
232
+
233
+ ## 🧪 Testing
234
+
235
+ ```bash
236
+ # Run all tests
237
+ npm run test -- ReactiveHeightManager.test.ts
238
+
239
+ # Verbose output
240
+ npm run test -- ReactiveHeightManager.test.ts --reporter=verbose
241
+
242
+ # Performance benchmarking
243
+ npm run test -- --grep "Performance Tests"
244
+ ```
245
+
246
+ ### Test Coverage
247
+
248
+ - ✅ **Initialization** (2 tests)
249
+ - ✅ **Performance** (3 tests) - Sub-millisecond operations
250
+ - ✅ **Accuracy** (2 tests) - Complex scenarios with alternating heights
251
+ - ✅ **State Management** (3 tests) - Updates, resets, configuration
252
+ - ✅ **Utilities** (3 tests) - Coverage tracking, debug info
253
+
254
+ ## 🏗️ Architecture
255
+
256
+ ### Core Principles
257
+
258
+ 1. **Reactive State**: Uses Svelte 5 `$state` runes
259
+ 2. **Incremental Processing**: Only process changed items
260
+ 3. **Memory Efficiency**: Track totals, not individual measurements
261
+ 4. **Type Safety**: Comprehensive TypeScript interfaces
262
+
263
+ ### Data Flow
264
+
265
+ ```text
266
+
267
+ Height Changes → processDirtyHeights() → Update State → getDerivedTotalHeight() → Reactive UI
268
+
269
+ ```
270
+
271
+ ### Internal State
272
+
273
+ ```typescript
274
+ private _totalMeasuredHeight = $state(0) // Sum of all measured heights
275
+ private _measuredCount = $state(0) // Count of measured items
276
+ private _itemLength = $state(0) // Total items
277
+ private _estimatedHeight = $state(40) // Default estimate
278
+ ```
279
+
280
+ ## 🔧 Types
281
+
282
+ ```typescript
283
+ interface HeightChange {
284
+ readonly index: number
285
+ readonly oldHeight: number | undefined
286
+ readonly newHeight: number
287
+ }
288
+
289
+ interface HeightManagerConfig {
290
+ itemLength: number
291
+ estimatedHeight: number
292
+ }
293
+
294
+ interface HeightManagerDebugInfo {
295
+ totalMeasuredHeight: number
296
+ measuredCount: number
297
+ itemLength: number
298
+ coveragePercent: number
299
+ estimatedHeight: number
300
+ }
301
+ ```
302
+
303
+ ## 📈 Roadmap
304
+
305
+ - [ ] Batch processing for very large change sets
306
+ - [ ] Configurable block-based calculations
307
+ - [ ] Export as standalone npm package
308
+ - [ ] React/Vue adapter examples
309
+ - [ ] Web Worker support for massive datasets
310
+
311
+ ## 🤝 Contributing
312
+
313
+ 1. Run tests: `npm run test -- ReactiveHeightManager.test.ts`
314
+ 2. Add performance benchmarks for new features
315
+ 3. Maintain O(1) or O(dirty) complexity
316
+ 4. Update type definitions
317
+
318
+ ## 📄 License
319
+
320
+ Part of the svelte-virtual-list project. See main [LICENSE](../../../LICENSE) file.
321
+
322
+ ---
323
+
324
+ **Need help?** Check the [Integration Examples](./INTEGRATION_EXAMPLE.md) for detailed usage patterns.
@@ -0,0 +1,116 @@
1
+ import type { HeightChange, HeightManagerConfig, HeightManagerDebugInfo } from './types.js';
2
+ /**
3
+ * ReactiveHeightManager - A standalone reactive height calculation system
4
+ *
5
+ * Efficiently manages height calculations for virtualized lists by:
6
+ * - Tracking measured vs unmeasured items incrementally
7
+ * - Processing only dirty/changed items (O(dirty) instead of O(all))
8
+ * - Providing reactive state updates using Svelte 5 runes
9
+ * - Maintaining accurate total height calculations
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const manager = new ReactiveHeightManager({ itemLength: 10000, estimatedHeight: 40 })
14
+ *
15
+ * // Process height changes incrementally
16
+ * manager.processDirtyHeights(dirtyResults)
17
+ *
18
+ * // Update calculated item height
19
+ * manager.calculatedItemHeight = 42
20
+ *
21
+ * // Get reactive total height (automatically updates)
22
+ * const totalHeight = manager.totalHeight
23
+ * ```
24
+ */
25
+ export declare class ReactiveHeightManager {
26
+ private _totalMeasuredHeight;
27
+ private _measuredCount;
28
+ private _itemLength;
29
+ private _itemHeight;
30
+ private _averageHeight;
31
+ private _totalHeight;
32
+ private _measuredFlags;
33
+ private recomputeDerivedHeights;
34
+ /**
35
+ * Get total measured height of all measured items
36
+ */
37
+ get totalMeasuredHeight(): number;
38
+ /**
39
+ * Get count of items that have been measured
40
+ */
41
+ get measuredCount(): number;
42
+ /**
43
+ * Get total number of items in the list
44
+ */
45
+ get itemLength(): number;
46
+ /**
47
+ * Get/Set the height to use for unmeasured items (reactive)
48
+ */
49
+ get itemHeight(): number;
50
+ set itemHeight(value: number);
51
+ /**
52
+ * Get the calculated average height of measured items
53
+ * Falls back to itemHeight if no items have been measured yet
54
+ */
55
+ get averageHeight(): number;
56
+ /**
57
+ * Get the reactive total height of all items (measured + estimated)
58
+ * This automatically updates when any dependencies change
59
+ */
60
+ get totalHeight(): number;
61
+ /**
62
+ * Create a new ReactiveHeightManager instance
63
+ *
64
+ * @param config - Configuration object containing itemLength and itemHeight
65
+ */
66
+ constructor(config: HeightManagerConfig);
67
+ /**
68
+ * Process height changes incrementally - O(dirty items) instead of O(all items)
69
+ *
70
+ * This is the core optimization: instead of recalculating totals for all items,
71
+ * we only process the items that have changed, maintaining running totals.
72
+ *
73
+ * Accepts any object that has index, oldHeight, and newHeight properties,
74
+ * allowing consumers to pass objects with additional fields.
75
+ *
76
+ * @param dirtyResults - Array of height changes to process
77
+ */
78
+ processDirtyHeights(dirtyResults: HeightChange[]): void;
79
+ /**
80
+ * Update when items array length changes
81
+ *
82
+ * @param newLength - New total number of items
83
+ */
84
+ updateItemLength(newLength: number): void;
85
+ /**
86
+ * Update estimated height for unmeasured items
87
+ *
88
+ * @param newEstimatedHeight - New estimated height
89
+ */
90
+ updateEstimatedHeight(newEstimatedHeight: number): void;
91
+ /**
92
+ * Reset all state to initial values
93
+ *
94
+ * Useful for testing or when completely reinitializing the list
95
+ */
96
+ reset(): void;
97
+ /**
98
+ * Get comprehensive debug information
99
+ *
100
+ * @returns Debug information object
101
+ */
102
+ getDebugInfo(): HeightManagerDebugInfo;
103
+ /**
104
+ * Get the percentage of items that have been measured
105
+ *
106
+ * @returns Percentage (0-100) of measured items
107
+ */
108
+ getMeasurementCoverage(): number;
109
+ /**
110
+ * Check if the manager has sufficient measurement data
111
+ *
112
+ * @param threshold - Minimum percentage of items that should be measured (default: 10)
113
+ * @returns true if coverage meets threshold
114
+ */
115
+ hasSufficientMeasurements(threshold?: number): boolean;
116
+ }