@humanspeak/svelte-virtual-list 0.2.3 → 0.2.5

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,4 +1,4 @@
1
- Copyright (c) 2024 Humanspeak, Inc.
1
+ Copyright (c) 2024-2025 Humanspeak, Inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
package/README.md CHANGED
@@ -3,9 +3,10 @@
3
3
  [![NPM version](https://img.shields.io/npm/v/@humanspeak/svelte-virtual-list.svg)](https://www.npmjs.com/package/@humanspeak/svelte-virtual-list)
4
4
  [![Build Status](https://github.com/humanspeak/svelte-virtual-list/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/humanspeak/svelte-virtual-list/actions/workflows/npm-publish.yml)
5
5
  [![Coverage Status](https://coveralls.io/repos/github/humanspeak/svelte-virtual-list/badge.svg?branch=main)](https://coveralls.io/github/humanspeak/svelte-virtual-list?branch=main)
6
- [![License](https://img.shields.io/npm/l/@humanspeak/svelte-virtual-list.svg)](https://github.com/humanspeak/svelte-virtual-list/blob/main/LICENSE.md)
6
+ [![License](https://img.shields.io/npm/l/@humanspeak/svelte-virtual-list.svg)](https://github.com/humanspeak/svelte-virtual-list/blob/main/LICENSE)
7
7
  [![Downloads](https://img.shields.io/npm/dm/@humanspeak/svelte-virtual-list.svg)](https://www.npmjs.com/package/@humanspeak/svelte-virtual-list)
8
8
  [![CodeQL](https://github.com/humanspeak/svelte-virtual-list/actions/workflows/codeql.yml/badge.svg)](https://github.com/humanspeak/svelte-virtual-list/actions/workflows/codeql.yml)
9
+ [![Install size](https://packagephobia.com/badge?p=@humanspeak/svelte-virtual-list)](https://packagephobia.com/result?p=@humanspeak/svelte-virtual-list)
9
10
  [![Code Style: Trunk](https://img.shields.io/badge/code%20style-trunk-blue.svg)](https://trunk.io)
10
11
  [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
11
12
  [![Types](https://img.shields.io/npm/types/@humanspeak/svelte-virtual-list.svg)](https://www.npmjs.com/package/@humanspeak/svelte-virtual-list)
@@ -27,6 +28,40 @@ A high-performance virtual list component for Svelte 5 applications that efficie
27
28
  - 🧠 Memory-optimized for 10k+ items
28
29
  - 🧪 Comprehensive test coverage (vitest and playwright)
29
30
  - 🚀 Progressive initialization for large datasets
31
+ - 🕹️ Programmatic scrolling with `scrollToIndex`
32
+
33
+ ## scrollToIndex: Programmatic Scrolling
34
+
35
+ You can now programmatically scroll to any item in the list using the `scrollToIndex` method. This is useful for chat apps, jump-to-item navigation, and more. Thank you for the feature request.
36
+
37
+ ### Usage Example
38
+
39
+ ```svelte
40
+ <script lang="ts">
41
+ import SvelteVirtualList from '@humanspeak/svelte-virtual-list'
42
+ let listRef
43
+ const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }))
44
+
45
+ function goToItem5000() {
46
+ // Scroll to item 5000 with smooth scrolling
47
+ listRef.scrollToIndex(5000, true)
48
+ }
49
+ </script>
50
+
51
+ <button on:click={goToItem5000}> Scroll to item 5000 </button>
52
+ <SvelteVirtualList {items} bind:this={listRef}>
53
+ {#snippet renderItem(item)}
54
+ <div>{item.text}</div>
55
+ {/snippet}
56
+ </SvelteVirtualList>
57
+ ```
58
+
59
+ ### API
60
+
61
+ - `scrollToIndex(index: number, smoothScroll = true, shouldThrowOnBounds = true)`
62
+ - `index`: The item index to scroll to (0-based)
63
+ - `smoothScroll`: If true, uses smooth scrolling (default: true)
64
+ - `shouldThrowOnBounds`: If true, throws if index is out of bounds (default: true)
30
65
 
31
66
  ## Installation
32
67
 
@@ -112,7 +147,7 @@ npm install @humanspeak/svelte-virtual-list
112
147
 
113
148
  ## License
114
149
 
115
- MIT © [Humanspeak, Inc.](LICENSE.md)
150
+ MIT © [Humanspeak, Inc.](LICENSE)
116
151
 
117
152
  ## Credits
118
153
 
@@ -122,19 +122,22 @@
122
122
  * - Progressive size adjustment system
123
123
  */
124
124
 
125
- import { onMount } from 'svelte'
126
- import { BROWSER } from 'esm-env'
127
125
  import type { SvelteVirtualListProps } from './types.js'
126
+ import { calculateAverageHeightDebounced } from './utils/heightCalculation.js'
127
+ import { createRafScheduler } from './utils/raf.js'
128
128
  import {
129
129
  calculateScrollPosition,
130
- calculateVisibleRange,
131
130
  calculateTransformY,
131
+ calculateVisibleRange,
132
+ processChunked,
132
133
  updateHeightAndScroll as utilsUpdateHeightAndScroll,
133
- calculateAverageHeight,
134
- processChunked
134
+ getScrollOffsetForIndex
135
135
  } from './utils/virtualList.js'
136
- import { rafSchedule } from './utils/raf.js'
137
- import { shouldShowDebugInfo, createDebugInfo } from './utils/virtualListDebug.js'
136
+ import { createDebugInfo, shouldShowDebugInfo } from './utils/virtualListDebug.js'
137
+ import { BROWSER } from 'esm-env'
138
+ import { onMount, tick } from 'svelte'
139
+
140
+ const rafSchedule = createRafScheduler()
138
141
 
139
142
  /**
140
143
  * Core configuration props with default values
@@ -193,76 +196,23 @@
193
196
  let prevVisibleRange = $state<{ start: number; end: number } | null>(null)
194
197
  let prevHeight = $state<number>(0)
195
198
 
196
- /**
197
- * Calculates and updates the average height of visible items with debouncing.
198
- *
199
- * This function optimizes performance by:
200
- * - Debouncing calculations to prevent excessive DOM reads
201
- * - Caching item heights to minimize recalculations
202
- * - Only updating when significant changes are detected
203
- *
204
- * Implementation details:
205
- * - Uses a 200ms debounce timeout
206
- * - Tracks calculation state to prevent concurrent updates
207
- * - Caches heights in heightCache for reuse
208
- * - Only updates if height difference > 1px
209
- *
210
- * State interactions:
211
- * - Updates calculatedItemHeight
212
- * - Updates lastMeasuredIndex
213
- * - Modifies heightCache
214
- * - Uses/sets isCalculatingHeight flag
215
- *
216
- * @example
217
- * ```typescript
218
- * // Automatically called when items are rendered
219
- * $effect(() => {
220
- * if (BROWSER && itemElements.length > 0 && !isCalculatingHeight) {
221
- * calculateAverageHeightDebounced()
222
- * }
223
- * })
224
- * ```
225
- *
226
- * @returns {void}
227
- */
228
- const calculateAverageHeightDebounced = () => {
229
- if (!BROWSER || isCalculatingHeight || heightUpdateTimeout) return
230
- isCalculatingHeight = true
231
-
232
- if (heightUpdateTimeout) {
233
- clearTimeout(heightUpdateTimeout)
234
- }
235
-
236
- heightUpdateTimeout = setTimeout(() => {
237
- const visibleRange = visibleItems()
238
- const currentIndex = visibleRange.start
239
-
240
- if (currentIndex !== lastMeasuredIndex) {
241
- const { newHeight, newLastMeasuredIndex, updatedHeightCache } =
242
- calculateAverageHeight(
243
- itemElements,
244
- visibleRange,
245
- heightCache,
246
- lastMeasuredIndex,
247
- calculatedItemHeight
248
- )
249
-
250
- if (Math.abs(newHeight - calculatedItemHeight) > 1) {
251
- calculatedItemHeight = newHeight
252
- lastMeasuredIndex = newLastMeasuredIndex
253
- heightCache = updatedHeightCache
254
- }
255
- }
256
-
257
- isCalculatingHeight = false
258
- heightUpdateTimeout = null
259
- }, 200)
260
- }
261
-
262
199
  // Trigger height calculation when items are rendered
263
200
  $effect(() => {
264
201
  if (BROWSER && itemElements.length > 0 && !isCalculatingHeight) {
265
- calculateAverageHeightDebounced()
202
+ heightUpdateTimeout = calculateAverageHeightDebounced(
203
+ isCalculatingHeight,
204
+ heightUpdateTimeout,
205
+ visibleItems,
206
+ itemElements,
207
+ heightCache,
208
+ lastMeasuredIndex,
209
+ calculatedItemHeight,
210
+ (result) => {
211
+ calculatedItemHeight = result.newHeight
212
+ lastMeasuredIndex = result.newLastMeasuredIndex
213
+ heightCache = result.updatedHeightCache
214
+ }
215
+ )
266
216
  }
267
217
  })
268
218
 
@@ -305,7 +255,7 @@
305
255
  const targetScrollTop = Math.max(0, totalHeight - height)
306
256
 
307
257
  // Add delay to ensure layout is complete
308
- setTimeout(() => {
258
+ tick().then(() => {
309
259
  if (viewportElement) {
310
260
  // Start at the bottom for bottom-to-top mode
311
261
  viewportElement.scrollTop = targetScrollTop
@@ -320,7 +270,7 @@
320
270
  initialized = true
321
271
  })
322
272
  }
323
- }, 50)
273
+ })
324
274
  }
325
275
  })
326
276
 
@@ -406,12 +356,12 @@
406
356
  */
407
357
  const updateHeightAndScroll = (immediate = false) => {
408
358
  if (!initialized && mode === 'bottomToTop') {
409
- setTimeout(() => {
359
+ tick().then(() => {
410
360
  if (containerElement) {
411
361
  const initialHeight = containerElement.getBoundingClientRect().height
412
362
  height = initialHeight
413
363
 
414
- setTimeout(() => {
364
+ tick().then(() => {
415
365
  if (containerElement && viewportElement) {
416
366
  const finalHeight = containerElement.getBoundingClientRect().height
417
367
  height = finalHeight
@@ -438,9 +388,9 @@
438
388
  }
439
389
  })
440
390
  }
441
- }, 100)
391
+ })
442
392
  }
443
- }, 100)
393
+ })
444
394
  return
445
395
  }
446
396
 
@@ -541,6 +491,86 @@
541
491
  prevHeight = calculatedItemHeight
542
492
  }
543
493
  })
494
+
495
+ /**
496
+ * Scrolls the virtual list to the item at the given index.
497
+ *
498
+ * @function scrollToIndex
499
+ * @param index The index of the item to scroll to.
500
+ * @param smoothScroll (default: true) Whether to use smooth scrolling.
501
+ * @param shouldThrowOnBounds (default: true) Whether to throw an error if the index is out of bounds.
502
+ *
503
+ * @example
504
+ * // Svelte usage:
505
+ * // In your <script> block:
506
+ * import SvelteVirtualList from '@humanspeak/svelte-virtual-list';
507
+ * let virtualList;
508
+ * const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
509
+ *
510
+ * // In your markup:
511
+ * <button onclick={() => virtualList.scrollToIndex(5000)}>
512
+ * Scroll to 5000
513
+ * </button>
514
+ * <SvelteVirtualList {items} bind:this={virtualList}>
515
+ * {#snippet renderItem(item)}
516
+ * <div>{item.text}</div>
517
+ * {/snippet}
518
+ * </SvelteVirtualList>
519
+ *
520
+ * @returns {void}
521
+ * @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
522
+ */
523
+ export const scrollToIndex = (
524
+ index: number,
525
+ smoothScroll = true,
526
+ shouldThrowOnBounds = true
527
+ ): void => {
528
+ if (!items.length) return
529
+ if (!viewportElement) {
530
+ tick().then(() => {
531
+ if (!viewportElement) return
532
+ doScroll()
533
+ })
534
+ return
535
+ }
536
+ doScroll()
537
+
538
+ function doScroll() {
539
+ const target = Number.isFinite(index) ? Math.trunc(index) : 0
540
+ const clampedIndex = Math.max(0, Math.min(target, items.length - 1))
541
+ if ((target < 0 || target >= items.length) && shouldThrowOnBounds) {
542
+ throw new Error(
543
+ `scrollToIndex: index ${target} is out of bounds (0-${items.length - 1})`
544
+ )
545
+ }
546
+ if (mode === 'topToBottom') {
547
+ const scrollTopTarget = getScrollOffsetForIndex(
548
+ heightCache,
549
+ calculatedItemHeight,
550
+ clampedIndex
551
+ )
552
+ viewportElement.scrollTo({
553
+ top: scrollTopTarget,
554
+ behavior: smoothScroll ? 'smooth' : 'auto'
555
+ })
556
+ } else if (mode === 'bottomToTop') {
557
+ // Invert the index for reversed rendering
558
+ const reversedIndex = items.length - 1 - clampedIndex
559
+ const itemBottom = getScrollOffsetForIndex(
560
+ heightCache,
561
+ calculatedItemHeight,
562
+ reversedIndex + 1
563
+ )
564
+ const scrollTopTarget = Math.max(0, itemBottom - height)
565
+ viewportElement.scrollTo({
566
+ top: scrollTopTarget,
567
+ behavior: smoothScroll ? 'smooth' : 'auto'
568
+ })
569
+ } else {
570
+ console.warn('scrollToIndex: unknown mode:', mode)
571
+ }
572
+ }
573
+ }
544
574
  </script>
545
575
 
546
576
  <!--
@@ -597,7 +627,7 @@
597
627
  )}
598
628
  {debugFunction
599
629
  ? debugFunction(debugInfo)
600
- : console.log('Virtual List Debug:', debugInfo)}
630
+ : console.info('Virtual List Debug:', debugInfo)}
601
631
  {/if}
602
632
  <!-- Render each visible item -->
603
633
  <div bind:this={itemElements[i]}>
@@ -1,3 +1,81 @@
1
+ /**
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
+ */
1
79
  import type { SvelteVirtualListProps } from './types.js';
2
80
  /**
3
81
  * A high-performance virtualized list component that efficiently renders large datasets
@@ -41,6 +119,35 @@ import type { SvelteVirtualListProps } from './types.js';
41
119
  * - Chunked processing for smooth performance
42
120
  * - Progress tracking during initialization
43
121
  */
44
- declare const SvelteVirtualList: import("svelte").Component<SvelteVirtualListProps, {}, "">;
122
+ declare const SvelteVirtualList: import("svelte").Component<SvelteVirtualListProps, {
123
+ /**
124
+ * Scrolls the virtual list to the item at the given index.
125
+ *
126
+ * @function scrollToIndex
127
+ * @param index The index of the item to scroll to.
128
+ * @param smoothScroll (default: true) Whether to use smooth scrolling.
129
+ * @param shouldThrowOnBounds (default: true) Whether to throw an error if the index is out of bounds.
130
+ *
131
+ * @example
132
+ * // Svelte usage:
133
+ * // In your <script> block:
134
+ * import SvelteVirtualList from '@humanspeak/svelte-virtual-list';
135
+ * let virtualList;
136
+ * const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
137
+ *
138
+ * // In your markup:
139
+ * <button onclick={() => virtualList.scrollToIndex(5000)}>
140
+ * Scroll to 5000
141
+ * </button>
142
+ * <SvelteVirtualList {items} bind:this={virtualList}>
143
+ * {#snippet renderItem(item)}
144
+ * <div>{item.text}</div>
145
+ * {/snippet}
146
+ * </SvelteVirtualList>
147
+ *
148
+ * @returns {void}
149
+ * @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
150
+ */ scrollToIndex: (index: number, smoothScroll?: boolean, shouldThrowOnBounds?: boolean) => void;
151
+ }, "">;
45
152
  type SvelteVirtualList = ReturnType<typeof SvelteVirtualList>;
46
153
  export default SvelteVirtualList;
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
  /**
@@ -0,0 +1,77 @@
1
+ import type { HeightCache } from './types.js';
2
+ /**
3
+ * Calculates and updates the average height of visible items with debouncing.
4
+ *
5
+ * This function optimizes performance by:
6
+ * - Debouncing calculations to prevent excessive DOM reads (200ms default)
7
+ * - Caching item heights to minimize recalculations
8
+ * - Only updating when significant changes are detected (>1px difference)
9
+ * - Early returns to prevent unnecessary processing
10
+ *
11
+ * Implementation details:
12
+ * - Uses a debounce timeout to batch height calculations
13
+ * - Tracks calculation state to prevent concurrent updates
14
+ * - Caches heights in heightCache for reuse
15
+ * - Validates browser environment and calculation state
16
+ * - Checks for meaningful height changes before updates
17
+ *
18
+ * State interactions:
19
+ * - Updates calculatedItemHeight when significant changes occur
20
+ * - Updates lastMeasuredIndex to track progress
21
+ * - Modifies heightCache to store measured heights
22
+ * - Uses isCalculatingHeight flag for concurrency control
23
+ *
24
+ * Guard clauses:
25
+ * - Returns null if not in browser environment
26
+ * - Returns null if calculation is already in progress
27
+ * - Returns null if update timeout is pending
28
+ * - Returns null if current index matches last measured
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * // Automatically called when items are rendered
33
+ * $effect(() => {
34
+ * if (BROWSER && itemElements.length > 0 && !isCalculatingHeight) {
35
+ * calculateAverageHeightDebounced(
36
+ * false,
37
+ * null,
38
+ * () => getVisibleRange(),
39
+ * itemElements,
40
+ * heightCache,
41
+ * lastMeasuredIndex,
42
+ * currentHeight,
43
+ * handleUpdate
44
+ * )
45
+ * }
46
+ * })
47
+ * ```
48
+ *
49
+ * Change History:
50
+ *
51
+ * 2025-01-22
52
+ * - Added comprehensive test coverage for all guard clauses
53
+ * - Improved browser environment detection
54
+ * - Enhanced debounce timing precision
55
+ * - Added proper cleanup for timeouts
56
+ * - Documented all edge cases and failure modes
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
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: HeightCache, lastMeasuredIndex: number, calculatedItemHeight: number, onUpdate: (result: {
74
+ newHeight: number;
75
+ newLastMeasuredIndex: number;
76
+ updatedHeightCache: HeightCache;
77
+ }) => void, debounceTime?: number) => NodeJS.Timeout | null;
@@ -0,0 +1,90 @@
1
+ import { BROWSER } from 'esm-env';
2
+ import { calculateAverageHeight } from './virtualList.js';
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 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 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
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
+ *
59
+ *
60
+ * @param isCalculatingHeight - Flag to prevent concurrent calculations
61
+ * @param heightUpdateTimeout - Reference to existing update timeout
62
+ * @param visibleItemsGetter - Function to get current visible range
63
+ * @param itemElements - Array of DOM elements to measure
64
+ * @param heightCache - Cache of previously measured heights
65
+ * @param lastMeasuredIndex - Index of last measured element
66
+ * @param calculatedItemHeight - Current average height
67
+ * @param onUpdate - Callback for height updates
68
+ * @param debounceTime - Time to wait between calculations (default: 200ms)
69
+ * @returns Timeout object or null if calculation was skipped
70
+ */
71
+ export const calculateAverageHeightDebounced = (isCalculatingHeight, heightUpdateTimeout, visibleItemsGetter, itemElements, heightCache, lastMeasuredIndex, calculatedItemHeight,
72
+ /* trunk-ignore(eslint/no-unused-vars) */
73
+ onUpdate, debounceTime = 200) => {
74
+ if (!BROWSER || isCalculatingHeight || heightUpdateTimeout)
75
+ return null;
76
+ const visibleRange = visibleItemsGetter();
77
+ const currentIndex = visibleRange.start;
78
+ if (currentIndex === lastMeasuredIndex)
79
+ return null;
80
+ return setTimeout(() => {
81
+ const { newHeight, newLastMeasuredIndex, updatedHeightCache } = calculateAverageHeight(itemElements, visibleRange, heightCache, calculatedItemHeight);
82
+ if (Math.abs(newHeight - calculatedItemHeight) > 1) {
83
+ onUpdate({
84
+ newHeight,
85
+ newLastMeasuredIndex,
86
+ updatedHeightCache
87
+ });
88
+ }
89
+ }, debounceTime);
90
+ };
@@ -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
+ }
@@ -39,3 +39,9 @@ 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>;
@@ -71,7 +71,6 @@ export declare const updateHeightAndScroll: (state: VirtualListState, setters: V
71
71
  * @param {HTMLElement[]} itemElements - Array of currently rendered item elements
72
72
  * @param {{ start: number }} visibleRange - Object containing the start index of visible items
73
73
  * @param {Record<number, number>} heightCache - Cache of previously measured item heights
74
- * @param {number} lastMeasuredIndex - Index of the last measured item
75
74
  * @param {number} currentItemHeight - Current average item height being used
76
75
  *
77
76
  * @returns {{
@@ -85,13 +84,12 @@ export declare const updateHeightAndScroll: (state: VirtualListState, setters: V
85
84
  * itemElements,
86
85
  * { start: 0 },
87
86
  * {},
88
- * -1,
89
87
  * 40
90
88
  * )
91
89
  */
92
90
  export declare const calculateAverageHeight: (itemElements: HTMLElement[], visibleRange: {
93
91
  start: number;
94
- }, heightCache: Record<number, number>, lastMeasuredIndex: number, currentItemHeight: number) => {
92
+ }, heightCache: Record<number, number>, currentItemHeight: number) => {
95
93
  newHeight: number;
96
94
  newLastMeasuredIndex: number;
97
95
  updatedHeightCache: Record<number, number>;
@@ -120,4 +118,39 @@ export declare const calculateAverageHeight: (itemElements: HTMLElement[], visib
120
118
  * () => console.log('All items processed')
121
119
  * )
122
120
  */
123
- export declare const processChunked: (items: any[], chunkSize: number, onProgress: (processed: number) => void, onComplete: () => void) => Promise<void>;
121
+ export declare const processChunked: (items: any[], // eslint-disable-line @typescript-eslint/no-explicit-any
122
+ chunkSize: number, onProgress: (processed: number) => void, // eslint-disable-line no-unused-vars
123
+ onComplete: () => void) => Promise<void>;
124
+ /**
125
+ * Builds a block sum array for fast offset calculation in large virtual lists.
126
+ * Each entry in the array is the total height up to the end of that block (exclusive).
127
+ *
128
+ * @param {Record<number, number>} heightCache - Map of measured item heights
129
+ * @param {number} calculatedItemHeight - Estimated height for unmeasured items
130
+ * @param {number} totalItems - Total number of items in the list
131
+ * @param {number} blockSize - Number of items per block
132
+ * @returns {number[]} Array of prefix sums at each block boundary
133
+ */
134
+ export declare const buildBlockSums: (heightCache: Record<number, number>, calculatedItemHeight: number, totalItems: number, blockSize?: number) => number[];
135
+ /**
136
+ * Calculates the scroll offset (in pixels) needed to bring a specific item into view in a virtual list.
137
+ *
138
+ * Uses block memoization for efficient O(b) offset calculation, where b = block size (default 1000).
139
+ * For very large lists, this avoids O(n) iteration for every scroll.
140
+ *
141
+ * - For indices >= blockSize, sums the block prefix, then only iterates the tail within the block.
142
+ * - For small indices, falls back to the original logic.
143
+ *
144
+ * @param {Record<number, number>} heightCache - Map of measured item heights
145
+ * @param {number} calculatedItemHeight - Estimated height for unmeasured items
146
+ * @param {number} idx - The index to scroll to (exclusive)
147
+ * @param {number[]} [blockSums] - Optional precomputed block sums (for repeated queries)
148
+ * @param {number} [blockSize=1000] - Block size for memoization
149
+ * @returns {number} The total offset in pixels from the top of the list to the start of the item at idx.
150
+ *
151
+ * @example
152
+ * // For best performance with repeated queries:
153
+ * const blockSums = buildBlockSums(heightCache, calculatedItemHeight, items.length);
154
+ * const offset = getScrollOffsetForIndex(heightCache, calculatedItemHeight, 12345, blockSums);
155
+ */
156
+ export declare const getScrollOffsetForIndex: (heightCache: Record<number, number>, calculatedItemHeight: number, idx: number, blockSums?: number[], blockSize?: number) => number;
@@ -108,7 +108,6 @@ export const updateHeightAndScroll = (state, setters, immediate = false) => {
108
108
  * @param {HTMLElement[]} itemElements - Array of currently rendered item elements
109
109
  * @param {{ start: number }} visibleRange - Object containing the start index of visible items
110
110
  * @param {Record<number, number>} heightCache - Cache of previously measured item heights
111
- * @param {number} lastMeasuredIndex - Index of the last measured item
112
111
  * @param {number} currentItemHeight - Current average item height being used
113
112
  *
114
113
  * @returns {{
@@ -122,16 +121,15 @@ export const updateHeightAndScroll = (state, setters, immediate = false) => {
122
121
  * itemElements,
123
122
  * { start: 0 },
124
123
  * {},
125
- * -1,
126
124
  * 40
127
125
  * )
128
126
  */
129
- export const calculateAverageHeight = (itemElements, visibleRange, heightCache, lastMeasuredIndex, currentItemHeight) => {
127
+ export const calculateAverageHeight = (itemElements, visibleRange, heightCache, currentItemHeight) => {
130
128
  const validElements = itemElements.filter((el) => el);
131
129
  if (validElements.length === 0) {
132
130
  return {
133
131
  newHeight: currentItemHeight,
134
- newLastMeasuredIndex: lastMeasuredIndex,
132
+ newLastMeasuredIndex: visibleRange.start,
135
133
  updatedHeightCache: heightCache
136
134
  };
137
135
  }
@@ -140,14 +138,23 @@ export const calculateAverageHeight = (itemElements, visibleRange, heightCache,
140
138
  validElements.forEach((el, i) => {
141
139
  const itemIndex = visibleRange.start + i;
142
140
  if (!newHeightCache[itemIndex]) {
143
- newHeightCache[itemIndex] = el.getBoundingClientRect().height;
141
+ try {
142
+ const height = el.getBoundingClientRect().height;
143
+ if (Number.isFinite(height) && height > 0) {
144
+ newHeightCache[itemIndex] = height;
145
+ }
146
+ }
147
+ catch {
148
+ // Skip invalid measurements
149
+ }
144
150
  }
145
151
  });
146
- // Calculate average from cached heights
147
- const heights = Object.values(newHeightCache);
148
- const averageHeight = heights.reduce((sum, h) => sum + h, 0) / heights.length;
152
+ // Calculate average from valid cached heights
153
+ const validHeights = Object.values(newHeightCache).filter((h) => Number.isFinite(h) && h > 0);
149
154
  return {
150
- newHeight: averageHeight > 0 && !isNaN(averageHeight) ? averageHeight : currentItemHeight,
155
+ newHeight: validHeights.length > 0
156
+ ? validHeights.reduce((sum, h) => sum + h, 0) / validHeights.length
157
+ : currentItemHeight,
151
158
  newLastMeasuredIndex: visibleRange.start,
152
159
  updatedHeightCache: newHeightCache
153
160
  };
@@ -195,3 +202,68 @@ onComplete) => {
195
202
  };
196
203
  await processChunk(0);
197
204
  };
205
+ /**
206
+ * Builds a block sum array for fast offset calculation in large virtual lists.
207
+ * Each entry in the array is the total height up to the end of that block (exclusive).
208
+ *
209
+ * @param {Record<number, number>} heightCache - Map of measured item heights
210
+ * @param {number} calculatedItemHeight - Estimated height for unmeasured items
211
+ * @param {number} totalItems - Total number of items in the list
212
+ * @param {number} blockSize - Number of items per block
213
+ * @returns {number[]} Array of prefix sums at each block boundary
214
+ */
215
+ export const buildBlockSums = (heightCache, calculatedItemHeight, totalItems, blockSize = 1000) => {
216
+ const blockSums = [];
217
+ let sum = 0;
218
+ for (let i = 0; i < totalItems; i++) {
219
+ sum += heightCache[i] ?? calculatedItemHeight;
220
+ if ((i + 1) % blockSize === 0) {
221
+ blockSums.push(sum);
222
+ }
223
+ }
224
+ // Push the last partial block if needed
225
+ if (totalItems % blockSize !== 0) {
226
+ blockSums.push(sum);
227
+ }
228
+ return blockSums;
229
+ };
230
+ /**
231
+ * Calculates the scroll offset (in pixels) needed to bring a specific item into view in a virtual list.
232
+ *
233
+ * Uses block memoization for efficient O(b) offset calculation, where b = block size (default 1000).
234
+ * For very large lists, this avoids O(n) iteration for every scroll.
235
+ *
236
+ * - For indices >= blockSize, sums the block prefix, then only iterates the tail within the block.
237
+ * - For small indices, falls back to the original logic.
238
+ *
239
+ * @param {Record<number, number>} heightCache - Map of measured item heights
240
+ * @param {number} calculatedItemHeight - Estimated height for unmeasured items
241
+ * @param {number} idx - The index to scroll to (exclusive)
242
+ * @param {number[]} [blockSums] - Optional precomputed block sums (for repeated queries)
243
+ * @param {number} [blockSize=1000] - Block size for memoization
244
+ * @returns {number} The total offset in pixels from the top of the list to the start of the item at idx.
245
+ *
246
+ * @example
247
+ * // For best performance with repeated queries:
248
+ * const blockSums = buildBlockSums(heightCache, calculatedItemHeight, items.length);
249
+ * const offset = getScrollOffsetForIndex(heightCache, calculatedItemHeight, 12345, blockSums);
250
+ */
251
+ export const getScrollOffsetForIndex = (heightCache, calculatedItemHeight, idx, blockSums, blockSize = 1000) => {
252
+ if (idx <= 0)
253
+ return 0;
254
+ if (!blockSums) {
255
+ // Fallback: O(n) for a single query
256
+ let offset = 0;
257
+ for (let i = 0; i < idx; i++) {
258
+ offset += heightCache[i] ?? calculatedItemHeight;
259
+ }
260
+ return offset;
261
+ }
262
+ const blockIdx = Math.floor(idx / blockSize);
263
+ let offset = blockIdx > 0 ? blockSums[blockIdx - 1] : 0;
264
+ const start = blockIdx * blockSize;
265
+ for (let i = start; i < idx; i++) {
266
+ offset += heightCache[i] ?? calculatedItemHeight;
267
+ }
268
+ return offset;
269
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-virtual-list",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "A lightweight, high-performance virtual list component for Svelte 5 that renders large datasets with minimal memory usage. Features include dynamic height support, smooth scrolling, TypeScript support, and efficient DOM recycling. Ideal for infinite scrolling lists, data tables, chat interfaces, and any application requiring the rendering of thousands of items without compromising performance. Zero dependencies and fully customizable.",
5
5
  "keywords": [
6
6
  "svelte",
@@ -57,6 +57,7 @@
57
57
  "prepublishOnly": "npm run package",
58
58
  "preview": "vite preview",
59
59
  "test": "vitest run --coverage",
60
+ "test:all": "npm run test && npm run test:e2e",
60
61
  "test:e2e": "playwright test",
61
62
  "test:e2e:debug": "playwright test --debug",
62
63
  "test:e2e:report": "playwright show-report",
@@ -64,56 +65,57 @@
64
65
  "test:only": "vitest run",
65
66
  "test:watch": "vitest"
66
67
  },
68
+ "overrides": {
69
+ "@sveltejs/kit": {
70
+ "cookie": "^0.7.0"
71
+ }
72
+ },
67
73
  "dependencies": {
68
74
  "esm-env": "^1.2.2"
69
75
  },
70
76
  "devDependencies": {
71
- "@eslint/js": "^9.18.0",
72
- "@playwright/test": "^1.49.1",
73
- "@sveltejs/adapter-auto": "^3.3.1",
74
- "@sveltejs/kit": "^2.15.2",
75
- "@sveltejs/package": "^2.3.7",
77
+ "@eslint/compat": "^1.2.9",
78
+ "@eslint/js": "^9.28.0",
79
+ "@faker-js/faker": "^9.8.0",
80
+ "@playwright/test": "^1.52.0",
81
+ "@sveltejs/adapter-auto": "^6.0.1",
82
+ "@sveltejs/kit": "^2.21.1",
83
+ "@sveltejs/package": "^2.3.11",
76
84
  "@sveltejs/vite-plugin-svelte": "^5.0.3",
77
85
  "@testing-library/jest-dom": "^6.6.3",
78
- "@testing-library/svelte": "^5.2.6",
79
- "@testing-library/user-event": "^14.5.2",
80
- "@types/node": "^22.10.6",
81
- "@typescript-eslint/eslint-plugin": "^8.20.0",
82
- "@typescript-eslint/parser": "^8.20.0",
83
- "@vitest/coverage-v8": "^3.0.0-beta.4",
84
- "eslint": "^9.18.0",
85
- "eslint-config-prettier": "^10.0.1",
86
- "eslint-plugin-svelte": "^2.46.1",
87
- "globals": "^15.14.0",
88
- "jsdom": "^26.0.0",
89
- "prettier": "^3.4.2",
86
+ "@testing-library/svelte": "^5.2.8",
87
+ "@testing-library/user-event": "^14.6.1",
88
+ "@types/node": "^22.15.29",
89
+ "@typescript-eslint/eslint-plugin": "^8.33.0",
90
+ "@typescript-eslint/parser": "^8.33.0",
91
+ "@vitest/coverage-v8": "^3.1.4",
92
+ "eslint": "^9.28.0",
93
+ "eslint-config-prettier": "^10.1.5",
94
+ "eslint-plugin-import": "^2.31.0",
95
+ "eslint-plugin-svelte": "^3.9.0",
96
+ "eslint-plugin-unused-imports": "^4.1.4",
97
+ "globals": "^16.2.0",
98
+ "jsdom": "^26.1.0",
99
+ "prettier": "^3.5.3",
90
100
  "prettier-plugin-organize-imports": "^4.1.0",
91
- "prettier-plugin-svelte": "^3.3.3",
92
- "publint": "^0.3.2",
93
- "svelte": "^5.18.0",
94
- "svelte-check": "^4.1.4",
95
- "typescript": "^5.7.3",
96
- "vite": "^6.0.7",
97
- "vitest": "^3.0.0-beta.4"
101
+ "prettier-plugin-svelte": "^3.4.0",
102
+ "publint": "^0.3.12",
103
+ "svelte": "^5.33.12",
104
+ "svelte-check": "^4.2.1",
105
+ "typescript": "^5.8.3",
106
+ "typescript-eslint": "^8.33.0",
107
+ "vite": "^6.3.5",
108
+ "vitest": "^3.1.4"
98
109
  },
99
110
  "peerDependencies": {
100
111
  "svelte": "^5.0.0"
101
112
  },
102
113
  "volta": {
103
- "node": "22.12.0"
114
+ "node": "22.16.0"
104
115
  },
105
116
  "publishConfig": {
106
117
  "access": "public"
107
118
  },
108
- "overrides": {
109
- "@sveltejs/kit": {
110
- "cookie": "^0.7.0"
111
- },
112
- "jsdom": {
113
- "cssstyle": "2.3.0",
114
- "@asamuzakjp/css-color": "1.0.0"
115
- }
116
- },
117
119
  "tags": [
118
120
  "svelte",
119
121
  "virtual-list",