@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.
- package/dist/SvelteVirtualList.svelte +461 -130
- package/dist/SvelteVirtualList.svelte.d.ts +2 -2
- package/dist/reactive-height-manager/INTEGRATION_EXAMPLE.md +136 -0
- package/dist/reactive-height-manager/README.md +324 -0
- package/dist/reactive-height-manager/ReactiveHeightManager.svelte.d.ts +116 -0
- package/dist/reactive-height-manager/ReactiveHeightManager.svelte.js +200 -0
- package/dist/reactive-height-manager/benchmark.d.ts +5 -0
- package/dist/reactive-height-manager/benchmark.js +25 -0
- package/dist/reactive-height-manager/index.d.ts +50 -0
- package/dist/reactive-height-manager/index.js +55 -0
- package/dist/reactive-height-manager/types.d.ts +41 -0
- package/dist/reactive-height-manager/types.js +1 -0
- package/dist/utils/heightCalculation.d.ts +8 -1
- package/dist/utils/heightCalculation.js +5 -4
- package/dist/utils/heightChangeDetection.d.ts +12 -0
- package/dist/utils/heightChangeDetection.js +20 -0
- package/dist/utils/resizeObserver.d.ts +0 -33
- package/dist/utils/resizeObserver.js +0 -57
- package/dist/utils/scrollCalculation.d.ts +2 -2
- package/dist/utils/scrollCalculation.js +34 -21
- package/dist/utils/throttle.d.ts +95 -0
- package/dist/utils/throttle.js +155 -0
- package/dist/utils/virtualList.d.ts +11 -14
- package/dist/utils/virtualList.js +100 -53
- package/dist/utils/virtualListDebug.d.ts +1 -1
- package/dist/utils/virtualListDebug.js +1 -2
- package/package.json +22 -21
|
@@ -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
|
+
<!-- [](./ReactiveHeightManager.test.ts)
|
|
6
|
+
[](#performance)
|
|
7
|
+
[](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
|
+
}
|