@humanspeak/svelte-virtual-list 0.2.5 โ†’ 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.
package/README.md CHANGED
@@ -28,11 +28,11 @@ A high-performance virtual list component for Svelte 5 applications that efficie
28
28
  - ๐Ÿง  Memory-optimized for 10k+ items
29
29
  - ๐Ÿงช Comprehensive test coverage (vitest and playwright)
30
30
  - ๐Ÿš€ Progressive initialization for large datasets
31
- - ๐Ÿ•น๏ธ Programmatic scrolling with `scrollToIndex`
31
+ - ๐Ÿ•น๏ธ Programmatic scrolling with `scroll`
32
32
 
33
- ## scrollToIndex: Programmatic Scrolling
33
+ ## scroll: Programmatic Scrolling
34
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.
35
+ You can now programmatically scroll to any item in the list using the `scroll` method. This is useful for chat apps, jump-to-item navigation, and more. You can check the usage in `src/routes/tests/scroll`. Thank you for the feature request!
36
36
 
37
37
  ### Usage Example
38
38
 
@@ -43,8 +43,8 @@ You can now programmatically scroll to any item in the list using the `scrollToI
43
43
  const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }))
44
44
 
45
45
  function goToItem5000() {
46
- // Scroll to item 5000 with smooth scrolling
47
- listRef.scrollToIndex(5000, true)
46
+ // Scroll to item 5000 with smooth scrolling and auto alignment
47
+ listRef.scroll({ index: 5000, smoothScroll: true, align: 'auto' })
48
48
  }
49
49
  </script>
50
50
 
@@ -58,10 +58,23 @@ You can now programmatically scroll to any item in the list using the `scrollToI
58
58
 
59
59
  ### API
60
60
 
61
- - `scrollToIndex(index: number, smoothScroll = true, shouldThrowOnBounds = true)`
61
+ - `scroll(options: { index: number; smoothScroll?: boolean; shouldThrowOnBounds?: boolean; align?: 'auto' | 'top' | 'bottom' | 'nearest' })`
62
62
  - `index`: The item index to scroll to (0-based)
63
63
  - `smoothScroll`: If true, uses smooth scrolling (default: true)
64
64
  - `shouldThrowOnBounds`: If true, throws if index is out of bounds (default: true)
65
+ - `align`: Where to align the item in the viewport:
66
+ - `'auto'` (default): Only scroll if not visible, align to top or bottom as appropriate
67
+ - `'top'`: Always align to the top
68
+ - `'bottom'`: Always align to the bottom
69
+ - `'nearest'`: Scroll as little as possible to bring the item into view (like native scrollIntoView({ block: 'nearest' }))
70
+
71
+ #### Usage Examples
72
+
73
+ ```svelte
74
+ <button on:click={() => listRef.scroll({ index: 5000, align: 'nearest' })}>
75
+ Scroll to item 5000 (nearest)
76
+ </button>
77
+ ```
65
78
 
66
79
  ## Installation
67
80
 
@@ -1,45 +1,63 @@
1
1
  <!--
2
- @component
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.
6
-
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
19
-
20
- Usage:
2
+ @component SvelteVirtualList
3
+
4
+ A high-performance, memory-efficient virtualized list component for Svelte 5.
5
+ Renders only visible items plus a buffer, supporting dynamic item heights,
6
+ bi-directional (top-to-bottom and bottom-to-top) scrolling, and programmatic control.
7
+
8
+ =============================
9
+ == Key Features ==
10
+ =============================
11
+ - Dynamic item height support (no fixed height required)
12
+ - Top-to-bottom and bottom-to-top (chat-style) scrolling
13
+ - Programmatic scrolling with flexible alignment (top, bottom, auto)
14
+ - Smooth scrolling and buffer size configuration
15
+ - SSR compatible and hydration-friendly
16
+ - TypeScript and Svelte 5 runes/snippets support
17
+ - Customizable styling via class props
18
+ - Debug mode for development and testing
19
+ - Optimized for large lists (10k+ items)
20
+ - Comprehensive test coverage (unit and E2E)
21
+
22
+ =============================
23
+ == Usage Example ==
24
+ =============================
21
25
  ```svelte
22
26
  <SvelteVirtualList
23
27
  items={data}
24
- defaultEstimatedItemHeight={40}
25
- mode="topToBottom"
28
+ mode="bottomToTop"
29
+ bind:this={listRef}
26
30
  >
27
- {#snippet renderItem(item, index)}
28
- <div class="item">{item.text}</div>
31
+ {#snippet renderItem(item)}
32
+ <div>{item.text}</div>
29
33
  {/snippet}
30
34
  </SvelteVirtualList>
31
35
  ```
32
36
 
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
37
+ =============================
38
+ == Architecture Notes ==
39
+ =============================
40
+ - Uses a four-layer DOM structure for optimal performance
41
+ - Only visible items + buffer are mounted in the DOM
42
+ - Height caching and estimation for dynamic content
43
+ - Handles resize events and dynamic content changes
44
+ - Supports chunked initialization for very large lists
45
+ - All scrolling logic is centralized in the scroll() method
46
+ - Bi-directional support: mode="topToBottom" or "bottomToTop"
47
+ - Designed for extensibility and easy debugging
48
+
49
+ =============================
50
+ == For Contributors ==
51
+ =============================
52
+ - Please keep all scrolling logic in the scroll() method
53
+ - Add new features behind feature flags or as optional props
54
+ - Write tests for all new features (see /test and /tests/scroll)
55
+ - Use TypeScript and Svelte 5 runes for all new code
56
+ - Document all exported functions and props with JSDoc
57
+ - See README.md for API and usage details
58
+ - For questions, open an issue or discussion on GitHub
59
+
60
+ MIT License ยฉ Humanspeak, Inc.
43
61
  -->
44
62
 
45
63
  <script lang="ts">
@@ -122,16 +140,21 @@
122
140
  * - Progressive size adjustment system
123
141
  */
124
142
 
125
- import type { SvelteVirtualListProps } from './types.js'
143
+ import {
144
+ DEFAULT_SCROLL_OPTIONS,
145
+ type SvelteVirtualListPreviousVisibleRange,
146
+ type SvelteVirtualListProps,
147
+ type SvelteVirtualListScrollOptions
148
+ } from './types.js'
126
149
  import { calculateAverageHeightDebounced } from './utils/heightCalculation.js'
127
150
  import { createRafScheduler } from './utils/raf.js'
128
151
  import {
129
152
  calculateScrollPosition,
130
153
  calculateTransformY,
131
154
  calculateVisibleRange,
155
+ getScrollOffsetForIndex,
132
156
  processChunked,
133
- updateHeightAndScroll as utilsUpdateHeightAndScroll,
134
- getScrollOffsetForIndex
157
+ updateHeightAndScroll as utilsUpdateHeightAndScroll
135
158
  } from './utils/virtualList.js'
136
159
  import { createDebugInfo, shouldShowDebugInfo } from './utils/virtualListDebug.js'
137
160
  import { BROWSER } from 'esm-env'
@@ -185,37 +208,43 @@
185
208
  */
186
209
  let heightUpdateTimeout: ReturnType<typeof setTimeout> | null = null // Debounce timer for height updates
187
210
  let resizeObserver: ResizeObserver | null = null // Watches for container size changes
211
+ let itemResizeObserver: ResizeObserver | null = null // Watches for individual item size changes
188
212
 
189
213
  /**
190
214
  * Performance Optimization State
191
215
  */
192
216
  let heightCache = $state<Record<number, number>>({}) // Cache of measured item heights
217
+ let dirtyItems = $state(new Set<number>()) // Set of item indices that need height recalculation
193
218
  const chunkSize = $state(50) // Number of items to process in each chunk
194
219
  let processedItems = $state(0) // Number of items processed during initialization
195
220
 
196
- let prevVisibleRange = $state<{ start: number; end: number } | null>(null)
221
+ let prevVisibleRange = $state<SvelteVirtualListPreviousVisibleRange | null>(null)
197
222
  let prevHeight = $state<number>(0)
198
223
 
199
224
  // Trigger height calculation when items are rendered
200
225
  $effect(() => {
201
226
  if (BROWSER && itemElements.length > 0 && !isCalculatingHeight) {
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
- )
227
+ updateHeight()
216
228
  }
217
229
  })
218
230
 
231
+ const updateHeight = () => {
232
+ heightUpdateTimeout = calculateAverageHeightDebounced(
233
+ isCalculatingHeight,
234
+ heightUpdateTimeout,
235
+ visibleItems,
236
+ itemElements,
237
+ heightCache,
238
+ lastMeasuredIndex,
239
+ calculatedItemHeight,
240
+ (result) => {
241
+ calculatedItemHeight = result.newHeight
242
+ lastMeasuredIndex = result.newLastMeasuredIndex
243
+ heightCache = result.updatedHeightCache
244
+ }
245
+ )
246
+ }
247
+
219
248
  // Add new effect to handle height changes
220
249
  $effect(() => {
221
250
  if (BROWSER && initialized && mode === 'bottomToTop' && viewportElement) {
@@ -291,10 +320,10 @@
291
320
  * console.log(`Rendering items from ${range.start} to ${range.end}`)
292
321
  * ```
293
322
  *
294
- * @returns {{ start: number, end: number }} Object containing start and end indices of visible items
323
+ * @returns {SvelteVirtualListPreviousVisibleRange} Object containing start and end indices of visible items
295
324
  */
296
- const visibleItems = $derived(() => {
297
- if (!items.length) return { start: 0, end: 0 }
325
+ const visibleItems = $derived((): SvelteVirtualListPreviousVisibleRange => {
326
+ if (!items.length) return { start: 0, end: 0 } as SvelteVirtualListPreviousVisibleRange
298
327
  const viewportHeight = height || 0
299
328
 
300
329
  return calculateVisibleRange(
@@ -460,6 +489,44 @@
460
489
  }
461
490
  })
462
491
 
492
+ // Create itemResizeObserver immediately when in browser
493
+ if (BROWSER) {
494
+ // Watch for individual item size changes
495
+ itemResizeObserver = new ResizeObserver((entries) => {
496
+ let shouldRecalculate = false
497
+
498
+ if (debug) {
499
+ console.log(`ResizeObserver fired for ${entries.length} entries`)
500
+ }
501
+
502
+ for (const entry of entries) {
503
+ const element = entry.target as HTMLElement
504
+ const elementIndex = itemElements.indexOf(element)
505
+
506
+ if (elementIndex !== -1) {
507
+ const actualIndex = visibleItems().start + elementIndex
508
+
509
+ // ResizeObserver fired = element resized, so add to dirty queue
510
+ dirtyItems.add(actualIndex)
511
+ shouldRecalculate = true
512
+
513
+ if (debug) {
514
+ console.log(
515
+ `Item ${actualIndex} marked dirty (resized), queue size: ${dirtyItems.size}`
516
+ )
517
+ }
518
+ }
519
+ }
520
+
521
+ if (shouldRecalculate) {
522
+ // Trigger virtual list recalculation
523
+ rafSchedule(() => {
524
+ updateHeight()
525
+ })
526
+ }
527
+ })
528
+ }
529
+
463
530
  // Setup and cleanup
464
531
  onMount(() => {
465
532
  if (BROWSER) {
@@ -480,6 +547,9 @@
480
547
  if (resizeObserver) {
481
548
  resizeObserver.disconnect()
482
549
  }
550
+ if (itemResizeObserver) {
551
+ itemResizeObserver.disconnect()
552
+ }
483
553
  }
484
554
  }
485
555
  })
@@ -495,6 +565,9 @@
495
565
  /**
496
566
  * Scrolls the virtual list to the item at the given index.
497
567
  *
568
+ * @deprecated This function is deprecated and will be removed in a future version.
569
+ * Use the new scroll method from the component instance instead.
570
+ *
498
571
  * @function scrollToIndex
499
572
  * @param index The index of the item to scroll to.
500
573
  * @param smoothScroll (default: true) Whether to use smooth scrolling.
@@ -525,49 +598,239 @@
525
598
  smoothScroll = true,
526
599
  shouldThrowOnBounds = true
527
600
  ): void => {
601
+ // Deprecation warning
602
+ console.warn(
603
+ 'SvelteVirtualList: scrollToIndex is deprecated and will be removed in a future version. ' +
604
+ 'Use the new scroll method from the component instance instead.'
605
+ )
606
+
607
+ // Call the new scroll function with the provided parameters
608
+ scroll({ index, smoothScroll, shouldThrowOnBounds })
609
+ }
610
+
611
+ /**
612
+ * Scrolls the virtual list to the item at the given index using a type-based options approach.
613
+ *
614
+ * @function scroll
615
+ * @param options Configuration options for scrolling behavior.
616
+ *
617
+ * @example
618
+ * // Svelte usage:
619
+ * // In your <script> block:
620
+ * import SvelteVirtualList from './index.js';
621
+ * let virtualList;
622
+ * const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
623
+ *
624
+ * <button onclick={() => virtualList.scroll({ index: 5000 })}>
625
+ * Scroll to 5000
626
+ * </button>
627
+ * <SvelteVirtualList {items} bind:this={virtualList}>
628
+ * {#snippet renderItem(item)}
629
+ * <div>{item.text}</div>
630
+ * {/snippet}
631
+ * </SvelteVirtualList>
632
+ *
633
+ * @returns {void}
634
+ * @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
635
+ */
636
+ export const scroll = (options: SvelteVirtualListScrollOptions): void => {
637
+ const { index, smoothScroll, shouldThrowOnBounds, align } = {
638
+ ...DEFAULT_SCROLL_OPTIONS,
639
+ ...options
640
+ }
641
+
528
642
  if (!items.length) return
529
643
  if (!viewportElement) {
530
644
  tick().then(() => {
531
645
  if (!viewportElement) return
532
- doScroll()
646
+ scroll({ index, smoothScroll, shouldThrowOnBounds, align })
533
647
  })
534
648
  return
535
649
  }
536
- doScroll()
537
650
 
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) {
651
+ // Bounds checking
652
+ let targetIndex = index
653
+ if (targetIndex < 0 || targetIndex >= items.length) {
654
+ if (shouldThrowOnBounds) {
542
655
  throw new Error(
543
- `scrollToIndex: index ${target} is out of bounds (0-${items.length - 1})`
656
+ `scroll: index ${targetIndex} is out of bounds (0-${items.length - 1})`
544
657
  )
658
+ } else {
659
+ targetIndex = Math.max(0, Math.min(targetIndex, items.length - 1))
545
660
  }
546
- if (mode === 'topToBottom') {
547
- const scrollTopTarget = getScrollOffsetForIndex(
661
+ }
662
+
663
+ const { start: firstVisibleIndex, end: lastVisibleIndex } = visibleItems()
664
+ let scrollTarget: number | null = null
665
+
666
+ if (mode === 'bottomToTop') {
667
+ const totalHeight = items.length * calculatedItemHeight
668
+ const itemOffset = targetIndex * calculatedItemHeight
669
+ const itemHeight = calculatedItemHeight
670
+ if (align === 'auto') {
671
+ // If item is above the viewport, align to top
672
+ if (targetIndex < firstVisibleIndex) {
673
+ scrollTarget = Math.max(0, totalHeight - (itemOffset + itemHeight))
674
+ // If item is below the viewport, align to bottom
675
+ } else if (targetIndex > lastVisibleIndex - 1) {
676
+ scrollTarget = Math.max(0, totalHeight - itemOffset - height)
677
+ } else {
678
+ // Item is visible but not aligned: align to nearest edge
679
+ // Calculate the offset of the item relative to the viewport
680
+ const itemTop = totalHeight - (itemOffset + itemHeight)
681
+ const itemBottom = totalHeight - itemOffset
682
+ const distanceToTop = Math.abs(scrollTop - itemTop)
683
+ const distanceToBottom = Math.abs(scrollTop + height - itemBottom)
684
+ if (distanceToTop < distanceToBottom) {
685
+ // Closer to top, align to top
686
+ scrollTarget = itemTop
687
+ } else {
688
+ // Closer to bottom, align to bottom
689
+ scrollTarget = Math.max(0, itemBottom - height)
690
+ }
691
+ }
692
+ } else if (align === 'top') {
693
+ // Align to top
694
+ scrollTarget = Math.max(0, totalHeight - (itemOffset + itemHeight))
695
+ } else if (align === 'bottom') {
696
+ // Align to bottom
697
+ scrollTarget = Math.max(0, totalHeight - itemOffset - height)
698
+ } else if (align === 'nearest') {
699
+ // If not visible, align to nearest edge; if visible, do nothing
700
+ const itemTop = totalHeight - (itemOffset + itemHeight)
701
+ const itemBottom = totalHeight - itemOffset
702
+ if (itemBottom <= scrollTop || itemTop >= scrollTop + height) {
703
+ // Not visible, align to nearest edge
704
+ const distanceToTop = Math.abs(scrollTop - itemTop)
705
+ const distanceToBottom = Math.abs(scrollTop + height - itemBottom)
706
+ if (distanceToTop < distanceToBottom) {
707
+ scrollTarget = itemTop
708
+ } else {
709
+ scrollTarget = Math.max(0, itemBottom - height)
710
+ }
711
+ } else {
712
+ // Already visible, do nothing
713
+ return
714
+ }
715
+ }
716
+ } else {
717
+ // topToBottom (default)
718
+ if (align === 'auto') {
719
+ // If item is above the viewport, align to top
720
+ if (targetIndex < firstVisibleIndex) {
721
+ scrollTarget = getScrollOffsetForIndex(
722
+ heightCache,
723
+ calculatedItemHeight,
724
+ targetIndex
725
+ )
726
+ // If item is below the viewport, align to bottom
727
+ } else if (targetIndex > lastVisibleIndex - 1) {
728
+ const itemBottom = getScrollOffsetForIndex(
729
+ heightCache,
730
+ calculatedItemHeight,
731
+ targetIndex + 1
732
+ )
733
+ scrollTarget = Math.max(0, itemBottom - height)
734
+ } else {
735
+ // Item is visible but not aligned: align to nearest edge
736
+ const itemTop = getScrollOffsetForIndex(
737
+ heightCache,
738
+ calculatedItemHeight,
739
+ targetIndex
740
+ )
741
+ const itemBottom = getScrollOffsetForIndex(
742
+ heightCache,
743
+ calculatedItemHeight,
744
+ targetIndex + 1
745
+ )
746
+ const distanceToTop = Math.abs(scrollTop - itemTop)
747
+ const distanceToBottom = Math.abs(scrollTop + height - itemBottom)
748
+ if (distanceToTop < distanceToBottom) {
749
+ // Closer to top, align to top
750
+ scrollTarget = itemTop
751
+ } else {
752
+ // Closer to bottom, align to bottom
753
+ scrollTarget = Math.max(0, itemBottom - height)
754
+ }
755
+ }
756
+ } else if (align === 'top') {
757
+ scrollTarget = getScrollOffsetForIndex(
548
758
  heightCache,
549
759
  calculatedItemHeight,
550
- clampedIndex
760
+ targetIndex
551
761
  )
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
762
+ } else if (align === 'bottom') {
559
763
  const itemBottom = getScrollOffsetForIndex(
560
764
  heightCache,
561
765
  calculatedItemHeight,
562
- reversedIndex + 1
766
+ targetIndex + 1
767
+ )
768
+ scrollTarget = Math.max(0, itemBottom - height)
769
+ } else if (align === 'nearest') {
770
+ const itemTop = getScrollOffsetForIndex(
771
+ heightCache,
772
+ calculatedItemHeight,
773
+ targetIndex
774
+ )
775
+ const itemBottom = getScrollOffsetForIndex(
776
+ heightCache,
777
+ calculatedItemHeight,
778
+ targetIndex + 1
779
+ )
780
+ if (itemBottom <= scrollTop || itemTop >= scrollTop + height) {
781
+ // Not visible, align to nearest edge
782
+ const distanceToTop = Math.abs(scrollTop - itemTop)
783
+ const distanceToBottom = Math.abs(scrollTop + height - itemBottom)
784
+ if (distanceToTop < distanceToBottom) {
785
+ scrollTarget = itemTop
786
+ } else {
787
+ scrollTarget = Math.max(0, itemBottom - height)
788
+ }
789
+ } else {
790
+ // Already visible, do nothing
791
+ return
792
+ }
793
+ }
794
+ }
795
+
796
+ if (scrollTarget !== null) {
797
+ viewportElement.scrollTo({
798
+ top: scrollTarget,
799
+ behavior: smoothScroll ? 'smooth' : 'auto'
800
+ })
801
+ }
802
+ }
803
+
804
+ /**
805
+ * Custom Svelte action to automatically observe item elements for size changes.
806
+ * This action is applied to each item element to detect when its dimensions change.
807
+ *
808
+ * @param element - The HTML element to observe
809
+ * @returns {{ destroy: () => void }} Object with destroy method for cleanup
810
+ */
811
+ function autoObserveItemResize(element: HTMLElement) {
812
+ if (itemResizeObserver) {
813
+ itemResizeObserver.observe(element)
814
+ if (debug) {
815
+ console.log(
816
+ 'Started observing element:',
817
+ element,
818
+ 'Current height:',
819
+ element.getBoundingClientRect().height
563
820
  )
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)
821
+ }
822
+ } else if (debug) {
823
+ console.log('itemResizeObserver not available for element:', element)
824
+ }
825
+
826
+ return {
827
+ destroy() {
828
+ if (itemResizeObserver) {
829
+ itemResizeObserver.unobserve(element)
830
+ if (debug) {
831
+ console.log('Stopped observing element:', element)
832
+ }
833
+ }
571
834
  }
572
835
  }
573
836
  }
@@ -630,7 +893,7 @@
630
893
  : console.info('Virtual List Debug:', debugInfo)}
631
894
  {/if}
632
895
  <!-- Render each visible item -->
633
- <div bind:this={itemElements[i]}>
896
+ <div bind:this={itemElements[i]} use:autoObserveItemResize>
634
897
  {@render renderItem(
635
898
  currentItem,
636
899
  mode === 'bottomToTop'
@@ -678,4 +941,10 @@
678
941
  left: 0;
679
942
  top: 0;
680
943
  }
944
+
945
+ /* Item wrapper divs should size to their content */
946
+ .virtual-list-items > div {
947
+ width: 100%;
948
+ display: block;
949
+ }
681
950
  </style>
@@ -76,53 +76,75 @@
76
76
  * - Height caching and estimation system
77
77
  * - Progressive size adjustment system
78
78
  */
79
- import type { SvelteVirtualListProps } from './types.js';
79
+ import { type SvelteVirtualListProps, type SvelteVirtualListScrollOptions } from './types.js';
80
80
  /**
81
- * A high-performance virtualized list component that efficiently renders large datasets
82
- * by only mounting DOM nodes for visible items and a small buffer. Optimized for handling
83
- * lists of 10k+ items through chunked processing and progressive initialization.
81
+ * SvelteVirtualList
84
82
  *
85
- * Props:
86
- * - `items` - Array of items to render
87
- * - `defaultEstimatedItemHeight` - Initial height estimate for items (default: 40px)
88
- * - `mode` - Scroll direction: 'topToBottom' or 'bottomToTop' (default: 'topToBottom')
89
- * - `debug` - Enable debug logging (default: false)
90
- * - `bufferSize` - Number of items to render outside visible area (default: 20)
91
- * - `containerClass` - Custom class for container element
92
- * - `viewportClass` - Custom class for viewport element
93
- * - `contentClass` - Custom class for content wrapper
94
- * - `itemsClass` - Custom class for items wrapper
95
- * - `debugFunction` - Custom debug logging function
96
- * - `testId` - Base test ID for component elements
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.
97
86
  *
98
- * Usage:
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)
100
+ *
101
+ * =============================
102
+ * == Usage Example ==
103
+ * =============================
99
104
  * ```svelte
100
105
  * <SvelteVirtualList
101
106
  * items={data}
102
- * defaultEstimatedItemHeight={40}
103
- * mode="topToBottom"
107
+ * mode="bottomToTop"
108
+ * bind:this={listRef}
104
109
  * >
105
- * {#snippet renderItem(item, index)}
106
- * <div class="item">{item.text}</div>
110
+ * {#snippet renderItem(item)}
111
+ * <div>{item.text}</div>
107
112
  * {/snippet}
108
113
  * </SvelteVirtualList>
109
114
  * ```
110
115
  *
111
- * Features:
112
- * - Dynamic height calculation
113
- * - Bidirectional scrolling
114
- * - Configurable buffer size
115
- * - Debug mode
116
- * - Custom styling
117
- * - Progressive initialization for large datasets
118
- * - Memory-optimized for 10k+ items
119
- * - Chunked processing for smooth performance
120
- * - 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.
121
140
  */
122
141
  declare const SvelteVirtualList: import("svelte").Component<SvelteVirtualListProps, {
123
142
  /**
124
143
  * Scrolls the virtual list to the item at the given index.
125
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
+ *
126
148
  * @function scrollToIndex
127
149
  * @param index The index of the item to scroll to.
128
150
  * @param smoothScroll (default: true) Whether to use smooth scrolling.
@@ -148,6 +170,31 @@ declare const SvelteVirtualList: import("svelte").Component<SvelteVirtualListPro
148
170
  * @returns {void}
149
171
  * @throws {Error} If the index is out of bounds and shouldThrowOnBounds is true
150
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;
151
198
  }, "">;
152
199
  type SvelteVirtualList = ReturnType<typeof SvelteVirtualList>;
153
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
@@ -82,3 +82,33 @@ export type SvelteVirtualListDebugInfo = {
82
82
  processedItems: number;
83
83
  averageItemHeight: number;
84
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
+ };
@@ -1,24 +1,23 @@
1
- import type { HeightCache } from './types.js';
2
1
  /**
3
2
  * Calculates and updates the average height of visible items with debouncing.
4
3
  *
5
4
  * This function optimizes performance by:
6
5
  * - Debouncing calculations to prevent excessive DOM reads (200ms default)
7
- * - Caching item heights to minimize recalculations
6
+ * - Caching item heights with dirty tracking to minimize recalculations
8
7
  * - Only updating when significant changes are detected (>1px difference)
9
8
  * - Early returns to prevent unnecessary processing
10
9
  *
11
10
  * Implementation details:
12
11
  * - Uses a debounce timeout to batch height calculations
13
12
  * - Tracks calculation state to prevent concurrent updates
14
- * - Caches heights in heightCache for reuse
13
+ * - Caches heights in heightCache with currentHeight and dirty flags for reuse
15
14
  * - Validates browser environment and calculation state
16
15
  * - Checks for meaningful height changes before updates
17
16
  *
18
17
  * State interactions:
19
18
  * - Updates calculatedItemHeight when significant changes occur
20
19
  * - Updates lastMeasuredIndex to track progress
21
- * - Modifies heightCache to store measured heights
20
+ * - Modifies heightCache to store measured heights with dirty tracking
22
21
  * - Uses isCalculatingHeight flag for concurrency control
23
22
  *
24
23
  * Guard clauses:
@@ -54,13 +53,14 @@ import type { HeightCache } from './types.js';
54
53
  * - Enhanced debounce timing precision
55
54
  * - Added proper cleanup for timeouts
56
55
  * - Documented all edge cases and failure modes
56
+ * - Updated to work with new HeightCache structure with dirty tracking
57
57
  *
58
58
  *
59
59
  * @param isCalculatingHeight - Flag to prevent concurrent calculations
60
60
  * @param heightUpdateTimeout - Reference to existing update timeout
61
61
  * @param visibleItemsGetter - Function to get current visible range
62
62
  * @param itemElements - Array of DOM elements to measure
63
- * @param heightCache - Cache of previously measured heights
63
+ * @param heightCache - Cache of previously measured heights with dirty tracking
64
64
  * @param lastMeasuredIndex - Index of last measured element
65
65
  * @param calculatedItemHeight - Current average height
66
66
  * @param onUpdate - Callback for height updates
@@ -70,8 +70,8 @@ import type { HeightCache } from './types.js';
70
70
  export declare const calculateAverageHeightDebounced: (isCalculatingHeight: boolean, heightUpdateTimeout: ReturnType<typeof setTimeout> | null, visibleItemsGetter: () => {
71
71
  start: number;
72
72
  end: number;
73
- }, itemElements: HTMLElement[], heightCache: HeightCache, lastMeasuredIndex: number, calculatedItemHeight: number, onUpdate: (result: {
73
+ }, itemElements: HTMLElement[], heightCache: Record<number, number>, lastMeasuredIndex: number, calculatedItemHeight: number, onUpdate: (result: {
74
74
  newHeight: number;
75
75
  newLastMeasuredIndex: number;
76
- updatedHeightCache: HeightCache;
76
+ updatedHeightCache: Record<number, number>;
77
77
  }) => void, debounceTime?: number) => NodeJS.Timeout | null;
@@ -1,25 +1,25 @@
1
- import { BROWSER } from 'esm-env';
2
1
  import { calculateAverageHeight } from './virtualList.js';
2
+ import { BROWSER } from 'esm-env';
3
3
  /**
4
4
  * Calculates and updates the average height of visible items with debouncing.
5
5
  *
6
6
  * This function optimizes performance by:
7
7
  * - Debouncing calculations to prevent excessive DOM reads (200ms default)
8
- * - Caching item heights to minimize recalculations
8
+ * - Caching item heights with dirty tracking to minimize recalculations
9
9
  * - Only updating when significant changes are detected (>1px difference)
10
10
  * - Early returns to prevent unnecessary processing
11
11
  *
12
12
  * Implementation details:
13
13
  * - Uses a debounce timeout to batch height calculations
14
14
  * - Tracks calculation state to prevent concurrent updates
15
- * - Caches heights in heightCache for reuse
15
+ * - Caches heights in heightCache with currentHeight and dirty flags for reuse
16
16
  * - Validates browser environment and calculation state
17
17
  * - Checks for meaningful height changes before updates
18
18
  *
19
19
  * State interactions:
20
20
  * - Updates calculatedItemHeight when significant changes occur
21
21
  * - Updates lastMeasuredIndex to track progress
22
- * - Modifies heightCache to store measured heights
22
+ * - Modifies heightCache to store measured heights with dirty tracking
23
23
  * - Uses isCalculatingHeight flag for concurrency control
24
24
  *
25
25
  * Guard clauses:
@@ -55,13 +55,14 @@ import { calculateAverageHeight } from './virtualList.js';
55
55
  * - Enhanced debounce timing precision
56
56
  * - Added proper cleanup for timeouts
57
57
  * - Documented all edge cases and failure modes
58
+ * - Updated to work with new HeightCache structure with dirty tracking
58
59
  *
59
60
  *
60
61
  * @param isCalculatingHeight - Flag to prevent concurrent calculations
61
62
  * @param heightUpdateTimeout - Reference to existing update timeout
62
63
  * @param visibleItemsGetter - Function to get current visible range
63
64
  * @param itemElements - Array of DOM elements to measure
64
- * @param heightCache - Cache of previously measured heights
65
+ * @param heightCache - Cache of previously measured heights with dirty tracking
65
66
  * @param lastMeasuredIndex - Index of last measured element
66
67
  * @param calculatedItemHeight - Current average height
67
68
  * @param onUpdate - Callback for height updates
@@ -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>;
@@ -1,4 +1,4 @@
1
- import type { SvelteVirtualListMode } from '../types.js';
1
+ import type { SvelteVirtualListMode, SvelteVirtualListPreviousVisibleRange } from '../types.js';
2
2
  import type { VirtualListSetters, VirtualListState } from './types.js';
3
3
  /**
4
4
  * Calculates the maximum scroll position for a virtual list.
@@ -26,12 +26,9 @@ export declare const calculateScrollPosition: (totalItems: number, itemHeight: n
26
26
  * @param {number} totalItems - Total number of items in the list
27
27
  * @param {number} bufferSize - Number of items to render outside the visible area
28
28
  * @param {SvelteVirtualListMode} mode - Scroll direction mode
29
- * @returns {{ start: number, end: number }} Range of indices to render
29
+ * @returns {SvelteVirtualListPreviousVisibleRange} Range of indices to render
30
30
  */
31
- export declare const calculateVisibleRange: (scrollTop: number, viewportHeight: number, itemHeight: number, totalItems: number, bufferSize: number, mode: SvelteVirtualListMode) => {
32
- start: number;
33
- end: number;
34
- };
31
+ export declare const calculateVisibleRange: (scrollTop: number, viewportHeight: number, itemHeight: number, totalItems: number, bufferSize: number, mode: SvelteVirtualListMode) => SvelteVirtualListPreviousVisibleRange;
35
32
  /**
36
33
  * Calculates the CSS transform value for positioning the virtual list items.
37
34
  *
@@ -64,19 +61,19 @@ export declare const updateHeightAndScroll: (state: VirtualListState, setters: V
64
61
  * Calculates the average height of visible items in a virtual list.
65
62
  *
66
63
  * This function optimizes performance by:
67
- * 1. Using a height cache to store measured item heights
64
+ * 1. Using a height cache to store measured item heights with dirty tracking
68
65
  * 2. Only measuring new items not in the cache
69
66
  * 3. Calculating a running average of all measured heights
70
67
  *
71
68
  * @param {HTMLElement[]} itemElements - Array of currently rendered item elements
72
69
  * @param {{ start: number }} visibleRange - Object containing the start index of visible items
73
- * @param {Record<number, number>} heightCache - Cache of previously measured item heights
70
+ * @param {HeightCache} heightCache - Cache of previously measured item heights with dirty tracking
74
71
  * @param {number} currentItemHeight - Current average item height being used
75
72
  *
76
73
  * @returns {{
77
74
  * newHeight: number,
78
75
  * newLastMeasuredIndex: number,
79
- * updatedHeightCache: Record<number, number>
76
+ * updatedHeightCache: HeightCache
80
77
  * }} Object containing new calculated height, last measured index, and updated cache
81
78
  *
82
79
  * @example
@@ -125,7 +122,7 @@ onComplete: () => void) => Promise<void>;
125
122
  * Builds a block sum array for fast offset calculation in large virtual lists.
126
123
  * Each entry in the array is the total height up to the end of that block (exclusive).
127
124
  *
128
- * @param {Record<number, number>} heightCache - Map of measured item heights
125
+ * @param {HeightCache} heightCache - Map of measured item heights with dirty tracking
129
126
  * @param {number} calculatedItemHeight - Estimated height for unmeasured items
130
127
  * @param {number} totalItems - Total number of items in the list
131
128
  * @param {number} blockSize - Number of items per block
@@ -141,7 +138,7 @@ export declare const buildBlockSums: (heightCache: Record<number, number>, calcu
141
138
  * - For indices >= blockSize, sums the block prefix, then only iterates the tail within the block.
142
139
  * - For small indices, falls back to the original logic.
143
140
  *
144
- * @param {Record<number, number>} heightCache - Map of measured item heights
141
+ * @param {HeightCache} heightCache - Map of measured item heights with dirty tracking
145
142
  * @param {number} calculatedItemHeight - Estimated height for unmeasured items
146
143
  * @param {number} idx - The index to scroll to (exclusive)
147
144
  * @param {number[]} [blockSums] - Optional precomputed block sums (for repeated queries)
@@ -29,7 +29,7 @@ export const calculateScrollPosition = (totalItems, itemHeight, containerHeight)
29
29
  * @param {number} totalItems - Total number of items in the list
30
30
  * @param {number} bufferSize - Number of items to render outside the visible area
31
31
  * @param {SvelteVirtualListMode} mode - Scroll direction mode
32
- * @returns {{ start: number, end: number }} Range of indices to render
32
+ * @returns {SvelteVirtualListPreviousVisibleRange} Range of indices to render
33
33
  */
34
34
  export const calculateVisibleRange = (scrollTop, viewportHeight, itemHeight, totalItems, bufferSize, mode) => {
35
35
  if (mode === 'bottomToTop') {
@@ -101,19 +101,19 @@ export const updateHeightAndScroll = (state, setters, immediate = false) => {
101
101
  * Calculates the average height of visible items in a virtual list.
102
102
  *
103
103
  * This function optimizes performance by:
104
- * 1. Using a height cache to store measured item heights
104
+ * 1. Using a height cache to store measured item heights with dirty tracking
105
105
  * 2. Only measuring new items not in the cache
106
106
  * 3. Calculating a running average of all measured heights
107
107
  *
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
- * @param {Record<number, number>} heightCache - Cache of previously measured item heights
110
+ * @param {HeightCache} heightCache - Cache of previously measured item heights with dirty tracking
111
111
  * @param {number} currentItemHeight - Current average item height being used
112
112
  *
113
113
  * @returns {{
114
114
  * newHeight: number,
115
115
  * newLastMeasuredIndex: number,
116
- * updatedHeightCache: Record<number, number>
116
+ * updatedHeightCache: HeightCache
117
117
  * }} Object containing new calculated height, last measured index, and updated cache
118
118
  *
119
119
  * @example
@@ -206,7 +206,7 @@ onComplete) => {
206
206
  * Builds a block sum array for fast offset calculation in large virtual lists.
207
207
  * Each entry in the array is the total height up to the end of that block (exclusive).
208
208
  *
209
- * @param {Record<number, number>} heightCache - Map of measured item heights
209
+ * @param {HeightCache} heightCache - Map of measured item heights with dirty tracking
210
210
  * @param {number} calculatedItemHeight - Estimated height for unmeasured items
211
211
  * @param {number} totalItems - Total number of items in the list
212
212
  * @param {number} blockSize - Number of items per block
@@ -236,7 +236,7 @@ export const buildBlockSums = (heightCache, calculatedItemHeight, totalItems, bl
236
236
  * - For indices >= blockSize, sums the block prefix, then only iterates the tail within the block.
237
237
  * - For small indices, falls back to the original logic.
238
238
  *
239
- * @param {Record<number, number>} heightCache - Map of measured item heights
239
+ * @param {HeightCache} heightCache - Map of measured item heights with dirty tracking
240
240
  * @param {number} calculatedItemHeight - Estimated height for unmeasured items
241
241
  * @param {number} idx - The index to scroll to (exclusive)
242
242
  * @param {number[]} [blockSums] - Optional precomputed block sums (for repeated queries)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-virtual-list",
3
- "version": "0.2.5",
3
+ "version": "0.2.6-beta.0",
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",
@@ -43,7 +43,8 @@
43
43
  "files": [
44
44
  "dist",
45
45
  "!dist/**/*.test.*",
46
- "!dist/**/*.spec.*"
46
+ "!dist/**/*.spec.*",
47
+ "!dist/test/**/*"
47
48
  ],
48
49
  "scripts": {
49
50
  "build": "vite build && npm run package",
@@ -71,41 +72,44 @@
71
72
  }
72
73
  },
73
74
  "dependencies": {
74
- "esm-env": "^1.2.2"
75
+ "esm-env": "^1.2.2",
76
+ "runed": "^0.31.1"
75
77
  },
76
78
  "devDependencies": {
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",
79
+ "@eslint/compat": "^1.3.1",
80
+ "@eslint/js": "^9.32.0",
81
+ "@faker-js/faker": "^9.9.0",
82
+ "@playwright/test": "^1.54.1",
81
83
  "@sveltejs/adapter-auto": "^6.0.1",
82
- "@sveltejs/kit": "^2.21.1",
83
- "@sveltejs/package": "^2.3.11",
84
- "@sveltejs/vite-plugin-svelte": "^5.0.3",
85
- "@testing-library/jest-dom": "^6.6.3",
84
+ "@sveltejs/kit": "^2.26.1",
85
+ "@sveltejs/package": "^2.4.0",
86
+ "@sveltejs/vite-plugin-svelte": "^6.1.0",
87
+ "@testing-library/jest-dom": "^6.6.4",
86
88
  "@testing-library/svelte": "^5.2.8",
87
89
  "@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",
90
+ "@types/node": "^24.1.0",
91
+ "@typescript-eslint/eslint-plugin": "^8.38.0",
92
+ "@typescript-eslint/parser": "^8.38.0",
93
+ "@vitest/coverage-v8": "^3.2.4",
94
+ "eslint": "^9.32.0",
95
+ "eslint-config-prettier": "^10.1.8",
96
+ "eslint-plugin-import": "^2.32.0",
97
+ "eslint-plugin-svelte": "^3.11.0",
96
98
  "eslint-plugin-unused-imports": "^4.1.4",
97
- "globals": "^16.2.0",
99
+ "globals": "^16.3.0",
98
100
  "jsdom": "^26.1.0",
99
- "prettier": "^3.5.3",
100
- "prettier-plugin-organize-imports": "^4.1.0",
101
+ "prettier": "^3.6.2",
102
+ "prettier-plugin-organize-imports": "^4.2.0",
103
+ "prettier-plugin-sort-json": "^4.1.1",
101
104
  "prettier-plugin-svelte": "^3.4.0",
105
+ "prettier-plugin-tailwindcss": "^0.6.14",
102
106
  "publint": "^0.3.12",
103
- "svelte": "^5.33.12",
104
- "svelte-check": "^4.2.1",
107
+ "svelte": "^5.37.1",
108
+ "svelte-check": "^4.3.0",
105
109
  "typescript": "^5.8.3",
106
- "typescript-eslint": "^8.33.0",
107
- "vite": "^6.3.5",
108
- "vitest": "^3.1.4"
110
+ "typescript-eslint": "^8.38.0",
111
+ "vite": "^7.0.6",
112
+ "vitest": "^3.2.4"
109
113
  },
110
114
  "peerDependencies": {
111
115
  "svelte": "^5.0.0"