feat(web): infinite scroll on /library/artists and /library/albums
Replaces the explicit 'Load more' button on both library list pages with an IntersectionObserver-based sentinel that triggers fetchNextPage automatically as the operator scrolls near the bottom (300px rootMargin). New InfiniteScrollSentinel component: - Stays disabled while a fetch is in flight (prevents repeat firing on tall pages or fast scrolls). - Tears down its observer when hasNextPage flips false. - Defensive against environments without IntersectionObserver (jsdom); tests provide a synchronous mock. Also fixes a pre-existing test setup gap on the albums page test: AlbumCard renders LikeButton which calls useQueryClient(), and the page.test.ts wasn't wrapped in a QueryClientProvider — added the same useQueryClient mock the AlbumCard.test.ts already uses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
// Renders an invisible sentinel element near the bottom of a paged list.
|
||||
// When the sentinel enters the viewport (within `rootMargin`), onIntersect
|
||||
// fires — typically calling TanStack Query's fetchNextPage. The caller is
|
||||
// responsible for setting `enabled = hasNextPage && !isFetchingNextPage`
|
||||
// so the observer stays disconnected while a fetch is in flight; otherwise
|
||||
// a tall list could fire onIntersect repeatedly during a single scroll.
|
||||
|
||||
let {
|
||||
onIntersect,
|
||||
enabled = true,
|
||||
rootMargin = '300px'
|
||||
}: {
|
||||
onIntersect: () => void;
|
||||
enabled?: boolean;
|
||||
rootMargin?: string;
|
||||
} = $props();
|
||||
|
||||
let sentinel: HTMLElement | undefined = $state();
|
||||
|
||||
$effect(() => {
|
||||
if (!enabled || !sentinel) return;
|
||||
if (typeof IntersectionObserver === 'undefined') return;
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0]?.isIntersecting) onIntersect();
|
||||
},
|
||||
{ rootMargin }
|
||||
);
|
||||
observer.observe(sentinel);
|
||||
return () => observer.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div bind:this={sentinel} aria-hidden="true" class="h-px"></div>
|
||||
Reference in New Issue
Block a user