Files
sigpro-ui/components/Autocomplete.js
natxocc e842ed8041
All checks were successful
Deploy Docs to Synology / deploy (push) Successful in 3s
adapt new Input
2026-04-23 13:22:49 +02:00

92 lines
3.0 KiB
JavaScript

// components/Autocomplete.js
import { $, h, each, watch } from "sigpro";
export const Autocomplete = (props) => {
const query = $("");
const isOpen = $(false);
const cursor = $(-1);
const filteredItems = $([]);
watch(() => {
const v = typeof props.value === "function" ? props.value() : props.value;
return v || "";
}, (newVal) => setTimeout(() => query(newVal), 0));
watch(() => {
const q = String(query()).toLowerCase();
const allItems = typeof props.items === "function" ? props.items() : props.items;
const filtered = q
? allItems.filter((item) =>
(typeof item === "string" ? item : item.label).toLowerCase().includes(q)
)
: allItems;
filteredItems(filtered);
});
const pick = (item) => {
const display = typeof item === "string" ? item : item.label;
const actual = typeof item === "string" ? item : item.value;
query(display);
if (typeof props.value === "function") props.value(actual);
props.onselect?.(item);
isOpen(false);
cursor(-1);
};
const handleKeyDown = (e) => {
const list = filteredItems();
if (e.key === "ArrowDown") {
e.preventDefault();
isOpen(true);
cursor(Math.min(cursor() + 1, list.length - 1));
} else if (e.key === "ArrowUp") {
e.preventDefault();
cursor(Math.max(cursor() - 1, 0));
} else if (e.key === "Enter" && cursor() >= 0) {
e.preventDefault();
pick(list[cursor()]);
} else if (e.key === "Escape") {
isOpen(false);
}
};
return h("div", { class: `relative w-full ${props.class ?? ''}` }, [
h("label", { class: "input input-bordered w-full" }, [
h("span", { class: "icon-[lucide--search]" }),
h("input", {
...props,
type: "text",
class: "grow",
placeholder: props.placeholder || "Buscar...",
value: query,
onfocus: () => isOpen(true),
onblur: () => setTimeout(() => isOpen(false), 150),
onkeydown: handleKeyDown,
oninput: (e) => {
const newVal = e.target.value;
query(newVal);
if (typeof props.value === "function") props.value(newVal);
isOpen(true);
cursor(-1);
}
})
]),
h("ul", {
class: "absolute left-0 w-full menu bg-base-100 rounded-box mt-1 p-2 shadow-xl max-h-60 overflow-y-auto border border-base-300 z-50",
style: () => `display: ${isOpen() && filteredItems().length ? "block" : "none"};`
}, [
each(filteredItems, (item, idx) =>
h("li", {}, [
h("a", {
class: () => `block w-full ${cursor() === idx ? "active bg-primary text-primary-content" : ""}`,
onclick: () => pick(item),
onmouseenter: () => cursor(idx)
}, typeof item === "string" ? item : item.label)
]),
(item, idx) => (typeof item === "string" ? item : item.value) + idx
),
() => filteredItems().length === 0 ? h("li", { class: "flex justify-center p-4 opacity-50" }, h("span", { class: "icon-[lucide--search-x] text-2xl" })) : null
])
]);
};