diff --git a/README.md b/README.md
index 7a0e16a..dabd234 100644
--- a/README.md
+++ b/README.md
@@ -51,8 +51,13 @@ const model = monaco.editor.createModel(
- Controlled parent rerenders retain the live editor host, so focus, selection,
history, and completion state survive model-content callbacks. Actual component
unmount remains the single disposal boundary.
-- Wrapper-owned `path` values must be unique; pass an existing Monaco model
- through `model={monaco.editor.getModel(uri)}` when sharing is intentional.
+- Wrapper-owned `path` values (usage without a `model` prop) must be unique.
+ Externally-owned models are exempt from this wrapper check; pass an existing
+ Monaco model through `model={monaco.editor.getModel(uri)}` when sharing is
+ intentional, and retain responsibility for its identity and disposal.
+- Monaco's `setTheme` API is process-global. The `theme` prop configures that
+ shared Monaco namespace, so simultaneous editors cannot display different
+ themes; the most recently applied theme wins.
## Layout
diff --git a/src/components/monaco-editor/monaco-editor.types.ts b/src/components/monaco-editor/monaco-editor.types.ts
index 8ac3d06..ee100ab 100644
--- a/src/components/monaco-editor/monaco-editor.types.ts
+++ b/src/components/monaco-editor/monaco-editor.types.ts
@@ -53,6 +53,7 @@ export type MonacoEditorProps = Omit<
defaultValue?: string;
language?: string;
path?: string | MonacoUri;
+ /** Monaco theme name. Monaco applies themes process-wide, so simultaneous editors cannot use different themes. */
theme?: string;
monaco?: MonacoNamespace;
loadMonaco?: MonacoLoader;
diff --git a/tests/jsdom/components/monaco-editor/render.test.tsx b/tests/jsdom/components/monaco-editor/render.test.tsx
index de0eb38..1ffacc4 100644
--- a/tests/jsdom/components/monaco-editor/render.test.tsx
+++ b/tests/jsdom/components/monaco-editor/render.test.tsx
@@ -155,6 +155,66 @@ describe('MonacoEditor - jsdom', () => {
expect(fake.editors[0].getModel()?.getLanguageId()).toBe('javascript');
});
+ it('should expose Monaco theme changes as namespace-global across instances', async () => {
+ const fake = createFakeMonaco();
+ container = mount(
+
+ );
+ mountExtra(
+
+ );
+ await flushUpdates();
+ await flushUpdates();
+ await flushUpdates();
+
+ expect(fake.editors).toHaveLength(2);
+ expect(fake.themeCalls).toEqual(['vs-dark', 'vs-light']);
+ });
+
+ it('should dispose each wrapper-owned model exactly once during rapid path replacement', async () => {
+ const fake = createFakeMonaco();
+ let setPath!: (path: string) => void;
+ function Harness() {
+ const path = state('file:///src/0.ts');
+ setPath = path.set;
+ return (
+
+ );
+ }
+ container = mount();
+ await flushUpdates();
+ await flushUpdates();
+ for (let index = 1; index <= 8; index += 1) {
+ setPath(`file:///src/${index}.ts`);
+ await flushUpdates();
+ await flushUpdates();
+ }
+
+ expect(fake.createdModels).toHaveLength(9);
+ expect(
+ fake.createdModels.slice(0, -1).every((model) => model.disposeCalls === 1)
+ ).toBe(true);
+ expect(
+ fake.createdModels[fake.createdModels.length - 1]?.disposeCalls
+ ).toBe(0);
+ expect(fake.editors[0].disposed).toBe(false);
+ });
+
it('should keep the editor mounted when its change event updates controlled parent state', async () => {
const fake = createFakeMonaco();
@@ -454,6 +514,19 @@ describe('MonacoEditor - jsdom', () => {
expect(hostRef.current).toBeNull();
expect(editorRef.current).toBeNull();
expect(monacoRef.current).toBeNull();
+
+ const remounted = mountExtra(
+
+ );
+ await flushUpdates();
+ await flushUpdates();
+ await flushUpdates();
+ expect(remounted.querySelector('[data-askr-monaco-editor]')).not.toBeNull();
+ expect(fake.createCalls).toHaveLength(1);
});
it('should load a new Monaco namespace when a provided namespace is removed', async () => {