NODEDC_DESIGN_GUIDELINE/packages/ui-dom/src/select.ts

68 lines
2.0 KiB
TypeScript

import { createFloatingLayer, type FloatingLayerController, type FloatingLayerOptions } from "./floating";
export interface SelectControllerOptions<T extends string> extends Omit<FloatingLayerOptions, "closeOnSelect"> {
value: T;
onChange: (value: T) => void;
valueTarget?: HTMLElement;
}
export interface SelectController<T extends string> extends FloatingLayerController {
getValue: () => T;
setValue: (value: T, emit?: boolean) => void;
}
export function createSelectController<T extends string>({
value,
onChange,
valueTarget,
surface,
trigger,
...floatingOptions
}: SelectControllerOptions<T>): SelectController<T> {
let currentValue = value;
const floating = createFloatingLayer({
trigger,
surface,
closeOnSelect: true,
...floatingOptions,
});
const options = () => Array.from(surface.querySelectorAll<HTMLElement>("[data-nodedc-option][data-nodedc-value]"));
const sync = () => {
options().forEach((option) => {
const selected = option.dataset.nodedcValue === currentValue;
option.dataset.selected = selected ? "true" : "false";
option.setAttribute("aria-selected", String(selected));
if (selected && valueTarget) valueTarget.textContent = option.dataset.nodedcLabel ?? option.textContent ?? currentValue;
});
};
const setValue = (nextValue: T, emit = true) => {
currentValue = nextValue;
sync();
if (emit) onChange(nextValue);
};
const handleOptionClick = (event: MouseEvent) => {
if (!(event.target instanceof HTMLElement)) return;
const option = event.target.closest<HTMLElement>("[data-nodedc-option][data-nodedc-value]");
if (!option || option.hasAttribute("disabled")) return;
setValue(option.dataset.nodedcValue as T);
};
surface.addEventListener("click", handleOptionClick);
sync();
return {
...floating,
getValue: () => currentValue,
setValue,
destroy: () => {
surface.removeEventListener("click", handleOptionClick);
floating.destroy();
},
};
}