@humanspeak/svelte-virtual-list 0.3.1-beta.1 → 0.3.2

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 (26) hide show
  1. package/dist/SvelteVirtualList.svelte +262 -191
  2. package/dist/SvelteVirtualList.svelte.d.ts +5 -5
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.js +2 -0
  5. package/dist/{reactive-height-manager → reactive-list-manager}/INTEGRATION_EXAMPLE.md +10 -10
  6. package/dist/{reactive-height-manager → reactive-list-manager}/README.md +17 -17
  7. package/dist/reactive-list-manager/ReactiveListManager.svelte.d.ts +221 -0
  8. package/dist/reactive-list-manager/ReactiveListManager.svelte.js +635 -0
  9. package/dist/reactive-list-manager/RecomputeScheduler.d.ts +12 -0
  10. package/dist/reactive-list-manager/RecomputeScheduler.js +54 -0
  11. package/dist/reactive-list-manager/benchmark.d.ts +5 -0
  12. package/dist/{reactive-height-manager → reactive-list-manager}/benchmark.js +3 -3
  13. package/dist/{reactive-height-manager → reactive-list-manager}/index.d.ts +8 -12
  14. package/dist/{reactive-height-manager → reactive-list-manager}/index.js +10 -13
  15. package/dist/{reactive-height-manager → reactive-list-manager}/test/TestComponent.svelte +9 -9
  16. package/dist/{reactive-height-manager → reactive-list-manager}/test/TestComponent.svelte.d.ts +5 -5
  17. package/dist/{reactive-height-manager → reactive-list-manager}/types.d.ts +9 -3
  18. package/dist/utils/virtualList.d.ts +2 -2
  19. package/dist/utils/virtualList.js +44 -17
  20. package/package.json +134 -133
  21. package/dist/reactive-height-manager/ReactiveHeightManager.svelte.d.ts +0 -116
  22. package/dist/reactive-height-manager/ReactiveHeightManager.svelte.js +0 -200
  23. package/dist/reactive-height-manager/benchmark.d.ts +0 -5
  24. package/dist/utils/resizeObserver.d.ts +0 -89
  25. package/dist/utils/resizeObserver.js +0 -119
  26. /package/dist/{reactive-height-manager → reactive-list-manager}/types.js +0 -0
@@ -1,116 +0,0 @@
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
- }
@@ -1,200 +0,0 @@
1
- /**
2
- * ReactiveHeightManager - A standalone reactive height calculation system
3
- *
4
- * Efficiently manages height calculations for virtualized lists by:
5
- * - Tracking measured vs unmeasured items incrementally
6
- * - Processing only dirty/changed items (O(dirty) instead of O(all))
7
- * - Providing reactive state updates using Svelte 5 runes
8
- * - Maintaining accurate total height calculations
9
- *
10
- * @example
11
- * ```typescript
12
- * const manager = new ReactiveHeightManager({ itemLength: 10000, estimatedHeight: 40 })
13
- *
14
- * // Process height changes incrementally
15
- * manager.processDirtyHeights(dirtyResults)
16
- *
17
- * // Update calculated item height
18
- * manager.calculatedItemHeight = 42
19
- *
20
- * // Get reactive total height (automatically updates)
21
- * const totalHeight = manager.totalHeight
22
- * ```
23
- */
24
- export class ReactiveHeightManager {
25
- // Reactive state using Svelte 5 runes
26
- _totalMeasuredHeight = $state(0);
27
- _measuredCount = $state(0);
28
- _itemLength = $state(0);
29
- _itemHeight = $state(40);
30
- _averageHeight = $state(40);
31
- _totalHeight = $state(0);
32
- _measuredFlags = null;
33
- recomputeDerivedHeights() {
34
- const average = this._measuredCount > 0
35
- ? this._totalMeasuredHeight / this._measuredCount
36
- : this._itemHeight;
37
- this._averageHeight = average;
38
- const unmeasuredCount = this._itemLength - this._measuredCount;
39
- this._totalHeight = this._totalMeasuredHeight + unmeasuredCount * average;
40
- }
41
- /**
42
- * Get total measured height of all measured items
43
- */
44
- get totalMeasuredHeight() {
45
- return this._totalMeasuredHeight;
46
- }
47
- /**
48
- * Get count of items that have been measured
49
- */
50
- get measuredCount() {
51
- return this._measuredCount;
52
- }
53
- /**
54
- * Get total number of items in the list
55
- */
56
- get itemLength() {
57
- return this._itemLength;
58
- }
59
- /**
60
- * Get/Set the height to use for unmeasured items (reactive)
61
- */
62
- get itemHeight() {
63
- return this._itemHeight;
64
- }
65
- set itemHeight(value) {
66
- this._itemHeight = value;
67
- this.recomputeDerivedHeights();
68
- }
69
- /**
70
- * Get the calculated average height of measured items
71
- * Falls back to itemHeight if no items have been measured yet
72
- */
73
- get averageHeight() {
74
- return this._averageHeight;
75
- }
76
- /**
77
- * Get the reactive total height of all items (measured + estimated)
78
- * This automatically updates when any dependencies change
79
- */
80
- get totalHeight() {
81
- return this._totalHeight;
82
- }
83
- /**
84
- * Create a new ReactiveHeightManager instance
85
- *
86
- * @param config - Configuration object containing itemLength and itemHeight
87
- */
88
- constructor(config) {
89
- this._itemLength = config.itemLength;
90
- this._itemHeight = config.itemHeight;
91
- this._measuredFlags = new Uint8Array(Math.max(0, this._itemLength));
92
- this.recomputeDerivedHeights();
93
- }
94
- /**
95
- * Process height changes incrementally - O(dirty items) instead of O(all items)
96
- *
97
- * This is the core optimization: instead of recalculating totals for all items,
98
- * we only process the items that have changed, maintaining running totals.
99
- *
100
- * Accepts any object that has index, oldHeight, and newHeight properties,
101
- * allowing consumers to pass objects with additional fields.
102
- *
103
- * @param dirtyResults - Array of height changes to process
104
- */
105
- processDirtyHeights(dirtyResults) {
106
- if (dirtyResults.length === 0)
107
- return;
108
- // Batch calculate changes to trigger reactivity only once
109
- let heightDelta = 0;
110
- let countDelta = 0;
111
- for (const change of dirtyResults) {
112
- const { index, oldHeight, newHeight } = change;
113
- // Remove old contribution if it existed
114
- if (oldHeight !== undefined) {
115
- heightDelta -= oldHeight;
116
- countDelta -= 1;
117
- }
118
- // Add new contribution
119
- if (newHeight !== undefined) {
120
- heightDelta += newHeight;
121
- countDelta += 1;
122
- }
123
- // Track measured flag (best-effort; full coalescing handled separately)
124
- if (this._measuredFlags && index >= 0 && index < this._measuredFlags.length) {
125
- this._measuredFlags[index] = 1;
126
- }
127
- }
128
- if (heightDelta === 0 && countDelta === 0)
129
- return;
130
- // Apply all changes at once - triggers reactivity only once
131
- this._totalMeasuredHeight += heightDelta;
132
- this._measuredCount += countDelta;
133
- this.recomputeDerivedHeights();
134
- }
135
- /**
136
- * Update when items array length changes
137
- *
138
- * @param newLength - New total number of items
139
- */
140
- updateItemLength(newLength) {
141
- this._itemLength = newLength;
142
- this._measuredFlags = new Uint8Array(Math.max(0, newLength));
143
- this.recomputeDerivedHeights();
144
- }
145
- /**
146
- * Update estimated height for unmeasured items
147
- *
148
- * @param newEstimatedHeight - New estimated height
149
- */
150
- updateEstimatedHeight(newEstimatedHeight) {
151
- // Keep a single source of truth for the estimated height
152
- this._itemHeight = newEstimatedHeight;
153
- this.recomputeDerivedHeights();
154
- }
155
- /**
156
- * Reset all state to initial values
157
- *
158
- * Useful for testing or when completely reinitializing the list
159
- */
160
- reset() {
161
- this._totalMeasuredHeight = 0;
162
- this._measuredCount = 0;
163
- this._measuredFlags = this._itemLength > 0 ? new Uint8Array(this._itemLength) : null;
164
- // Note: Don't reset _itemLength, _itemHeight as they represent configuration, not measured state
165
- this.recomputeDerivedHeights();
166
- }
167
- /**
168
- * Get comprehensive debug information
169
- *
170
- * @returns Debug information object
171
- */
172
- getDebugInfo() {
173
- return {
174
- totalMeasuredHeight: this._totalMeasuredHeight,
175
- measuredCount: this._measuredCount,
176
- itemLength: this._itemLength,
177
- coveragePercent: this._itemLength > 0 ? (this._measuredCount / this._itemLength) * 100 : 0,
178
- itemHeight: this._itemHeight,
179
- averageHeight: this.averageHeight,
180
- totalHeight: this.totalHeight
181
- };
182
- }
183
- /**
184
- * Get the percentage of items that have been measured
185
- *
186
- * @returns Percentage (0-100) of measured items
187
- */
188
- getMeasurementCoverage() {
189
- return this.getDebugInfo().coveragePercent;
190
- }
191
- /**
192
- * Check if the manager has sufficient measurement data
193
- *
194
- * @param threshold - Minimum percentage of items that should be measured (default: 10)
195
- * @returns true if coverage meets threshold
196
- */
197
- hasSufficientMeasurements(threshold = 10) {
198
- return this.getMeasurementCoverage() >= threshold;
199
- }
200
- }
@@ -1,5 +0,0 @@
1
- export declare function benchmarkHeightManager(itemCount: number, dirtyCount: number, iterations?: number): {
2
- avgTime: number;
3
- totalTime: number;
4
- opsPerSecond: number;
5
- };
@@ -1,89 +0,0 @@
1
- /**
2
- * Configuration for item resize observation
3
- */
4
- export interface ItemResizeConfig {
5
- /** Debug mode for logging resize events */
6
- debug?: boolean;
7
- /** Callback when items are marked as dirty */
8
- onItemsDirty?: (dirtyIndices: Set<number>) => void;
9
- }
10
- /**
11
- * Configuration for container resize observation
12
- */
13
- export interface ContainerResizeConfig {
14
- /** Debug mode for logging resize events */
15
- debug?: boolean;
16
- /** Callback when container is resized */
17
- onResize?: (entry: ResizeObserverEntry) => void;
18
- }
19
- /**
20
- * Creates a ResizeObserver for monitoring container size changes.
21
- *
22
- * This function creates a ResizeObserver that watches for size changes in the
23
- * virtual list container and triggers appropriate updates to height and scroll position.
24
- *
25
- * @param config - Configuration options
26
- * @returns ResizeObserver instance
27
- *
28
- * @example
29
- * ```typescript
30
- * const containerResizeObserver = createContainerResizeObserver({
31
- * debug: true,
32
- * onResize: (entry) => {
33
- * const newHeight = entry.contentRect.height
34
- * updateHeightAndScroll(true)
35
- * }
36
- * })
37
- *
38
- * if (containerElement) {
39
- * containerResizeObserver.observe(containerElement)
40
- * }
41
- * ```
42
- */
43
- export declare const createContainerResizeObserver: (config?: ContainerResizeConfig) => ResizeObserver;
44
- /**
45
- * Utility to safely observe elements with automatic cleanup.
46
- *
47
- * This function provides a safe way to observe elements with a ResizeObserver,
48
- * handling cases where the observer might not be available or elements might be null.
49
- *
50
- * @param observer - ResizeObserver instance
51
- * @param element - Element to observe
52
- * @param debug - Whether to log debug information
53
- * @returns Cleanup function to stop observing
54
- *
55
- * @example
56
- * ```typescript
57
- * const cleanup = safeObserve(resizeObserver, element, true)
58
- *
59
- * // Later, stop observing
60
- * cleanup()
61
- * ```
62
- */
63
- export declare const safeObserve: (observer: ResizeObserver | null, element: HTMLElement | null, debug?: boolean) => (() => void);
64
- /**
65
- * Manages multiple ResizeObserver instances with automatic cleanup.
66
- *
67
- * This class provides a convenient way to manage multiple ResizeObserver instances
68
- * and ensures proper cleanup when the component is destroyed.
69
- */
70
- export declare class ResizeObserverManager {
71
- private observers;
72
- private cleanupFunctions;
73
- /**
74
- * Adds a ResizeObserver to the manager
75
- */
76
- addObserver(observer: ResizeObserver): void;
77
- /**
78
- * Adds a cleanup function to be called during cleanup
79
- */
80
- addCleanup(cleanup: () => void): void;
81
- /**
82
- * Observes an element with automatic cleanup tracking
83
- */
84
- observe(observer: ResizeObserver, element: HTMLElement, debug?: boolean): void;
85
- /**
86
- * Disconnects all observers and runs cleanup functions
87
- */
88
- cleanup(): void;
89
- }
@@ -1,119 +0,0 @@
1
- /**
2
- * Creates a ResizeObserver for monitoring container size changes.
3
- *
4
- * This function creates a ResizeObserver that watches for size changes in the
5
- * virtual list container and triggers appropriate updates to height and scroll position.
6
- *
7
- * @param config - Configuration options
8
- * @returns ResizeObserver instance
9
- *
10
- * @example
11
- * ```typescript
12
- * const containerResizeObserver = createContainerResizeObserver({
13
- * debug: true,
14
- * onResize: (entry) => {
15
- * const newHeight = entry.contentRect.height
16
- * updateHeightAndScroll(true)
17
- * }
18
- * })
19
- *
20
- * if (containerElement) {
21
- * containerResizeObserver.observe(containerElement)
22
- * }
23
- * ```
24
- */
25
- export const createContainerResizeObserver = (config = {}) => {
26
- const { debug = false, onResize } = config;
27
- return new ResizeObserver((entries) => {
28
- for (const entry of entries) {
29
- if (debug) {
30
- console.log('Container resized:', entry.contentRect);
31
- }
32
- onResize?.(entry);
33
- }
34
- });
35
- };
36
- /**
37
- * Utility to safely observe elements with automatic cleanup.
38
- *
39
- * This function provides a safe way to observe elements with a ResizeObserver,
40
- * handling cases where the observer might not be available or elements might be null.
41
- *
42
- * @param observer - ResizeObserver instance
43
- * @param element - Element to observe
44
- * @param debug - Whether to log debug information
45
- * @returns Cleanup function to stop observing
46
- *
47
- * @example
48
- * ```typescript
49
- * const cleanup = safeObserve(resizeObserver, element, true)
50
- *
51
- * // Later, stop observing
52
- * cleanup()
53
- * ```
54
- */
55
- export const safeObserve = (observer, element, debug = false) => {
56
- if (observer && element) {
57
- observer.observe(element);
58
- if (debug) {
59
- console.log('Started observing element:', element);
60
- }
61
- return () => {
62
- if (observer && element) {
63
- observer.unobserve(element);
64
- if (debug) {
65
- console.log('Stopped observing element:', element);
66
- }
67
- }
68
- };
69
- }
70
- if (debug && !observer) {
71
- console.log('ResizeObserver not available for element:', element);
72
- }
73
- return () => { }; // No-op cleanup function
74
- };
75
- /**
76
- * Manages multiple ResizeObserver instances with automatic cleanup.
77
- *
78
- * This class provides a convenient way to manage multiple ResizeObserver instances
79
- * and ensures proper cleanup when the component is destroyed.
80
- */
81
- export class ResizeObserverManager {
82
- observers = [];
83
- cleanupFunctions = [];
84
- /**
85
- * Adds a ResizeObserver to the manager
86
- */
87
- addObserver(observer) {
88
- this.observers.push(observer);
89
- }
90
- /**
91
- * Adds a cleanup function to be called during cleanup
92
- */
93
- addCleanup(cleanup) {
94
- this.cleanupFunctions.push(cleanup);
95
- }
96
- /**
97
- * Observes an element with automatic cleanup tracking
98
- */
99
- observe(observer, element, debug = false) {
100
- const cleanup = safeObserve(observer, element, debug);
101
- this.addCleanup(cleanup);
102
- }
103
- /**
104
- * Disconnects all observers and runs cleanup functions
105
- */
106
- cleanup() {
107
- // Disconnect all observers
108
- for (const observer of this.observers) {
109
- observer.disconnect();
110
- }
111
- // Run all cleanup functions
112
- for (const cleanup of this.cleanupFunctions) {
113
- cleanup();
114
- }
115
- // Clear arrays
116
- this.observers.length = 0;
117
- this.cleanupFunctions.length = 0;
118
- }
119
- }