export declare const mermaidViewTemplate = "\"use client\";\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { interactiveStyles, resetButton } from \"cherry-styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { Icon } from \"@/components/layout/Icon\";\n\nconst MIN_SCALE = 0.5;\nconst MAX_SCALE = 4;\nconst SCALE_STEP = 0.25;\nconst PAN_STEP = 48;\nconst MIN_DIAGRAM_HEIGHT = 320;\n// Must exceed MIN_DIAGRAM_HEIGHT plus the viewport padding (2 x 16px), so\n// diagrams sitting on the min-height floor don't auto-show the controls.\nconst AUTO_ACTIONS_MIN_HEIGHT = 420;\n\n// Whether the Fullscreen API is available. Read via useSyncExternalStore so it\n// resolves during render (SSR-safe: false on the server) instead of via a\n// synchronous setState inside an effect. The capability never changes, so the\n// subscribe callback is a stable no-op.\nconst subscribeFullscreenCapability = () => () => {};\nconst getFullscreenCapability = () =>\n typeof document !== \"undefined\" && Boolean(document.fullscreenEnabled);\nconst getFullscreenCapabilityServer = () => false;\n\nexport type MermaidPlacement =\n \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\";\n\ninterface MermaidViewProps {\n svg: string;\n width?: number | null;\n placement?: MermaidPlacement;\n actions?: boolean;\n theme?: Theme;\n}\n\nconst Viewport = styled.div<{ $draggable: boolean; $dragging: boolean }>`\n overflow: hidden;\n padding: 16px;\n ${({ $draggable, $dragging }) =>\n $draggable &&\n css`\n cursor: ${$dragging ? \"grabbing\" : \"grab\"};\n user-select: none;\n `}\n`;\n\nconst Stage = styled.div<{\n $scale: number;\n $x: number;\n $y: number;\n $dragging: boolean;\n}>`\n transform: translate(${({ $x }) => $x}px, ${({ $y }) => $y}px)\n scale(${({ $scale }) => $scale});\n transform-origin: center center;\n transition: ${({ $dragging }) =>\n $dragging ? \"none\" : \"transform 0.15s ease-out\"};\n margin: 0 auto;\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n\n & > svg {\n display: block;\n width: 100%;\n height: auto;\n min-height: ${MIN_DIAGRAM_HEIGHT}px;\n margin: 0 auto;\n }\n`;\n\nconst Figure = styled.figure<{ theme: Theme }>`\n position: relative;\n flex-shrink: 0;\n margin: 0;\n padding: 0;\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n background: ${({ theme }) => theme.colors.light};\n overflow: hidden;\n\n &:fullscreen {\n border: none;\n border-radius: 0;\n\n ${Viewport} {\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n ${Stage} {\n width: 100%;\n height: 100%;\n }\n\n ${Stage} > svg {\n height: 100%;\n }\n }\n`;\n\nconst Controls = styled.div<{ theme: Theme; $placement: MermaidPlacement }>`\n position: absolute;\n ${({ $placement }) => {\n const [vertical, horizontal] = $placement.split(\"-\");\n return css`\n ${vertical}: 8px;\n ${horizontal}: 8px;\n `;\n }}\n display: grid;\n grid-template-columns: repeat(3, 24px);\n grid-template-rows: repeat(3, 24px);\n gap: 2px;\n padding: 4px;\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n background: ${({ theme }) => theme.colors.light};\n box-shadow: ${({ theme }) => theme.shadows.sm};\n`;\n\nconst ControlButton = styled.button<{ theme: Theme }>`\n ${resetButton};\n ${interactiveStyles};\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n color: ${({ theme }) => theme.colors.grayDark};\n cursor: pointer;\n\n &:hover:not(:disabled) {\n background: ${({ theme }) => theme.colors.grayLight};\n color: ${({ theme }) => theme.colors.dark};\n }\n\n &:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n`;\n\nfunction MermaidView({\n svg,\n width,\n placement = \"bottom-right\",\n actions,\n ...props\n}: MermaidViewProps) {\n const [scale, setScale] = useState(1);\n const [offset, setOffset] = useState({ x: 0, y: 0 });\n const [autoShow, setAutoShow] = useState(false);\n const [dragging, setDragging] = useState(false);\n const [isFullscreen, setIsFullscreen] = useState(false);\n const canFullscreen = useSyncExternalStore(\n subscribeFullscreenCapability,\n getFullscreenCapability,\n getFullscreenCapabilityServer,\n );\n const viewportRef = useRef(null);\n const figureRef = useRef(null);\n const dragStart = useRef<{\n x: number;\n y: number;\n ox: number;\n oy: number;\n } | null>(null);\n\n useEffect(() => {\n if (actions !== undefined) return;\n const element = viewportRef.current;\n if (!element || typeof ResizeObserver === \"undefined\") return;\n\n const measure = () => {\n setAutoShow(\n element.getBoundingClientRect().height > AUTO_ACTIONS_MIN_HEIGHT,\n );\n };\n measure();\n const observer = new ResizeObserver(measure);\n observer.observe(element);\n return () => observer.disconnect();\n }, [actions]);\n\n useEffect(() => {\n const onChange = () =>\n setIsFullscreen(document.fullscreenElement === figureRef.current);\n document.addEventListener(\"fullscreenchange\", onChange);\n return () => document.removeEventListener(\"fullscreenchange\", onChange);\n }, []);\n\n const showControls = actions ?? autoShow;\n\n const zoom = useCallback((delta: number) => {\n setScale((current) =>\n Math.min(\n MAX_SCALE,\n Math.max(MIN_SCALE, Math.round((current + delta) * 100) / 100),\n ),\n );\n }, []);\n\n const pan = useCallback((x: number, y: number) => {\n setOffset((current) => ({ x: current.x + x, y: current.y + y }));\n }, []);\n\n const reset = useCallback(() => {\n setScale(1);\n setOffset({ x: 0, y: 0 });\n }, []);\n\n const toggleFullscreen = useCallback(() => {\n const element = figureRef.current;\n if (!element) return;\n if (document.fullscreenElement === element) {\n void document.exitFullscreen().catch(() => {});\n } else {\n void element.requestFullscreen().catch(() => {});\n }\n }, []);\n\n const handlePointerDown = (event: React.PointerEvent) => {\n if (!showControls || event.pointerType !== \"mouse\" || event.button !== 0) {\n return;\n }\n event.currentTarget.setPointerCapture(event.pointerId);\n dragStart.current = {\n x: event.clientX,\n y: event.clientY,\n ox: offset.x,\n oy: offset.y,\n };\n setDragging(true);\n };\n\n const handlePointerMove = (event: React.PointerEvent) => {\n const start = dragStart.current;\n if (!start) return;\n setOffset({\n x: start.ox + (event.clientX - start.x),\n y: start.oy + (event.clientY - start.y),\n });\n };\n\n const endDrag = (event: React.PointerEvent) => {\n if (!dragStart.current) return;\n dragStart.current = null;\n setDragging(false);\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId);\n }\n };\n\n const isReset = scale === 1 && offset.x === 0 && offset.y === 0;\n\n return (\n
\n \n \n \n\n {showControls && (\n \n zoom(-SCALE_STEP)}\n disabled={scale <= MIN_SCALE}\n >\n \n \n pan(0, -PAN_STEP)}\n >\n \n \n zoom(SCALE_STEP)}\n disabled={scale >= MAX_SCALE}\n >\n \n \n\n pan(-PAN_STEP, 0)}\n >\n \n \n \n \n \n pan(PAN_STEP, 0)}\n >\n \n \n\n {canFullscreen ? (\n \n \n \n ) : (\n \n )}\n pan(0, PAN_STEP)}\n >\n \n \n \n \n )}\n
\n );\n}\n\nexport { MermaidView };\n";