@flexilla/alpine-modal
Version:
AlpineJS plugin for creating Modal, Dialog, alert dialog components
319 lines (316 loc) • 15.4 kB
JavaScript
// ../../node_modules/@flexilla/modal/dist/modal.js
var p = Object.defineProperty;
var b = (o, t, e) => t in o ? p(o, t, { enumerable: true, configurable: true, writable: true, value: e }) : o[t] = e;
var s = (o, t, e) => b(o, typeof t != "symbol" ? t + "" : t, e);
var r = (o, t = document.body) => t.querySelector(o);
var c = (o, t = document.body) => Array.from(t.querySelectorAll(o));
var M = ({
newElement: o,
existingElement: t
}) => {
if (!(o instanceof HTMLElement) || !(t instanceof HTMLElement))
throw new Error("Both parameters must be valid HTML elements.");
const e = t.parentElement;
if (e)
e.insertBefore(o, t);
else
throw new Error("Existing element must have a parent element.");
};
var g = ({
element: o,
callback: t,
type: e,
keysCheck: n
}) => {
const a = getComputedStyle(o), i = a.animation;
if (i !== "none" && i !== "" && !n.includes(i)) {
const l = "animationend", d = () => {
o.removeEventListener(l, d), t();
};
o.addEventListener(l, d, { once: true });
} else
t();
};
var y = ({ element: o, callback: t }) => {
g({
element: o,
callback: t,
type: "animation",
keysCheck: ["none 0s ease 0s 1 normal none running"]
});
};
var f = (o, t, e) => {
const n = new CustomEvent(t, { detail: e });
o.dispatchEvent(n);
};
var E = (o, t, e) => {
if (!(t instanceof HTMLElement))
throw new Error("No modal-content found");
o.setAttribute("aria-hidden", e === "open" ? "false" : "true"), o.setAttribute("data-state", e), t.setAttribute("data-state", e);
const n = r("[data-modal-overlay]", o);
n instanceof HTMLElement && n.setAttribute("data-state", e);
};
var A = (o, t, e) => {
if (!o) {
t || (document.body.style.overflowY = "auto");
return;
}
c("[data-fx-modal][data-state=open]:not([data-allow-body-scroll=true]").filter((i) => i !== e).length === 0 && !t && (document.body.style.overflowY = "auto");
};
var v = class {
static initGlobalRegistry() {
window.$flexillaInstances || (window.$flexillaInstances = {});
}
static register(t, e, n) {
return this.initGlobalRegistry(), window.$flexillaInstances[t] || (window.$flexillaInstances[t] = []), this.getInstance(t, e) || (window.$flexillaInstances[t].push({ element: e, instance: n }), n);
}
static getInstance(t, e) {
var n, a;
return this.initGlobalRegistry(), (a = (n = window.$flexillaInstances[t]) == null ? void 0 : n.find(
(i) => i.element === e
)) == null ? void 0 : a.instance;
}
static removeInstance(t, e) {
this.initGlobalRegistry(), window.$flexillaInstances[t] && (window.$flexillaInstances[t] = window.$flexillaInstances[t].filter(
(n) => n.element !== e
));
}
};
var C = (o) => {
var t;
o instanceof HTMLElement && ((t = o.parentElement) == null || t.removeChild(o));
};
var x = ({ modalContent: o, overlayClassName: t }) => {
const e = document.createElement("span");
return e.setAttribute("aria-hidden", "true"), M({ newElement: e, existingElement: o }), e.classList.add(...t), e.setAttribute("data-modal-overlay", ""), e;
};
var m = class m2 {
/**
* Creates a new Modal instance
* @param modal - The modal element or selector string to initialize
* @param options - Configuration options for the modal behavior
* @param triggerElements - Optional trigger elements or selectors that open the modal
*/
constructor(t, e = {}, n) {
s(this, "modalElement");
s(this, "modalId");
s(this, "modalContent");
s(this, "triggerButtons", []);
s(this, "overlayElement");
s(this, "dispatchEventToDocument");
s(this, "options");
s(this, "state");
s(this, "animationEnter");
s(this, "animationExit");
s(this, "animateContent");
s(this, "hasDefaultOverlay");
s(this, "enableStackedModals");
s(this, "preventCloseModal");
s(this, "isKeyDownEventRegistered");
s(this, "closeButtons");
s(this, "overlayClassName");
s(this, "allowBodyScroll");
s(this, "initAsOpen");
s(this, "closeAll", (t2) => {
if (this.enableStackedModals)
return;
const e2 = c("[data-fx-modal][data-state=open]");
for (const n2 of e2) {
const a2 = n2.dataset.modalId;
if (a2 !== t2.dataset.modalId) {
n2.blur(), r("[data-modal-overlay]", n2).setAttribute("data-state", "close");
const l2 = r("[data-modal-content]", n2);
E(n2, l2, "close"), document.dispatchEvent(new CustomEvent(`modal:${a2}:close`));
}
}
});
s(this, "closeModalEsc", (t2) => {
t2.key === "Escape" && (t2.preventDefault(), this.preventCloseModal || this.hideModal());
});
s(this, "initModal", (t2, e2) => {
var h;
if (!(t2 instanceof HTMLDialogElement))
throw new Error("Modal Element must be a valid HTMLDialog Element");
const { allowBodyScroll: n2, animateContent: a2, preventCloseModal: i2, overlayClass: l2, enableStackedModals: d2 } = e2;
this.allowBodyScroll = t2.hasAttribute("data-allow-body-scroll") && t2.getAttribute("data-allow-body-scroll") !== "false" || n2 || false, this.preventCloseModal = t2.hasAttribute("data-prevent-close-modal") && t2.getAttribute("data-prevent-close-modal") !== "false" || i2 || false, this.enableStackedModals = t2.hasAttribute("data-enable-stacked") && t2.getAttribute("data-enable-stacked") !== "false" || d2 || false, this.overlayClassName = ((h = t2.dataset.modalOverlay) == null ? void 0 : h.split(" ")) || (l2 == null ? void 0 : l2.split(" ")) || "", this.isKeyDownEventRegistered = false, t2.setAttribute("data-allow-body-scroll", `${this.allowBodyScroll}`), this.closeButtons = c("[data-close-modal]", t2), this.hasDefaultOverlay = false, r("[data-modal-overlay]", t2) instanceof HTMLElement && (this.overlayElement = r("[data-modal-overlay]", t2), this.overlayElement.setAttribute("data-overlay-nature", "default"), this.hasDefaultOverlay = true), this.animateContent = a2, this.animationEnter = this.modalContent.dataset.enterAnimation || "", this.animationExit = this.modalContent.dataset.exitAnimation || "", this.overlayElement && this.overlayElement.setAttribute("data-state", "close"), this.addEvents();
});
s(this, "closeModalOnX", (t2) => {
t2.preventDefault(), this.hideModal();
});
s(this, "addEvents", () => {
for (const t2 of this.triggerButtons)
t2.addEventListener("click", this.showModal);
if (this.closeButtons.length > 0)
for (const t2 of this.closeButtons)
t2.addEventListener("click", this.closeModalOnX);
this.dispatchEventToDocument && document.addEventListener(`modal:${this.modalId}:open`, this.showModal), this.dispatchEventToDocument && document.addEventListener(`modal:${this.modalId}:close`, this.hideModal);
});
s(this, "showModal", () => {
var e2, n2, a2, i2, l2;
if (!(!this.initAsOpen && this.modalElement.getAttribute("data-state") === "open")) {
if (this.initAsOpen = false, this.closeAll(this.modalElement), this.overlayElement = this.hasDefaultOverlay ? this.overlayElement : x({
modalContent: this.modalContent,
overlayClassName: this.overlayClassName
}), (e2 = this.overlayElement) == null || e2.setAttribute("data-state", "open"), f(this.modalElement, "modal-open", { modalId: this.modalId }), this.animateContent || this.animationEnter !== "") {
const d2 = this.animateContent ? this.animateContent.enterAnimation : this.animationEnter;
d2 && d2 !== "" && this.modalContent.style.setProperty("--un-modal-animation", d2), E(this.modalElement, this.modalContent, "open"), y({
element: this.modalContent,
callback: () => {
this.modalContent.style.removeProperty("--un-modal-animation");
}
});
} else
E(this.modalElement, this.modalContent, "open");
this.allowBodyScroll || (document.body.style.overflow = "hidden"), this.isKeyDownEventRegistered || (document.addEventListener("keydown", this.closeModalEsc), this.isKeyDownEventRegistered = true), this.modalContent.focus(), this.preventCloseModal || this.overlayElement.addEventListener("click", this.hideModal), (a2 = (n2 = this.options).onShow) == null || a2.call(n2), (l2 = (i2 = this.options).onToggle) == null || l2.call(i2, { isHidden: false }), this.modalElement.showModal();
}
});
s(this, "closeModal", () => {
this.modalElement.setAttribute("aria-hidden", "true"), this.modalElement.setAttribute("data-state", "close"), this.modalElement.blur(), A(this.enableStackedModals || false, this.allowBodyScroll || false, this.modalElement), this.hasDefaultOverlay || C(this.overlayElement), f(this.modalElement, "modal-close", { modalId: this.modalElement.id });
});
s(this, "closeLastAction", () => {
var t2, e2, n2, a2;
this.isKeyDownEventRegistered && (document.removeEventListener("keydown", this.closeModalEsc), this.isKeyDownEventRegistered = false), this.modalElement.blur(), (e2 = (t2 = this.options).onHide) == null || e2.call(t2), (a2 = (n2 = this.options).onToggle) == null || a2.call(n2, { isHidden: true });
});
s(this, "hideModal", () => {
var a2, i2, l2, d2, h;
let t2 = false;
f(this.modalElement, "before-hide", {
modalId: this.modalId,
setExitAction: (u) => {
t2 = u;
}
});
const e2 = (l2 = (i2 = (a2 = this.options).beforeHide) == null ? void 0 : i2.call(a2)) == null ? void 0 : l2.cancelAction;
if (t2 || e2)
return;
const n2 = ((d2 = this.animateContent) == null ? void 0 : d2.exitAnimation) && this.animateContent.exitAnimation !== "" || this.animationExit && this.animationExit !== "";
if ((h = this.overlayElement) == null || h.setAttribute("data-state", "close"), this.modalContent.setAttribute("data-state", "close"), n2) {
const u = this.animationExit ? this.animationExit : this.animateContent && this.animateContent.exitAnimation || "";
this.modalContent.style.setProperty("--un-modal-animation", u);
}
y({
element: this.modalContent,
callback: () => {
n2 && this.modalContent.style.removeProperty("--un-modal-animation"), this.closeModal(), this.closeLastAction(), document.activeElement instanceof HTMLElement && document.activeElement.blur(), this.modalElement.close("modal-closed");
}
});
});
s(this, "cleanup", () => {
for (const t2 of this.triggerButtons)
t2.removeEventListener("click", this.showModal);
if (this.closeButtons.length > 0)
for (const t2 of this.closeButtons)
t2.removeEventListener("click", this.closeModalOnX);
!this.preventCloseModal && this.overlayElement instanceof HTMLElement && this.overlayElement.removeEventListener("click", this.hideModal), this.dispatchEventToDocument && document.removeEventListener(`modal:${this.modalId}:open`, this.showModal), this.dispatchEventToDocument && document.removeEventListener(`modal:${this.modalId}:close`, this.hideModal), v.removeInstance("modal", this.modalElement);
});
s(this, "setOptions", ({ state: t2, allowBodyscroll: e2 }) => {
t2 && (this.state = t2), e2 !== void 0 && (this.allowBodyScroll = e2), this.state === "open" ? this.showModal() : this.state === "close" && this.hideModal();
});
const a = typeof t == "string" ? r(t) : t;
if (!(a instanceof HTMLDialogElement))
throw new Error("Modal element not found or invalid. Please provide a valid HTMLDialogElement or selector.");
this.modalElement = a, this.options = e, this.state = (e == null ? void 0 : e.defaultState) || this.modalElement.dataset.state || "close";
const i = v.getInstance("modal", this.modalElement);
if (i)
return i;
this.modalElement.hasAttribute("data-fx-modal") || this.modalElement.setAttribute("data-fx-modal", "");
const l = r("[data-modal-content]", a);
if (!(l instanceof HTMLElement))
throw new Error("Modal content element not found or invalid. Please provide a valid HTMLElement or selector.");
this.modalContent = l;
const d = a.dataset.modalId;
this.modalId = `${d}`, this.initializeTriggers(n, d), this.dispatchEventToDocument = this.options.dispatchEventToDocument || true, this.initModal(this.modalElement, this.options), this.state === "open" ? (this.initAsOpen = true, this.showModal()) : (this.initAsOpen = false, this.modalElement.blur(), this.modalContent.setAttribute("data-state", "close"), this.modalElement.setAttribute("aria-hidden", "true"), this.modalElement.setAttribute("data-state", "close")), v.register("modal", this.modalElement, this);
}
initializeTriggers(t, e) {
if (!t && e) {
const a = c(`[data-modal-target='${e}'], [data-modal-trigger][data-modal-id='${e}']`);
this.triggerButtons = a;
return;
}
if (!t)
return;
const n = Array.isArray(t) ? t : [t];
this.triggerButtons = n.map((a) => {
if (typeof a == "string") {
const i = r(a);
if (!(i instanceof HTMLElement))
throw new Error(`Trigger element not found: ${a}`);
return i;
}
if (!(a instanceof HTMLElement))
throw new Error("Invalid trigger element provided");
return a;
});
}
};
s(m, "autoInit", (t = "[data-fx-modal]") => {
const e = c(t);
for (const n of e)
new m(n);
}), /**
* Creates and initializes a new Modal instance
*/
s(m, "init", (t, e = {}, n) => new m(t, e, n));
var w = m;
// src/index.js
function Modal(Alpine) {
Alpine.directive("modal", (el, {}, { cleanup }) => {
const modalId = el.getAttribute("data-modal-id");
if (!modalId) {
console.error(
"\u274C data-modal-id is required but missing on element:",
el
);
return;
}
if (!(el instanceof HTMLDialogElement)) {
console.error(
"\u274C x-modal must be used only on an HTMLDialogElement:",
el
);
return;
}
const content = el.querySelector("[data-modal-content]");
if (!content) {
console.error(
"\u274C data-modal-content Element is required but missing in Modal Element:",
el
);
return;
}
const modalInstance = new w(el, {
dispatchEventToDocument: false
});
if (!Alpine.store("modals")) {
Alpine.store("modals", {});
}
Alpine.store("modals")[modalId] = modalInstance;
const showModal = () => modalInstance.showModal();
const hideModal = () => modalInstance.hideModal();
document.addEventListener(`modal:${modalId}:open`, showModal);
document.addEventListener(`modal:${modalId}:close`, hideModal);
cleanup(() => {
modalInstance.cleanup();
delete Alpine.store("modals")[modalId];
document.removeEventListener(`modal:${modalId}:open`, showModal);
document.removeEventListener(`modal:${modalId}:close`, hideModal);
});
});
Alpine.magic("modal", () => (id) => {
if (!Alpine.store("modals")) {
console.warn("\u26A0\uFE0F Alpine store for modals is not initialized.");
return null;
}
if (!Alpine.store("modals")[id]) {
console.warn(`\u26A0\uFE0F No modal instance found for ID: ${id}`);
return null;
}
return Alpine.store("modals")[id];
});
}
var src_default = Modal;
// builds/module.js
var module_default = src_default;
export {
module_default as default
};