import React from 'react';
import { DrawIoEmbed } from 'react-drawio';
import { cn } from '@/lib/utils';
export interface DiagramEditorHandle {
getXml: () => string;
}
export interface DiagramEditorProps {
xml: string;
readOnly?: boolean;
className?: string;
onChange?: (xml: string) => void;
}
const BLANK_XML = '';
function detectDark(): boolean {
if (typeof document === 'undefined') return false;
const theme = document.documentElement.getAttribute('data-theme');
return theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches);
}
export const DiagramEditor = React.forwardRef(
function DiagramEditor({ xml, readOnly, className, onChange }, ref) {
const latestXmlRef = React.useRef(xml);
const drawioRef = React.useRef>(null);
const hasShownTemplate = React.useRef(false);
const [isDark, setIsDark] = React.useState(detectDark);
const stableXmlRef = React.useRef(xml);
// When the parent switches files (xml prop changes), reset the stable
// reference so the new content renders instead of the first file's content.
const prevXmlRef = React.useRef(xml);
if (prevXmlRef.current !== xml) {
prevXmlRef.current = xml;
stableXmlRef.current = xml;
latestXmlRef.current = xml;
}
const prevIsDark = React.useRef(isDark);
if (prevIsDark.current !== isDark) {
prevIsDark.current = isDark;
stableXmlRef.current = latestXmlRef.current;
}
const containerRef = React.useRef(null);
React.useEffect(() => {
const check = () => setIsDark(detectDark());
check();
const observer = new MutationObserver(check);
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
return () => observer.disconnect();
}, []);
// Focus the iframe once on mount so keyboard shortcuts work without
// clicking the canvas. Intentionally not keyed on isDark — re-focusing
// on every theme toggle would steal keyboard focus from the user.
React.useEffect(() => {
const id = setTimeout(() => {
containerRef.current?.querySelector('.diagrams-iframe')?.focus();
}, 600);
return () => clearTimeout(id);
}, []);
React.useImperativeHandle(ref, () => ({
getXml: () => latestXmlRef.current,
}));
const handleLoad = React.useCallback(() => {
if (!xml && !hasShownTemplate.current) {
hasShownTemplate.current = true;
setTimeout(() => {
drawioRef.current?.template({});
}, 500);
}
}, [xml]);
const handleAutoSave = React.useCallback((data: { xml: string }) => {
latestXmlRef.current = data.xml;
onChange?.(data.xml);
}, [onChange]);
return (
);
},
);