zoeys.computer/assets/js/hooks/select.js
2024-10-26 21:41:22 -04:00

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 };