ranui
Version:
A framework-agnostic Web Components UI library built on native custom elements, with TypeScript types, light/dark theming, SSR and PWA support.
262 lines (261 loc) • 14.5 kB
JavaScript
import { a as x, i as y, n as T, o as R, r as b, t as D } from "./utils-D1iZnChu.js";
import { t as A } from "./events-C59wGdni.js";
import { _ as h, i as d } from "./factory-ZNhcQ4tf.js";
import { a as z, n as B } from "./ssr-DSGwPtEd.js";
import "./icon-GZrfSeon.js";
var O = ".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{display:block}.ran-mermaid{position:relative;padding:var(--ran-mermaid-padding, 0)}.ran-mermaid-diagram{display:flex;justify-content:center;overflow-x:auto}.ran-mermaid-diagram svg{max-width:100%;height:auto}.ran-mermaid-toolbar{position:absolute;top:var(--ran-mermaid-toolbar-top, 8px);right:var(--ran-mermaid-toolbar-right, 8px);display:none;gap:var(--ran-mermaid-toolbar-gap, 4px);padding:4px;border-radius:var(--ran-radius-md, 8px);background:var(--ran-mermaid-toolbar-background, var(--ran-color-bg-elevated, #fff));box-shadow:var(--ran-mermaid-toolbar-shadow, var(--ran-shadow-menu, 0 2px 8px rgba(0, 0, 0, .12)));opacity:0;transition:opacity .15s ease;pointer-events:none}.ran-mermaid.has-controls .ran-mermaid-toolbar{display:flex}.ran-mermaid:hover .ran-mermaid-toolbar,.ran-mermaid-toolbar:focus-within{opacity:1;pointer-events:auto}.ran-mermaid-btn{display:inline-flex;align-items:center;justify-content:center;width:var(--ran-mermaid-button-size, 28px);height:var(--ran-mermaid-button-size, 28px);padding:0;border:none;border-radius:var(--ran-radius-sm, 6px);background:transparent;color:var(--ran-mermaid-button-color, var(--ran-color-text-secondary, #666));cursor:pointer}.ran-mermaid-btn:hover{background:var(--ran-mermaid-button-hover-background, var(--ran-color-bg-muted, rgba(0, 0, 0, .06)));color:var(--ran-mermaid-button-hover-color, var(--ran-color-text, #222))}.ran-mermaid-btn:focus-visible{outline:var(--ran-mermaid-button-focus-outline, 2px solid var(--ran-color-primary, #1890ff));outline-offset:1px}.ran-mermaid-menu{position:absolute;top:var(--ran-mermaid-menu-top, 44px);right:var(--ran-mermaid-toolbar-right, 8px);display:flex;flex-direction:column;min-width:132px;padding:4px;border-radius:var(--ran-radius-md, 8px);background:var(--ran-mermaid-toolbar-background, var(--ran-color-bg-elevated, #fff));box-shadow:var(--ran-mermaid-toolbar-shadow, var(--ran-shadow-menu, 0 2px 8px rgba(0, 0, 0, .12)));z-index:1}.ran-mermaid-menu-item{padding:6px 10px;border:none;border-radius:var(--ran-radius-sm, 6px);background:transparent;color:var(--ran-mermaid-menu-item-color, var(--ran-color-text, #222));text-align:left;font-size:13px;cursor:pointer}.ran-mermaid-menu-item:hover{background:var(--ran-mermaid-button-hover-background, var(--ran-color-bg-muted, rgba(0, 0, 0, .06)))}.ran-mermaid-error{margin:0;padding:12px;color:var(--ran-mermaid-error-color, #c0392b);white-space:pre-wrap;font-size:13px}", U = 0, j = class extends B {
_events = new A();
_shadowDom;
_wrap;
_diagram;
_toolbar;
_themeObserver;
_copyResetTimer;
_fullscreenClose;
_downloadMenu;
static get observedAttributes() {
return [
"code",
"theme",
"copy",
"download",
"fullscreen",
"sheet"
];
}
constructor() {
super(), this._shadowDom = T(this, O), this._wrap = D(this._shadowDom, ".ran-mermaid", () => d().class("ran-mermaid").part("mermaid").children(d().class("ran-mermaid-diagram").part("diagram"), d().class("ran-mermaid-toolbar").part("toolbar")).build()), this._diagram = this._wrap.querySelector(".ran-mermaid-diagram"), this._toolbar = this._wrap.querySelector(".ran-mermaid-toolbar");
}
get code() {
const e = b(this, "code");
return e ? decodeURIComponent(e) : (this.textContent || "").trim();
}
set code(e) {
x(this, "code", encodeURIComponent(e));
}
get theme() {
return b(this, "theme") || "auto";
}
set theme(e) {
x(this, "theme", e);
}
get sheet() {
return b(this, "sheet");
}
set sheet(e) {
x(this, "sheet", e);
}
get copyable() {
return this.hasAttribute("copy");
}
set copyable(e) {
y(this, "copy", e);
}
get downloadable() {
return this.hasAttribute("download");
}
set downloadable(e) {
y(this, "download", e);
}
get fullscreenable() {
return this.hasAttribute("fullscreen");
}
set fullscreenable(e) {
y(this, "fullscreen", e);
}
label(e, t) {
return b(this, `label-${e}`) || t;
}
resolveTheme() {
const e = this.theme;
if (e === "dark") return "dark";
if (e === "light") return "default";
const t = document.documentElement;
return t.classList.contains("dark") || t.getAttribute("data-ran-theme") === "dark" ? "dark" : "default";
}
emit(e, t) {
this.dispatchEvent(new CustomEvent(e, {
detail: t,
bubbles: !0,
composed: !0
}));
}
handlerExternalCss = () => {
R(this, this._shadowDom, "sheet", null, this.sheet);
};
render() {
const e = this.code;
if (!e) return;
const t = `ran-mermaid-${U++}`;
import("./mermaid.core-B2d5lQW2.js").then(async ({ default: a }) => {
a.initialize({
startOnLoad: !1,
securityLevel: "loose",
theme: this.resolveTheme(),
fontFamily: "inherit"
});
try {
const { svg: r } = await a.render(t, e);
this._diagram.innerHTML = r, this.buildToolbar(), this.emit("render", { ok: !0 });
} catch (r) {
document.getElementById(`d${t}`)?.remove(), document.getElementById(t)?.remove(), this.showError(String(r?.message || r));
}
}).catch((a) => this.showError(a.message));
}
showError(e) {
this._diagram.innerHTML = "";
const t = h("pre").class("ran-mermaid-error").part("error").build();
t.textContent = e, this._diagram.appendChild(t), this._toolbar.innerHTML = "", this._wrap.classList.remove("has-controls"), this.emit("error", { message: e });
}
iconButton(e, t, a) {
const r = h("button").class("ran-mermaid-btn").part("button").attr("type", "button").attr("aria-label", t).attr("title", t).children(h("r-icon").attr("name", e).attr("size", "16").attr("color", "currentColor")).build();
return this._events.on(r, "click", a), r;
}
buildToolbar() {
this._toolbar.innerHTML = "";
const e = [];
this.copyable && e.push(this.iconButton("copy", this.label("copy", "Copy source"), this.copySource)), this.downloadable && e.push(this.iconButton("download", this.label("download", "Download"), this.onDownload)), this.fullscreenable && e.push(this.iconButton("fullscreen", this.label("fullscreen", "Fullscreen"), this.openFullscreen)), e.forEach((t) => this._toolbar.appendChild(t)), this._wrap.classList.toggle("has-controls", e.length > 0);
}
copySource = () => {
const e = this.code;
!e || !navigator.clipboard || navigator.clipboard.writeText(e).then(() => {
this.emit("copied", { kind: "source" });
const t = this._toolbar.querySelector('r-icon[name="copy"]');
t && (t.setAttribute("name", "check"), window.clearTimeout(this._copyResetTimer), this._copyResetTimer = window.setTimeout(() => t.setAttribute("name", "copy"), 1200));
});
};
currentSvg() {
return this._diagram.querySelector("svg");
}
downloadFormats() {
const e = b(this, "download").trim();
return (e ? e.split(/\s+/) : [
"svg",
"png",
"source"
]).filter((t) => t === "svg" || t === "png" || t === "source");
}
onDownload = () => {
const e = this.downloadFormats();
e.length <= 1 ? this.triggerDownload(e[0] || "svg") : this.toggleDownloadMenu();
};
triggerDownload(e) {
e === "png" ? this.downloadPng() : e === "source" ? this.downloadSource() : this.downloadRawSvg();
}
saveBlob(e, t) {
const a = URL.createObjectURL(e), r = document.createElement("a");
r.href = a, r.download = t, r.click(), URL.revokeObjectURL(a);
}
downloadRawSvg() {
const e = this.currentSvg();
if (!e) return;
const t = new XMLSerializer().serializeToString(e);
this.saveBlob(new Blob([`<?xml version="1.0" encoding="UTF-8"?>
${t}`], { type: "image/svg+xml;charset=utf-8" }), "diagram.svg"), this.emit("download", { format: "svg" });
}
downloadSource() {
const e = this.code;
e && (this.saveBlob(new Blob([e], { type: "text/plain;charset=utf-8" }), "diagram.mmd"), this.emit("download", { format: "source" }));
}
downloadPng() {
const e = this.currentSvg();
if (!e) return;
const t = e.getBoundingClientRect(), a = Math.max(1, Math.round((t.width || 800) * 2)), r = Math.max(1, Math.round((t.height || 600) * 2)), c = new XMLSerializer().serializeToString(e), n = URL.createObjectURL(new Blob([c], { type: "image/svg+xml;charset=utf-8" })), i = new Image();
i.onload = () => {
try {
const s = document.createElement("canvas");
s.width = a, s.height = r;
const l = s.getContext("2d");
l && (l.fillStyle = this.resolveTheme() === "dark" ? "#1a1a1a" : "#ffffff", l.fillRect(0, 0, a, r), l.drawImage(i, 0, 0, a, r)), s.toBlob((u) => {
URL.revokeObjectURL(n), u ? (this.saveBlob(u, "diagram.png"), this.emit("download", { format: "png" })) : this.emit("error", { message: "PNG export failed" });
}, "image/png");
} catch {
URL.revokeObjectURL(n), this.emit("error", { message: "PNG export failed (diagram may use HTML labels)" });
}
}, i.onerror = () => {
URL.revokeObjectURL(n), this.emit("error", { message: "PNG export failed" });
}, i.src = n;
}
toggleDownloadMenu() {
if (this._downloadMenu) {
this.closeDownloadMenu();
return;
}
const e = d().class("ran-mermaid-menu").part("menu").build(), t = {
svg: this.label("download-svg", "SVG"),
png: this.label("download-png", "PNG"),
source: this.label("download-source", "Source (.mmd)")
};
this.downloadFormats().forEach((a) => {
const r = h("button").class("ran-mermaid-menu-item").attr("type", "button").build();
r.textContent = t[a] || a, this._events.on(r, "click", () => {
this.triggerDownload(a), this.closeDownloadMenu();
}), e.appendChild(r);
}), this._wrap.appendChild(e), this._downloadMenu = e;
}
closeDownloadMenu = () => {
this._downloadMenu?.remove(), this._downloadMenu = void 0;
};
openFullscreen = () => {
const e = this.currentSvg();
e && import("./modal.js").then(() => {
const t = document.createElement("r-modal");
t.setAttribute("title", this.label("diagram", "Diagram")), t.setAttribute("hide-header", ""), t.setAttribute("closable", ""), t.setAttribute("maskClosable", "true"), t.setAttribute("closeOnEsc", "true"), t.style.setProperty("--ran-modal-dialog-width", "min(1280px, 94vw)"), t.style.setProperty("--ran-modal-dialog-max-width", "94vw");
const a = d().build();
a.style.cssText = "position:relative;width:100%;height:82vh;overflow:hidden;";
const r = d().build();
r.style.cssText = "width:100%;height:100%;display:flex;align-items:center;justify-content:center;cursor:grab;touch-action:none;";
const c = d().build();
c.style.cssText = "transform-origin:center center;transition:transform .05s linear;", c.appendChild(e.cloneNode(!0)), r.appendChild(c), a.appendChild(r);
let n = 1, i = 0, s = 0, l = !1, u = 0, _ = 0;
const g = () => {
c.style.transform = `translate(${i}px, ${s}px) scale(${n})`;
}, v = (o) => {
n = Math.min(8, Math.max(0.4, n * o)), g();
}, E = () => {
n = 1, i = 0, s = 0, g();
}, C = new AbortController(), m = C.signal;
r.addEventListener("wheel", (o) => {
o.preventDefault(), v(o.deltaY < 0 ? 1.1 : 1 / 1.1);
}, {
signal: m,
passive: !1
}), r.addEventListener("pointerdown", (o) => {
l = !0, u = o.clientX - i, _ = o.clientY - s, r.style.cursor = "grabbing", r.setPointerCapture?.(o.pointerId);
}, { signal: m }), r.addEventListener("pointermove", (o) => {
l && (i = o.clientX - u, s = o.clientY - _, g());
}, { signal: m });
const k = () => {
l = !1, r.style.cursor = "grab";
};
r.addEventListener("pointerup", k, { signal: m }), r.addEventListener("pointercancel", k, { signal: m });
const p = d().build();
p.style.cssText = "position:absolute;bottom:12px;left:50%;transform:translateX(-50%);display:flex;gap:4px;padding:4px;border-radius:8px;background:var(--ran-color-bg-elevated,#fff);box-shadow:0 2px 8px rgba(0,0,0,.15);";
const f = (o, S, M) => {
const w = h("button").attr("type", "button").attr("aria-label", S).attr("title", S).children(h("r-icon").attr("name", o).attr("size", "18").attr("color", "currentColor")).build();
return w.style.cssText = "display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;border-radius:6px;background:transparent;color:var(--ran-color-text-secondary,#666);cursor:pointer;", w.addEventListener("click", M, { signal: m }), w;
};
p.appendChild(f("zoom-out", this.label("zoom-out", "Zoom out"), () => v(1 / 1.2))), p.appendChild(f("refresh", this.label("reset", "Reset"), E)), p.appendChild(f("zoom-in", this.label("zoom-in", "Zoom in"), () => v(1.2))), a.appendChild(p), t.appendChild(a), document.body.appendChild(t), t.setAttribute("open", ""), this.emit("fullscreenchange", { open: !0 });
const L = () => {
C.abort(), t.remove(), this._fullscreenClose = void 0, this.emit("fullscreenchange", { open: !1 });
};
t.addEventListener("afterclose", L, { once: !0 }), this._fullscreenClose = L;
});
};
connectedCallback() {
this.handlerExternalCss(), this.render(), this._events.on(this._wrap, "mouseleave", this.closeDownloadMenu), this.theme === "auto" && !this._themeObserver && (this._themeObserver = new MutationObserver(() => this.render()), this._themeObserver.observe(document.documentElement, {
attributes: !0,
attributeFilter: ["class", "data-ran-theme"]
}));
}
disconnectedCallback() {
this._events.abort(), this._themeObserver?.disconnect(), this._themeObserver = void 0, window.clearTimeout(this._copyResetTimer), this.closeDownloadMenu(), this._fullscreenClose?.();
}
attributeChangedCallback(e, t, a) {
t !== a && (e === "code" || e === "theme" ? this.render() : e === "copy" || e === "download" || e === "fullscreen" ? this.buildToolbar() : e === "sheet" && this.handlerExternalCss());
}
};
z("r-mermaid", j);
export {
j as t
};