import DOMPurify from "dompurify"; import { useEffect, useRef, useState } from "react"; // `mermaid` pulls in d3 and its layout engine, which is a heavy chunk that // every deck view would otherwise ship even when no slide has a diagram. // Load it lazily and only once, the first time a mermaid slide actually // renders — mirrors the shiki lazy-load pattern in // packages/core/src/client/blocks/library/HighlightedCode.tsx. type MermaidModule = typeof import("mermaid"); let mermaidLoader: Promise | null = null; function loadMermaid(): Promise { if (!mermaidLoader) { mermaidLoader = import("mermaid") .then((mod) => { const mermaid = mod.default; mermaid.initialize({ startOnLoad: false, securityLevel: "strict", theme: "dark", themeVariables: { darkMode: true, background: "transparent", primaryColor: "#1a1a2e", primaryTextColor: "#e0e0e0", primaryBorderColor: "#00E5FF", lineColor: "#00E5FF", secondaryColor: "#16213e", tertiaryColor: "#0f3460", fontFamily: "Poppins, sans-serif", }, flowchart: { curve: "basis" }, }); return mermaid; }) .catch((error) => { mermaidLoader = null; throw error; }); } return mermaidLoader; } let idCounter = 0; interface MermaidRendererProps { definition: string; className?: string; } export function MermaidRenderer({ definition, className, }: MermaidRendererProps) { const containerRef = useRef(null); const [svg, setSvg] = useState(""); const [error, setError] = useState(""); useEffect(() => { if (!definition.trim()) return; let cancelled = false; const id = `mermaid-${++idCounter}`; loadMermaid() .then((mermaid) => mermaid.render(id, definition.trim())) .then(({ svg: renderedSvg }) => { if (cancelled) return; // Mermaid 11.x runs DOMPurify internally with `securityLevel:"strict"`, // but we re-sanitize the SVG before injecting via dangerouslySetInnerHTML // so a future config drift or library regression cannot reintroduce // SVG-borne XSS (foreignObject scripts, javascript: hrefs, etc.). const sanitized = DOMPurify.sanitize(renderedSvg, { USE_PROFILES: { svg: true, svgFilters: true }, ADD_TAGS: ["foreignObject", "text", "tspan", "textPath"], ADD_ATTR: [ "dominant-baseline", "text-anchor", "dy", "font-family", "font-size", ], }); setSvg(sanitized); setError(""); }) .catch((err) => { if (cancelled) return; setError(err?.message || "Invalid mermaid syntax"); setSvg(""); }); return () => { cancelled = true; }; }, [definition]); if (error) { return (
{error}
); } if (!svg) return null; return (
); }