186 lines
5.5 KiB
JavaScript
186 lines
5.5 KiB
JavaScript
const SelectHook = {
|
|
mounted() {
|
|
const container = this.el;
|
|
const trigger = container.querySelector('[id$="-trigger"]');
|
|
const dropdown = container.querySelector('[id$="-dropdown"]');
|
|
const search = container.querySelector('[id$="-search"]');
|
|
const options = container.querySelector('[id$="-options"]');
|
|
const hiddenInput = container.querySelector('input[type="hidden"]');
|
|
const selectedText = container.querySelector('[id$="-selected"]');
|
|
|
|
const toggleDropdown = (event) => {
|
|
// Prevent the event from bubbling up to any parent form
|
|
if (event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
|
|
const isExpanded = dropdown.classList.contains("block");
|
|
if (!isExpanded) {
|
|
dropdown.classList.remove("hidden");
|
|
// Add animation classes
|
|
dropdown.classList.add(
|
|
"block",
|
|
"animate-in",
|
|
"fade-in",
|
|
"duration-200",
|
|
);
|
|
search.value = "";
|
|
filterOptions("");
|
|
search.focus();
|
|
} else {
|
|
// Add animation classes for hiding
|
|
dropdown.classList.remove("animate-in", "fade-in");
|
|
dropdown.classList.add("animate-out", "fade-out");
|
|
setTimeout(() => {
|
|
dropdown.classList.remove("block", "animate-out", "fade-out");
|
|
dropdown.classList.add("hidden");
|
|
}, 200);
|
|
}
|
|
};
|
|
|
|
const filterOptions = (query) => {
|
|
const items = options.querySelectorAll("li");
|
|
items.forEach((item) => {
|
|
const text = item.textContent.toLowerCase();
|
|
if (text.includes(query.toLowerCase())) {
|
|
item.style.display = "";
|
|
} else {
|
|
item.style.display = "none";
|
|
}
|
|
});
|
|
};
|
|
|
|
const selectOption = (option, event) => {
|
|
// Prevent form submission
|
|
if (event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
|
|
const value = option.dataset.value;
|
|
const label = option.querySelector("span").textContent;
|
|
|
|
// Update hidden input
|
|
hiddenInput.value = value;
|
|
|
|
// Update visible text
|
|
selectedText.textContent = label;
|
|
|
|
// Update check mark position
|
|
options.querySelectorAll("li").forEach((li) => {
|
|
const checkmark = li.querySelector("span:last-child");
|
|
if (checkmark) checkmark.remove();
|
|
|
|
if (li.dataset.value === value) {
|
|
li.setAttribute("aria-selected", "true");
|
|
const checkMarkSpan = document.createElement("span");
|
|
checkMarkSpan.className =
|
|
"absolute inset-y-0 right-0 flex items-center pr-4 ctp-text-lavender";
|
|
checkMarkSpan.innerHTML = `
|
|
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
|
</svg>
|
|
`;
|
|
li.appendChild(checkMarkSpan);
|
|
} else {
|
|
li.setAttribute("aria-selected", "false");
|
|
}
|
|
});
|
|
|
|
toggleDropdown();
|
|
|
|
// Dispatch change event without bubbling
|
|
const changeEvent = new Event("change", { bubbles: false });
|
|
hiddenInput.dispatchEvent(changeEvent);
|
|
|
|
// Push the value to the LiveView
|
|
this.pushEventTo(this.el, "change", { value: value });
|
|
};
|
|
|
|
// Event Listeners
|
|
trigger.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
toggleDropdown(e);
|
|
});
|
|
|
|
search.addEventListener("input", (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
filterOptions(e.target.value);
|
|
});
|
|
|
|
options.querySelectorAll("li").forEach((option) => {
|
|
option.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
selectOption(option, e);
|
|
});
|
|
});
|
|
|
|
// Prevent dropdowns from causing form submission
|
|
container.addEventListener("submit", (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
return false;
|
|
});
|
|
|
|
// Close dropdown when clicking outside
|
|
document.addEventListener("click", (e) => {
|
|
if (!container.contains(e.target)) {
|
|
dropdown.classList.add("hidden");
|
|
dropdown.classList.remove("block");
|
|
}
|
|
});
|
|
|
|
// Keyboard navigation
|
|
container.addEventListener("keydown", (e) => {
|
|
const items = Array.from(options.querySelectorAll("li")).filter(
|
|
(li) => li.style.display !== "none",
|
|
);
|
|
const currentIdx = items.findIndex((li) => li === document.activeElement);
|
|
|
|
switch (e.key) {
|
|
case "ArrowDown":
|
|
e.preventDefault();
|
|
if (currentIdx < items.length - 1) {
|
|
items[currentIdx + 1].focus();
|
|
}
|
|
break;
|
|
case "ArrowUp":
|
|
e.preventDefault();
|
|
if (currentIdx > 0) {
|
|
items[currentIdx - 1].focus();
|
|
}
|
|
break;
|
|
case "Enter":
|
|
e.preventDefault();
|
|
if (document.activeElement.tagName === "LI") {
|
|
selectOption(document.activeElement, e);
|
|
}
|
|
break;
|
|
case "Escape":
|
|
e.preventDefault();
|
|
toggleDropdown(e);
|
|
break;
|
|
}
|
|
});
|
|
|
|
// Prevent the dropdown from closing when clicking inside it
|
|
dropdown.addEventListener("click", (e) => {
|
|
e.stopPropagation();
|
|
});
|
|
|
|
// Prevent the search input from submitting the form
|
|
search.addEventListener("keydown", (e) => {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
};
|
|
|
|
export { SelectHook };
|