Menu
Public repository / 0216cf0f6008aae88a648a82e9b3a462a6688302
AkurAI-Mail / src/lib/webmail/RecipientCombobox.svelte
Latest change 22202d1bde69be4ace8a299dffd91f8ff24378df - Recover deployed webmail frontend (frontend parity with EC2 a6afffc) by Ólafur Búi Ólafsson
<script lang="ts">
import { completeRecipient, matchSuggestions, type Contact } from "$lib/webmail/recipients";
let { value = $bindable(""), suggestions = [], placeholder = "", label = "" }: { value?: string; suggestions?: Contact[]; placeholder?: string; label?: string } = $props();
const uid = $props.id();
const listId = `${uid}-suggestions`;
let input: HTMLInputElement | undefined = $state();
let open = $state(false);
let active = $state(-1);
const matches = $derived(matchSuggestions(value, suggestions));
function choose(email: string) {
value = completeRecipient(value, email);
open = false;
active = -1;
input?.focus();
}
function refresh() {
open = matches.length > 0;
active = -1;
}
function onKeydown(event: KeyboardEvent) {
if (!open || !matches.length) return;
if (event.key === "ArrowDown") {
event.preventDefault();
active = (active + 1) % matches.length;
} else if (event.key === "ArrowUp") {
event.preventDefault();
active = (active - 1 + matches.length) % matches.length;
} else if (event.key === "Enter" && active >= 0) {
event.preventDefault();
choose(matches[active]!.email);
} else if (event.key === "Escape") {
open = false;
active = -1;
}
}
</script>
<div class="combobox">
<input bind:this={input} bind:value {placeholder} aria-label={label} autocomplete="off" role="combobox" aria-autocomplete="list" aria-controls={listId} aria-expanded={open} oninput={refresh} onkeydown={onKeydown} onfocus={refresh} onblur={() => setTimeout(() => (open = false), 120)} />
{#if open && matches.length}
<ul class="combobox-list" id={listId} role="listbox">
{#each matches as entry, index}
<li><button type="button" role="option" aria-selected={index === active} class:active={index === active} onmousedown={(event) => { event.preventDefault(); choose(entry.email); }}>{entry.name ? `${entry.name} <${entry.email}>` : entry.email}</button></li>
{/each}
</ul>
{/if}
</div>
<style>
.combobox {
position: relative;
}
.combobox input {
width: 100%;
}
.combobox-list {
z-index: 90;
border: 1px solid var(--line);
background: var(--surface);
border-radius: 6px;
max-height: 220px;
margin: 0;
padding: 4px;
list-style: none;
position: absolute;
top: calc(100% + 4px);
left: 0;
right: 0;
overflow: auto;
box-shadow: 0 12px 30px #04100b40;
}
.combobox-list li {
display: block;
}
.combobox-list button {
text-align: left;
cursor: pointer;
background: 0 0;
border: 0;
border-radius: 6px;
width: 100%;
padding: 6px 8px;
font-size: 13px;
display: block;
}
.combobox-list button.active,
.combobox-list button:hover {
background: var(--rail);
color: var(--rail-text);
}
</style>