import React, { useEffect, useRef, memo } from 'react'; import * as monaco from 'monaco-editor'; import {loader} from '@monaco-editor/react'; loader.config({ paths: { vs: '/monaco-editor/min/vs' }, 'vs/nls': { availableLanguages: {}, }, // Specify which languages to load languages: ['javascript', 'json', 'xml','plaintext'] }); const MonacoEditor = memo(({ id, value = '', language = 'javascript', readOnly = false, height = '64', onChange = () => {} }) => { const editorRef = useRef(null); const containerRef = useRef(null); const subscriptionRef = useRef(null); // Ensure value is always a string const normalizedValue = value ?? ''; const getEditorOptions = (lang) => { const baseOptions = { automaticLayout: true, theme: 'vs-light', minimap: { enabled: false }, readOnly, scrollBeyondLastLine: false, fontSize: 14, lineHeight: 21, padding: { top: 8, bottom: 8 }, tabSize: 2, scrollbar: { vertical: 'auto', horizontal: 'auto' } }; switch (lang) { case 'json': return { ...baseOptions, formatOnPaste: true, formatOnType: true, autoClosingBrackets: 'always', autoClosingQuotes: 'always', bracketPairColorization: { enabled: true } }; case 'xml': return { ...baseOptions, formatOnType: true, autoClosingTags: true, autoClosingBrackets: 'always', autoClosingQuotes: 'always' }; default: return baseOptions; } }; useEffect(() => { if (!containerRef.current) return; const options = getEditorOptions(language); const editor = monaco.editor.create(containerRef.current, { ...options, value: normalizedValue, language: language }); editorRef.current = editor; const subscription = editor.onDidChangeModelContent(() => { const newValue = editor.getValue(); onChange(newValue); }); subscriptionRef.current = subscription; if (language === 'json' && normalizedValue) { try { monaco.languages.json.jsonDefaults.setDiagnosticsOptions({ validate: true, allowComments: false, schemas: [] }); const action = editor.getAction('editor.action.formatDocument'); if (action) { setTimeout(() => { action.run(); }, 100); } } catch (e) { console.warn('Failed to format JSON:', e); } } const resizeObserver = new ResizeObserver(() => { editor.layout(); }); resizeObserver.observe(containerRef.current); return () => { resizeObserver.disconnect(); subscription.dispose(); // Ensure proper cleanup if (editor) { try { const model = editor.getModel(); if (model) { model.dispose(); } editor.dispose(); } catch (e) { console.warn('Editor disposal error:', e); } } }; }, [language]); // Only recreate when language changes // Handle value updates useEffect(() => { if (editorRef.current) { const currentValue = editorRef.current.getValue(); if (currentValue !== normalizedValue) { editorRef.current.setValue(normalizedValue); } } }, [normalizedValue]); // Convert height to pixel value if it's a number const containerHeight = typeof height === 'number' ? `${height}px` : height.endsWith('px') ? height : `${height}px`; return (
); }); MonacoEditor.displayName = 'MonacoEditor'; export default MonacoEditor;