Mail-DMARC

 view release on metacpan or  search on metacpan

share/html/js/combo.js  view on Meta::CPAN

// A filtering picker for lists too long to scroll: a store can hold thousands
// of domains, and a plain select makes finding one a scrolling exercise.

import { el, clear } from './core.js';

const VISIBLE = 60;

export const combobox = ({ id, placeholder, onSelect }) => {
  const input = el('input', {
    type: 'text',
    role: 'combobox',
    id,
    class: 'combo-input',
    placeholder,
    autocomplete: 'off',
    spellcheck: 'false',
    'aria-expanded': 'false',
    'aria-autocomplete': 'list',
    'aria-controls': `${id}-list`,
  });
  const list = el('ul', { id: `${id}-list`, class: 'combo-list', role: 'listbox',
    hidden: true });
  const node = el('div', { class: 'combo' }, [input, list]);

  // items: [{ value, label, note }]. The empty value is the "everything" row.
  let items = [];
  let matches = [];
  let active = -1;
  let committed = { value: '', label: placeholder };

  const close = () => {
    list.hidden = true;
    input.setAttribute('aria-expanded', 'false');
    input.removeAttribute('aria-activedescendant');
    active = -1;
  };

  const revert = () => {
    input.value = committed.value ? committed.label : '';
    close();
  };

  const commit = (item) => {
    committed = item;
    input.value = item.value ? item.label : '';
    close();
    onSelect(item.value);
  };

  const highlight = (index) => {
    active = index;
    for (const [i, option] of [...list.children].entries()) {
      const on = i === index;
      option.classList.toggle('active', on);
      if (on) {
        input.setAttribute('aria-activedescendant', option.id);
        option.scrollIntoView({ block: 'nearest' });
      }
    }
  };

  // Domains are dotted labels, so a bare substring match buries the obvious
  // answer: typing "art" should offer artfulmail.net and theartfarm.com before
  // earthlink.net. Rank by where the match lands, keeping the existing order
  // inside each rank.
  const rank = (label, needle) => {
    if (label.startsWith(needle)) return 0;
    if (label.includes(`.${needle}`)) return 1;
    const at = label.indexOf(needle);
    if (at > 0 && !/[a-z0-9]/.test(label[at - 1])) return 1;
    return 2;



( run in 1.503 second using v1.01-cache-2.11-cpan-8dfa8b56332 )