feat(combobox): implement ComboboxController
This commit is contained in:
parent
1a531d018f
commit
bea8cba6ce
1 changed files with 91 additions and 0 deletions
91
app/javascript/controllers/combobox_controller.ts
Normal file
91
app/javascript/controllers/combobox_controller.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
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, valueInput, list, item, hint } = this.getElements();
|
||||
const hints = JSON.parse(list.dataset.hints ?? '{}') as Record<
|
||||
string,
|
||||
string
|
||||
>;
|
||||
this.#combobox = new ComboboxUI({
|
||||
input,
|
||||
valueInput,
|
||||
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<HTMLInputElement>('input[type="text"]');
|
||||
const valueInput = this.element.querySelector<HTMLInputElement>(
|
||||
'input[type="hidden"]'
|
||||
);
|
||||
const list = this.element.querySelector<HTMLUListElement>('[role=listbox]');
|
||||
const item = this.element.querySelector<HTMLTemplateElement>('template');
|
||||
const hint =
|
||||
this.element.querySelector<HTMLElement>('[aria-live]') ?? undefined;
|
||||
|
||||
invariant(
|
||||
isInputElement(input),
|
||||
'ComboboxController requires a input element'
|
||||
);
|
||||
invariant(
|
||||
isInputElement(valueInput),
|
||||
'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, valueInput, list, item, hint };
|
||||
}
|
||||
}
|
||||
|
||||
function getHintText(hints: Record<string, string>, 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';
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue