@vmz/plugin-monaco 0.0.4 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/Monaco.vmz +148 -40
- package/package.json +2 -2
- package/runtime.ts +255 -10
package/components/Monaco.vmz
CHANGED
|
@@ -3,74 +3,182 @@
|
|
|
3
3
|
</template>
|
|
4
4
|
|
|
5
5
|
<style>
|
|
6
|
+
/*
|
|
7
|
+
Isolate Monaco glyph metrics from page CSS.
|
|
8
|
+
Inherited letter-spacing / proportional fonts desync caret + selection overlays.
|
|
9
|
+
*/
|
|
6
10
|
.monaco-host {
|
|
7
11
|
display: block;
|
|
8
12
|
width: 100%;
|
|
9
13
|
height: 100%;
|
|
10
14
|
min-height: 0;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
position: relative;
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
font: 14px/21px Consolas, "Courier New", Menlo, Monaco, monospace !important;
|
|
19
|
+
letter-spacing: 0 !important;
|
|
20
|
+
word-spacing: 0 !important;
|
|
21
|
+
font-variant-ligatures: none !important;
|
|
22
|
+
text-rendering: auto;
|
|
23
|
+
-webkit-font-smoothing: antialiased;
|
|
24
|
+
}
|
|
25
|
+
.monaco-host .monaco-editor {
|
|
26
|
+
width: 100% !important;
|
|
27
|
+
height: 100% !important;
|
|
28
|
+
outline: none;
|
|
29
|
+
font-family: Consolas, "Courier New", Menlo, Monaco, monospace !important;
|
|
30
|
+
font-size: 14px !important;
|
|
31
|
+
line-height: 21px !important;
|
|
32
|
+
letter-spacing: 0 !important;
|
|
33
|
+
}
|
|
34
|
+
.monaco-host .monaco-editor .view-lines,
|
|
35
|
+
.monaco-host .monaco-editor .view-line,
|
|
36
|
+
.monaco-host .monaco-editor .view-line span,
|
|
37
|
+
.monaco-host .monaco-editor .margin-view-overlays,
|
|
38
|
+
.monaco-host .monaco-editor .lines-content {
|
|
39
|
+
font-family: Consolas, "Courier New", Menlo, Monaco, monospace !important;
|
|
40
|
+
font-size: 14px !important;
|
|
41
|
+
line-height: 21px !important;
|
|
42
|
+
letter-spacing: 0 !important;
|
|
43
|
+
word-spacing: 0 !important;
|
|
44
|
+
}
|
|
45
|
+
/*
|
|
46
|
+
Keep the hidden textarea from painting native OS selection
|
|
47
|
+
(shows up as yellow boxes / black bands when metrics desync).
|
|
48
|
+
*/
|
|
49
|
+
.monaco-host .monaco-editor .inputarea,
|
|
50
|
+
.monaco-host .monaco-editor textarea.inputarea,
|
|
51
|
+
.monaco-host .monaco-editor .native-edit-context {
|
|
52
|
+
opacity: 0 !important;
|
|
53
|
+
color: transparent !important;
|
|
54
|
+
background: transparent !important;
|
|
55
|
+
caret-color: transparent !important;
|
|
56
|
+
outline: none !important;
|
|
57
|
+
box-shadow: none !important;
|
|
58
|
+
border: 0 !important;
|
|
59
|
+
-webkit-user-select: text;
|
|
60
|
+
user-select: text;
|
|
61
|
+
}
|
|
62
|
+
.monaco-host .monaco-editor .inputarea::selection,
|
|
63
|
+
.monaco-host .monaco-editor textarea.inputarea::selection,
|
|
64
|
+
.monaco-host .monaco-editor .native-edit-context::selection {
|
|
65
|
+
background: transparent !important;
|
|
66
|
+
color: transparent !important;
|
|
67
|
+
}
|
|
68
|
+
.monaco-host .monaco-editor .inputarea::-moz-selection,
|
|
69
|
+
.monaco-host .monaco-editor textarea.inputarea::-moz-selection,
|
|
70
|
+
.monaco-host .monaco-editor .native-edit-context::-moz-selection {
|
|
71
|
+
background: transparent !important;
|
|
72
|
+
color: transparent !important;
|
|
11
73
|
}
|
|
12
74
|
</style>
|
|
13
75
|
|
|
14
76
|
<script client>
|
|
15
|
-
import { mountMonaco } from '@vmz/plugin-monaco/runtime';
|
|
16
|
-
|
|
17
77
|
export default class Monaco {
|
|
18
|
-
public value: string =
|
|
19
|
-
public language: string =
|
|
20
|
-
public theme: string =
|
|
21
|
-
public readOnly: boolean = false
|
|
22
|
-
/**
|
|
23
|
-
public
|
|
78
|
+
public value: string = ""
|
|
79
|
+
public language: string = "typescript"
|
|
80
|
+
public theme: string = "vs"
|
|
81
|
+
public readOnly: boolean = false
|
|
82
|
+
/** Optional JSON Schema URL (IR — completion + diagnostics). */
|
|
83
|
+
public jsonSchemaUrl: string = ""
|
|
84
|
+
public onChange: ((v: string) => void) | null = null
|
|
24
85
|
|
|
25
86
|
#api: {
|
|
26
|
-
getValue: () => string
|
|
27
|
-
setValue: (v: string) => void
|
|
28
|
-
dispose: () => void
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
#
|
|
87
|
+
getValue: () => string
|
|
88
|
+
setValue: (v: string) => void
|
|
89
|
+
dispose: () => void
|
|
90
|
+
monaco?: { editor?: { setTheme?: (t: string) => void } }
|
|
91
|
+
} | null = null
|
|
92
|
+
#applyingExternal = false
|
|
93
|
+
#valueWatch: (() => void) | null = null
|
|
94
|
+
#mountGen = 0
|
|
95
|
+
#onScheme: ((e: Event) => void) | null = null
|
|
96
|
+
|
|
97
|
+
#resolveTheme() {
|
|
98
|
+
if (typeof document === "undefined") return this.theme || "vs"
|
|
99
|
+
const dark =
|
|
100
|
+
document.documentElement.getAttribute("data-theme") === "dark" ||
|
|
101
|
+
document.documentElement.dataset.theme === "dark"
|
|
102
|
+
return dark ? "vs-dark" : "vs"
|
|
103
|
+
}
|
|
32
104
|
|
|
33
105
|
async onMount() {
|
|
34
|
-
if (typeof window ===
|
|
35
|
-
const
|
|
106
|
+
if (typeof window === "undefined") return
|
|
107
|
+
const gen = ++this.#mountGen
|
|
108
|
+
const root = this.__vmzDomRoot
|
|
36
109
|
const el =
|
|
37
|
-
(root && root.nodeType === 1 && root.matches?.(
|
|
38
|
-
root?.querySelector?.(
|
|
39
|
-
root
|
|
40
|
-
if (!el || el.nodeType !== 1) return
|
|
41
|
-
|
|
110
|
+
(root && root.nodeType === 1 && root.matches?.("[data-vmz-monaco]") && root) ||
|
|
111
|
+
root?.querySelector?.("[data-vmz-monaco]") ||
|
|
112
|
+
root
|
|
113
|
+
if (!el || el.nodeType !== 1) return
|
|
114
|
+
|
|
115
|
+
this.#api?.dispose()
|
|
116
|
+
this.#api = null
|
|
117
|
+
|
|
118
|
+
// Dynamic import — never evaluate monaco on the Node/SSR route path.
|
|
119
|
+
const { mountMonaco } = await import("@vmz/plugin-monaco/runtime")
|
|
120
|
+
if (gen !== this.#mountGen || this.__vmzDestroyed) return
|
|
121
|
+
|
|
122
|
+
const theme = this.#resolveTheme()
|
|
123
|
+
this.theme = theme
|
|
124
|
+
|
|
125
|
+
const api = await mountMonaco(el, {
|
|
42
126
|
value: this.value,
|
|
43
127
|
language: this.language,
|
|
44
|
-
theme
|
|
128
|
+
theme,
|
|
45
129
|
readOnly: this.readOnly,
|
|
130
|
+
jsonSchemaUrl: this.jsonSchemaUrl || undefined,
|
|
46
131
|
onChange: (v) => {
|
|
47
|
-
this.#applyingExternal = true
|
|
48
|
-
this.value = v
|
|
49
|
-
this.#applyingExternal = false
|
|
50
|
-
if (typeof this.onChange ===
|
|
132
|
+
this.#applyingExternal = true
|
|
133
|
+
this.value = v
|
|
134
|
+
this.#applyingExternal = false
|
|
135
|
+
if (typeof this.onChange === "function") this.onChange(v)
|
|
51
136
|
},
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
|
|
137
|
+
})
|
|
138
|
+
if (gen !== this.#mountGen || this.__vmzDestroyed) {
|
|
139
|
+
api.dispose()
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
this.#api = api
|
|
143
|
+
|
|
144
|
+
this.#onScheme = () => {
|
|
145
|
+
const next = this.#resolveTheme()
|
|
146
|
+
this.theme = next
|
|
147
|
+
try {
|
|
148
|
+
this.#api?.monaco?.editor?.setTheme?.(next)
|
|
149
|
+
} catch {
|
|
150
|
+
/* ignore */
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
window.addEventListener("davinci-theme", this.#onScheme)
|
|
154
|
+
window.addEventListener("storage", this.#onScheme)
|
|
155
|
+
|
|
156
|
+
let last = this.value
|
|
55
157
|
const tick = () => {
|
|
56
|
-
if (!this.#api || this.__vmzDestroyed) return
|
|
57
|
-
if (this.#applyingExternal) return
|
|
158
|
+
if (!this.#api || this.__vmzDestroyed) return
|
|
159
|
+
if (this.#applyingExternal) return
|
|
58
160
|
if (this.value !== last) {
|
|
59
|
-
last = this.value
|
|
161
|
+
last = this.value
|
|
60
162
|
if (this.#api.getValue() !== this.value) {
|
|
61
|
-
this.#api.setValue(this.value ??
|
|
163
|
+
this.#api.setValue(this.value ?? "")
|
|
62
164
|
}
|
|
63
165
|
}
|
|
64
|
-
}
|
|
65
|
-
const id = window.setInterval(tick, 50)
|
|
66
|
-
this.#valueWatch = () => window.clearInterval(id)
|
|
166
|
+
}
|
|
167
|
+
const id = window.setInterval(tick, 50)
|
|
168
|
+
this.#valueWatch = () => window.clearInterval(id)
|
|
67
169
|
}
|
|
68
170
|
|
|
69
171
|
onDestroy() {
|
|
70
|
-
this.#
|
|
71
|
-
this.#
|
|
72
|
-
|
|
73
|
-
|
|
172
|
+
this.#mountGen++
|
|
173
|
+
if (this.#onScheme && typeof window !== "undefined") {
|
|
174
|
+
window.removeEventListener("davinci-theme", this.#onScheme)
|
|
175
|
+
window.removeEventListener("storage", this.#onScheme)
|
|
176
|
+
}
|
|
177
|
+
this.#onScheme = null
|
|
178
|
+
this.#valueWatch?.()
|
|
179
|
+
this.#valueWatch = null
|
|
180
|
+
this.#api?.dispose()
|
|
181
|
+
this.#api = null
|
|
74
182
|
}
|
|
75
183
|
}
|
|
76
184
|
</script>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vmz/plugin-monaco",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "VMZ Monaco editor adapter - <Monaco>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@vmz/plugin": "0.
|
|
20
|
+
"@vmz/plugin": "0.1.1"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"monaco-editor": ">=0.52"
|
package/runtime.ts
CHANGED
|
@@ -1,35 +1,280 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Monaco mount helper (browser).
|
|
3
|
-
*
|
|
2
|
+
* Monaco mount helper (browser). Peer: monaco-editor.
|
|
3
|
+
*
|
|
4
|
+
* Dynamic-imports monaco so Node/SSR route collection never evaluates the
|
|
5
|
+
* browser bundle (`window is not defined`).
|
|
6
|
+
*
|
|
7
|
+
* Embedded VMZ hosts desync selection overlays unless we pin fonts, disable
|
|
8
|
+
* EditContext, remasure fonts, and drive layout via ResizeObserver.
|
|
4
9
|
*/
|
|
5
10
|
|
|
11
|
+
export type JsonSchemaRegistration = {
|
|
12
|
+
uri: string;
|
|
13
|
+
fileMatch?: string[];
|
|
14
|
+
schema: object;
|
|
15
|
+
};
|
|
16
|
+
|
|
6
17
|
export type MountMonacoOptions = {
|
|
7
18
|
value?: string;
|
|
8
19
|
language?: string;
|
|
9
20
|
theme?: string;
|
|
10
21
|
readOnly?: boolean;
|
|
11
22
|
onChange?: (v: string) => void;
|
|
23
|
+
jsonSchemaUrl?: string;
|
|
24
|
+
jsonSchemas?: JsonSchemaRegistration[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type MonacoEnv = {
|
|
28
|
+
getWorkerUrl?: (moduleId: string, label: string) => string;
|
|
29
|
+
getWorker?: (moduleId: string, label: string) => Worker;
|
|
12
30
|
};
|
|
13
31
|
|
|
32
|
+
let mountSeq = 0;
|
|
33
|
+
|
|
34
|
+
function ensureMonacoEnvironment(): void {
|
|
35
|
+
const g = globalThis as typeof globalThis & { MonacoEnvironment?: MonacoEnv };
|
|
36
|
+
if (g.MonacoEnvironment?.getWorker || g.MonacoEnvironment?.getWorkerUrl) return;
|
|
37
|
+
g.MonacoEnvironment = {
|
|
38
|
+
getWorkerUrl(_moduleId: string, label: string) {
|
|
39
|
+
if (label === 'json') return '/vendor/monaco-json.worker.js';
|
|
40
|
+
return '/vendor/monaco-editor.worker.js';
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ensureMonacoCss(): void {
|
|
46
|
+
if (typeof document === 'undefined') return;
|
|
47
|
+
if (document.querySelector('link[data-vmz-monaco-css]')) return;
|
|
48
|
+
const link = document.createElement('link');
|
|
49
|
+
link.rel = 'stylesheet';
|
|
50
|
+
link.href = '/vendor/plugin-monaco-runtime.css';
|
|
51
|
+
link.setAttribute('data-vmz-monaco-css', '');
|
|
52
|
+
document.head.appendChild(link);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function loadJsonSchema(url: string): Promise<JsonSchemaRegistration | null> {
|
|
56
|
+
try {
|
|
57
|
+
const res = await fetch(url);
|
|
58
|
+
if (!res.ok) return null;
|
|
59
|
+
const schema = (await res.json()) as object;
|
|
60
|
+
const id = typeof (schema as { $id?: unknown }).$id === 'string' ? String((schema as { $id: string }).$id) : url;
|
|
61
|
+
return {
|
|
62
|
+
uri: id,
|
|
63
|
+
fileMatch: ['*'],
|
|
64
|
+
schema,
|
|
65
|
+
};
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function disposeHostEditor(el: HTMLElement): void {
|
|
72
|
+
const prior = (el as HTMLElement & { __vmzMonaco?: { dispose: () => void } }).__vmzMonaco;
|
|
73
|
+
if (!prior) return;
|
|
74
|
+
try {
|
|
75
|
+
prior.dispose();
|
|
76
|
+
} catch {
|
|
77
|
+
/* ignore */
|
|
78
|
+
}
|
|
79
|
+
delete (el as HTMLElement & { __vmzMonaco?: unknown }).__vmzMonaco;
|
|
80
|
+
el.replaceChildren();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function deadApi() {
|
|
84
|
+
return {
|
|
85
|
+
editor: null as unknown as never,
|
|
86
|
+
monaco: null as unknown as never,
|
|
87
|
+
getValue: () => '',
|
|
88
|
+
setValue: (_v: string) => {},
|
|
89
|
+
dispose: () => {},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
14
93
|
export async function mountMonaco(el: HTMLElement, opts: MountMonacoOptions = {}) {
|
|
15
|
-
|
|
94
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
95
|
+
return deadApi();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const seq = ++mountSeq;
|
|
99
|
+
ensureMonacoEnvironment();
|
|
100
|
+
ensureMonacoCss();
|
|
101
|
+
|
|
102
|
+
// Prefer vendor-injected monaco (single ESM chunk); otherwise dynamic-import.
|
|
103
|
+
const injected = (globalThis as { __VMZ_MONACO__?: unknown }).__VMZ_MONACO__;
|
|
104
|
+
let MonacoNS: unknown = injected;
|
|
105
|
+
if (!MonacoNS) {
|
|
106
|
+
MonacoNS = await import('monaco-editor');
|
|
107
|
+
await import('monaco-editor/language/json/monaco.contribution.js');
|
|
108
|
+
}
|
|
109
|
+
const monaco = (MonacoNS as { languages?: unknown; default?: unknown }).languages
|
|
110
|
+
? MonacoNS
|
|
111
|
+
: ((MonacoNS as { default: typeof MonacoNS }).default ?? MonacoNS);
|
|
112
|
+
|
|
113
|
+
const language = opts.language ?? 'typescript';
|
|
114
|
+
disposeHostEditor(el);
|
|
115
|
+
|
|
116
|
+
if (language === 'json') {
|
|
117
|
+
const jsonDefaults =
|
|
118
|
+
(monaco as { json?: { jsonDefaults?: { setDiagnosticsOptions: (o: unknown) => void } } }).json?.jsonDefaults ??
|
|
119
|
+
(
|
|
120
|
+
monaco as {
|
|
121
|
+
languages?: {
|
|
122
|
+
json?: { jsonDefaults?: { setDiagnosticsOptions: (o: unknown) => void } };
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
).languages?.json?.jsonDefaults;
|
|
126
|
+
if (jsonDefaults) {
|
|
127
|
+
const schemas: JsonSchemaRegistration[] = [...(opts.jsonSchemas ?? [])];
|
|
128
|
+
if (opts.jsonSchemaUrl) {
|
|
129
|
+
const loaded = await loadJsonSchema(opts.jsonSchemaUrl);
|
|
130
|
+
if (loaded) schemas.push(loaded);
|
|
131
|
+
}
|
|
132
|
+
if (seq !== mountSeq) return deadApi();
|
|
133
|
+
if (schemas.length) {
|
|
134
|
+
jsonDefaults.setDiagnosticsOptions({
|
|
135
|
+
validate: true,
|
|
136
|
+
allowComments: true,
|
|
137
|
+
schemas: schemas.map((s) => ({
|
|
138
|
+
uri: s.uri,
|
|
139
|
+
fileMatch: s.fileMatch ?? ['*'],
|
|
140
|
+
schema: s.schema,
|
|
141
|
+
})),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (seq !== mountSeq) return deadApi();
|
|
148
|
+
|
|
149
|
+
const uri = monaco.Uri.parse(`inmemory://vmz-monaco/${seq}/${language}.json`);
|
|
150
|
+
const model = monaco.editor.createModel(opts.value ?? '', language, uri);
|
|
151
|
+
|
|
16
152
|
const editor = monaco.editor.create(el, {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
theme: opts.theme ?? 'vs-dark',
|
|
153
|
+
model,
|
|
154
|
+
theme: opts.theme ?? 'vs',
|
|
20
155
|
readOnly: !!opts.readOnly,
|
|
21
|
-
automaticLayout:
|
|
156
|
+
automaticLayout: false,
|
|
22
157
|
minimap: { enabled: false },
|
|
158
|
+
scrollBeyondLastLine: false,
|
|
159
|
+
tabSize: 2,
|
|
160
|
+
editContext: false,
|
|
161
|
+
fontFamily: 'Consolas, "Courier New", Menlo, Monaco, monospace',
|
|
162
|
+
fontSize: 14,
|
|
163
|
+
lineHeight: 21,
|
|
164
|
+
letterSpacing: 0,
|
|
165
|
+
fontLigatures: false,
|
|
166
|
+
fixedOverflowWidgets: true,
|
|
167
|
+
renderLineHighlight: 'none',
|
|
168
|
+
renderValidationDecorations: 'on',
|
|
169
|
+
occurrencesHighlight: 'off',
|
|
170
|
+
selectionHighlight: false,
|
|
171
|
+
matchBrackets: 'near',
|
|
172
|
+
guides: { indentation: true, highlightActiveIndentation: false },
|
|
173
|
+
padding: { top: 4, bottom: 4 },
|
|
174
|
+
scrollbar: {
|
|
175
|
+
verticalScrollbarSize: 10,
|
|
176
|
+
horizontalScrollbarSize: 10,
|
|
177
|
+
useShadows: false,
|
|
178
|
+
},
|
|
23
179
|
});
|
|
180
|
+
|
|
181
|
+
const layout = () => {
|
|
182
|
+
if (seq !== mountSeq) return;
|
|
183
|
+
try {
|
|
184
|
+
const rect = el.getBoundingClientRect();
|
|
185
|
+
editor.layout({
|
|
186
|
+
width: Math.max(0, Math.floor(rect.width)),
|
|
187
|
+
height: Math.max(0, Math.floor(rect.height)),
|
|
188
|
+
});
|
|
189
|
+
} catch {
|
|
190
|
+
/* ignore */
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
monaco.editor.remeasureFonts();
|
|
196
|
+
} catch {
|
|
197
|
+
/* ignore */
|
|
198
|
+
}
|
|
199
|
+
layout();
|
|
200
|
+
requestAnimationFrame(() => {
|
|
201
|
+
try {
|
|
202
|
+
monaco.editor.remeasureFonts();
|
|
203
|
+
} catch {
|
|
204
|
+
/* ignore */
|
|
205
|
+
}
|
|
206
|
+
layout();
|
|
207
|
+
});
|
|
208
|
+
if (document.fonts?.ready) {
|
|
209
|
+
void document.fonts.ready.then(() => {
|
|
210
|
+
if (seq !== mountSeq) return;
|
|
211
|
+
try {
|
|
212
|
+
monaco.editor.remeasureFonts();
|
|
213
|
+
} catch {
|
|
214
|
+
/* ignore */
|
|
215
|
+
}
|
|
216
|
+
layout();
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let ro: ResizeObserver | null = null;
|
|
221
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
222
|
+
ro = new ResizeObserver(() => layout());
|
|
223
|
+
ro.observe(el);
|
|
224
|
+
}
|
|
225
|
+
|
|
24
226
|
if (typeof opts.onChange === 'function') {
|
|
25
227
|
editor.onDidChangeModelContent(() => {
|
|
26
228
|
opts.onChange?.(editor.getValue());
|
|
27
229
|
});
|
|
28
230
|
}
|
|
29
|
-
|
|
231
|
+
|
|
232
|
+
const api = {
|
|
30
233
|
editor,
|
|
234
|
+
monaco,
|
|
31
235
|
getValue: () => editor.getValue(),
|
|
32
|
-
setValue: (v: string) =>
|
|
33
|
-
|
|
236
|
+
setValue: (v: string) => {
|
|
237
|
+
const next = v ?? '';
|
|
238
|
+
if (editor.getValue() === next) return;
|
|
239
|
+
// Full replace must NOT restore the previous selection — stale ranges
|
|
240
|
+
// paint ghost overlays (yellow bars / black bands) after content swaps.
|
|
241
|
+
editor.pushUndoStop();
|
|
242
|
+
editor.executeEdits(
|
|
243
|
+
'vmz-setValue',
|
|
244
|
+
[
|
|
245
|
+
{
|
|
246
|
+
range: model.getFullModelRange(),
|
|
247
|
+
text: next,
|
|
248
|
+
forceMoveMarkers: true,
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
[new monaco.Selection(1, 1, 1, 1)],
|
|
252
|
+
);
|
|
253
|
+
editor.pushUndoStop();
|
|
254
|
+
editor.revealPositionInCenterIfOutsideViewport({
|
|
255
|
+
lineNumber: 1,
|
|
256
|
+
column: 1,
|
|
257
|
+
});
|
|
258
|
+
},
|
|
259
|
+
dispose: () => {
|
|
260
|
+
try {
|
|
261
|
+
ro?.disconnect();
|
|
262
|
+
} catch {
|
|
263
|
+
/* ignore */
|
|
264
|
+
}
|
|
265
|
+
ro = null;
|
|
266
|
+
try {
|
|
267
|
+
editor.dispose();
|
|
268
|
+
} catch {
|
|
269
|
+
/* ignore */
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
model.dispose();
|
|
273
|
+
} catch {
|
|
274
|
+
/* ignore */
|
|
275
|
+
}
|
|
276
|
+
},
|
|
34
277
|
};
|
|
278
|
+
(el as HTMLElement & { __vmzMonaco?: typeof api }).__vmzMonaco = api;
|
|
279
|
+
return api;
|
|
35
280
|
}
|