<script lang="ts">
import ChevronDown from "$icon/chevron-down";
import ChevronUp from "$icon/chevron-up";
import Search from "$icon/search";
import X from "$icon/x";
import type { HTMLAttributes } from "svelte/elements";
import Button from "#lib/components/ui/Button.svelte";
import Input from "#lib/components/ui/Input.svelte";
import { MATCH_LIMIT } from "#lib/components/lines/search.ts";
interface Props extends Omit<HTMLAttributes<HTMLElement>, "class" | "onkeydown"> {
label: string;
query?: string;
total: number;
current: number;
input?: HTMLInputElement;
onstep: (delta: number) => void;
onclose?: () => void;
class?: string;
}
let {
label,
query = $bindable(""),
total,
current,
input = $bindable(),
onstep,
onclose,
class: className,
...rest
}: Props = $props();
const count = $derived(
query === ""
? undefined
: total === 0
? "No results"
: `${current + 1}/${total >= MATCH_LIMIT ? `${MATCH_LIMIT}+` : total}`
);
const onkeydown = (event: KeyboardEvent) => {
if (event.key === "Enter") {
event.preventDefault();
onstep(event.shiftKey ? -1 : 1);
} else if (event.key === "Escape" && (onclose || query !== "")) {
event.preventDefault();
if (onclose) onclose();
else query = "";
}
};
</script>
<search {...rest} class="flex items-center gap-3xs {className}">
<Input
bind:value={query}
bind:element={input}
type="search"
iconLeft={Search}
suffix={count}
{label}
placeholder="Find"
autocomplete="off"
spellcheck={false}
class="min-w-0 flex-1 [&_input::-webkit-search-cancel-button]:appearance-none"
{onkeydown}
/>
<span class="sr-only" role="status" aria-live="polite">{count ?? ""}</span>
<Button
variant="ghost"
icon={ChevronUp}
aria-label="Previous match"
disabled={total === 0}
onclick={() => onstep(-1)}
/>
<Button
variant="ghost"
icon={ChevronDown}
aria-label="Next match"
disabled={total === 0}
onclick={() => onstep(1)}
/>
{#if onclose}
<Button variant="ghost" icon={X} aria-label="Close search" onclick={onclose} />
{/if}
</search>