import { useCallback, useEffect, useId, useRef, useState } from 'react' import { createPortal } from 'react-dom' let mermaidPromise function loadMermaid() { if (!mermaidPromise) { mermaidPromise = import('mermaid').then((m) => m.default) } return mermaidPromise } function isDarkMode() { return ( typeof document !== 'undefined' && document.documentElement.classList.contains('dark') ) } function CloseIcon() { return ( ) } export function Mermaid({ chart, alt = 'Diagram' }) { let reactId = useId().replace(/[^a-zA-Z0-9]/g, '') let renderCount = useRef(0) let renderRun = useRef(0) let hasRendered = useRef(false) let [svg, setSvg] = useState('') let [error, setError] = useState(null) let [zoomed, setZoomed] = useState(false) let [closing, setClosing] = useState(false) useEffect(() => { let cancelled = false let lastTheme = null async function render() { let dark = isDarkMode() // Skip redundant re-renders when an unrelated class toggles on . if (lastTheme === dark && hasRendered.current) return lastTheme = dark let run = ++renderRun.current try { let mermaid = await loadMermaid() mermaid.initialize({ startOnLoad: false, securityLevel: 'strict', theme: dark ? 'dark' : 'default', fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif", }) let id = `mermaid-${reactId}-${renderCount.current++}` let { svg: out } = await mermaid.render(id, chart) if (!cancelled && run === renderRun.current) { hasRendered.current = true setError(null) setSvg(out) } } catch (e) { if (!cancelled && run === renderRun.current) { setError(e?.message || String(e)) } } } render() // Re-render when the user toggles light/dark so the diagram matches the theme. let observer = new MutationObserver(() => render()) observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'], }) return () => { cancelled = true observer.disconnect() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [chart]) let close = useCallback(() => { setClosing(true) setTimeout(() => { setZoomed(false) setClosing(false) }, 200) }, []) useEffect(() => { if (!zoomed) return let onKey = (e) => { if (e.key === 'Escape') close() } document.addEventListener('keydown', onKey) return () => document.removeEventListener('keydown', onKey) }, [zoomed, close]) if (error) { return (
{`Mermaid render error: ${error}\n\n${chart}`}
)
}
if (!svg) {
return
}
// Mermaid caps the SVG with `max-width: