forked from npmx-dev/npmx.dev
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuseVirtualInfiniteScroll.ts
More file actions
114 lines (98 loc) · 3.27 KB
/
useVirtualInfiniteScroll.ts
File metadata and controls
114 lines (98 loc) · 3.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import type { MaybeRefOrGetter, Ref } from 'vue'
export interface WindowVirtualizerHandle {
readonly scrollOffset: number
readonly viewportSize: number
findItemIndex: (offset: number) => number
getItemOffset: (index: number) => number
scrollToIndex: (
index: number,
opts?: { align?: 'start' | 'center' | 'end'; smooth?: boolean },
) => void
}
interface UseVirtualInfiniteScrollOptions {
/** Reference to the WindowVirtualizer component */
listRef: Ref<WindowVirtualizerHandle | null>
/** Current item count */
itemCount: Ref<number>
/** Whether there are more items to load */
hasMore: Ref<boolean>
/** Whether currently loading */
isLoading: Ref<boolean>
/** Page size for calculating current page (reactive) */
pageSize: MaybeRefOrGetter<number>
/** Threshold in items before end to trigger load */
threshold?: number
/** Callback to load more items */
onLoadMore: () => void
/** Callback when visible page changes (for URL updates) */
onPageChange?: (page: number) => void
}
/**
* Composable for handling infinite scroll with virtua's WindowVirtualizer
* Detects when user scrolls near the end and triggers loading more items
* Also tracks current visible page for URL persistence
*/
export function useVirtualInfiniteScroll(options: UseVirtualInfiniteScrollOptions) {
const {
listRef,
itemCount,
hasMore,
isLoading,
pageSize,
threshold = 5,
onLoadMore,
onPageChange,
} = options
// Track last fetched count to prevent duplicate fetches
const fetchedCountRef = shallowRef(-1)
// Track current page to avoid unnecessary updates
const currentPage = shallowRef(1)
function handleScroll() {
const list = listRef.value
if (!list) return
// Calculate current visible page based on first visible item
const startIndex = list.findItemIndex(list.scrollOffset)
const currentPageSize = toValue(pageSize)
const newPage = Math.floor(startIndex / currentPageSize) + 1
if (newPage !== currentPage.value && onPageChange) {
currentPage.value = newPage
onPageChange(newPage)
}
// Don't fetch if already loading or no more items
if (isLoading.value || !hasMore.value) return
// Don't fetch if we already fetched at this count
const count = itemCount.value
if (fetchedCountRef.value >= count) return
// Check if we're near the end
const endOffset = list.scrollOffset + list.viewportSize
const endIndex = list.findItemIndex(endOffset)
if (endIndex + threshold >= count) {
fetchedCountRef.value = count
onLoadMore()
}
}
/**
* Scroll to a specific page (1-indexed)
* Call this after data is loaded to restore scroll position
*/
function scrollToPage(page: number) {
const list = listRef.value
if (!list || page < 1) return
const targetIndex = (page - 1) * toValue(pageSize)
list.scrollToIndex(targetIndex, { align: 'start' })
currentPage.value = page
}
// Reset state when item count changes significantly (new search)
watch(itemCount, (newCount, oldCount) => {
// If count decreased or reset, clear the fetched tracking
if (newCount < oldCount || newCount === 0) {
fetchedCountRef.value = -1
currentPage.value = 1
}
})
return {
handleScroll,
scrollToPage,
currentPage,
}
}