@humanspeak/svelte-virtual-list 0.2.6-beta.1 → 0.2.6-beta.3
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/dist/SvelteVirtualList.svelte +44 -194
- package/dist/SvelteVirtualList.svelte.d.ts +19 -6
- package/dist/utils/resizeObserver.d.ts +122 -0
- package/dist/utils/resizeObserver.js +176 -0
- package/dist/utils/scrollCalculation.d.ts +47 -0
- package/dist/utils/scrollCalculation.js +173 -0
- package/dist/utils/virtualListDebug.d.ts +6 -6
- package/dist/utils/virtualListDebug.js +7 -7
- package/package.json +2 -3
|
@@ -41,15 +41,16 @@
|
|
|
41
41
|
- Only visible items + buffer are mounted in the DOM
|
|
42
42
|
- Height caching and estimation for dynamic content
|
|
43
43
|
- Handles resize events and dynamic content changes
|
|
44
|
-
-
|
|
45
|
-
-
|
|
44
|
+
- Optimized for very large lists through virtualization
|
|
45
|
+
- Modular architecture with extracted utility functions
|
|
46
46
|
- Bi-directional support: mode="topToBottom" or "bottomToTop"
|
|
47
47
|
- Designed for extensibility and easy debugging
|
|
48
48
|
|
|
49
49
|
=============================
|
|
50
50
|
== For Contributors ==
|
|
51
51
|
=============================
|
|
52
|
-
-
|
|
52
|
+
- Complex logic is extracted to dedicated utility files in src/lib/utils/
|
|
53
|
+
- Scroll positioning logic is in scrollCalculation.ts (well-tested)
|
|
53
54
|
- Add new features behind feature flags or as optional props
|
|
54
55
|
- Write tests for all new features (see /test and /tests/scroll)
|
|
55
56
|
- Use TypeScript and Svelte 5 runes for all new code
|
|
@@ -110,7 +111,14 @@
|
|
|
110
111
|
* - Added comprehensive documentation
|
|
111
112
|
* - Optimized debug output to reduce noise
|
|
112
113
|
*
|
|
113
|
-
* 9.
|
|
114
|
+
* 9. Architecture Refactoring ✓
|
|
115
|
+
* - Extracted scroll calculation logic to scrollCalculation.ts utility
|
|
116
|
+
* - Extracted ResizeObserver utilities to resizeObserver.ts
|
|
117
|
+
* - Added comprehensive test coverage for extracted utilities
|
|
118
|
+
* - Improved separation of concerns and maintainability
|
|
119
|
+
* - Simplified initialization (removed unnecessary chunked processing)
|
|
120
|
+
*
|
|
121
|
+
* 10. Future Improvements (Planned)
|
|
114
122
|
* - Add horizontal scrolling support
|
|
115
123
|
* - Implement variable-sized item caching
|
|
116
124
|
* - Add keyboard navigation support
|
|
@@ -128,14 +136,19 @@
|
|
|
128
136
|
* - Debug output optimization
|
|
129
137
|
* - Accurate size calculations with caching
|
|
130
138
|
* - Responsive size adjustments
|
|
139
|
+
* - Modular architecture with testable utility functions
|
|
131
140
|
*
|
|
132
141
|
* Current Architecture:
|
|
133
142
|
* - Four-layer DOM structure for optimal performance
|
|
134
143
|
* - State management using Svelte 5's $state
|
|
135
144
|
* - Reactive height and scroll calculations
|
|
136
145
|
* - Configurable buffer zones for smooth scrolling
|
|
137
|
-
* -
|
|
138
|
-
*
|
|
146
|
+
* - Modular utility system with dedicated helper files:
|
|
147
|
+
* * scrollCalculation.ts: Complex scroll positioning logic
|
|
148
|
+
* * resizeObserver.ts: ResizeObserver management utilities
|
|
149
|
+
* * heightCalculation.ts: Debounced height measurement
|
|
150
|
+
* * virtualList.ts: Core virtual list calculations
|
|
151
|
+
* * virtualListDebug.ts: Debug information utilities
|
|
139
152
|
* - Height caching and estimation system
|
|
140
153
|
* - Progressive size adjustment system
|
|
141
154
|
*/
|
|
@@ -152,16 +165,16 @@
|
|
|
152
165
|
calculateScrollPosition,
|
|
153
166
|
calculateTransformY,
|
|
154
167
|
calculateVisibleRange,
|
|
155
|
-
getScrollOffsetForIndex,
|
|
156
|
-
processChunked,
|
|
157
168
|
updateHeightAndScroll as utilsUpdateHeightAndScroll
|
|
158
169
|
} from './utils/virtualList.js'
|
|
159
170
|
import { createDebugInfo, shouldShowDebugInfo } from './utils/virtualListDebug.js'
|
|
171
|
+
import { calculateScrollTarget } from './utils/scrollCalculation.js'
|
|
172
|
+
|
|
160
173
|
import { BROWSER } from 'esm-env'
|
|
161
174
|
import { onMount, tick } from 'svelte'
|
|
162
175
|
|
|
163
176
|
const rafSchedule = createRafScheduler()
|
|
164
|
-
const INTERNAL_DEBUG =
|
|
177
|
+
const INTERNAL_DEBUG = false
|
|
165
178
|
/**
|
|
166
179
|
* Core configuration props with default values
|
|
167
180
|
* @type {SvelteVirtualListProps}
|
|
@@ -215,8 +228,6 @@
|
|
|
215
228
|
*/
|
|
216
229
|
let heightCache = $state<Record<number, number>>({}) // Cache of measured item heights
|
|
217
230
|
let dirtyItems = $state(new Set<number>()) // Set of item indices that need height recalculation
|
|
218
|
-
const chunkSize = $state(50) // Number of items to process in each chunk
|
|
219
|
-
let processedItems = $state(0) // Number of items processed during initialization
|
|
220
231
|
|
|
221
232
|
let prevVisibleRange = $state<SvelteVirtualListPreviousVisibleRange | null>(null)
|
|
222
233
|
let prevHeight = $state<number>(0)
|
|
@@ -530,53 +541,6 @@
|
|
|
530
541
|
)
|
|
531
542
|
}
|
|
532
543
|
|
|
533
|
-
/**
|
|
534
|
-
* Initializes large datasets in chunks to prevent UI blocking.
|
|
535
|
-
*
|
|
536
|
-
* This function processes items in smaller chunks using setTimeout to yield
|
|
537
|
-
* to the main thread, allowing other UI operations to remain responsive.
|
|
538
|
-
* Progress is tracked and reported through the processedItems state.
|
|
539
|
-
*
|
|
540
|
-
* For datasets larger than 1000 items, this method is automatically used
|
|
541
|
-
* instead of immediate initialization. The chunk size is controlled by the
|
|
542
|
-
* component's chunkSize state (default: 50).
|
|
543
|
-
*
|
|
544
|
-
* @async
|
|
545
|
-
* @example
|
|
546
|
-
* ```typescript
|
|
547
|
-
* // Component initialization
|
|
548
|
-
* $effect(() => {
|
|
549
|
-
* if (BROWSER && items.length > 1000) {
|
|
550
|
-
* initializeChunked()
|
|
551
|
-
* } else {
|
|
552
|
-
* initialized = true
|
|
553
|
-
* }
|
|
554
|
-
* })
|
|
555
|
-
* ```
|
|
556
|
-
*
|
|
557
|
-
* @throws {Error} If processChunked fails to complete initialization
|
|
558
|
-
* @returns {Promise<void>} Resolves when all chunks have been processed
|
|
559
|
-
*/
|
|
560
|
-
const initializeChunked = async () => {
|
|
561
|
-
if (!items.length) return
|
|
562
|
-
|
|
563
|
-
await processChunked(
|
|
564
|
-
items,
|
|
565
|
-
chunkSize,
|
|
566
|
-
(processed) => (processedItems = processed),
|
|
567
|
-
() => (initialized = true)
|
|
568
|
-
)
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// Modify the mount effect to use chunked initialization
|
|
572
|
-
$effect(() => {
|
|
573
|
-
if (BROWSER && items.length > 1000) {
|
|
574
|
-
initializeChunked()
|
|
575
|
-
} else {
|
|
576
|
-
initialized = true
|
|
577
|
-
}
|
|
578
|
-
})
|
|
579
|
-
|
|
580
544
|
// Create itemResizeObserver immediately when in browser
|
|
581
545
|
if (BROWSER) {
|
|
582
546
|
// Watch for individual item size changes
|
|
@@ -749,144 +713,30 @@
|
|
|
749
713
|
}
|
|
750
714
|
|
|
751
715
|
const { start: firstVisibleIndex, end: lastVisibleIndex } = visibleItems()
|
|
752
|
-
let scrollTarget: number | null = null
|
|
753
716
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
// Calculate the offset of the item relative to the viewport
|
|
768
|
-
const itemTop = totalHeight - (itemOffset + itemHeight)
|
|
769
|
-
const itemBottom = totalHeight - itemOffset
|
|
770
|
-
const distanceToTop = Math.abs(scrollTop - itemTop)
|
|
771
|
-
const distanceToBottom = Math.abs(scrollTop + height - itemBottom)
|
|
772
|
-
if (distanceToTop < distanceToBottom) {
|
|
773
|
-
// Closer to top, align to top
|
|
774
|
-
scrollTarget = itemTop
|
|
775
|
-
} else {
|
|
776
|
-
// Closer to bottom, align to bottom
|
|
777
|
-
scrollTarget = Math.max(0, itemBottom - height)
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
} else if (align === 'top') {
|
|
781
|
-
// Align to top
|
|
782
|
-
scrollTarget = Math.max(0, totalHeight - (itemOffset + itemHeight))
|
|
783
|
-
} else if (align === 'bottom') {
|
|
784
|
-
// Align to bottom
|
|
785
|
-
scrollTarget = Math.max(0, totalHeight - itemOffset - height)
|
|
786
|
-
} else if (align === 'nearest') {
|
|
787
|
-
// If not visible, align to nearest edge; if visible, do nothing
|
|
788
|
-
const itemTop = totalHeight - (itemOffset + itemHeight)
|
|
789
|
-
const itemBottom = totalHeight - itemOffset
|
|
790
|
-
if (itemBottom <= scrollTop || itemTop >= scrollTop + height) {
|
|
791
|
-
// Not visible, align to nearest edge
|
|
792
|
-
const distanceToTop = Math.abs(scrollTop - itemTop)
|
|
793
|
-
const distanceToBottom = Math.abs(scrollTop + height - itemBottom)
|
|
794
|
-
if (distanceToTop < distanceToBottom) {
|
|
795
|
-
scrollTarget = itemTop
|
|
796
|
-
} else {
|
|
797
|
-
scrollTarget = Math.max(0, itemBottom - height)
|
|
798
|
-
}
|
|
799
|
-
} else {
|
|
800
|
-
// Already visible, do nothing
|
|
801
|
-
return
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
} else {
|
|
805
|
-
// topToBottom (default)
|
|
806
|
-
if (align === 'auto') {
|
|
807
|
-
// If item is above the viewport, align to top
|
|
808
|
-
if (targetIndex < firstVisibleIndex) {
|
|
809
|
-
scrollTarget = getScrollOffsetForIndex(
|
|
810
|
-
heightCache,
|
|
811
|
-
calculatedItemHeight,
|
|
812
|
-
targetIndex
|
|
813
|
-
)
|
|
814
|
-
// If item is below the viewport, align to bottom
|
|
815
|
-
} else if (targetIndex > lastVisibleIndex - 1) {
|
|
816
|
-
const itemBottom = getScrollOffsetForIndex(
|
|
817
|
-
heightCache,
|
|
818
|
-
calculatedItemHeight,
|
|
819
|
-
targetIndex + 1
|
|
820
|
-
)
|
|
821
|
-
scrollTarget = Math.max(0, itemBottom - height)
|
|
822
|
-
} else {
|
|
823
|
-
// Item is visible but not aligned: align to nearest edge
|
|
824
|
-
const itemTop = getScrollOffsetForIndex(
|
|
825
|
-
heightCache,
|
|
826
|
-
calculatedItemHeight,
|
|
827
|
-
targetIndex
|
|
828
|
-
)
|
|
829
|
-
const itemBottom = getScrollOffsetForIndex(
|
|
830
|
-
heightCache,
|
|
831
|
-
calculatedItemHeight,
|
|
832
|
-
targetIndex + 1
|
|
833
|
-
)
|
|
834
|
-
const distanceToTop = Math.abs(scrollTop - itemTop)
|
|
835
|
-
const distanceToBottom = Math.abs(scrollTop + height - itemBottom)
|
|
836
|
-
if (distanceToTop < distanceToBottom) {
|
|
837
|
-
// Closer to top, align to top
|
|
838
|
-
scrollTarget = itemTop
|
|
839
|
-
} else {
|
|
840
|
-
// Closer to bottom, align to bottom
|
|
841
|
-
scrollTarget = Math.max(0, itemBottom - height)
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
} else if (align === 'top') {
|
|
845
|
-
scrollTarget = getScrollOffsetForIndex(
|
|
846
|
-
heightCache,
|
|
847
|
-
calculatedItemHeight,
|
|
848
|
-
targetIndex
|
|
849
|
-
)
|
|
850
|
-
} else if (align === 'bottom') {
|
|
851
|
-
const itemBottom = getScrollOffsetForIndex(
|
|
852
|
-
heightCache,
|
|
853
|
-
calculatedItemHeight,
|
|
854
|
-
targetIndex + 1
|
|
855
|
-
)
|
|
856
|
-
scrollTarget = Math.max(0, itemBottom - height)
|
|
857
|
-
} else if (align === 'nearest') {
|
|
858
|
-
const itemTop = getScrollOffsetForIndex(
|
|
859
|
-
heightCache,
|
|
860
|
-
calculatedItemHeight,
|
|
861
|
-
targetIndex
|
|
862
|
-
)
|
|
863
|
-
const itemBottom = getScrollOffsetForIndex(
|
|
864
|
-
heightCache,
|
|
865
|
-
calculatedItemHeight,
|
|
866
|
-
targetIndex + 1
|
|
867
|
-
)
|
|
868
|
-
if (itemBottom <= scrollTop || itemTop >= scrollTop + height) {
|
|
869
|
-
// Not visible, align to nearest edge
|
|
870
|
-
const distanceToTop = Math.abs(scrollTop - itemTop)
|
|
871
|
-
const distanceToBottom = Math.abs(scrollTop + height - itemBottom)
|
|
872
|
-
if (distanceToTop < distanceToBottom) {
|
|
873
|
-
scrollTarget = itemTop
|
|
874
|
-
} else {
|
|
875
|
-
scrollTarget = Math.max(0, itemBottom - height)
|
|
876
|
-
}
|
|
877
|
-
} else {
|
|
878
|
-
// Already visible, do nothing
|
|
879
|
-
return
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
}
|
|
717
|
+
// Use extracted scroll calculation utility
|
|
718
|
+
const scrollTarget = calculateScrollTarget({
|
|
719
|
+
mode,
|
|
720
|
+
align,
|
|
721
|
+
targetIndex,
|
|
722
|
+
itemsLength: items.length,
|
|
723
|
+
calculatedItemHeight,
|
|
724
|
+
height,
|
|
725
|
+
scrollTop,
|
|
726
|
+
firstVisibleIndex,
|
|
727
|
+
lastVisibleIndex,
|
|
728
|
+
heightCache
|
|
729
|
+
})
|
|
883
730
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
behavior: smoothScroll ? 'smooth' : 'auto'
|
|
888
|
-
})
|
|
731
|
+
// Handle early return for 'nearest' alignment when item is already visible
|
|
732
|
+
if (scrollTarget === null) {
|
|
733
|
+
return
|
|
889
734
|
}
|
|
735
|
+
|
|
736
|
+
viewportElement.scrollTo({
|
|
737
|
+
top: scrollTarget,
|
|
738
|
+
behavior: smoothScroll ? 'smooth' : 'auto'
|
|
739
|
+
})
|
|
890
740
|
}
|
|
891
741
|
|
|
892
742
|
/**
|
|
@@ -985,7 +835,7 @@
|
|
|
985
835
|
{@const debugInfo = createDebugInfo(
|
|
986
836
|
visibleItems(),
|
|
987
837
|
items.length,
|
|
988
|
-
|
|
838
|
+
Object.keys(heightCache).length,
|
|
989
839
|
calculatedItemHeight
|
|
990
840
|
)}
|
|
991
841
|
{debugFunction
|
|
@@ -47,7 +47,14 @@
|
|
|
47
47
|
* - Added comprehensive documentation
|
|
48
48
|
* - Optimized debug output to reduce noise
|
|
49
49
|
*
|
|
50
|
-
* 9.
|
|
50
|
+
* 9. Architecture Refactoring ✓
|
|
51
|
+
* - Extracted scroll calculation logic to scrollCalculation.ts utility
|
|
52
|
+
* - Extracted ResizeObserver utilities to resizeObserver.ts
|
|
53
|
+
* - Added comprehensive test coverage for extracted utilities
|
|
54
|
+
* - Improved separation of concerns and maintainability
|
|
55
|
+
* - Simplified initialization (removed unnecessary chunked processing)
|
|
56
|
+
*
|
|
57
|
+
* 10. Future Improvements (Planned)
|
|
51
58
|
* - Add horizontal scrolling support
|
|
52
59
|
* - Implement variable-sized item caching
|
|
53
60
|
* - Add keyboard navigation support
|
|
@@ -65,14 +72,19 @@
|
|
|
65
72
|
* - Debug output optimization
|
|
66
73
|
* - Accurate size calculations with caching
|
|
67
74
|
* - Responsive size adjustments
|
|
75
|
+
* - Modular architecture with testable utility functions
|
|
68
76
|
*
|
|
69
77
|
* Current Architecture:
|
|
70
78
|
* - Four-layer DOM structure for optimal performance
|
|
71
79
|
* - State management using Svelte 5's $state
|
|
72
80
|
* - Reactive height and scroll calculations
|
|
73
81
|
* - Configurable buffer zones for smooth scrolling
|
|
74
|
-
* -
|
|
75
|
-
*
|
|
82
|
+
* - Modular utility system with dedicated helper files:
|
|
83
|
+
* * scrollCalculation.ts: Complex scroll positioning logic
|
|
84
|
+
* * resizeObserver.ts: ResizeObserver management utilities
|
|
85
|
+
* * heightCalculation.ts: Debounced height measurement
|
|
86
|
+
* * virtualList.ts: Core virtual list calculations
|
|
87
|
+
* * virtualListDebug.ts: Debug information utilities
|
|
76
88
|
* - Height caching and estimation system
|
|
77
89
|
* - Progressive size adjustment system
|
|
78
90
|
*/
|
|
@@ -120,15 +132,16 @@ import { type SvelteVirtualListProps, type SvelteVirtualListScrollOptions } from
|
|
|
120
132
|
* - Only visible items + buffer are mounted in the DOM
|
|
121
133
|
* - Height caching and estimation for dynamic content
|
|
122
134
|
* - Handles resize events and dynamic content changes
|
|
123
|
-
* -
|
|
124
|
-
* -
|
|
135
|
+
* - Optimized for very large lists through virtualization
|
|
136
|
+
* - Modular architecture with extracted utility functions
|
|
125
137
|
* - Bi-directional support: mode="topToBottom" or "bottomToTop"
|
|
126
138
|
* - Designed for extensibility and easy debugging
|
|
127
139
|
*
|
|
128
140
|
* =============================
|
|
129
141
|
* == For Contributors ==
|
|
130
142
|
* =============================
|
|
131
|
-
* -
|
|
143
|
+
* - Complex logic is extracted to dedicated utility files in src/lib/utils/
|
|
144
|
+
* - Scroll positioning logic is in scrollCalculation.ts (well-tested)
|
|
132
145
|
* - Add new features behind feature flags or as optional props
|
|
133
146
|
* - Write tests for all new features (see /test and /tests/scroll)
|
|
134
147
|
* - Use TypeScript and Svelte 5 runes for all new code
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for item resize observation
|
|
3
|
+
*/
|
|
4
|
+
export interface ItemResizeConfig {
|
|
5
|
+
/** Debug mode for logging resize events */
|
|
6
|
+
debug?: boolean;
|
|
7
|
+
/** Callback when items are marked as dirty */
|
|
8
|
+
onItemsDirty?: (dirtyIndices: Set<number>) => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Creates a ResizeObserver for monitoring individual item size changes.
|
|
12
|
+
*
|
|
13
|
+
* This function creates a ResizeObserver that watches for size changes in list items
|
|
14
|
+
* and maintains a dirty set of items that need height recalculation. It's designed
|
|
15
|
+
* specifically for virtual list components where item heights may change dynamically.
|
|
16
|
+
*
|
|
17
|
+
* @param itemElements - Array of item elements to watch
|
|
18
|
+
* @param getVisibleRange - Function to get current visible range
|
|
19
|
+
* @param dirtyItems - Set to track items that need recalculation
|
|
20
|
+
* @param config - Configuration options
|
|
21
|
+
* @returns ResizeObserver instance
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const itemElements = $state<HTMLElement[]>([])
|
|
26
|
+
* const dirtyItems = $state(new Set<number>())
|
|
27
|
+
*
|
|
28
|
+
* const resizeObserver = createItemResizeObserver(
|
|
29
|
+
* itemElements,
|
|
30
|
+
* () => ({ start: 0, end: 10 }),
|
|
31
|
+
* dirtyItems,
|
|
32
|
+
* {
|
|
33
|
+
* debug: true,
|
|
34
|
+
* onItemsDirty: (indices) => console.log('Items dirty:', indices)
|
|
35
|
+
* }
|
|
36
|
+
* )
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare const createItemResizeObserver: (itemElements: HTMLElement[], getVisibleRange: () => {
|
|
40
|
+
start: number;
|
|
41
|
+
end: number;
|
|
42
|
+
}, dirtyItems: Set<number>, config?: ItemResizeConfig) => ResizeObserver;
|
|
43
|
+
/**
|
|
44
|
+
* Configuration for container resize observation
|
|
45
|
+
*/
|
|
46
|
+
export interface ContainerResizeConfig {
|
|
47
|
+
/** Debug mode for logging resize events */
|
|
48
|
+
debug?: boolean;
|
|
49
|
+
/** Callback when container is resized */
|
|
50
|
+
onResize?: (entry: ResizeObserverEntry) => void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Creates a ResizeObserver for monitoring container size changes.
|
|
54
|
+
*
|
|
55
|
+
* This function creates a ResizeObserver that watches for size changes in the
|
|
56
|
+
* virtual list container and triggers appropriate updates to height and scroll position.
|
|
57
|
+
*
|
|
58
|
+
* @param config - Configuration options
|
|
59
|
+
* @returns ResizeObserver instance
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const containerResizeObserver = createContainerResizeObserver({
|
|
64
|
+
* debug: true,
|
|
65
|
+
* onResize: (entry) => {
|
|
66
|
+
* const newHeight = entry.contentRect.height
|
|
67
|
+
* updateHeightAndScroll(true)
|
|
68
|
+
* }
|
|
69
|
+
* })
|
|
70
|
+
*
|
|
71
|
+
* if (containerElement) {
|
|
72
|
+
* containerResizeObserver.observe(containerElement)
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare const createContainerResizeObserver: (config?: ContainerResizeConfig) => ResizeObserver;
|
|
77
|
+
/**
|
|
78
|
+
* Utility to safely observe elements with automatic cleanup.
|
|
79
|
+
*
|
|
80
|
+
* This function provides a safe way to observe elements with a ResizeObserver,
|
|
81
|
+
* handling cases where the observer might not be available or elements might be null.
|
|
82
|
+
*
|
|
83
|
+
* @param observer - ResizeObserver instance
|
|
84
|
+
* @param element - Element to observe
|
|
85
|
+
* @param debug - Whether to log debug information
|
|
86
|
+
* @returns Cleanup function to stop observing
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const cleanup = safeObserve(resizeObserver, element, true)
|
|
91
|
+
*
|
|
92
|
+
* // Later, stop observing
|
|
93
|
+
* cleanup()
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export declare const safeObserve: (observer: ResizeObserver | null, element: HTMLElement | null, debug?: boolean) => (() => void);
|
|
97
|
+
/**
|
|
98
|
+
* Manages multiple ResizeObserver instances with automatic cleanup.
|
|
99
|
+
*
|
|
100
|
+
* This class provides a convenient way to manage multiple ResizeObserver instances
|
|
101
|
+
* and ensures proper cleanup when the component is destroyed.
|
|
102
|
+
*/
|
|
103
|
+
export declare class ResizeObserverManager {
|
|
104
|
+
private observers;
|
|
105
|
+
private cleanupFunctions;
|
|
106
|
+
/**
|
|
107
|
+
* Adds a ResizeObserver to the manager
|
|
108
|
+
*/
|
|
109
|
+
addObserver(observer: ResizeObserver): void;
|
|
110
|
+
/**
|
|
111
|
+
* Adds a cleanup function to be called during cleanup
|
|
112
|
+
*/
|
|
113
|
+
addCleanup(cleanup: () => void): void;
|
|
114
|
+
/**
|
|
115
|
+
* Observes an element with automatic cleanup tracking
|
|
116
|
+
*/
|
|
117
|
+
observe(observer: ResizeObserver, element: HTMLElement, debug?: boolean): void;
|
|
118
|
+
/**
|
|
119
|
+
* Disconnects all observers and runs cleanup functions
|
|
120
|
+
*/
|
|
121
|
+
cleanup(): void;
|
|
122
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a ResizeObserver for monitoring individual item size changes.
|
|
3
|
+
*
|
|
4
|
+
* This function creates a ResizeObserver that watches for size changes in list items
|
|
5
|
+
* and maintains a dirty set of items that need height recalculation. It's designed
|
|
6
|
+
* specifically for virtual list components where item heights may change dynamically.
|
|
7
|
+
*
|
|
8
|
+
* @param itemElements - Array of item elements to watch
|
|
9
|
+
* @param getVisibleRange - Function to get current visible range
|
|
10
|
+
* @param dirtyItems - Set to track items that need recalculation
|
|
11
|
+
* @param config - Configuration options
|
|
12
|
+
* @returns ResizeObserver instance
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const itemElements = $state<HTMLElement[]>([])
|
|
17
|
+
* const dirtyItems = $state(new Set<number>())
|
|
18
|
+
*
|
|
19
|
+
* const resizeObserver = createItemResizeObserver(
|
|
20
|
+
* itemElements,
|
|
21
|
+
* () => ({ start: 0, end: 10 }),
|
|
22
|
+
* dirtyItems,
|
|
23
|
+
* {
|
|
24
|
+
* debug: true,
|
|
25
|
+
* onItemsDirty: (indices) => console.log('Items dirty:', indices)
|
|
26
|
+
* }
|
|
27
|
+
* )
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export const createItemResizeObserver = (itemElements, getVisibleRange, dirtyItems, config = {}) => {
|
|
31
|
+
const { debug = false, onItemsDirty } = config;
|
|
32
|
+
return new ResizeObserver((entries) => {
|
|
33
|
+
let shouldRecalculate = false;
|
|
34
|
+
const newDirtyItems = new Set();
|
|
35
|
+
if (debug) {
|
|
36
|
+
console.log(`ResizeObserver fired for ${entries.length} entries`);
|
|
37
|
+
}
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
const element = entry.target;
|
|
40
|
+
const elementIndex = itemElements.indexOf(element);
|
|
41
|
+
if (elementIndex !== -1) {
|
|
42
|
+
const visibleRange = getVisibleRange();
|
|
43
|
+
const actualIndex = visibleRange.start + elementIndex;
|
|
44
|
+
// ResizeObserver fired = element resized, so add to dirty queue
|
|
45
|
+
dirtyItems.add(actualIndex);
|
|
46
|
+
newDirtyItems.add(actualIndex);
|
|
47
|
+
shouldRecalculate = true;
|
|
48
|
+
if (debug) {
|
|
49
|
+
console.log(`Item ${actualIndex} marked dirty (resized), queue size: ${dirtyItems.size}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (shouldRecalculate && onItemsDirty) {
|
|
54
|
+
onItemsDirty(newDirtyItems);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Creates a ResizeObserver for monitoring container size changes.
|
|
60
|
+
*
|
|
61
|
+
* This function creates a ResizeObserver that watches for size changes in the
|
|
62
|
+
* virtual list container and triggers appropriate updates to height and scroll position.
|
|
63
|
+
*
|
|
64
|
+
* @param config - Configuration options
|
|
65
|
+
* @returns ResizeObserver instance
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* const containerResizeObserver = createContainerResizeObserver({
|
|
70
|
+
* debug: true,
|
|
71
|
+
* onResize: (entry) => {
|
|
72
|
+
* const newHeight = entry.contentRect.height
|
|
73
|
+
* updateHeightAndScroll(true)
|
|
74
|
+
* }
|
|
75
|
+
* })
|
|
76
|
+
*
|
|
77
|
+
* if (containerElement) {
|
|
78
|
+
* containerResizeObserver.observe(containerElement)
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export const createContainerResizeObserver = (config = {}) => {
|
|
83
|
+
const { debug = false, onResize } = config;
|
|
84
|
+
return new ResizeObserver((entries) => {
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
if (debug) {
|
|
87
|
+
console.log('Container resized:', entry.contentRect);
|
|
88
|
+
}
|
|
89
|
+
onResize?.(entry);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Utility to safely observe elements with automatic cleanup.
|
|
95
|
+
*
|
|
96
|
+
* This function provides a safe way to observe elements with a ResizeObserver,
|
|
97
|
+
* handling cases where the observer might not be available or elements might be null.
|
|
98
|
+
*
|
|
99
|
+
* @param observer - ResizeObserver instance
|
|
100
|
+
* @param element - Element to observe
|
|
101
|
+
* @param debug - Whether to log debug information
|
|
102
|
+
* @returns Cleanup function to stop observing
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const cleanup = safeObserve(resizeObserver, element, true)
|
|
107
|
+
*
|
|
108
|
+
* // Later, stop observing
|
|
109
|
+
* cleanup()
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export const safeObserve = (observer, element, debug = false) => {
|
|
113
|
+
if (observer && element) {
|
|
114
|
+
observer.observe(element);
|
|
115
|
+
if (debug) {
|
|
116
|
+
console.log('Started observing element:', element);
|
|
117
|
+
}
|
|
118
|
+
return () => {
|
|
119
|
+
if (observer && element) {
|
|
120
|
+
observer.unobserve(element);
|
|
121
|
+
if (debug) {
|
|
122
|
+
console.log('Stopped observing element:', element);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (debug && !observer) {
|
|
128
|
+
console.log('ResizeObserver not available for element:', element);
|
|
129
|
+
}
|
|
130
|
+
return () => { }; // No-op cleanup function
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Manages multiple ResizeObserver instances with automatic cleanup.
|
|
134
|
+
*
|
|
135
|
+
* This class provides a convenient way to manage multiple ResizeObserver instances
|
|
136
|
+
* and ensures proper cleanup when the component is destroyed.
|
|
137
|
+
*/
|
|
138
|
+
export class ResizeObserverManager {
|
|
139
|
+
observers = [];
|
|
140
|
+
cleanupFunctions = [];
|
|
141
|
+
/**
|
|
142
|
+
* Adds a ResizeObserver to the manager
|
|
143
|
+
*/
|
|
144
|
+
addObserver(observer) {
|
|
145
|
+
this.observers.push(observer);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Adds a cleanup function to be called during cleanup
|
|
149
|
+
*/
|
|
150
|
+
addCleanup(cleanup) {
|
|
151
|
+
this.cleanupFunctions.push(cleanup);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Observes an element with automatic cleanup tracking
|
|
155
|
+
*/
|
|
156
|
+
observe(observer, element, debug = false) {
|
|
157
|
+
const cleanup = safeObserve(observer, element, debug);
|
|
158
|
+
this.addCleanup(cleanup);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Disconnects all observers and runs cleanup functions
|
|
162
|
+
*/
|
|
163
|
+
cleanup() {
|
|
164
|
+
// Disconnect all observers
|
|
165
|
+
for (const observer of this.observers) {
|
|
166
|
+
observer.disconnect();
|
|
167
|
+
}
|
|
168
|
+
// Run all cleanup functions
|
|
169
|
+
for (const cleanup of this.cleanupFunctions) {
|
|
170
|
+
cleanup();
|
|
171
|
+
}
|
|
172
|
+
// Clear arrays
|
|
173
|
+
this.observers.length = 0;
|
|
174
|
+
this.cleanupFunctions.length = 0;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { SvelteVirtualListMode, SvelteVirtualListScrollAlignment } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parameters for calculating scroll target position
|
|
4
|
+
*/
|
|
5
|
+
export interface ScrollTargetParams {
|
|
6
|
+
mode: SvelteVirtualListMode;
|
|
7
|
+
align: SvelteVirtualListScrollAlignment;
|
|
8
|
+
targetIndex: number;
|
|
9
|
+
itemsLength: number;
|
|
10
|
+
calculatedItemHeight: number;
|
|
11
|
+
height: number;
|
|
12
|
+
scrollTop: number;
|
|
13
|
+
firstVisibleIndex: number;
|
|
14
|
+
lastVisibleIndex: number;
|
|
15
|
+
heightCache: Record<number, number>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Calculates the target scroll position for scrolling to a specific item index.
|
|
19
|
+
*
|
|
20
|
+
* This function handles both topToBottom and bottomToTop scroll modes with different
|
|
21
|
+
* alignment options (auto, top, bottom, nearest). It takes into account the current
|
|
22
|
+
* viewport state and calculates the optimal scroll position.
|
|
23
|
+
*
|
|
24
|
+
* @param params - Parameters for scroll target calculation
|
|
25
|
+
* @returns The target scroll position in pixels, or null if no scroll is needed
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const scrollTarget = calculateScrollTarget({
|
|
30
|
+
* mode: 'topToBottom',
|
|
31
|
+
* align: 'auto',
|
|
32
|
+
* targetIndex: 100,
|
|
33
|
+
* itemsLength: 1000,
|
|
34
|
+
* calculatedItemHeight: 50,
|
|
35
|
+
* height: 400,
|
|
36
|
+
* scrollTop: 200,
|
|
37
|
+
* firstVisibleIndex: 4,
|
|
38
|
+
* lastVisibleIndex: 12,
|
|
39
|
+
* heightCache: {}
|
|
40
|
+
* })
|
|
41
|
+
*
|
|
42
|
+
* if (scrollTarget !== null) {
|
|
43
|
+
* viewportElement.scrollTo({ top: scrollTarget })
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare const calculateScrollTarget: (params: ScrollTargetParams) => number | null;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { getScrollOffsetForIndex } from './virtualList.js';
|
|
2
|
+
/**
|
|
3
|
+
* Calculates the target scroll position for scrolling to a specific item index.
|
|
4
|
+
*
|
|
5
|
+
* This function handles both topToBottom and bottomToTop scroll modes with different
|
|
6
|
+
* alignment options (auto, top, bottom, nearest). It takes into account the current
|
|
7
|
+
* viewport state and calculates the optimal scroll position.
|
|
8
|
+
*
|
|
9
|
+
* @param params - Parameters for scroll target calculation
|
|
10
|
+
* @returns The target scroll position in pixels, or null if no scroll is needed
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const scrollTarget = calculateScrollTarget({
|
|
15
|
+
* mode: 'topToBottom',
|
|
16
|
+
* align: 'auto',
|
|
17
|
+
* targetIndex: 100,
|
|
18
|
+
* itemsLength: 1000,
|
|
19
|
+
* calculatedItemHeight: 50,
|
|
20
|
+
* height: 400,
|
|
21
|
+
* scrollTop: 200,
|
|
22
|
+
* firstVisibleIndex: 4,
|
|
23
|
+
* lastVisibleIndex: 12,
|
|
24
|
+
* heightCache: {}
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* if (scrollTarget !== null) {
|
|
28
|
+
* viewportElement.scrollTo({ top: scrollTarget })
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const calculateScrollTarget = (params) => {
|
|
33
|
+
const { mode, align, targetIndex, itemsLength, calculatedItemHeight, height, scrollTop, firstVisibleIndex, lastVisibleIndex, heightCache } = params;
|
|
34
|
+
if (mode === 'bottomToTop') {
|
|
35
|
+
return calculateBottomToTopScrollTarget({
|
|
36
|
+
align,
|
|
37
|
+
targetIndex,
|
|
38
|
+
itemsLength,
|
|
39
|
+
calculatedItemHeight,
|
|
40
|
+
height,
|
|
41
|
+
scrollTop,
|
|
42
|
+
firstVisibleIndex,
|
|
43
|
+
lastVisibleIndex
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
return calculateTopToBottomScrollTarget({
|
|
48
|
+
align,
|
|
49
|
+
targetIndex,
|
|
50
|
+
calculatedItemHeight,
|
|
51
|
+
height,
|
|
52
|
+
scrollTop,
|
|
53
|
+
firstVisibleIndex,
|
|
54
|
+
lastVisibleIndex,
|
|
55
|
+
heightCache
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Calculates scroll target for bottom-to-top mode
|
|
61
|
+
*/
|
|
62
|
+
const calculateBottomToTopScrollTarget = (params) => {
|
|
63
|
+
const { align, targetIndex, itemsLength, calculatedItemHeight, height, scrollTop, firstVisibleIndex, lastVisibleIndex } = params;
|
|
64
|
+
const totalHeight = itemsLength * calculatedItemHeight;
|
|
65
|
+
const itemOffset = targetIndex * calculatedItemHeight;
|
|
66
|
+
const itemHeight = calculatedItemHeight;
|
|
67
|
+
if (align === 'auto') {
|
|
68
|
+
// If item is above the viewport, align to top
|
|
69
|
+
if (targetIndex < firstVisibleIndex) {
|
|
70
|
+
return Math.max(0, totalHeight - (itemOffset + itemHeight));
|
|
71
|
+
}
|
|
72
|
+
// If item is below the viewport, align to bottom
|
|
73
|
+
else if (targetIndex > lastVisibleIndex - 1) {
|
|
74
|
+
return Math.max(0, totalHeight - itemOffset - height);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// Item is visible but not aligned: align to nearest edge
|
|
78
|
+
const itemTop = totalHeight - (itemOffset + itemHeight);
|
|
79
|
+
const itemBottom = totalHeight - itemOffset;
|
|
80
|
+
const distanceToTop = Math.abs(scrollTop - itemTop);
|
|
81
|
+
const distanceToBottom = Math.abs(scrollTop + height - itemBottom);
|
|
82
|
+
if (distanceToTop < distanceToBottom) {
|
|
83
|
+
return itemTop;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
return Math.max(0, itemBottom - height);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (align === 'top') {
|
|
91
|
+
return Math.max(0, totalHeight - (itemOffset + itemHeight));
|
|
92
|
+
}
|
|
93
|
+
else if (align === 'bottom') {
|
|
94
|
+
return Math.max(0, totalHeight - itemOffset - height);
|
|
95
|
+
}
|
|
96
|
+
else if (align === 'nearest') {
|
|
97
|
+
const itemTop = totalHeight - (itemOffset + itemHeight);
|
|
98
|
+
const itemBottom = totalHeight - itemOffset;
|
|
99
|
+
if (itemBottom <= scrollTop || itemTop >= scrollTop + height) {
|
|
100
|
+
// Not visible, align to nearest edge
|
|
101
|
+
const distanceToTop = Math.abs(scrollTop - itemTop);
|
|
102
|
+
const distanceToBottom = Math.abs(scrollTop + height - itemBottom);
|
|
103
|
+
if (distanceToTop < distanceToBottom) {
|
|
104
|
+
return itemTop;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
return Math.max(0, itemBottom - height);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Already visible, do nothing
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Calculates scroll target for top-to-bottom mode
|
|
119
|
+
*/
|
|
120
|
+
const calculateTopToBottomScrollTarget = (params) => {
|
|
121
|
+
const { align, targetIndex, calculatedItemHeight, height, scrollTop, firstVisibleIndex, lastVisibleIndex, heightCache } = params;
|
|
122
|
+
if (align === 'auto') {
|
|
123
|
+
// If item is above the viewport, align to top
|
|
124
|
+
if (targetIndex < firstVisibleIndex) {
|
|
125
|
+
return getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex);
|
|
126
|
+
}
|
|
127
|
+
// If item is below the viewport, align to bottom
|
|
128
|
+
else if (targetIndex > lastVisibleIndex - 1) {
|
|
129
|
+
const itemBottom = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex + 1);
|
|
130
|
+
return Math.max(0, itemBottom - height);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// Item is visible but not aligned: align to nearest edge
|
|
134
|
+
const itemTop = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex);
|
|
135
|
+
const itemBottom = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex + 1);
|
|
136
|
+
const distanceToTop = Math.abs(scrollTop - itemTop);
|
|
137
|
+
const distanceToBottom = Math.abs(scrollTop + height - itemBottom);
|
|
138
|
+
if (distanceToTop < distanceToBottom) {
|
|
139
|
+
return itemTop;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
return Math.max(0, itemBottom - height);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else if (align === 'top') {
|
|
147
|
+
return getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex);
|
|
148
|
+
}
|
|
149
|
+
else if (align === 'bottom') {
|
|
150
|
+
const itemBottom = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex + 1);
|
|
151
|
+
return Math.max(0, itemBottom - height);
|
|
152
|
+
}
|
|
153
|
+
else if (align === 'nearest') {
|
|
154
|
+
const itemTop = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex);
|
|
155
|
+
const itemBottom = getScrollOffsetForIndex(heightCache, calculatedItemHeight, targetIndex + 1);
|
|
156
|
+
if (itemBottom <= scrollTop || itemTop >= scrollTop + height) {
|
|
157
|
+
// Not visible, align to nearest edge
|
|
158
|
+
const distanceToTop = Math.abs(scrollTop - itemTop);
|
|
159
|
+
const distanceToBottom = Math.abs(scrollTop + height - itemBottom);
|
|
160
|
+
if (distanceToTop < distanceToBottom) {
|
|
161
|
+
return itemTop;
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
return Math.max(0, itemBottom - height);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
// Already visible, do nothing
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
};
|
|
@@ -40,9 +40,9 @@ export declare function shouldShowDebugInfo(prevRange: {
|
|
|
40
40
|
*
|
|
41
41
|
* This utility function generates a structured debug object that captures the complete
|
|
42
42
|
* state of a virtual list at any given moment. It includes critical metrics such as
|
|
43
|
-
* visible item count, viewport boundaries, total items,
|
|
44
|
-
* height calculations. This information is essential for performance
|
|
45
|
-
* debugging scroll behavior, and optimizing virtual list configurations.
|
|
43
|
+
* visible item count, viewport boundaries, total items, processed items with measured
|
|
44
|
+
* heights, and height calculations. This information is essential for performance
|
|
45
|
+
* monitoring, debugging scroll behavior, and optimizing virtual list configurations.
|
|
46
46
|
*
|
|
47
47
|
* Performance considerations:
|
|
48
48
|
* - All calculations are O(1)
|
|
@@ -51,7 +51,7 @@ export declare function shouldShowDebugInfo(prevRange: {
|
|
|
51
51
|
*
|
|
52
52
|
* @param visibleRange - Current visible range object containing start and end indices
|
|
53
53
|
* @param totalItems - Total number of items in the virtual list
|
|
54
|
-
* @param processedItems - Number of items
|
|
54
|
+
* @param processedItems - Number of items with measured heights (heightCache.length)
|
|
55
55
|
* @param averageItemHeight - Current calculated average height per item in pixels
|
|
56
56
|
* @returns {SvelteVirtualListDebugInfo} A structured debug information object
|
|
57
57
|
*
|
|
@@ -59,8 +59,8 @@ export declare function shouldShowDebugInfo(prevRange: {
|
|
|
59
59
|
* const debugInfo = createDebugInfo(
|
|
60
60
|
* { start: 0, end: 10 },
|
|
61
61
|
* 1000,
|
|
62
|
-
*
|
|
63
|
-
*
|
|
62
|
+
* 50,
|
|
63
|
+
* 45
|
|
64
64
|
* );
|
|
65
65
|
* console.log('Virtual List State:', debugInfo);
|
|
66
66
|
*
|
|
@@ -39,9 +39,9 @@ export function shouldShowDebugInfo(prevRange, currentRange, prevHeight, current
|
|
|
39
39
|
*
|
|
40
40
|
* This utility function generates a structured debug object that captures the complete
|
|
41
41
|
* state of a virtual list at any given moment. It includes critical metrics such as
|
|
42
|
-
* visible item count, viewport boundaries, total items,
|
|
43
|
-
* height calculations. This information is essential for performance
|
|
44
|
-
* debugging scroll behavior, and optimizing virtual list configurations.
|
|
42
|
+
* visible item count, viewport boundaries, total items, processed items with measured
|
|
43
|
+
* heights, and height calculations. This information is essential for performance
|
|
44
|
+
* monitoring, debugging scroll behavior, and optimizing virtual list configurations.
|
|
45
45
|
*
|
|
46
46
|
* Performance considerations:
|
|
47
47
|
* - All calculations are O(1)
|
|
@@ -50,7 +50,7 @@ export function shouldShowDebugInfo(prevRange, currentRange, prevHeight, current
|
|
|
50
50
|
*
|
|
51
51
|
* @param visibleRange - Current visible range object containing start and end indices
|
|
52
52
|
* @param totalItems - Total number of items in the virtual list
|
|
53
|
-
* @param processedItems - Number of items
|
|
53
|
+
* @param processedItems - Number of items with measured heights (heightCache.length)
|
|
54
54
|
* @param averageItemHeight - Current calculated average height per item in pixels
|
|
55
55
|
* @returns {SvelteVirtualListDebugInfo} A structured debug information object
|
|
56
56
|
*
|
|
@@ -58,8 +58,8 @@ export function shouldShowDebugInfo(prevRange, currentRange, prevHeight, current
|
|
|
58
58
|
* const debugInfo = createDebugInfo(
|
|
59
59
|
* { start: 0, end: 10 },
|
|
60
60
|
* 1000,
|
|
61
|
-
*
|
|
62
|
-
*
|
|
61
|
+
* 50,
|
|
62
|
+
* 45
|
|
63
63
|
* );
|
|
64
64
|
* console.log('Virtual List State:', debugInfo);
|
|
65
65
|
*
|
|
@@ -71,7 +71,7 @@ export function createDebugInfo(visibleRange, totalItems, processedItems, averag
|
|
|
71
71
|
startIndex: visibleRange.start,
|
|
72
72
|
endIndex: visibleRange.end,
|
|
73
73
|
totalItems,
|
|
74
|
-
processedItems,
|
|
74
|
+
processedItems, // Number of items with measured heights in heightCache
|
|
75
75
|
averageItemHeight
|
|
76
76
|
};
|
|
77
77
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanspeak/svelte-virtual-list",
|
|
3
|
-
"version": "0.2.6-beta.
|
|
3
|
+
"version": "0.2.6-beta.3",
|
|
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",
|
|
@@ -73,8 +73,7 @@
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
|
-
"esm-env": "^1.2.2"
|
|
77
|
-
"runed": "^0.31.1"
|
|
76
|
+
"esm-env": "^1.2.2"
|
|
78
77
|
},
|
|
79
78
|
"devDependencies": {
|
|
80
79
|
"@eslint/compat": "^1.3.1",
|