import { useEscapeKeydown } from '../../internal/actions/index.js'; import { addEventListener, addMeltEventListener, makeElement, createElHelpers, effect, executeCallbacks, isContentEditable, isHTMLInputElement, kbd, noop, omit, } from '../../internal/helpers/index.js'; import { writable } from 'svelte/store'; import { createListbox } from '../listbox/create.js'; // prettier-ignore export const INTERACTION_KEYS = [kbd.ARROW_LEFT, kbd.ESCAPE, kbd.ARROW_RIGHT, kbd.SHIFT, kbd.CAPS_LOCK, kbd.CONTROL, kbd.ALT, kbd.META, kbd.ENTER, kbd.F1, kbd.F2, kbd.F3, kbd.F4, kbd.F5, kbd.F6, kbd.F7, kbd.F8, kbd.F9, kbd.F10, kbd.F11, kbd.F12]; const { name } = createElHelpers('combobox'); /** * Creates an ARIA-1.2-compliant combobox. * * @TODO multi-select using `tags-input` builder? */ export function createCombobox(props) { const listbox = createListbox({ ...props, builder: 'combobox', typeahead: false }); const inputValue = writable(''); const touchedInput = writable(false); /* -------- */ /* ELEMENTS */ /* -------- */ /** Action and attributes for the text input. */ const input = makeElement(name('input'), { stores: [listbox.elements.trigger, inputValue], returned: ([$trigger, $inputValue]) => { return { ...omit($trigger, 'action'), role: 'combobox', value: $inputValue, autocomplete: 'off', }; }, action: (node) => { const unsubscribe = executeCallbacks(addMeltEventListener(node, 'input', (e) => { if (!isHTMLInputElement(e.target) && !isContentEditable(e.target)) return; touchedInput.set(true); }), // This shouldn't be cancelled ever, so we don't use addMeltEventListener. addEventListener(node, 'input', (e) => { if (isHTMLInputElement(e.target)) { inputValue.set(e.target.value); } if (isContentEditable(e.target)) { inputValue.set(e.target.innerText); } })); let unsubEscapeKeydown = noop; const escape = useEscapeKeydown(node, { handler: () => { listbox.helpers.closeMenu(); }, }); if (escape && escape.destroy) { unsubEscapeKeydown = escape.destroy; } const { destroy } = listbox.elements.trigger(node); return { destroy() { destroy?.(); unsubscribe(); unsubEscapeKeydown(); }, }; }, }); effect(listbox.states.open, ($open) => { if (!$open) { touchedInput.set(false); } }); return { ...listbox, elements: { ...omit(listbox.elements, 'trigger'), input, }, states: { ...listbox.states, touchedInput, inputValue, }, }; }