@humanspeak/svelte-virtual-list 0.2.6-beta.0 → 0.2.6-beta.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.
@@ -0,0 +1,114 @@
1
+ import { processChunked } from './virtualList.js';
2
+ /**
3
+ * Determines whether to use chunked initialization based on item count and threshold.
4
+ *
5
+ * @param itemCount - Number of items to initialize
6
+ * @param threshold - Threshold above which chunked initialization is used (default: 1000)
7
+ * @returns True if chunked initialization should be used
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const useChunked = shouldUseChunkedInitialization(5000) // true
12
+ * const useImmediate = shouldUseChunkedInitialization(500) // false
13
+ * ```
14
+ */
15
+ export const shouldUseChunkedInitialization = (itemCount, threshold = 1000) => {
16
+ return itemCount > threshold;
17
+ };
18
+ /**
19
+ * Initializes a virtual list with items, using chunked processing for large datasets.
20
+ *
21
+ * This function automatically determines whether to use immediate or chunked initialization
22
+ * based on the number of items. For large datasets, it processes items in chunks to
23
+ * prevent UI blocking, yielding to the main thread between chunks.
24
+ *
25
+ * @param config - Configuration object for initialization
26
+ * @returns Promise that resolves when initialization is complete
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * import { initializeVirtualList } from './initialization.js'
31
+ *
32
+ * // Initialize with progress tracking
33
+ * await initializeVirtualList({
34
+ * items: largeDataset,
35
+ * chunkSize: 50,
36
+ * onProgress: (processed, total) => {
37
+ * console.log(`Progress: ${processed}/${total}`)
38
+ * },
39
+ * onComplete: () => {
40
+ * console.log('Initialization complete!')
41
+ * }
42
+ * })
43
+ * ```
44
+ */
45
+ export const initializeVirtualList = async (config) => {
46
+ const { items, chunkSize, chunkThreshold = 1000, onProgress, onComplete } = config;
47
+ if (!items.length) {
48
+ onComplete?.();
49
+ return;
50
+ }
51
+ if (shouldUseChunkedInitialization(items.length, chunkThreshold)) {
52
+ await processChunked(items, chunkSize, (processedItems) => onProgress?.(processedItems, items.length), () => onComplete?.());
53
+ }
54
+ else {
55
+ // Immediate initialization for small datasets
56
+ onProgress?.(items.length, items.length);
57
+ onComplete?.();
58
+ }
59
+ };
60
+ /**
61
+ * Calculates the optimal chunk size for initialization based on item count and device capabilities.
62
+ *
63
+ * This function provides a heuristic for determining an appropriate chunk size that balances
64
+ * performance and responsiveness. It considers both the total number of items and the
65
+ * estimated processing time per item.
66
+ *
67
+ * @param itemCount - Total number of items to process
68
+ * @param baseChunkSize - Base chunk size to use as a starting point (default: 50)
69
+ * @returns Recommended chunk size
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const chunkSize = calculateOptimalChunkSize(10000) // Returns optimized chunk size
74
+ * const smallChunkSize = calculateOptimalChunkSize(100) // Returns smaller chunk size
75
+ * ```
76
+ */
77
+ export const calculateOptimalChunkSize = (itemCount, baseChunkSize = 50) => {
78
+ // For very large datasets, use smaller chunks to maintain responsiveness
79
+ if (itemCount > 50000) {
80
+ return Math.max(25, baseChunkSize / 2);
81
+ }
82
+ // For medium datasets, use base chunk size
83
+ if (itemCount > 5000) {
84
+ return baseChunkSize;
85
+ }
86
+ // For smaller datasets, we can use larger chunks
87
+ if (itemCount > 1000) {
88
+ return Math.min(100, baseChunkSize * 2);
89
+ }
90
+ // For very small datasets, process all at once
91
+ return itemCount;
92
+ };
93
+ /**
94
+ * Creates a progress tracking object for initialization.
95
+ *
96
+ * @param processed - Number of items processed
97
+ * @param total - Total number of items
98
+ * @returns Progress information object
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * const progress = createProgressInfo(750, 1000)
103
+ * console.log(progress.percentage) // 75
104
+ * console.log(progress.isComplete) // false
105
+ * ```
106
+ */
107
+ export const createProgressInfo = (processed, total) => {
108
+ return {
109
+ processed,
110
+ total,
111
+ percentage: total > 0 ? Math.round((processed / total) * 100) : 100,
112
+ isComplete: processed >= total
113
+ };
114
+ };
@@ -0,0 +1,122 @@
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
+ * Creates a ResizeObserver for monitoring individual item size changes.
12
+ *
13
+ * This function creates a ResizeObserver that watches for size changes in list items
14
+ * and maintains a dirty set of items that need height recalculation. It's designed
15
+ * specifically for virtual list components where item heights may change dynamically.
16
+ *
17
+ * @param itemElements - Array of item elements to watch
18
+ * @param getVisibleRange - Function to get current visible range
19
+ * @param dirtyItems - Set to track items that need recalculation
20
+ * @param config - Configuration options
21
+ * @returns ResizeObserver instance
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const itemElements = $state<HTMLElement[]>([])
26
+ * const dirtyItems = $state(new Set<number>())
27
+ *
28
+ * const resizeObserver = createItemResizeObserver(
29
+ * itemElements,
30
+ * () => ({ start: 0, end: 10 }),
31
+ * dirtyItems,
32
+ * {
33
+ * debug: true,
34
+ * onItemsDirty: (indices) => console.log('Items dirty:', indices)
35
+ * }
36
+ * )
37
+ * ```
38
+ */
39
+ export declare const createItemResizeObserver: (itemElements: HTMLElement[], getVisibleRange: () => {
40
+ start: number;
41
+ end: number;
42
+ }, dirtyItems: Set<number>, config?: ItemResizeConfig) => ResizeObserver;
43
+ /**
44
+ * Configuration for container resize observation
45
+ */
46
+ export interface ContainerResizeConfig {
47
+ /** Debug mode for logging resize events */
48
+ debug?: boolean;
49
+ /** Callback when container is resized */
50
+ onResize?: (entry: ResizeObserverEntry) => void;
51
+ }
52
+ /**
53
+ * Creates a ResizeObserver for monitoring container size changes.
54
+ *
55
+ * This function creates a ResizeObserver that watches for size changes in the
56
+ * virtual list container and triggers appropriate updates to height and scroll position.
57
+ *
58
+ * @param config - Configuration options
59
+ * @returns ResizeObserver instance
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const containerResizeObserver = createContainerResizeObserver({
64
+ * debug: true,
65
+ * onResize: (entry) => {
66
+ * const newHeight = entry.contentRect.height
67
+ * updateHeightAndScroll(true)
68
+ * }
69
+ * })
70
+ *
71
+ * if (containerElement) {
72
+ * containerResizeObserver.observe(containerElement)
73
+ * }
74
+ * ```
75
+ */
76
+ export declare const createContainerResizeObserver: (config?: ContainerResizeConfig) => ResizeObserver;
77
+ /**
78
+ * Utility to safely observe elements with automatic cleanup.
79
+ *
80
+ * This function provides a safe way to observe elements with a ResizeObserver,
81
+ * handling cases where the observer might not be available or elements might be null.
82
+ *
83
+ * @param observer - ResizeObserver instance
84
+ * @param element - Element to observe
85
+ * @param debug - Whether to log debug information
86
+ * @returns Cleanup function to stop observing
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * const cleanup = safeObserve(resizeObserver, element, true)
91
+ *
92
+ * // Later, stop observing
93
+ * cleanup()
94
+ * ```
95
+ */
96
+ export declare const safeObserve: (observer: ResizeObserver | null, element: HTMLElement | null, debug?: boolean) => (() => void);
97
+ /**
98
+ * Manages multiple ResizeObserver instances with automatic cleanup.
99
+ *
100
+ * This class provides a convenient way to manage multiple ResizeObserver instances
101
+ * and ensures proper cleanup when the component is destroyed.
102
+ */
103
+ export declare class ResizeObserverManager {
104
+ private observers;
105
+ private cleanupFunctions;
106
+ /**
107
+ * Adds a ResizeObserver to the manager
108
+ */
109
+ addObserver(observer: ResizeObserver): void;
110
+ /**
111
+ * Adds a cleanup function to be called during cleanup
112
+ */
113
+ addCleanup(cleanup: () => void): void;
114
+ /**
115
+ * Observes an element with automatic cleanup tracking
116
+ */
117
+ observe(observer: ResizeObserver, element: HTMLElement, debug?: boolean): void;
118
+ /**
119
+ * Disconnects all observers and runs cleanup functions
120
+ */
121
+ cleanup(): void;
122
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Creates a ResizeObserver for monitoring individual item size changes.
3
+ *
4
+ * This function creates a ResizeObserver that watches for size changes in list items
5
+ * and maintains a dirty set of items that need height recalculation. It's designed
6
+ * specifically for virtual list components where item heights may change dynamically.
7
+ *
8
+ * @param itemElements - Array of item elements to watch
9
+ * @param getVisibleRange - Function to get current visible range
10
+ * @param dirtyItems - Set to track items that need recalculation
11
+ * @param config - Configuration options
12
+ * @returns ResizeObserver instance
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const itemElements = $state<HTMLElement[]>([])
17
+ * const dirtyItems = $state(new Set<number>())
18
+ *
19
+ * const resizeObserver = createItemResizeObserver(
20
+ * itemElements,
21
+ * () => ({ start: 0, end: 10 }),
22
+ * dirtyItems,
23
+ * {
24
+ * debug: true,
25
+ * onItemsDirty: (indices) => console.log('Items dirty:', indices)
26
+ * }
27
+ * )
28
+ * ```
29
+ */
30
+ export const createItemResizeObserver = (itemElements, getVisibleRange, dirtyItems, config = {}) => {
31
+ const { debug = false, onItemsDirty } = config;
32
+ return new ResizeObserver((entries) => {
33
+ let shouldRecalculate = false;
34
+ const newDirtyItems = new Set();
35
+ if (debug) {
36
+ console.log(`ResizeObserver fired for ${entries.length} entries`);
37
+ }
38
+ for (const entry of entries) {
39
+ const element = entry.target;
40
+ const elementIndex = itemElements.indexOf(element);
41
+ if (elementIndex !== -1) {
42
+ const visibleRange = getVisibleRange();
43
+ const actualIndex = visibleRange.start + elementIndex;
44
+ // ResizeObserver fired = element resized, so add to dirty queue
45
+ dirtyItems.add(actualIndex);
46
+ newDirtyItems.add(actualIndex);
47
+ shouldRecalculate = true;
48
+ if (debug) {
49
+ console.log(`Item ${actualIndex} marked dirty (resized), queue size: ${dirtyItems.size}`);
50
+ }
51
+ }
52
+ }
53
+ if (shouldRecalculate && onItemsDirty) {
54
+ onItemsDirty(newDirtyItems);
55
+ }
56
+ });
57
+ };
58
+ /**
59
+ * Creates a ResizeObserver for monitoring container size changes.
60
+ *
61
+ * This function creates a ResizeObserver that watches for size changes in the
62
+ * virtual list container and triggers appropriate updates to height and scroll position.
63
+ *
64
+ * @param config - Configuration options
65
+ * @returns ResizeObserver instance
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * const containerResizeObserver = createContainerResizeObserver({
70
+ * debug: true,
71
+ * onResize: (entry) => {
72
+ * const newHeight = entry.contentRect.height
73
+ * updateHeightAndScroll(true)
74
+ * }
75
+ * })
76
+ *
77
+ * if (containerElement) {
78
+ * containerResizeObserver.observe(containerElement)
79
+ * }
80
+ * ```
81
+ */
82
+ export const createContainerResizeObserver = (config = {}) => {
83
+ const { debug = false, onResize } = config;
84
+ return new ResizeObserver((entries) => {
85
+ for (const entry of entries) {
86
+ if (debug) {
87
+ console.log('Container resized:', entry.contentRect);
88
+ }
89
+ onResize?.(entry);
90
+ }
91
+ });
92
+ };
93
+ /**
94
+ * Utility to safely observe elements with automatic cleanup.
95
+ *
96
+ * This function provides a safe way to observe elements with a ResizeObserver,
97
+ * handling cases where the observer might not be available or elements might be null.
98
+ *
99
+ * @param observer - ResizeObserver instance
100
+ * @param element - Element to observe
101
+ * @param debug - Whether to log debug information
102
+ * @returns Cleanup function to stop observing
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * const cleanup = safeObserve(resizeObserver, element, true)
107
+ *
108
+ * // Later, stop observing
109
+ * cleanup()
110
+ * ```
111
+ */
112
+ export const safeObserve = (observer, element, debug = false) => {
113
+ if (observer && element) {
114
+ observer.observe(element);
115
+ if (debug) {
116
+ console.log('Started observing element:', element);
117
+ }
118
+ return () => {
119
+ if (observer && element) {
120
+ observer.unobserve(element);
121
+ if (debug) {
122
+ console.log('Stopped observing element:', element);
123
+ }
124
+ }
125
+ };
126
+ }
127
+ if (debug && !observer) {
128
+ console.log('ResizeObserver not available for element:', element);
129
+ }
130
+ return () => { }; // No-op cleanup function
131
+ };
132
+ /**
133
+ * Manages multiple ResizeObserver instances with automatic cleanup.
134
+ *
135
+ * This class provides a convenient way to manage multiple ResizeObserver instances
136
+ * and ensures proper cleanup when the component is destroyed.
137
+ */
138
+ export class ResizeObserverManager {
139
+ observers = [];
140
+ cleanupFunctions = [];
141
+ /**
142
+ * Adds a ResizeObserver to the manager
143
+ */
144
+ addObserver(observer) {
145
+ this.observers.push(observer);
146
+ }
147
+ /**
148
+ * Adds a cleanup function to be called during cleanup
149
+ */
150
+ addCleanup(cleanup) {
151
+ this.cleanupFunctions.push(cleanup);
152
+ }
153
+ /**
154
+ * Observes an element with automatic cleanup tracking
155
+ */
156
+ observe(observer, element, debug = false) {
157
+ const cleanup = safeObserve(observer, element, debug);
158
+ this.addCleanup(cleanup);
159
+ }
160
+ /**
161
+ * Disconnects all observers and runs cleanup functions
162
+ */
163
+ cleanup() {
164
+ // Disconnect all observers
165
+ for (const observer of this.observers) {
166
+ observer.disconnect();
167
+ }
168
+ // Run all cleanup functions
169
+ for (const cleanup of this.cleanupFunctions) {
170
+ cleanup();
171
+ }
172
+ // Clear arrays
173
+ this.observers.length = 0;
174
+ this.cleanupFunctions.length = 0;
175
+ }
176
+ }
@@ -0,0 +1,47 @@
1
+ import type { SvelteVirtualListMode, SvelteVirtualListScrollAlignment } from '../types.js';
2
+ /**
3
+ * Parameters for calculating scroll target position
4
+ */
5
+ export interface ScrollTargetParams {
6
+ mode: SvelteVirtualListMode;
7
+ align: SvelteVirtualListScrollAlignment;
8
+ targetIndex: number;
9
+ itemsLength: number;
10
+ calculatedItemHeight: number;
11
+ height: number;
12
+ scrollTop: number;
13
+ firstVisibleIndex: number;
14
+ lastVisibleIndex: number;
15
+ heightCache: Record<number, number>;
16
+ }
17
+ /**
18
+ * Calculates the target scroll position for scrolling to a specific item index.
19
+ *
20
+ * This function handles both topToBottom and bottomToTop scroll modes with different
21
+ * alignment options (auto, top, bottom, nearest). It takes into account the current
22
+ * viewport state and calculates the optimal scroll position.
23
+ *
24
+ * @param params - Parameters for scroll target calculation
25
+ * @returns The target scroll position in pixels, or null if no scroll is needed
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const scrollTarget = calculateScrollTarget({
30
+ * mode: 'topToBottom',
31
+ * align: 'auto',
32
+ * targetIndex: 100,
33
+ * itemsLength: 1000,
34
+ * calculatedItemHeight: 50,
35
+ * height: 400,
36
+ * scrollTop: 200,
37
+ * firstVisibleIndex: 4,
38
+ * lastVisibleIndex: 12,
39
+ * heightCache: {}
40
+ * })
41
+ *
42
+ * if (scrollTarget !== null) {
43
+ * viewportElement.scrollTo({ top: scrollTarget })
44
+ * }
45
+ * ```
46
+ */
47
+ export declare const calculateScrollTarget: (params: ScrollTargetParams) => number | null;
@@ -0,0 +1,173 @@
1
+ import { getScrollOffsetForIndex } from './virtualList.js';
2
+ /**
3
+ * Calculates the target scroll position for scrolling to a specific item index.
4
+ *
5
+ * This function handles both topToBottom and bottomToTop scroll modes with different
6
+ * alignment options (auto, top, bottom, nearest). It takes into account the current
7
+ * viewport state and calculates the optimal scroll position.
8
+ *
9
+ * @param params - Parameters for scroll target calculation
10
+ * @returns The target scroll position in pixels, or null if no scroll is needed
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const scrollTarget = calculateScrollTarget({
15
+ * mode: 'topToBottom',
16
+ * align: 'auto',
17
+ * targetIndex: 100,
18
+ * itemsLength: 1000,
19
+ * calculatedItemHeight: 50,
20
+ * height: 400,
21
+ * scrollTop: 200,
22
+ * firstVisibleIndex: 4,
23
+ * lastVisibleIndex: 12,
24
+ * heightCache: {}
25
+ * })
26
+ *
27
+ * if (scrollTarget !== null) {
28
+ * viewportElement.scrollTo({ top: scrollTarget })
29
+ * }
30
+ * ```
31
+ */
32
+ export const calculateScrollTarget = (params) => {
33
+ const { mode, align, targetIndex, itemsLength, calculatedItemHeight, height, scrollTop, firstVisibleIndex, lastVisibleIndex, heightCache } = params;
34
+ if (mode === 'bottomToTop') {
35
+ return calculateBottomToTopScrollTarget({
36
+ align,
37
+ targetIndex,
38
+ itemsLength,
39
+ calculatedItemHeight,
40
+ height,
41
+ scrollTop,
42
+ firstVisibleIndex,
43
+ lastVisibleIndex
44
+ });
45
+ }
46
+ else {
47
+ return calculateTopToBottomScrollTarget({
48
+ align,
49
+ targetIndex,
50
+ calculatedItemHeight,
51
+ height,
52
+ scrollTop,
53
+ firstVisibleIndex,
54
+ lastVisibleIndex,
55
+ heightCache
56
+ });
57
+ }
58
+ };
59
+ /**
60
+ * Calculates scroll target for bottom-to-top mode
61
+ */
62
+ const calculateBottomToTopScrollTarget = (params) => {
63
+ const { align, targetIndex, itemsLength, calculatedItemHeight, height, scrollTop, firstVisibleIndex, lastVisibleIndex } = params;
64
+ const totalHeight = itemsLength * calculatedItemHeight;
65
+ const itemOffset = targetIndex * calculatedItemHeight;
66
+ const itemHeight = calculatedItemHeight;
67
+ if (align === 'auto') {
68
+ // If item is above the viewport, align to top
69
+ if (targetIndex < firstVisibleIndex) {
70
+ return Math.max(0, totalHeight - (itemOffset + itemHeight));
71
+ }
72
+ // If item is below the viewport, align to bottom
73
+ else if (targetIndex > lastVisibleIndex - 1) {
74
+ return Math.max(0, totalHeight - itemOffset - height);
75
+ }
76
+ else {
77
+ // Item is visible but not aligned: align to nearest edge
78
+ const itemTop = totalHeight - (itemOffset + itemHeight);
79
+ const itemBottom = totalHeight - itemOffset;
80
+ const distanceToTop = Math.abs(scrollTop - itemTop);
81
+ const distanceToBottom = Math.abs(scrollTop + height - itemBottom);
82
+ if (distanceToTop < distanceToBottom) {
83
+ return itemTop;
84
+ }
85
+ else {
86
+ return Math.max(0, itemBottom - height);
87
+ }
88
+ }
89
+ }
90
+ else if (align === 'top') {
91
+ return Math.max(0, totalHeight - (itemOffset + itemHeight));
92
+ }
93
+ else if (align === 'bottom') {
94
+ return Math.max(0, totalHeight - itemOffset - height);
95
+ }
96
+ else if (align === 'nearest') {
97
+ const itemTop = totalHeight - (itemOffset + itemHeight);
98
+ const itemBottom = totalHeight - itemOffset;
99
+ if (itemBottom <= scrollTop || itemTop >= scrollTop + height) {
100
+ // Not visible, align to nearest edge
101
+ const distanceToTop = Math.abs(scrollTop - itemTop);
102
+ const distanceToBottom = Math.abs(scrollTop + height - itemBottom);
103
+ if (distanceToTop < distanceToBottom) {
104
+ return itemTop;
105
+ }
106
+ else {
107
+ return Math.max(0, itemBottom - height);
108
+ }
109
+ }
110
+ else {
111
+ // Already visible, do nothing
112
+ return null;
113
+ }
114
+ }
115
+ return null;
116
+ };
117
+ /**
118
+ * Calculates scroll target for top-to-bottom mode
119
+ */
120
+ const calculateTopToBottomScrollTarget = (params) => {
121
+ const { align, targetIndex, calculatedItemHeight, height, scrollTop, firstVisibleIndex, lastVisibleIndex, heightCache } = params;
122
+ if (align === 'auto') {
123
+ // If item is above the viewport, align to top
124
+ if (targetIndex < firstVisibleIndex) {
125
+ return getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex);
126
+ }
127
+ // If item is below the viewport, align to bottom
128
+ else if (targetIndex > lastVisibleIndex - 1) {
129
+ const itemBottom = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex + 1);
130
+ return Math.max(0, itemBottom - height);
131
+ }
132
+ else {
133
+ // Item is visible but not aligned: align to nearest edge
134
+ const itemTop = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex);
135
+ const itemBottom = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex + 1);
136
+ const distanceToTop = Math.abs(scrollTop - itemTop);
137
+ const distanceToBottom = Math.abs(scrollTop + height - itemBottom);
138
+ if (distanceToTop < distanceToBottom) {
139
+ return itemTop;
140
+ }
141
+ else {
142
+ return Math.max(0, itemBottom - height);
143
+ }
144
+ }
145
+ }
146
+ else if (align === 'top') {
147
+ return getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex);
148
+ }
149
+ else if (align === 'bottom') {
150
+ const itemBottom = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex + 1);
151
+ return Math.max(0, itemBottom - height);
152
+ }
153
+ else if (align === 'nearest') {
154
+ const itemTop = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex);
155
+ const itemBottom = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex + 1);
156
+ if (itemBottom <= scrollTop || itemTop >= scrollTop + height) {
157
+ // Not visible, align to nearest edge
158
+ const distanceToTop = Math.abs(scrollTop - itemTop);
159
+ const distanceToBottom = Math.abs(scrollTop + height - itemBottom);
160
+ if (distanceToTop < distanceToBottom) {
161
+ return itemTop;
162
+ }
163
+ else {
164
+ return Math.max(0, itemBottom - height);
165
+ }
166
+ }
167
+ else {
168
+ // Already visible, do nothing
169
+ return null;
170
+ }
171
+ }
172
+ return null;
173
+ };
@@ -86,10 +86,11 @@ export declare const updateHeightAndScroll: (state: VirtualListState, setters: V
86
86
  */
87
87
  export declare const calculateAverageHeight: (itemElements: HTMLElement[], visibleRange: {
88
88
  start: number;
89
- }, heightCache: Record<number, number>, currentItemHeight: number) => {
89
+ }, heightCache: Record<number, number>, currentItemHeight: number, dirtyItems: Set<number>) => {
90
90
  newHeight: number;
91
91
  newLastMeasuredIndex: number;
92
92
  updatedHeightCache: Record<number, number>;
93
+ clearedDirtyItems: Set<number>;
93
94
  };
94
95
  /**
95
96
  * Processes large arrays in chunks to prevent UI blocking.