@humanspeak/svelte-virtual-list 0.2.6 → 0.3.1-beta.0

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 +14 -2
  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
@@ -0,0 +1,119 @@
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
+ }
@@ -0,0 +1,47 @@
1
+ import type { SvelteVirtualListMode, SvelteVirtualListScrollAlign } from '../types.js';
2
+ /**
3
+ * Parameters for calculating scroll target position
4
+ */
5
+ export interface ScrollTargetParams {
6
+ mode: SvelteVirtualListMode;
7
+ align: SvelteVirtualListScrollAlign;
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,167 @@
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
+ heightCache
45
+ });
46
+ }
47
+ else {
48
+ return calculateTopToBottomScrollTarget({
49
+ align,
50
+ targetIndex,
51
+ calculatedItemHeight,
52
+ height,
53
+ scrollTop,
54
+ firstVisibleIndex,
55
+ lastVisibleIndex,
56
+ heightCache
57
+ });
58
+ }
59
+ };
60
+ /**
61
+ * Calculates scroll target for bottom-to-top mode
62
+ */
63
+ const calculateBottomToTopScrollTarget = (params) => {
64
+ const { align, targetIndex, itemsLength, calculatedItemHeight, height, scrollTop, firstVisibleIndex, lastVisibleIndex, heightCache } = params;
65
+ // Use getScrollOffsetForIndex for accurate positioning with height cache
66
+ const totalHeight = getScrollOffsetForIndex(heightCache, calculatedItemHeight, itemsLength);
67
+ const itemOffset = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex);
68
+ const itemHeight = calculatedItemHeight;
69
+ if (align === 'auto') {
70
+ // If item is above the viewport, align to top
71
+ if (targetIndex < firstVisibleIndex) {
72
+ return Math.max(0, totalHeight - (itemOffset + itemHeight));
73
+ }
74
+ else if (targetIndex > lastVisibleIndex - 1) {
75
+ // In bottomToTop, "below" means higher indices that need HIGHER scrollTop
76
+ return Math.max(0, totalHeight - itemOffset - height);
77
+ }
78
+ else {
79
+ const itemTop = totalHeight - (itemOffset + itemHeight);
80
+ const itemBottom = totalHeight - itemOffset;
81
+ const distanceToTop = Math.abs(scrollTop - itemTop);
82
+ const distanceToBottom = Math.abs(scrollTop + height - itemBottom);
83
+ return distanceToTop < distanceToBottom ? itemTop : Math.max(0, itemBottom - height);
84
+ }
85
+ }
86
+ else if (align === 'top') {
87
+ return Math.max(0, totalHeight - (itemOffset + itemHeight));
88
+ }
89
+ else if (align === 'bottom') {
90
+ return Math.max(0, totalHeight - itemOffset - height);
91
+ }
92
+ else if (align === 'nearest') {
93
+ const itemTop = totalHeight - (itemOffset + itemHeight);
94
+ const itemBottom = totalHeight - itemOffset;
95
+ if (itemBottom <= scrollTop || itemTop >= scrollTop + height) {
96
+ // Not visible, align to nearest edge
97
+ const distanceToTop = Math.abs(scrollTop - itemTop);
98
+ const distanceToBottom = Math.abs(scrollTop + height - itemBottom);
99
+ return distanceToTop < distanceToBottom ? itemTop : Math.max(0, itemBottom - height);
100
+ }
101
+ else {
102
+ // Already visible, do nothing
103
+ return null;
104
+ }
105
+ }
106
+ return null;
107
+ };
108
+ /**
109
+ * Calculates scroll target for top-to-bottom mode
110
+ */
111
+ const calculateTopToBottomScrollTarget = (params) => {
112
+ const { align, targetIndex, calculatedItemHeight, height, scrollTop, firstVisibleIndex, lastVisibleIndex, heightCache } = params;
113
+ if (align === 'auto') {
114
+ // If item is above the viewport, align to top
115
+ if (targetIndex < firstVisibleIndex) {
116
+ const scrollTarget = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex);
117
+ return scrollTarget;
118
+ }
119
+ // If item is below the viewport, align to bottom
120
+ else if (targetIndex > lastVisibleIndex - 1) {
121
+ const itemBottom = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex + 1);
122
+ const scrollTarget = Math.max(0, itemBottom - height);
123
+ return scrollTarget;
124
+ }
125
+ else {
126
+ // Item is visible but not aligned: align to nearest edge
127
+ const itemTop = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex);
128
+ const itemBottom = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex + 1);
129
+ const distanceToTop = Math.abs(scrollTop - itemTop);
130
+ const distanceToBottom = Math.abs(scrollTop + height - itemBottom);
131
+ if (distanceToTop < distanceToBottom) {
132
+ return itemTop;
133
+ }
134
+ else {
135
+ return Math.max(0, itemBottom - height);
136
+ }
137
+ }
138
+ }
139
+ else if (align === 'top') {
140
+ const scrollTarget = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex);
141
+ return scrollTarget;
142
+ }
143
+ else if (align === 'bottom') {
144
+ const itemBottom = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex + 1);
145
+ return Math.max(0, itemBottom - height);
146
+ }
147
+ else if (align === 'nearest') {
148
+ const itemTop = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex);
149
+ const itemBottom = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex + 1);
150
+ if (itemBottom <= scrollTop || itemTop >= scrollTop + height) {
151
+ // Not visible, align to nearest edge
152
+ const distanceToTop = Math.abs(scrollTop - itemTop);
153
+ const distanceToBottom = Math.abs(scrollTop + height - itemBottom);
154
+ if (distanceToTop < distanceToBottom) {
155
+ return itemTop;
156
+ }
157
+ else {
158
+ return Math.max(0, itemBottom - height);
159
+ }
160
+ }
161
+ else {
162
+ // Already visible, do nothing
163
+ return null;
164
+ }
165
+ }
166
+ return null;
167
+ };
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Throttling utilities for performance optimization.
3
+ *
4
+ * @fileoverview Provides throttling functions to limit execution frequency of callbacks,
5
+ * particularly useful for preventing excessive reactive effect triggers and debounced function calls.
6
+ */
7
+ /**
8
+ * Time provider abstraction that can be mocked in tests.
9
+ * Uses performance.now() in production for high precision timing,
10
+ * but can fallback to Date.now() for testing environments.
11
+ */
12
+ export declare const timeProvider: {
13
+ now: () => number;
14
+ };
15
+ /**
16
+ * Creates a throttled version of a callback function that limits execution frequency.
17
+ *
18
+ * The throttled function will execute immediately on first call, then ignore subsequent
19
+ * calls until the specified delay has elapsed. This is different from debouncing, which
20
+ * delays execution until after calls stop coming.
21
+ *
22
+ * @template T - The type of the callback function
23
+ * @param callback - The function to throttle
24
+ * @param delay - Minimum time between executions in milliseconds (default: 16ms ≈ 60fps)
25
+ * @returns A throttled version of the callback function
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * // Basic usage
30
+ * const throttledLog = createThrottledCallback(
31
+ * (message: string) => console.log(message),
32
+ * 100
33
+ * );
34
+ *
35
+ * // Called immediately
36
+ * throttledLog("First call");
37
+ *
38
+ * // Ignored (within 100ms)
39
+ * throttledLog("Second call");
40
+ *
41
+ * // After 100ms, this would execute
42
+ * setTimeout(() => throttledLog("Third call"), 150);
43
+ * ```
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * // Throttling reactive effects in Svelte
48
+ * const throttledUpdate = createThrottledCallback(() => {
49
+ * if (BROWSER && dirtyItemsCount > 0) {
50
+ * updateHeight();
51
+ * }
52
+ * }, 16); // ~60fps
53
+ *
54
+ * $effect(() => {
55
+ * throttledUpdate();
56
+ * });
57
+ * ```
58
+ */
59
+ export declare const createThrottledCallback: <T extends (..._args: unknown[]) => void>(callback: T, delay?: number) => T;
60
+ /**
61
+ * Creates a throttled callback with leading and trailing execution options.
62
+ *
63
+ * Unlike the basic throttle, this version allows control over whether the function
64
+ * executes on the leading edge (immediately) and/or trailing edge (after delay).
65
+ *
66
+ * @template T - The type of the callback function
67
+ * @param callback - The function to throttle
68
+ * @param delay - Minimum time between executions in milliseconds
69
+ * @param options - Configuration options
70
+ * @param options.leading - Execute on the leading edge (default: true)
71
+ * @param options.trailing - Execute on the trailing edge (default: false)
72
+ * @returns A throttled version of the callback function
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * // Execute immediately and after delay
77
+ * const throttledWithTrailing = createAdvancedThrottledCallback(
78
+ * () => console.log("Throttled call"),
79
+ * 100,
80
+ * { leading: true, trailing: true }
81
+ * );
82
+ * ```
83
+ */
84
+ export declare const createAdvancedThrottledCallback: <T extends (..._args: unknown[]) => void>(callback: T, delay: number, options?: {
85
+ leading?: boolean;
86
+ trailing?: boolean;
87
+ }) => T;
88
+ /**
89
+ * Type definitions for throttle utilities
90
+ */
91
+ export type ThrottledCallback<T extends (..._args: unknown[]) => void> = T;
92
+ export type ThrottleOptions = {
93
+ leading?: boolean;
94
+ trailing?: boolean;
95
+ };
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Throttling utilities for performance optimization.
3
+ *
4
+ * @fileoverview Provides throttling functions to limit execution frequency of callbacks,
5
+ * particularly useful for preventing excessive reactive effect triggers and debounced function calls.
6
+ */
7
+ /**
8
+ * Time provider abstraction that can be mocked in tests.
9
+ * Uses performance.now() in production for high precision timing,
10
+ * but can fallback to Date.now() for testing environments.
11
+ */
12
+ export const timeProvider = {
13
+ now: () => {
14
+ // Use performance.now() for high precision in production
15
+ if (typeof performance !== 'undefined' && performance.now) {
16
+ return performance.now();
17
+ }
18
+ // Fallback to Date.now() (mainly for testing or older environments)
19
+ return Date.now();
20
+ }
21
+ };
22
+ /**
23
+ * Creates a throttled version of a callback function that limits execution frequency.
24
+ *
25
+ * The throttled function will execute immediately on first call, then ignore subsequent
26
+ * calls until the specified delay has elapsed. This is different from debouncing, which
27
+ * delays execution until after calls stop coming.
28
+ *
29
+ * @template T - The type of the callback function
30
+ * @param callback - The function to throttle
31
+ * @param delay - Minimum time between executions in milliseconds (default: 16ms ≈ 60fps)
32
+ * @returns A throttled version of the callback function
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * // Basic usage
37
+ * const throttledLog = createThrottledCallback(
38
+ * (message: string) => console.log(message),
39
+ * 100
40
+ * );
41
+ *
42
+ * // Called immediately
43
+ * throttledLog("First call");
44
+ *
45
+ * // Ignored (within 100ms)
46
+ * throttledLog("Second call");
47
+ *
48
+ * // After 100ms, this would execute
49
+ * setTimeout(() => throttledLog("Third call"), 150);
50
+ * ```
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * // Throttling reactive effects in Svelte
55
+ * const throttledUpdate = createThrottledCallback(() => {
56
+ * if (BROWSER && dirtyItemsCount > 0) {
57
+ * updateHeight();
58
+ * }
59
+ * }, 16); // ~60fps
60
+ *
61
+ * $effect(() => {
62
+ * throttledUpdate();
63
+ * });
64
+ * ```
65
+ */
66
+ export const createThrottledCallback = (callback, delay = 16 // ~60fps default for smooth UI updates
67
+ ) => {
68
+ let lastExecutionTime = 0;
69
+ let isFirstCall = true;
70
+ return ((...args) => {
71
+ const now = timeProvider.now();
72
+ if (isFirstCall || now - lastExecutionTime >= delay) {
73
+ isFirstCall = false;
74
+ lastExecutionTime = now;
75
+ callback(...args);
76
+ }
77
+ });
78
+ };
79
+ /**
80
+ * Creates a throttled callback with leading and trailing execution options.
81
+ *
82
+ * Unlike the basic throttle, this version allows control over whether the function
83
+ * executes on the leading edge (immediately) and/or trailing edge (after delay).
84
+ *
85
+ * @template T - The type of the callback function
86
+ * @param callback - The function to throttle
87
+ * @param delay - Minimum time between executions in milliseconds
88
+ * @param options - Configuration options
89
+ * @param options.leading - Execute on the leading edge (default: true)
90
+ * @param options.trailing - Execute on the trailing edge (default: false)
91
+ * @returns A throttled version of the callback function
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * // Execute immediately and after delay
96
+ * const throttledWithTrailing = createAdvancedThrottledCallback(
97
+ * () => console.log("Throttled call"),
98
+ * 100,
99
+ * { leading: true, trailing: true }
100
+ * );
101
+ * ```
102
+ */
103
+ export const createAdvancedThrottledCallback = (callback, delay, options = {}) => {
104
+ const { leading = true, trailing = false } = options;
105
+ let lastExecutionTime = 0;
106
+ let trailingTimeoutId = null;
107
+ let lastArgs = null;
108
+ let isFirstCall = true;
109
+ const execute = (args) => {
110
+ lastExecutionTime = timeProvider.now();
111
+ callback(...args);
112
+ };
113
+ return ((...args) => {
114
+ const now = timeProvider.now();
115
+ const timeSinceLastExecution = isFirstCall ? delay : now - lastExecutionTime;
116
+ lastArgs = args;
117
+ // Clear any pending trailing execution
118
+ if (trailingTimeoutId) {
119
+ clearTimeout(trailingTimeoutId);
120
+ trailingTimeoutId = null;
121
+ }
122
+ if (timeSinceLastExecution >= delay) {
123
+ // Can execute immediately
124
+ if (leading) {
125
+ isFirstCall = false;
126
+ execute(args);
127
+ }
128
+ // Schedule trailing if needed
129
+ if (trailing && !leading) {
130
+ trailingTimeoutId = setTimeout(() => {
131
+ if (lastArgs) {
132
+ execute(lastArgs);
133
+ }
134
+ trailingTimeoutId = null;
135
+ }, delay);
136
+ }
137
+ }
138
+ else {
139
+ // Still within throttle window, but handle first call
140
+ if (isFirstCall && leading) {
141
+ isFirstCall = false;
142
+ execute(args);
143
+ }
144
+ else if (trailing) {
145
+ const remainingTime = delay - timeSinceLastExecution;
146
+ trailingTimeoutId = setTimeout(() => {
147
+ if (lastArgs) {
148
+ execute(lastArgs);
149
+ }
150
+ trailingTimeoutId = null;
151
+ }, remainingTime);
152
+ }
153
+ }
154
+ });
155
+ };
@@ -39,9 +39,3 @@ export type VirtualListSetters = {
39
39
  setScrollTop: (scrollTop: number) => void;
40
40
  setInitialized: (initialized: boolean) => void;
41
41
  };
42
- /**
43
- * Cache for storing measured item heights
44
- * - Key: Item index in the list
45
- * - Value: Measured height in pixels
46
- */
47
- export type HeightCache = Record<number, number>;