({
totalItems,
maxVisible = DEFAULT_MAX_VISIBLE,
selectedIndex = 0,
}: UsePaginationOptions)
| 46 | } |
| 47 | |
| 48 | export function usePagination<T>({ |
| 49 | totalItems, |
| 50 | maxVisible = DEFAULT_MAX_VISIBLE, |
| 51 | selectedIndex = 0, |
| 52 | }: UsePaginationOptions): UsePaginationResult<T> { |
| 53 | const needsPagination = totalItems > maxVisible |
| 54 | |
| 55 | // Use a ref to track the previous scroll offset for smooth scrolling |
| 56 | const scrollOffsetRef = useRef(0) |
| 57 | |
| 58 | // Compute the scroll offset based on selectedIndex |
| 59 | // This ensures the selected item is always visible |
| 60 | const scrollOffset = useMemo(() => { |
| 61 | if (!needsPagination) return 0 |
| 62 | |
| 63 | const prevOffset = scrollOffsetRef.current |
| 64 | |
| 65 | // If selected item is above the visible window, scroll up |
| 66 | if (selectedIndex < prevOffset) { |
| 67 | scrollOffsetRef.current = selectedIndex |
| 68 | return selectedIndex |
| 69 | } |
| 70 | |
| 71 | // If selected item is below the visible window, scroll down |
| 72 | if (selectedIndex >= prevOffset + maxVisible) { |
| 73 | const newOffset = selectedIndex - maxVisible + 1 |
| 74 | scrollOffsetRef.current = newOffset |
| 75 | return newOffset |
| 76 | } |
| 77 | |
| 78 | // Selected item is within visible window, keep current offset |
| 79 | // But ensure offset is still valid |
| 80 | const maxOffset = Math.max(0, totalItems - maxVisible) |
| 81 | const clampedOffset = Math.min(prevOffset, maxOffset) |
| 82 | scrollOffsetRef.current = clampedOffset |
| 83 | return clampedOffset |
| 84 | }, [selectedIndex, maxVisible, needsPagination, totalItems]) |
| 85 | |
| 86 | const startIndex = scrollOffset |
| 87 | const endIndex = Math.min(scrollOffset + maxVisible, totalItems) |
| 88 | |
| 89 | const getVisibleItems = useCallback( |
| 90 | (items: T[]): T[] => { |
| 91 | if (!needsPagination) return items |
| 92 | return items.slice(startIndex, endIndex) |
| 93 | }, |
| 94 | [needsPagination, startIndex, endIndex], |
| 95 | ) |
| 96 | |
| 97 | const toActualIndex = useCallback( |
| 98 | (visibleIndex: number): number => { |
| 99 | return startIndex + visibleIndex |
| 100 | }, |
| 101 | [startIndex], |
| 102 | ) |
| 103 | |
| 104 | const isOnCurrentPage = useCallback( |
| 105 | (actualIndex: number): boolean => { |
no test coverage detected