@vkm-js/dropdown
Version:
An Alpinejs plugin that creates a beautiful and customizable dropdown.
85 lines (83 loc) • 4.16 kB
JavaScript
// packages/dropdown/src/index.js
function src_default(Alpine) {
Alpine.data("dropdown", () => ({
open: false,
exitHover: false,
keyboardTrigger: false,
toggle() {
this.open = this.open ? this.close() : true;
},
close(focusAfter) {
this.open = false;
this.keyboardTrigger = false;
focusAfter && focusAfter.focus();
}
}));
Alpine.directive("dropdown", (el, { modifiers }, { cleanup }) => {
let { triggerEl, dropdownEl, position, closeOnClickInside, isHoverable } = getDropdownOptions(el, modifiers);
const placement = "x-anchor." + (position || "bottom-start");
if (!triggerEl || !dropdownEl) {
return !triggerEl ? console.warn("Dropdown JS: Attribute data-trigger is not set!") : console.warn("Dropdown JS: Attribute data-dropdown is not set!");
}
if (!el.id) {
el.id = crypto.getRandomValues(new Uint32Array(1))[0].toString(36) + Date.now().toString(36);
}
el.setAttribute("x-data", "dropdown");
el.classList.add("relative");
el.setAttribute("x-on:keydown.esc.window", "close()");
el.setAttribute("x-on:keydown.escape.prevent.stop", "close()");
if (isHoverable) {
triggerEl.setAttribute("x-on:mouseover", "open = true");
el.setAttribute("x-on:mouseenter", "console.log('mousenter'),exitHover ? clearTimeout(exitHover) : true");
el.setAttribute("x-on:mouseleave.prevent", "console.log('mouseleave'), exitHover = setTimeout(() => { open = false }, 50)");
} else {
triggerEl.setAttribute("x-on:click", "toggle()");
}
if (!triggerEl.id) {
triggerEl.id = `btn-${el.id}`;
}
triggerEl.setAttribute("x-ref", "dropdownBtn");
triggerEl.setAttribute("x-on:keydown.space.prevent", "keyboardTrigger = true");
triggerEl.setAttribute("x-on:keydown.enter.prevent", "keyboardTrigger = true");
triggerEl.setAttribute("x-on:keydown.down.prevent", "keyboardTrigger = true");
triggerEl.setAttribute(":aria-expanded", "(open || keyboardTrigger).toString()");
triggerEl.setAttribute("aria-haspopup", "true");
triggerEl.setAttribute("aria-label", "Dropdown");
dropdownEl.setAttribute("x-cloak", "");
dropdownEl.setAttribute("x-transition.origin.top.left", "");
dropdownEl.setAttribute("x-trap", "keyboardTrigger");
dropdownEl.setAttribute(placement, "$refs.dropdownBtn");
dropdownEl.setAttribute("x-ref", "panel");
dropdownEl.setAttribute("x-show", "open || keyboardTrigger");
if (!modifiers.includes("custom")) {
dropdownEl.classList.add("absolute", "mt-2", "shadow-lg", "bg-white/90", "dark:bg-black/90", "rounded-sm", "flex", "flex-col", "w-full", "min-w-72", "z-1000", "py-2");
} else {
dropdownEl.classList.add("absolute", "z-1000");
}
dropdownEl.setAttribute("x-on:click.outside", "close()");
if (closeOnClickInside) {
dropdownEl.setAttribute("x-on:click", "close()");
}
dropdownEl.setAttribute("x-on:keydown.down.prevent", "$focus.wrap().next()");
dropdownEl.setAttribute("x-on:keydown.up.prevent", "$focus.wrap().previous()");
dropdownEl.setAttribute("x-on:keydown.tab", "close()");
dropdownEl.setAttribute("x-on:keydown.shift.tab", "close()");
dropdownEl.setAttribute("role", "menu");
dropdownEl.setAttribute("aria-orientation", "vertical");
dropdownEl.setAttribute("aria-labelledby", triggerEl.id);
el.setAttribute("x-data", "dropdown");
});
function getDropdownOptions(el, modifiers) {
let triggerEl = el.querySelector("[data-trigger]"), dropdownEl = el.querySelector("[data-dropdown]"), position = getPlacement(modifiers), isHoverable = modifiers.includes("hover"), closeOnClickInside = modifiers.includes("select");
return { triggerEl, dropdownEl, position, closeOnClickInside, isHoverable };
}
function getPlacement(modifiers) {
return ["top", "top-start", "top-end", "right", "right-start", "right-end", "bottom", "bottom-start", "bottom-end", "left", "left-start", "left-end"].find((i) => modifiers.includes(i)) || "";
}
}
// packages/dropdown/builds/module.js
var module_default = src_default;
export {
module_default as default,
src_default as dropdown
};