@humanspeak/svelte-virtual-list 0.2.4 → 0.2.6-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.
@@ -1,46 +1,200 @@
1
- import type { SvelteVirtualListProps } from './types.js';
2
1
  /**
3
- * A high-performance virtualized list component that efficiently renders large datasets
4
- * by only mounting DOM nodes for visible items and a small buffer. Optimized for handling
5
- * lists of 10k+ items through chunked processing and progressive initialization.
2
+ * SvelteVirtualList Implementation Journey
3
+ *
4
+ * Evolution & Architecture:
5
+ * 1. Initial Implementation ✓
6
+ * - Basic virtual scrolling with fixed height items
7
+ * - Single direction scrolling (top-to-bottom)
8
+ * - Simple viewport calculations
9
+ *
10
+ * 2. Dynamic Height Enhancement ✓
11
+ * - Added dynamic height calculation system
12
+ * - Implemented debounced measurements
13
+ * - Created height averaging mechanism for performance
14
+ *
15
+ * 3. Bidirectional Scrolling ✓
16
+ * - Added bottomToTop mode
17
+ * - Solved complex initialization issues with flexbox
18
+ * - Implemented careful scroll position management
19
+ *
20
+ * 4. Performance Optimizations ✓
21
+ * - Added element recycling through keyed each blocks
22
+ * - Implemented RAF for smooth animations
23
+ * - Optimized DOM updates with transform translations
24
+ *
25
+ * 5. Stability Improvements ✓
26
+ * - Added ResizeObserver for responsive updates
27
+ * - Implemented proper cleanup on component destruction
28
+ * - Added debug mode for development assistance
29
+ *
30
+ * 6. Large Dataset Optimizations ✓
31
+ * - Implemented chunked processing for 10k+ items
32
+ * - Added progressive initialization system
33
+ * - Deferred height calculations for better initial load
34
+ * - Optimized memory usage for large lists
35
+ * - Added progress tracking for initialization
36
+ *
37
+ * 7. Size Management Improvements ✓
38
+ * - Implemented height caching system for measured items
39
+ * - Added smart height estimation for unmeasured items
40
+ * - Optimized resize handling with debouncing
41
+ * - Added height recalculation on content changes
42
+ * - Implemented progressive height adjustments
43
+ *
44
+ * 8. Code Quality & Maintainability ✓
45
+ * - Extracted debug utilities for better testing
46
+ * - Improved type safety throughout
47
+ * - Added comprehensive documentation
48
+ * - Optimized debug output to reduce noise
49
+ *
50
+ * 9. Future Improvements (Planned)
51
+ * - Add horizontal scrolling support
52
+ * - Implement variable-sized item caching
53
+ * - Add keyboard navigation support
54
+ * - Support for dynamic item updates
55
+ * - Add accessibility enhancements
56
+ *
57
+ * Technical Challenges Solved:
58
+ * - Bottom-to-top scrolling in flexbox layouts
59
+ * - Dynamic height calculations without layout thrashing
60
+ * - Smooth scrolling on various devices
61
+ * - Memory management for large lists
62
+ * - Browser compatibility issues
63
+ * - Performance optimization for 10k+ items
64
+ * - Progressive initialization for large datasets
65
+ * - Debug output optimization
66
+ * - Accurate size calculations with caching
67
+ * - Responsive size adjustments
68
+ *
69
+ * Current Architecture:
70
+ * - Four-layer DOM structure for optimal performance
71
+ * - State management using Svelte 5's $state
72
+ * - Reactive height and scroll calculations
73
+ * - Configurable buffer zones for smooth scrolling
74
+ * - Chunked processing system for large datasets
75
+ * - Separated debug utilities for better testing
76
+ * - Height caching and estimation system
77
+ * - Progressive size adjustment system
78
+ */
79
+ import { type SvelteVirtualListProps, type SvelteVirtualListScrollOptions } from './types.js';
80
+ /**
81
+ * SvelteVirtualList
82
+ *
83
+ * A high-performance, memory-efficient virtualized list component for Svelte 5.
84
+ * Renders only visible items plus a buffer, supporting dynamic item heights,
85
+ * bi-directional (top-to-bottom and bottom-to-top) scrolling, and programmatic control.
6
86
  *
7
- * Props:
8
- * - `items` - Array of items to render
9
- * - `defaultEstimatedItemHeight` - Initial height estimate for items (default: 40px)
10
- * - `mode` - Scroll direction: 'topToBottom' or 'bottomToTop' (default: 'topToBottom')
11
- * - `debug` - Enable debug logging (default: false)
12
- * - `bufferSize` - Number of items to render outside visible area (default: 20)
13
- * - `containerClass` - Custom class for container element
14
- * - `viewportClass` - Custom class for viewport element
15
- * - `contentClass` - Custom class for content wrapper
16
- * - `itemsClass` - Custom class for items wrapper
17
- * - `debugFunction` - Custom debug logging function
18
- * - `testId` - Base test ID for component elements
87
+ * =============================
88
+ * == Key Features ==
89
+ * =============================
90
+ * - Dynamic item height support (no fixed height required)
91
+ * - Top-to-bottom and bottom-to-top (chat-style) scrolling
92
+ * - Programmatic scrolling with flexible alignment (top, bottom, auto)
93
+ * - Smooth scrolling and buffer size configuration
94
+ * - SSR compatible and hydration-friendly
95
+ * - TypeScript and Svelte 5 runes/snippets support
96
+ * - Customizable styling via class props
97
+ * - Debug mode for development and testing
98
+ * - Optimized for large lists (10k+ items)
99
+ * - Comprehensive test coverage (unit and E2E)
19
100
  *
20
- * Usage:
101
+ * =============================
102
+ * == Usage Example ==
103
+ * =============================
21
104
  * ```svelte
22
105
  * <SvelteVirtualList
23
106
  * items={data}
24
- * defaultEstimatedItemHeight={40}
25
- * mode="topToBottom"
107
+ * mode="bottomToTop"
108
+ * bind:this={listRef}
26
109
  * >
27
- * {#snippet renderItem(item, index)}
28
- * <div class="item">{item.text}</div>
110
+ * {#snippet renderItem(item)}
111
+ * <div>{item.text}</div>
29
112
  * {/snippet}
30
113
  * </SvelteVirtualList>
31
114
  * ```
32
115
  *
33
- * Features:
34
- * - Dynamic height calculation
35
- * - Bidirectional scrolling
36
- * - Configurable buffer size
37
- * - Debug mode
38
- * - Custom styling
39
- * - Progressive initialization for large datasets
40
- * - Memory-optimized for 10k+ items
41
- * - Chunked processing for smooth performance
42
- * - Progress tracking during initialization
116
+ * =============================
117
+ * == Architecture Notes ==
118
+ * =============================
119
+ * - Uses a four-layer DOM structure for optimal performance
120
+ * - Only visible items + buffer are mounted in the DOM
121
+ * - Height caching and estimation for dynamic content
122
+ * - Handles resize events and dynamic content changes
123
+ * - Supports chunked initialization for very large lists
124
+ * - All scrolling logic is centralized in the scroll() method
125
+ * - Bi-directional support: mode="topToBottom" or "bottomToTop"
126
+ * - Designed for extensibility and easy debugging
127
+ *
128
+ * =============================
129
+ * == For Contributors ==
130
+ * =============================
131
+ * - Please keep all scrolling logic in the scroll() method
132
+ * - Add new features behind feature flags or as optional props
133
+ * - Write tests for all new features (see /test and /tests/scroll)
134
+ * - Use TypeScript and Svelte 5 runes for all new code
135
+ * - Document all exported functions and props with JSDoc
136
+ * - See README.md for API and usage details
137
+ * - For questions, open an issue or discussion on GitHub
138
+ *
139
+ * MIT License © Humanspeak, Inc.
43
140
  */
44
- declare const SvelteVirtualList: import("svelte").Component<SvelteVirtualListProps, {}, "">;
141
+ declare const SvelteVirtualList: import("svelte").Component<SvelteVirtualListProps, {
142
+ /**
143
+ * Scrolls the virtual list to the item at the given index.
144
+ *
145
+ * @deprecated This function is deprecated and will be removed in a future version.
146
+ * Use the new scroll method from the component instance instead.
147
+ *
148
+ * @function scrollToIndex
149
+ * @param index The index of the item to scroll to.
150
+ * @param smoothScroll (default: true) Whether to use smooth scrolling.
151
+ * @param shouldThrowOnBounds (default: true) Whether to throw an error if the index is out of bounds.
152
+ *
153
+ * @example
154
+ * // Svelte usage:
155
+ * // In your <script> block:
156
+ * import SvelteVirtualList from '@humanspeak/svelte-virtual-list';
157
+ * let virtualList;
158
+ * const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
159
+ *
160
+ * // In your markup:
161
+ * <button onclick={() => virtualList.scrollToIndex(5000)}>
162
+ * Scroll to 5000
163
+ * </button>
164
+ * <SvelteVirtualList {items} bind:this={virtualList}>
165
+ * {#snippet renderItem(item)}
166
+ * <div>{item.text}</div>
167
+ * {/snippet}
168
+ * </SvelteVirtualList>
169
+ *
170
+ * @returns {void}
171
+ * @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
172
+ */ scrollToIndex: (index: number, smoothScroll?: boolean, shouldThrowOnBounds?: boolean) => void;
173
+ /**
174
+ * Scrolls the virtual list to the item at the given index using a type-based options approach.
175
+ *
176
+ * @function scroll
177
+ * @param options Configuration options for scrolling behavior.
178
+ *
179
+ * @example
180
+ * // Svelte usage:
181
+ * // In your <script> block:
182
+ * import SvelteVirtualList from './index.js';
183
+ * let virtualList;
184
+ * const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
185
+ *
186
+ * <button onclick={() => virtualList.scroll({ index: 5000 })}>
187
+ * Scroll to 5000
188
+ * </button>
189
+ * <SvelteVirtualList {items} bind:this={virtualList}>
190
+ * {#snippet renderItem(item)}
191
+ * <div>{item.text}</div>
192
+ * {/snippet}
193
+ * </SvelteVirtualList>
194
+ *
195
+ * @returns {void}
196
+ * @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
197
+ */ scroll: (options: SvelteVirtualListScrollOptions) => void;
198
+ }, "">;
45
199
  type SvelteVirtualList = ReturnType<typeof SvelteVirtualList>;
46
200
  export default SvelteVirtualList;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import SvelteVirtualList from './SvelteVirtualList.svelte';
2
- import type { SvelteVirtualListDebugInfo, SvelteVirtualListMode, SvelteVirtualListProps } from './types.js';
2
+ import type { SvelteVirtualListDebugInfo, SvelteVirtualListMode, SvelteVirtualListProps, SvelteVirtualListScrollAlign, SvelteVirtualListScrollOptions } from './types.js';
3
3
  export default SvelteVirtualList;
4
- export type { SvelteVirtualListDebugInfo, SvelteVirtualListMode, SvelteVirtualListProps };
4
+ export type { SvelteVirtualListDebugInfo, SvelteVirtualListMode, SvelteVirtualListProps, SvelteVirtualListScrollAlign, SvelteVirtualListScrollOptions };
package/dist/types.d.ts CHANGED
@@ -9,35 +9,59 @@ export type SvelteVirtualListMode = 'topToBottom' | 'bottomToTop';
9
9
  * Configuration properties for the SvelteVirtualList component.
10
10
  *
11
11
  * @typedef {Object} SvelteVirtualListProps
12
- * @property {number} [bufferSize] - Number of items to render outside the visible viewport
13
- * for smooth scrolling.
14
- * @property {string} [containerClass] - CSS class to apply to the outer container element.
15
- * @property {string} [contentClass] - CSS class to apply to the content wrapper element.
16
- * @property {number} [defaultEstimatedItemHeight] - Initial height estimate for each item in pixels.
17
- * Used for optimization before actual measurements are available.
18
- * @property {boolean} [debug] - When true, enables debug mode with additional logging and information.
19
- * @property {Function} [debugFunction] - Custom callback to handle debug information.
20
- * Receives a {@link SvelteVirtualListDebugInfo} object.
21
- * @property {Array<any>} items - The complete array of items to be virtualized.
22
- * @property {string} [itemsClass] - CSS class to apply to individual item containers.
23
- * @property {SvelteVirtualListMode} [mode='topToBottom'] - Determines the scroll and render direction.
24
- * @property {Snippet<[item: any, index: number]>} renderItem - Svelte snippet function that defines
25
- * how each item should be rendered. Receives the item and its index as arguments.
26
- * @property {string} [testId] - Base test ID for component elements to facilitate testing.
27
- * @property {string} [viewportClass] - CSS class to apply to the scrollable viewport element.
28
12
  */
29
13
  export type SvelteVirtualListProps = {
14
+ /**
15
+ * Number of items to render outside the visible viewport for smooth scrolling.
16
+ * @default 20
17
+ */
30
18
  bufferSize?: number;
19
+ /**
20
+ * CSS class to apply to the outer container element.
21
+ */
31
22
  containerClass?: string;
23
+ /**
24
+ * CSS class to apply to the content wrapper element.
25
+ */
32
26
  contentClass?: string;
27
+ /**
28
+ * Initial height estimate for each item in pixels. Used for optimization before actual measurements are available.
29
+ * @default 40
30
+ */
33
31
  defaultEstimatedItemHeight?: number;
32
+ /**
33
+ * When true, enables debug mode with additional logging and information.
34
+ * @default false
35
+ */
34
36
  debug?: boolean;
37
+ /**
38
+ * Custom callback to handle debug information. Receives a SvelteVirtualListDebugInfo object.
39
+ */
35
40
  debugFunction?: (_info: SvelteVirtualListDebugInfo) => void;
41
+ /**
42
+ * The complete array of items to be virtualized.
43
+ */
36
44
  items: any[];
45
+ /**
46
+ * CSS class to apply to individual item containers.
47
+ */
37
48
  itemsClass?: string;
49
+ /**
50
+ * Determines the scroll and render direction.
51
+ * @default 'topToBottom'
52
+ */
38
53
  mode?: SvelteVirtualListMode;
54
+ /**
55
+ * Svelte snippet function that defines how each item should be rendered. Receives the item and its index as arguments.
56
+ */
39
57
  renderItem: Snippet<[item: any, index: number]>;
58
+ /**
59
+ * Base test ID for component elements to facilitate testing.
60
+ */
40
61
  testId?: string;
62
+ /**
63
+ * CSS class to apply to the scrollable viewport element.
64
+ */
41
65
  viewportClass?: string;
42
66
  };
43
67
  /**
@@ -58,3 +82,33 @@ export type SvelteVirtualListDebugInfo = {
58
82
  processedItems: number;
59
83
  averageItemHeight: number;
60
84
  };
85
+ /**
86
+ * Alignment options for programmatic scrolling.
87
+ */
88
+ export type SvelteVirtualListScrollAlign = 'auto' | 'top' | 'bottom' | 'nearest';
89
+ /**
90
+ * Options for scrolling to a specific index in the virtual list.
91
+ */
92
+ export interface SvelteVirtualListScrollOptions {
93
+ /** The index of the item to scroll to. */
94
+ index: number;
95
+ /** Whether to use smooth scrolling animation. Default: true */
96
+ smoothScroll?: boolean;
97
+ /** Whether to throw an error if the index is out of bounds. Default: true */
98
+ shouldThrowOnBounds?: boolean;
99
+ /** Alignment for the scrolled item: 'auto', 'top', or 'bottom'. Default: 'auto' */
100
+ align?: SvelteVirtualListScrollAlign;
101
+ }
102
+ /**
103
+ * Default options for scrolling.
104
+ */
105
+ export declare const DEFAULT_SCROLL_OPTIONS: Partial<SvelteVirtualListScrollOptions>;
106
+ export type SvelteVirtualListHeightCacheItem = {
107
+ currentHeight: number;
108
+ dirty: boolean;
109
+ };
110
+ export type SvelteVirtualListHeightCache = Record<number, SvelteVirtualListHeightCacheItem>;
111
+ export type SvelteVirtualListPreviousVisibleRange = {
112
+ start: number;
113
+ end: number;
114
+ };
package/dist/types.js CHANGED
@@ -1 +1,8 @@
1
- export {};
1
+ /**
2
+ * Default options for scrolling.
3
+ */
4
+ export const DEFAULT_SCROLL_OPTIONS = {
5
+ smoothScroll: true,
6
+ shouldThrowOnBounds: true,
7
+ align: 'auto'
8
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Calculates and updates the average height of visible items with debouncing.
3
+ *
4
+ * This function optimizes performance by:
5
+ * - Debouncing calculations to prevent excessive DOM reads (200ms default)
6
+ * - Caching item heights with dirty tracking to minimize recalculations
7
+ * - Only updating when significant changes are detected (>1px difference)
8
+ * - Early returns to prevent unnecessary processing
9
+ *
10
+ * Implementation details:
11
+ * - Uses a debounce timeout to batch height calculations
12
+ * - Tracks calculation state to prevent concurrent updates
13
+ * - Caches heights in heightCache with currentHeight and dirty flags for reuse
14
+ * - Validates browser environment and calculation state
15
+ * - Checks for meaningful height changes before updates
16
+ *
17
+ * State interactions:
18
+ * - Updates calculatedItemHeight when significant changes occur
19
+ * - Updates lastMeasuredIndex to track progress
20
+ * - Modifies heightCache to store measured heights with dirty tracking
21
+ * - Uses isCalculatingHeight flag for concurrency control
22
+ *
23
+ * Guard clauses:
24
+ * - Returns null if not in browser environment
25
+ * - Returns null if calculation is already in progress
26
+ * - Returns null if update timeout is pending
27
+ * - Returns null if current index matches last measured
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * // Automatically called when items are rendered
32
+ * $effect(() => {
33
+ * if (BROWSER && itemElements.length > 0 && !isCalculatingHeight) {
34
+ * calculateAverageHeightDebounced(
35
+ * false,
36
+ * null,
37
+ * () => getVisibleRange(),
38
+ * itemElements,
39
+ * heightCache,
40
+ * lastMeasuredIndex,
41
+ * currentHeight,
42
+ * handleUpdate
43
+ * )
44
+ * }
45
+ * })
46
+ * ```
47
+ *
48
+ * Change History:
49
+ *
50
+ * 2025-01-22
51
+ * - Added comprehensive test coverage for all guard clauses
52
+ * - Improved browser environment detection
53
+ * - Enhanced debounce timing precision
54
+ * - Added proper cleanup for timeouts
55
+ * - Documented all edge cases and failure modes
56
+ * - Updated to work with new HeightCache structure with dirty tracking
57
+ *
58
+ *
59
+ * @param isCalculatingHeight - Flag to prevent concurrent calculations
60
+ * @param heightUpdateTimeout - Reference to existing update timeout
61
+ * @param visibleItemsGetter - Function to get current visible range
62
+ * @param itemElements - Array of DOM elements to measure
63
+ * @param heightCache - Cache of previously measured heights with dirty tracking
64
+ * @param lastMeasuredIndex - Index of last measured element
65
+ * @param calculatedItemHeight - Current average height
66
+ * @param onUpdate - Callback for height updates
67
+ * @param debounceTime - Time to wait between calculations (default: 200ms)
68
+ * @returns Timeout object or null if calculation was skipped
69
+ */
70
+ export declare const calculateAverageHeightDebounced: (isCalculatingHeight: boolean, heightUpdateTimeout: ReturnType<typeof setTimeout> | null, visibleItemsGetter: () => {
71
+ start: number;
72
+ end: number;
73
+ }, itemElements: HTMLElement[], heightCache: Record<number, number>, lastMeasuredIndex: number, calculatedItemHeight: number, onUpdate: (result: {
74
+ newHeight: number;
75
+ newLastMeasuredIndex: number;
76
+ updatedHeightCache: Record<number, number>;
77
+ }) => void, debounceTime?: number) => NodeJS.Timeout | null;
@@ -0,0 +1,91 @@
1
+ import { calculateAverageHeight } from './virtualList.js';
2
+ import { BROWSER } from 'esm-env';
3
+ /**
4
+ * Calculates and updates the average height of visible items with debouncing.
5
+ *
6
+ * This function optimizes performance by:
7
+ * - Debouncing calculations to prevent excessive DOM reads (200ms default)
8
+ * - Caching item heights with dirty tracking to minimize recalculations
9
+ * - Only updating when significant changes are detected (>1px difference)
10
+ * - Early returns to prevent unnecessary processing
11
+ *
12
+ * Implementation details:
13
+ * - Uses a debounce timeout to batch height calculations
14
+ * - Tracks calculation state to prevent concurrent updates
15
+ * - Caches heights in heightCache with currentHeight and dirty flags for reuse
16
+ * - Validates browser environment and calculation state
17
+ * - Checks for meaningful height changes before updates
18
+ *
19
+ * State interactions:
20
+ * - Updates calculatedItemHeight when significant changes occur
21
+ * - Updates lastMeasuredIndex to track progress
22
+ * - Modifies heightCache to store measured heights with dirty tracking
23
+ * - Uses isCalculatingHeight flag for concurrency control
24
+ *
25
+ * Guard clauses:
26
+ * - Returns null if not in browser environment
27
+ * - Returns null if calculation is already in progress
28
+ * - Returns null if update timeout is pending
29
+ * - Returns null if current index matches last measured
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * // Automatically called when items are rendered
34
+ * $effect(() => {
35
+ * if (BROWSER && itemElements.length > 0 && !isCalculatingHeight) {
36
+ * calculateAverageHeightDebounced(
37
+ * false,
38
+ * null,
39
+ * () => getVisibleRange(),
40
+ * itemElements,
41
+ * heightCache,
42
+ * lastMeasuredIndex,
43
+ * currentHeight,
44
+ * handleUpdate
45
+ * )
46
+ * }
47
+ * })
48
+ * ```
49
+ *
50
+ * Change History:
51
+ *
52
+ * 2025-01-22
53
+ * - Added comprehensive test coverage for all guard clauses
54
+ * - Improved browser environment detection
55
+ * - Enhanced debounce timing precision
56
+ * - Added proper cleanup for timeouts
57
+ * - Documented all edge cases and failure modes
58
+ * - Updated to work with new HeightCache structure with dirty tracking
59
+ *
60
+ *
61
+ * @param isCalculatingHeight - Flag to prevent concurrent calculations
62
+ * @param heightUpdateTimeout - Reference to existing update timeout
63
+ * @param visibleItemsGetter - Function to get current visible range
64
+ * @param itemElements - Array of DOM elements to measure
65
+ * @param heightCache - Cache of previously measured heights with dirty tracking
66
+ * @param lastMeasuredIndex - Index of last measured element
67
+ * @param calculatedItemHeight - Current average height
68
+ * @param onUpdate - Callback for height updates
69
+ * @param debounceTime - Time to wait between calculations (default: 200ms)
70
+ * @returns Timeout object or null if calculation was skipped
71
+ */
72
+ export const calculateAverageHeightDebounced = (isCalculatingHeight, heightUpdateTimeout, visibleItemsGetter, itemElements, heightCache, lastMeasuredIndex, calculatedItemHeight,
73
+ /* trunk-ignore(eslint/no-unused-vars) */
74
+ onUpdate, debounceTime = 200) => {
75
+ if (!BROWSER || isCalculatingHeight || heightUpdateTimeout)
76
+ return null;
77
+ const visibleRange = visibleItemsGetter();
78
+ const currentIndex = visibleRange.start;
79
+ if (currentIndex === lastMeasuredIndex)
80
+ return null;
81
+ return setTimeout(() => {
82
+ const { newHeight, newLastMeasuredIndex, updatedHeightCache } = calculateAverageHeight(itemElements, visibleRange, heightCache, calculatedItemHeight);
83
+ if (Math.abs(newHeight - calculatedItemHeight) > 1) {
84
+ onUpdate({
85
+ newHeight,
86
+ newLastMeasuredIndex,
87
+ updatedHeightCache
88
+ });
89
+ }
90
+ }, debounceTime);
91
+ };
@@ -1,8 +1,32 @@
1
1
  /**
2
- * Schedules a function to be executed on the next animation frame.
3
- * If a function is already scheduled, the new function will replace it.
4
- * This helps prevent multiple RAF calls and ensures smooth animations.
2
+ * Creates a requestAnimationFrame (RAF) scheduler for debouncing animation frame callbacks.
5
3
  *
6
- * @param fn - The function to be executed on the next animation frame
4
+ * This factory returns a function that schedules a callback to run on the next animation frame.
5
+ * If multiple calls are made before the frame executes, only the last callback is executed.
6
+ *
7
+ * This is ideal for scenarios where you want to batch or debounce UI updates, such as scroll or resize handlers,
8
+ * without risking global state leaks or cross-component interference.
9
+ *
10
+ * ### Why use this?
11
+ * - Prevents redundant RAF calls and excessive re-renders.
12
+ * - Ensures only the latest callback is executed per frame.
13
+ * - Each scheduler instance is independent—no global state is shared.
14
+ *
15
+ * @example
16
+ * // Svelte usage example:
17
+ * <script lang="ts">
18
+ * import { createRafScheduler } from './raf.js';
19
+ * const rafSchedule = createRafScheduler();
20
+ * function onScroll() {
21
+ * rafSchedule(() => {
22
+ * // Perform expensive DOM measurement or update
23
+ * });
24
+ * }
25
+ * </script>
26
+ * <div on:scroll={onScroll}> ... </div>
27
+ *
28
+ * @returns {(fn: () => void) => void} A scheduler function. Call with a callback to schedule it for the next animation frame.
29
+ *
30
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
7
31
  */
8
- export declare const rafSchedule: (fn: () => void) => void;
32
+ export declare function createRafScheduler(): (fn: () => void) => void;
package/dist/utils/raf.js CHANGED
@@ -1,22 +1,48 @@
1
- let scheduled = false;
2
- let callback = null;
3
1
  /**
4
- * Schedules a function to be executed on the next animation frame.
5
- * If a function is already scheduled, the new function will replace it.
6
- * This helps prevent multiple RAF calls and ensures smooth animations.
2
+ * Creates a requestAnimationFrame (RAF) scheduler for debouncing animation frame callbacks.
7
3
  *
8
- * @param fn - The function to be executed on the next animation frame
4
+ * This factory returns a function that schedules a callback to run on the next animation frame.
5
+ * If multiple calls are made before the frame executes, only the last callback is executed.
6
+ *
7
+ * This is ideal for scenarios where you want to batch or debounce UI updates, such as scroll or resize handlers,
8
+ * without risking global state leaks or cross-component interference.
9
+ *
10
+ * ### Why use this?
11
+ * - Prevents redundant RAF calls and excessive re-renders.
12
+ * - Ensures only the latest callback is executed per frame.
13
+ * - Each scheduler instance is independent—no global state is shared.
14
+ *
15
+ * @example
16
+ * // Svelte usage example:
17
+ * <script lang="ts">
18
+ * import { createRafScheduler } from './raf.js';
19
+ * const rafSchedule = createRafScheduler();
20
+ * function onScroll() {
21
+ * rafSchedule(() => {
22
+ * // Perform expensive DOM measurement or update
23
+ * });
24
+ * }
25
+ * </script>
26
+ * <div on:scroll={onScroll}> ... </div>
27
+ *
28
+ * @returns {(fn: () => void) => void} A scheduler function. Call with a callback to schedule it for the next animation frame.
29
+ *
30
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
9
31
  */
10
- export const rafSchedule = (fn) => {
11
- callback = fn;
12
- if (!scheduled) {
13
- scheduled = true;
14
- requestAnimationFrame(() => {
15
- scheduled = false;
16
- if (callback) {
17
- callback();
18
- callback = null;
19
- }
20
- });
21
- }
22
- };
32
+ export function createRafScheduler() {
33
+ let scheduled = false;
34
+ let callback = null;
35
+ return (fn) => {
36
+ callback = fn;
37
+ if (!scheduled) {
38
+ scheduled = true;
39
+ requestAnimationFrame(() => {
40
+ scheduled = false;
41
+ if (callback) {
42
+ callback();
43
+ callback = null;
44
+ }
45
+ });
46
+ }
47
+ };
48
+ }