import { type ReactNode, useEffect, useId, useRef, useState } from 'react'; import { styles } from './styles'; export type MermaidPlacement = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; export interface MermaidProps { /** Diagram definition. Falls back to string children when omitted. */ chart?: string; title?: ReactNode; /** * Show zoom/pan/reset controls. Defaults to true when the diagram exceeds * 120px in height (Mintlify behavior). Pass false to hide. */ actions?: boolean; /** Corner for the controls. Defaults to "bottom-right". */ placement?: MermaidPlacement; children?: ReactNode; } function childrenToText(node: ReactNode): string { if (typeof node === 'string' || typeof node === 'number') { return String(node); } if (Array.isArray(node)) { return node.map(childrenToText).join(''); } if (node && typeof node === 'object' && 'props' in (node as object)) { const props = (node as { props?: { children?: ReactNode } }).props; return childrenToText(props?.children); } return ''; } /** Extracts raw diagram source from MDX children (string or element). */ export function mermaidSource(chart: string | undefined, children: ReactNode): string { const raw = typeof chart === 'string' && chart.trim() ? chart : childrenToText(children); return raw.replace(/^\n+|\n+$/g, '').replace(/\\n$/, ''); } function isDarkMode(): boolean { if (typeof document === 'undefined') { return false; } return document.documentElement.classList.contains('dark'); } const PLACEMENT_CLASS: Record = { 'top-left': 'top-2 left-2', 'top-right': 'top-2 right-2', 'bottom-left': 'bottom-2 left-2', 'bottom-right': 'bottom-2 right-2', }; /** * Renders a Mermaid diagram. Server/prerender output is the raw definition in * a
 so search indexing and no-JS still see the content; the client
 * replaces it with SVG via a lazy `mermaid` import (kept out of the SSR bundle).
 */
export function Mermaid({
  chart,
  title,
  actions,
  placement = 'bottom-right',
  children,
}: MermaidProps) {
  const source = mermaidSource(chart, children);
  const containerRef = useRef(null);
  const svgHostRef = useRef(null);
  const diagramId = useId().replace(/[^a-zA-Z0-9]/g, '');
  const [svg, setSvg] = useState(null);
  const [error, setError] = useState(false);
  const [zoom, setZoom] = useState(1);
  const [dark, setDark] = useState(false);

  useEffect(() => {
    setDark(isDarkMode());
    const observer = new MutationObserver(() => setDark(isDarkMode()));
    observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
    return () => observer.disconnect();
  }, []);

  useEffect(() => {
    if (!source.trim() || typeof window === 'undefined') {
      return;
    }

    let cancelled = false;
    setError(false);
    setSvg(null);
    (async () => {
      try {
        const { default: mermaid } = await import('mermaid');
        if (cancelled) {
          return;
        }
        mermaid.initialize({
          startOnLoad: false,
          theme: dark ? 'dark' : 'neutral',
          securityLevel: 'strict',
        });
        const { svg: rendered } = await mermaid.render(`shiso-mermaid-${diagramId}`, source);
        if (!cancelled) {
          setSvg(rendered);
        }
      } catch {
        if (!cancelled) {
          setError(true);
        }
      }
    })();

    return () => {
      cancelled = true;
    };
  }, [source, diagramId, dark]);

  // Pan is native scroll; zoom controls scale the SVG host.
  const showControls = actions ?? true;

  return (
    
{title ?
{title}
: null}
{svg && !error ? (
) : (
            {source}
          
)} {error ?

Could not render this diagram.

: null} {showControls && svg && !error ? (
) : null}
); }