Typing in the search box navigated on every pause, so single letters and round-trips back to the same term each triggered a full YouTube search and a new grid of covers. The debounce is longer, a term identical to the current one no longer navigates, YouTube is only queried past a minimum length, and the infinite scroll waits until the sentinel is closer before pulling a page.
40 lines
950 B
Svelte
40 lines
950 B
Svelte
<script lang="ts">
|
|
import LoaderCircle from '@lucide/svelte/icons/loader-circle';
|
|
import { INFINITE_SCROLL_ROOT_MARGIN } from '$lib/config';
|
|
|
|
interface Props {
|
|
onLoadMore: () => void;
|
|
hasMore: boolean;
|
|
loading: boolean;
|
|
rootMargin?: string;
|
|
}
|
|
|
|
let { onLoadMore, hasMore, loading, rootMargin = INFINITE_SCROLL_ROOT_MARGIN }: Props = $props();
|
|
|
|
let sentinel = $state<HTMLDivElement>();
|
|
let visible = $state(false);
|
|
|
|
$effect(() => {
|
|
const node = sentinel;
|
|
if (!node) return;
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
visible = entries[0].isIntersecting;
|
|
},
|
|
{ rootMargin }
|
|
);
|
|
observer.observe(node);
|
|
return () => observer.disconnect();
|
|
});
|
|
|
|
$effect(() => {
|
|
if (visible && hasMore && !loading) onLoadMore();
|
|
});
|
|
</script>
|
|
|
|
<div bind:this={sentinel} class="flex min-h-12 items-center justify-center py-8">
|
|
{#if loading}
|
|
<LoaderCircle class="h-5 w-5 animate-spin text-muted" />
|
|
{/if}
|
|
</div>
|