feat(web): add SearchInput header component (debounced URL sync)
Pre-fills from page.url.searchParams.get('q'); typing debounces 250ms
then goto('/search?q=…'); first nav from elsewhere pushes, subsequent
typing on /search replaces. Empty input on /search drops the param.
Escape clears the input. URL → value re-sync via \$effect uses
untrack() on the value comparison so it doesn't loop with typing.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { goto } from '$app/navigation';
|
||||
import { untrack } from 'svelte';
|
||||
|
||||
let value = $state(page.url.searchParams.get('q') ?? '');
|
||||
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
// Re-sync local value when the URL changes from outside (back/forward,
|
||||
// result-link clicks). Read `value` non-reactively to avoid a feedback
|
||||
// loop where the user's typing would race against the URL re-read.
|
||||
$effect(() => {
|
||||
const fromUrl = page.url.searchParams.get('q') ?? '';
|
||||
if (fromUrl !== untrack(() => value)) value = fromUrl;
|
||||
});
|
||||
|
||||
function navigate(next: string) {
|
||||
const onSearchPage = page.url.pathname.startsWith('/search');
|
||||
const trimmed = next.trim();
|
||||
if (trimmed === '') {
|
||||
if (onSearchPage) goto('/search', { replaceState: true });
|
||||
return;
|
||||
}
|
||||
goto(`/search?q=${encodeURIComponent(trimmed)}`, { replaceState: onSearchPage });
|
||||
}
|
||||
|
||||
function onInput(e: Event) {
|
||||
value = (e.currentTarget as HTMLInputElement).value;
|
||||
if (timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null;
|
||||
navigate(value);
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function onKey(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
value = '';
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
navigate('');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Search artists, albums, tracks…"
|
||||
aria-label="Search"
|
||||
{value}
|
||||
oninput={onInput}
|
||||
onkeydown={onKey}
|
||||
class="w-full max-w-md rounded border border-border bg-surface px-3 py-1 text-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-accent"
|
||||
/>
|
||||
Reference in New Issue
Block a user