2.9 KB
MonacoEditor.jsx
import * as React from 'react';
import { useEffect, useRef } from 'react';
import { useMonaco } from '../hooks/useMonaco.js';
export function MonacoEditor({
value = '',
onChange,
onSave,
language = 'html',
theme = 'vs-dark',
}) {
const containerRef = useRef(null);
const editorRef = useRef(null);
const isUpdatingRef = useRef(false);
// Use refs for callbacks to avoid stale closures
const onChangeRef = useRef(onChange);
const onSaveRef = useRef(onSave);
const { monaco, loading, error } = useMonaco();
// Keep callback refs up to date
useEffect(() => {
onChangeRef.current = onChange;
}, [onChange]);
useEffect(() => {
onSaveRef.current = onSave;
}, [onSave]);
// Create editor when Monaco is ready
useEffect(() => {
if (!monaco || !containerRef.current || editorRef.current) return;
const editor = monaco.editor.create(containerRef.current, {
value: value,
language: language,
theme: theme,
automaticLayout: true,
fontSize: 14,
lineNumbers: 'on',
minimap: { enabled: false },
wordWrap: 'on',
scrollBeyondLastLine: false,
tabSize: 2,
});
editorRef.current = editor;
// Handle content changes
editor.onDidChangeModelContent(() => {
if (isUpdatingRef.current) return;
const newValue = editor.getValue();
onChangeRef.current?.(newValue);
});
// Add Ctrl+S save command
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
onSaveRef.current?.();
});
return () => {
editor.dispose();
editorRef.current = null;
};
}, [monaco]);
// Update value from external changes (e.g., version restore)
useEffect(() => {
const editor = editorRef.current;
if (!editor) return;
const currentValue = editor.getValue();
if (value !== currentValue) {
isUpdatingRef.current = true;
// Preserve cursor position
const position = editor.getPosition();
editor.setValue(value);
if (position) {
editor.setPosition(position);
}
isUpdatingRef.current = false;
}
}, [value]);
// Update theme when it changes
useEffect(() => {
if (monaco) {
monaco.editor.setTheme(theme);
}
}, [monaco, theme]);
// Update language when it changes
useEffect(() => {
const editor = editorRef.current;
if (!editor || !monaco) return;
const model = editor.getModel();
if (model) {
monaco.editor.setModelLanguage(model, language);
}
}, [monaco, language]);
if (error) {
return (
<div className="flex items-center justify-center h-full bg-base-300 text-error p-4">
Failed to load editor: {error.message}
</div>
);
}
if (loading) {
return (
<div className="flex items-center justify-center h-full bg-base-300">
<span className="loading loading-spinner loading-lg" />
</div>
);
}
return <div ref={containerRef} className="w-full h-full" />;
}