UNPKG

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