AkurAI Build
Menu

Public repository / 3af23b0e937520000e1aa43c7e2eb9de2675abd1

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>