68 lines
2.0 KiB
TypeScript
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();
|
|
},
|
|
};
|
|
}
|
|
|