All checks were successful
Deploy Docs to Synology / deploy (push) Successful in 3s
92 lines
3.0 KiB
JavaScript
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
|
|
])
|
|
]);
|
|
}; |