Files
Sloneczko-manager/src/lib/components/GuestField.svelte
2026-07-05 15:53:52 +02:00

158 lines
3.6 KiB
Svelte

<script lang="ts">
import type { Guest } from "$lib/types";
import * as db from "$lib/db";
interface Props {
value: string;
/** Wybranie gościa z podpowiedzi — rodzic uzupełnia pozostałe pola. */
onselect: (guest: Guest) => void;
}
let { value = $bindable(), onselect }: Props = $props();
let suggestions = $state<Guest[]>([]);
let open = $state(false);
let highlighted = $state(0);
let searchToken = 0;
async function oninput() {
const q = value.trim();
if (q.length < 2) {
suggestions = [];
open = false;
return;
}
const token = ++searchToken;
const found = await db.searchGuests(q);
if (token !== searchToken) return;
suggestions = found;
highlighted = 0;
open = found.length > 0;
}
function choose(guest: Guest) {
value = guest.name;
open = false;
onselect(guest);
}
function onkeydown(event: KeyboardEvent) {
if (!open) return;
if (event.key === "ArrowDown") {
event.preventDefault();
highlighted = (highlighted + 1) % suggestions.length;
} else if (event.key === "ArrowUp") {
event.preventDefault();
highlighted = (highlighted - 1 + suggestions.length) % suggestions.length;
} else if (event.key === "Enter") {
event.preventDefault();
choose(suggestions[highlighted]);
} else if (event.key === "Escape") {
// zamknij tylko podpowiedzi, nie cały dialog
event.preventDefault();
event.stopPropagation();
open = false;
}
}
function onblur() {
open = false;
}
</script>
<div class="wrap">
<input
type="text"
bind:value
{oninput}
{onkeydown}
{onblur}
required
placeholder="Jan Kowalski"
autocomplete="off"
role="combobox"
aria-expanded={open}
aria-autocomplete="list"
aria-controls="guest-suggestions"
/>
{#if open}
<ul class="suggestions" id="guest-suggestions" role="listbox">
{#each suggestions as guest, i (guest.id)}
<li role="option" aria-selected={i === highlighted}>
<button
type="button"
class:hl={i === highlighted}
onclick={() => choose(guest)}
onpointerdown={(e) => e.preventDefault()}
>
<span class="n">{guest.name}</span>
{#if guest.phone}<span class="d">{guest.phone}</span>{/if}
{#if guest.address}<span class="d">{guest.address}</span>{/if}
</button>
</li>
{/each}
</ul>
{/if}
</div>
<style>
.wrap {
position: relative;
}
input {
width: 100%;
font: inherit;
font-size: 14px;
color: var(--text);
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
padding: 7px 9px;
}
.suggestions {
position: absolute;
top: calc(100% + 2px);
left: 0;
right: 0;
z-index: 10;
margin: 0;
padding: 4px;
list-style: none;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 4px 14px rgb(0 0 0 / 0.15);
max-height: 220px;
overflow-y: auto;
}
.suggestions button {
display: flex;
gap: 8px;
align-items: baseline;
width: 100%;
font: inherit;
font-size: 13px;
text-align: left;
padding: 6px 8px;
border: none;
border-radius: 5px;
background: transparent;
color: var(--text);
cursor: pointer;
}
.suggestions button:hover,
.suggestions button.hl {
background: var(--today);
}
.n {
font-weight: 600;
}
.d {
font-size: 12px;
color: var(--muted);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
</style>