import invariant from 'tiny-invariant'; import { isInputElement, isElement } from '@coldwired/utils'; import { Hint } from '../shared/combobox'; import { ComboboxUI } from '../shared/combobox-ui'; import { ApplicationController } from './application_controller'; export class ComboboxController extends ApplicationController { #combobox?: ComboboxUI; connect() { const { input, selectedValueInput, valueSlots, list, item, hint } = this.getElements(); const hints = JSON.parse(list.dataset.hints ?? '{}') as Record< string, string >; this.#combobox = new ComboboxUI({ input, selectedValueInput, valueSlots, list, item, hint, allowsCustomValue: this.element.hasAttribute('data-allows-custom-value'), getHintText: (hint) => getHintText(hints, hint) }); this.#combobox.init(); } disconnect() { this.#combobox?.destroy(); } private getElements() { const input = this.element.querySelector('input[type="text"]'); const selectedValueInput = this.element.querySelector( 'input[type="hidden"]' ); const valueSlots = this.element.querySelectorAll( 'input[type="hidden"][data-value-slot]' ); const list = this.element.querySelector('[role=listbox]'); const item = this.element.querySelector('template'); const hint = this.element.querySelector('[aria-live]') ?? undefined; invariant( isInputElement(input), 'ComboboxController requires a input element' ); invariant( isInputElement(selectedValueInput), 'ComboboxController requires a hidden input element' ); invariant( isElement(list), 'ComboboxController requires a [role=listbox] element' ); invariant( isElement(item), 'ComboboxController requires a template element' ); return { input, selectedValueInput, valueSlots, list, item, hint }; } } function getHintText(hints: Record, hint: Hint): string { const slot = hints[getSlotName(hint)]; switch (hint.type) { case 'empty': return slot; case 'selected': return slot.replace('{label}', hint.label ?? ''); default: return slot .replace('{count}', String(hint.count)) .replace('{label}', hint.label ?? ''); } } function getSlotName(hint: Hint): string { switch (hint.type) { case 'empty': return 'empty'; case 'selected': return 'selected'; default: if (hint.count == 1) { return hint.label ? 'oneWithLabel' : 'one'; } return hint.label ? 'manyWithLabel' : 'many'; } }