ranui
Version:
A framework-agnostic Web Components UI library built on native custom elements, with TypeScript types, light/dark theming, SSR and PWA support.
367 lines (366 loc) • 19.8 kB
JavaScript
import { a as T, n as O, o as F, r as D, s as B, t as L } from "./utils-D1iZnChu.js";
import { t as M } from "./events-C59wGdni.js";
import { _ as d, i as l, n as b, p as g } from "./factory-ZNhcQ4tf.js";
import { a as z, n as P } from "./ssr-DSGwPtEd.js";
var H = ".remove-wap-active-focus{outline:0;-webkit-tap-highlight-color:transparent}.remove-wap-active-focus:active,.remove-wap-active-focus:focus{outline:0;-webkit-tap-highlight-color:transparent}:host{position:var(--ran-modal-host-position, static)}.ran-modal-root{position:var(--ran-modal-root-position, fixed);inset:var(--ran-modal-root-inset, 0);z-index:var(--ran-modal-root-z-index, var(--ran-z-modal, 1000));display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity .3s,visibility .3s}.ran-modal-root[open]{opacity:1;visibility:visible}.ran-modal-mask{position:var(--ran-modal-mask-position, absolute);inset:var(--ran-modal-mask-inset, 0);background:var(--ran-modal-mask-background, rgba(0, 0, 0, .45))}.ran-modal-dialog{position:var(--ran-modal-dialog-position, relative);width:var(--ran-modal-dialog-width, min(560px, calc(100vw - 32px) ));margin:var(--ran-modal-dialog-margin, 0 auto);background:var(--ran-modal-dialog-background, var(--ran-color-bg-elevated, #fff));border-image-source:var(--ran-modal-dialog-border-image-source, none);border-image-slice:var(--ran-modal-dialog-border-image-slice, 8);border-image-width:var(--ran-modal-dialog-border-image-width, 4px);border-image-outset:0;border-image-repeat:stretch;border-radius:var(--ran-modal-dialog-border-radius, var(--ran-radius-md, 12px));box-shadow:var(--ran-modal-dialog-box-shadow, var(--ran-shadow-modal, var(--ran-shadow-elevated, 0 10px 32px rgba(0, 0, 0, .2))));outline:var(--ran-modal-dialog-outline, none);max-height:var(--ran-modal-dialog-max-height, 80vh );overflow:var(--ran-modal-dialog-overflow, hidden);transform:translateY(var(--ran-modal-dialog-offset-y, clamp(-56px, -6vh, -24px))) scale(.9);transition:transform .3s cubic-bezier(.075,.82,.165,1)}.ran-modal-root[open] .ran-modal-dialog{transform:translateY(var(--ran-modal-dialog-offset-y, clamp(-56px, -6vh, -24px))) scale(1)}.ran-modal-header{display:var(--ran-modal-header-display, flex);align-items:var(--ran-modal-header-align-items, center);justify-content:var(--ran-modal-header-justify-content, space-between);padding:var(--ran-modal-header-padding, 16px 18px);border-bottom:var(--ran-modal-header-border-bottom, 1px solid var(--ran-color-border-secondary, #f0f0f0))}.ran-modal-root.hide-header .ran-modal-header{position:absolute;top:0;right:0;z-index:1;padding:var(--ran-modal-hide-header-padding, 10px);border-bottom:none;background:transparent}.ran-modal-root.hide-header .ran-modal-title{display:none}.ran-modal-title{margin:var(--ran-modal-title-margin, 0);font-size:var(--ran-modal-title-font-size, 16px);line-height:var(--ran-modal-title-line-height, 1.5);color:var(--ran-modal-title-color, var(--ran-color-text, #111827))}.ran-modal-close{border:var(--ran-modal-close-border, none);background:var(--ran-modal-close-background, transparent);color:var(--ran-modal-close-color, var(--ran-color-text-secondary, #667085));font-size:var(--ran-modal-close-font-size, 18px);cursor:var(--ran-modal-close-cursor, pointer);padding:var(--ran-modal-close-padding, 0 4px)}.ran-modal-body{padding:var(--ran-modal-body-padding, 18px);max-height:var(--ran-modal-body-max-height, 68vh );overflow:var(--ran-modal-body-overflow, auto);color:var(--ran-modal-body-color, var(--ran-color-text-secondary, #344054))}.ran-modal-footer{display:none;align-items:var(--ran-modal-footer-align-items, center);justify-content:var(--ran-modal-footer-justify-content, flex-end);gap:var(--ran-modal-footer-gap, 10px);padding:var(--ran-modal-footer-padding, 12px 18px 18px);border-top:var(--ran-modal-footer-border-top, 1px solid var(--ran-color-border-secondary, #f0f0f0))}.ran-modal-root.has-footer .ran-modal-footer{display:flex}.ran-modal-footer::slotted(.ran-modal-actions){display:inline-flex;align-items:center;justify-content:flex-end;gap:10px}.ran-modal-footer::slotted(.ran-modal-action){border:1px solid var(--ran-color-border, #d0d5dd);background:var(--ran-color-bg-elevated, #fff);border-radius:var(--ran-radius-sm, 6px);height:34px;line-height:32px;padding:0 14px;cursor:pointer;font-size:14px;color:var(--ran-color-text-secondary, #344054)}.ran-modal-footer::slotted(.ran-modal-action-confirm){border-color:var(--ran-color-primary, #155eef);background:var(--ran-color-primary, #155eef);color:#fff}.ran-modal-footer::slotted(.ran-modal-action:disabled){opacity:.6;cursor:not-allowed}", v = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])', I = class o extends P {
static _openStack = [];
static _bodyScrollLockCount = 0;
static _bodyOverflow = "";
static _bodyPaddingRight = "";
static _inertSnapshot = /* @__PURE__ */ new Map();
_events = new M();
_shadowDom;
_root;
_mask;
_dialog;
_title;
_closeBtn;
_slot;
_footer;
_footerSlot;
_labelId;
_previousActiveElement;
_isDialogOpen;
_scrollLocked;
_afterOpenTimer;
_afterCloseTimer;
static get observedAttributes() {
return [
"open",
"title",
"maskClosable",
"closeOnEsc",
"lockScroll",
"autoFocus",
"closable",
"hide-header",
"sheet"
];
}
constructor() {
super(), this._labelId = `ran-modal-title-${Math.random().toString(36).slice(2, 9)}`, this._previousActiveElement = null, this._isDialogOpen = !1, this._scrollLocked = !1, this._afterOpenTimer = null, this._afterCloseTimer = null, this._shadowDom = O(this, H);
const t = L(this._shadowDom, ".ran-modal-root", () => l().class("ran-modal-root").part("root").children(l().class("ran-modal-mask").part("mask"), l().class("ran-modal-dialog").part("dialog").role("dialog").attr("aria-modal", "true").tabIndex(-1).labelledBy(this._labelId).children(d("header").class("ran-modal-header").part("header").children(d("h3").class("ran-modal-title").part("title").id(this._labelId), d("button").class("ran-modal-close").part("close").attr("type", "button").label("Close dialog").text("x")), l().class("ran-modal-body").part("body").children(g()), d("footer").class("ran-modal-footer").part("footer").children(g().attr("name", "footer")))).build());
this._root = t, this._mask = this._root.querySelector(".ran-modal-mask"), this._dialog = this._root.querySelector(".ran-modal-dialog"), this._title = this._root.querySelector(".ran-modal-title"), this._closeBtn = this._root.querySelector(".ran-modal-close"), this._slot = this._root.querySelector("slot"), this._footer = this._root.querySelector(".ran-modal-footer"), this._footerSlot = this._root.querySelector('slot[name="footer"]'), this.syncTitle(), this.syncClosable(), this.syncFooter(), this.syncHeader();
}
get open() {
return this.hasAttribute("open") && !B.includes(this.getAttribute("open"));
}
set open(t) {
!t || t === "false" ? this.removeAttribute("open") : this.setAttribute("open", "");
}
get title() {
return this.getAttribute("title") || "";
}
set title(t) {
this.setAttribute("title", t || "");
}
get maskClosable() {
const t = this.getAttribute("maskClosable");
return !(t === "false" || t === "0");
}
set maskClosable(t) {
!t || t === "false" || t === "0" ? this.setAttribute("maskClosable", "false") : this.setAttribute("maskClosable", "true");
}
get sheet() {
return D(this, "sheet");
}
set sheet(t) {
T(this, "sheet", t);
}
get closeOnEsc() {
const t = this.getAttribute("closeOnEsc");
return !(t === "false" || t === "0");
}
set closeOnEsc(t) {
!t || t === "false" || t === "0" ? this.setAttribute("closeOnEsc", "false") : this.setAttribute("closeOnEsc", "true");
}
get lockScroll() {
const t = this.getAttribute("lockScroll");
return !(t === "false" || t === "0");
}
set lockScroll(t) {
!t || t === "false" || t === "0" ? this.setAttribute("lockScroll", "false") : this.setAttribute("lockScroll", "true");
}
get autoFocus() {
const t = this.getAttribute("autoFocus");
return !(t === "false" || t === "0");
}
set autoFocus(t) {
!t || t === "false" || t === "0" ? this.setAttribute("autoFocus", "false") : this.setAttribute("autoFocus", "true");
}
get closable() {
const t = this.getAttribute("closable");
return !(t === "false" || t === "0");
}
set closable(t) {
!t || t === "false" || t === "0" ? this.setAttribute("closable", "false") : this.setAttribute("closable", "true");
}
get hideHeader() {
const t = this.getAttribute("hide-header");
return t !== null && t !== "false" && t !== "0";
}
set hideHeader(t) {
!t || t === "false" || t === "0" ? this.removeAttribute("hide-header") : this.setAttribute("hide-header", "");
}
handlerExternalCss = () => {
F(this, this._shadowDom, "sheet", null, this.sheet);
};
syncTitle = () => {
this._title.textContent = this.title || "Modal", this.hideHeader && this._dialog.setAttribute("aria-label", this.title || "Modal");
};
syncClosable = () => {
const t = this.closable;
this._closeBtn.hidden = !t, this._closeBtn.setAttribute("aria-hidden", t ? "false" : "true"), this._closeBtn.tabIndex = t ? 0 : -1;
};
syncHeader = () => {
const t = this.hideHeader;
this._root.classList.toggle("hide-header", t), t ? (this._dialog.setAttribute("aria-label", this.title || "Modal"), this._dialog.removeAttribute("aria-labelledby")) : (this._dialog.setAttribute("aria-labelledby", this._labelId), this._dialog.removeAttribute("aria-label"));
};
syncFooter = () => {
const t = (this._footerSlot?.assignedNodes({ flatten: !0 }) || []).some((e) => e.nodeType === Node.ELEMENT_NODE ? !0 : e.nodeType === Node.TEXT_NODE ? (e.textContent || "").trim().length > 0 : !1);
this._root.classList.toggle("has-footer", t);
};
getFocusableElements = () => {
const t = Array.from(this._dialog.querySelectorAll(v)), e = Array.from(this.querySelectorAll(v));
return [...t, ...e].filter((r) => {
if (r.hasAttribute("disabled")) return !1;
const i = r;
return i.offsetParent !== null || i.getClientRects().length > 0;
});
};
static getTopModal = () => o._openStack.length === 0 ? null : o._openStack[o._openStack.length - 1] || null;
static pushOpenModal = (t) => {
o._openStack.includes(t) || o._openStack.push(t);
};
static removeOpenModal = (t) => {
o._openStack = o._openStack.filter((e) => e !== t);
};
static syncBackgroundInert = () => {
if (typeof document > "u") return;
const t = document.body;
if (!t) return;
for (const [r, i] of o._inertSnapshot.entries())
r.inert = i.inert, i.ariaHidden === null ? r.removeAttribute("aria-hidden") : r.setAttribute("aria-hidden", i.ariaHidden);
if (o._inertSnapshot.clear(), o._openStack.length === 0) return;
const e = o._openStack.map((r) => r);
Array.from(t.children).forEach((r) => {
e.some((i) => r === i || r.contains(i)) || (o._inertSnapshot.set(r, {
inert: r.inert,
ariaHidden: r.getAttribute("aria-hidden")
}), r.inert = !0, r.setAttribute("aria-hidden", "true"));
});
};
static lockBodyScroll = () => {
if (typeof document > "u") return;
const t = document.body;
if (t) {
if (o._bodyScrollLockCount === 0) {
o._bodyOverflow = t.style.overflow, o._bodyPaddingRight = t.style.paddingRight;
const e = Math.max(0, window.innerWidth - document.documentElement.clientWidth);
if (e > 0) {
const r = Number.parseFloat(window.getComputedStyle(t).paddingRight) || 0;
t.style.paddingRight = `${r + e}px`;
}
t.style.overflow = "hidden";
}
o._bodyScrollLockCount += 1;
}
};
static unlockBodyScroll = () => {
if (typeof document > "u") return;
const t = document.body;
!t || o._bodyScrollLockCount === 0 || (o._bodyScrollLockCount -= 1, o._bodyScrollLockCount === 0 && (t.style.overflow = o._bodyOverflow, t.style.paddingRight = o._bodyPaddingRight));
};
clearAfterTimers = () => {
this._afterOpenTimer !== null && (window.clearTimeout(this._afterOpenTimer), this._afterOpenTimer = null), this._afterCloseTimer !== null && (window.clearTimeout(this._afterCloseTimer), this._afterCloseTimer = null);
};
getTransitionTimeout = () => {
const t = window.getComputedStyle(this._dialog), e = (n) => {
const s = n.split(",")[0]?.trim() || "0s";
return s.endsWith("ms") ? Number.parseFloat(s) : s.endsWith("s") ? Number.parseFloat(s) * 1e3 : Number.parseFloat(s) || 0;
}, r = e(t.transitionDelay), i = e(t.transitionDuration);
return Math.max(0, r + i);
};
emitAfterOpen = () => {
const t = this.getTransitionTimeout();
this._afterOpenTimer = window.setTimeout(() => {
this._afterOpenTimer = null, this.dispatchEvent(new CustomEvent("afteropen"));
}, t);
};
emitAfterClose = (t) => {
const e = this.getTransitionTimeout();
this._afterCloseTimer = window.setTimeout(() => {
this._afterCloseTimer = null, this.dispatchEvent(new CustomEvent("afterclose", { detail: { trigger: t } }));
}, e);
};
trapFocus = (t) => {
if (t.key !== "Tab") return;
const e = this.getFocusableElements();
if (e.length === 0) {
t.preventDefault(), this._dialog.focus();
return;
}
const r = e[0], i = e[e.length - 1], n = this._shadowDom.activeElement || document.activeElement;
if (t.shiftKey && n === r) {
t.preventDefault(), i.focus();
return;
}
!t.shiftKey && n === i && (t.preventDefault(), r.focus());
};
close = (t = "program") => {
if (!this.open) return;
const e = new CustomEvent("beforeclose", {
cancelable: !0,
detail: { trigger: t }
});
this.dispatchEvent(e) && (this.removeAttribute("open"), this.dispatchEvent(new CustomEvent("close", { detail: { trigger: t } })), this.emitAfterClose(t));
};
openDialog = () => {
if (this._isDialogOpen || this._root.hasAttribute("open")) return;
const t = new CustomEvent("beforeopen", { cancelable: !0 });
if (!this.dispatchEvent(t)) {
this.removeAttribute("open");
return;
}
this._previousActiveElement = document.activeElement, o.pushOpenModal(this), o.syncBackgroundInert(), this.lockScroll && (o.lockBodyScroll(), this._scrollLocked = !0), this._isDialogOpen = !0, this._root.setAttribute("open", ""), this._dialog.setAttribute("aria-hidden", "false"), this.dispatchEvent(new CustomEvent("open")), this.clearAfterTimers(), this.emitAfterOpen(), requestAnimationFrame(() => {
this.autoFocus && (this.getFocusableElements()[0] || this._dialog).focus();
});
};
closeDialog = () => {
if (!this._isDialogOpen) {
this._root.removeAttribute("open"), this._dialog.setAttribute("aria-hidden", "true");
return;
}
this._isDialogOpen = !1, o.removeOpenModal(this), o.syncBackgroundInert(), this._scrollLocked && (o.unlockBodyScroll(), this._scrollLocked = !1), this._root.removeAttribute("open"), this._dialog.setAttribute("aria-hidden", "true"), this._previousActiveElement instanceof HTMLElement && this._previousActiveElement.focus();
};
onMaskClick = (t) => {
this.maskClosable && (t.composedPath().includes(this._dialog) || this.close("mask"));
};
onCloseButtonClick = () => {
this.close("button");
};
onKeydown = (t) => {
if (this.open && o.getTopModal() === this) {
if (t.key === "Escape" && this.closeOnEsc) {
t.preventDefault(), this.close("escape");
return;
}
this.trapFocus(t);
}
};
onDocumentFocusIn = (t) => {
if (!this.open || o.getTopModal() !== this) return;
const e = t.composedPath();
e.includes(this) || e.includes(this._dialog) || (this.getFocusableElements()[0] || this._dialog).focus();
};
connectedCallback() {
this.handlerExternalCss(), this._events.on(this._mask, "click", this.onMaskClick).on(this._closeBtn, "click", this.onCloseButtonClick).on(document, "keydown", this.onKeydown).on(document, "focusin", this.onDocumentFocusIn).on(this._footerSlot, "slotchange", this.syncFooter), this.syncTitle(), this.syncClosable(), this.syncFooter(), this.syncHeader(), this.open ? this.openDialog() : this.closeDialog();
}
disconnectedCallback() {
this._root.hasAttribute("open") && this.closeDialog(), this._events.abort(), this.clearAfterTimers();
}
attributeChangedCallback(t, e, r) {
if (e !== r) {
if (t === "title") {
this.syncTitle();
return;
}
if (t === "closable") {
this.syncClosable();
return;
}
if (t === "hide-header") {
this.syncHeader();
return;
}
if (t === "open") {
this.open ? this.openDialog() : this.closeDialog();
return;
}
t === "sheet" && this.handlerExternalCss();
}
}
static normalizeProgrammaticOptions = (t) => typeof t == "string" ? { content: t } : t || {};
static open = (t) => {
if (typeof document > "u") return Promise.resolve({
action: "dismiss",
trigger: "program"
});
const { title: e = "Confirm", content: r = "", okText: i = "OK", cancelText: n = "Cancel", showCancel: s = !1, maskClosable: y = !1, closeOnEsc: _ = !0, lockScroll: k = !0, autoFocus: C = !0, closable: x = !0, onConfirm: w, onCancel: A } = o.normalizeProgrammaticOptions(t);
return new Promise((S) => {
const a = d("r-modal").build();
a.title = e, a.maskClosable = y, a.closeOnEsc = _, a.lockScroll = k, a.autoFocus = C, a.closable = x;
const u = l().build();
typeof r == "string" ? u.textContent = r : r instanceof Node && u.appendChild(r), a.appendChild(u);
let m = "dismiss";
const f = l().attr("slot", "footer").class("ran-modal-actions");
if (s) {
const h = b().attr("type", "button").class("ran-modal-action ran-modal-action-cancel").text(n).build();
h.addEventListener("click", async () => {
await A?.() !== !1 && (m = "cancel", a.close("program"));
}), f.children(h);
}
const c = b().attr("type", "button").class("ran-modal-action ran-modal-action-confirm").text(i).build();
c.addEventListener("click", async () => {
c.disabled = !0;
try {
if (await w?.() === !1) return;
m = "confirm", a.close("program");
} finally {
c.disabled = !1;
}
}), f.children(c), a.appendChild(f.build());
const p = (h) => {
const E = h.detail?.trigger || "program";
a.removeEventListener("afterclose", p), a.parentElement && a.parentElement.removeChild(a), S({
action: m,
trigger: E
});
};
a.addEventListener("afterclose", p), document.body.appendChild(a), a.open = !0;
});
};
static confirm = (t) => {
const e = o.normalizeProgrammaticOptions(t);
return o.open({
...e,
showCancel: !0
});
};
static info = (t) => {
const e = o.normalizeProgrammaticOptions(t);
return o.open({
...e,
showCancel: !1,
title: e.title || "Info"
});
};
static success = (t) => {
const e = o.normalizeProgrammaticOptions(t);
return o.open({
...e,
showCancel: !1,
title: e.title || "Success"
});
};
static warning = (t) => {
const e = o.normalizeProgrammaticOptions(t);
return o.open({
...e,
showCancel: !1,
title: e.title || "Warning"
});
};
static error = (t) => {
const e = o.normalizeProgrammaticOptions(t);
return o.open({
...e,
showCancel: !1,
title: e.title || "Error"
});
};
};
z("r-modal", I);
export {
I as t
};