@coldsmirk/inkstone-react 0.8.2

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/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # @coldsmirk/inkstone-react
2
+
3
+ Drop-in React code editors with the integration chores solved once: a preconfigured Monaco **`<MonacoEditor>`** (offline workers, Shiki catppuccin themes, dark switching, opt-in zh-CN UI) and a controlled CodeMirror 6 **`<CodeMirrorEditor>`** (catppuccin themes, restyled chrome, MiniJinja support, sizing). Drop in a component; the plumbing is already wired.
4
+
5
+ Part of [inkstone](https://github.com/coldsmirk/inkstone) — this package composes [`@coldsmirk/inkstone-core`](https://www.npmjs.com/package/@coldsmirk/inkstone-core) (shared Shiki highlighter), [`@coldsmirk/inkstone-monaco`](https://www.npmjs.com/package/@coldsmirk/inkstone-monaco) (offline Monaco host), and [`@coldsmirk/inkstone-codemirror`](https://www.npmjs.com/package/@coldsmirk/inkstone-codemirror) (controlled CM6 host + MiniJinja). See the [repository README](https://github.com/coldsmirk/inkstone#readme) for the full guide; the highlights below are what you touch daily.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @coldsmirk/inkstone-react monaco-editor react \
11
+ @codemirror/state @codemirror/view @codemirror/language @codemirror/autocomplete @codemirror/commands @codemirror/search
12
+ ```
13
+
14
+ `monaco-editor`, `react`, and the `@codemirror/*` packages are **peer dependencies** — your app owns the single copy of each (`@codemirror/state` breaks at runtime if two copies load). Node.js >= 22 for build / SSR hosts.
15
+
16
+ ## `<MonacoEditor>`
17
+
18
+ ```tsx
19
+ import { MonacoEditor } from "@coldsmirk/inkstone-react";
20
+
21
+ <MonacoEditor
22
+ language="javascript"
23
+ dark={dark}
24
+ value={source}
25
+ onChange={setSource}
26
+ />;
27
+ ```
28
+
29
+ One component brings: the local `monaco-editor` instance (no CDN), bundled offline workers, Shiki catppuccin highlighting registered before first paint, and light/dark switching via `dark`.
30
+
31
+ - **Locale** — chrome speaks stock English; `locale="zh-cn"` switches to Monaco's official Simplified-Chinese catalog. Page-global and locked by the first editor to mount, so give every editor the same value.
32
+ - **Type & metrics** — `fontSize` defaults to 14, `lineHeight` to 1.6 (Monaco's convention: values below 8 multiply the font size); `DEFAULT_MONACO_OPTIONS` is exported for hosts that need the same baseline.
33
+ - **Loading / failure** — the host, the highlighter, and the Monaco chunk each have a loading window; `loading` and `failure` render nodes for them. A host load failure is not cached — a fresh mount retries.
34
+ - **Embedded-friendly** — `find.addExtraSpaceOnTop` is off by default and the find-widget tooltip geometry of [monaco-editor#5177](https://github.com/microsoft/monaco-editor/issues/5177) is worked around. `disableContextMenu` / `disableFind` / `disableCommandPalette` switch off Monaco's browsing chrome per editor.
35
+ - **Escape hatches** — `options` (raw `IStandaloneEditorConstructionOptions`), `hostOptions` (e.g. a custom `getWorker`; first mount wins), `beforeMount` / `onMount` for language-service configuration.
36
+
37
+ Never `import "monaco-editor"` statically anywhere in the app — it locks the UI to English (type-only imports are fine). See "The one Monaco rule" in the repository README.
38
+
39
+ ## `<CodeMirrorEditor>`
40
+
41
+ ```tsx
42
+ import { CodeMirrorEditor } from "@coldsmirk/inkstone-react";
43
+
44
+ <CodeMirrorEditor
45
+ language="json"
46
+ dark={dark}
47
+ height={320}
48
+ value={doc}
49
+ onChange={setDoc}
50
+ />;
51
+ ```
52
+
53
+ A controlled CM6 editor: create-once view, external value changes reconcile without resetting cursor/undo, catppuccin latte/macchiato flipped by `dark`, and restyled chrome (completion tooltip with a currentColor SVG icon set, themed scrollbars, real search-panel metrics).
54
+
55
+ - **`language`** lazily imports the matching grammar as its own chunk. Includes the MiniJinja family — `minijinja` standalone or `minijinja-<host>` mixed variants (`minijinja-yaml`, `-json`, `-html`, `-sql`, …).
56
+ - **`context`** — pass `{ schema }` (JSON Schema) or `{ sample }` (representative value) and MiniJinja completion suggests the render context's variables with member access. Memoize/hoist it — a fresh identity each render re-normalizes.
57
+ - **`search`** (opt-in) adds the standard search keymap and panel; `locale="zh-cn"` localizes it per editor.
58
+ - **Metrics & layout** — `fontSize` 14 and `lineHeight` 1.6 by default; `height` / `width` size the editor; `lineWrapping`, `showLineNumbers`, `folding` are opt-in.
59
+ - **Extensions** — `extensions` is create-once; `dynamicExtensions` reconfigures live. The catppuccin theme sits at low precedence, so a caller theme in `dynamicExtensions` overrides it.
60
+
61
+ `useShikiHighlighter` and the `codeMirrorLanguages` / `normalizeContext` / `searchPhrasesZhCn` helpers are re-exported for hosts that build custom surfaces next to the components.
62
+
63
+ ## License
64
+
65
+ UNLICENSED — proprietary. All rights reserved; no use, copying, or redistribution without the author's permission.
package/dist/index.cjs ADDED
@@ -0,0 +1,537 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _catppuccin_codemirror = require("@catppuccin/codemirror");
3
+ let _catppuccin_palette = require("@catppuccin/palette");
4
+ let _codemirror_language = require("@codemirror/language");
5
+ let _codemirror_state = require("@codemirror/state");
6
+ let _codemirror_view = require("@codemirror/view");
7
+ let _coldsmirk_inkstone_codemirror = require("@coldsmirk/inkstone-codemirror");
8
+ let react = require("react");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ let _coldsmirk_inkstone_core = require("@coldsmirk/inkstone-core");
11
+ let _coldsmirk_inkstone_monaco = require("@coldsmirk/inkstone-monaco");
12
+ let _monaco_editor_react = require("@monaco-editor/react");
13
+ let _shikijs_monaco = require("@shikijs/monaco");
14
+ //#region src/codemirror-editor.tsx
15
+ function toCssSize(value) {
16
+ if (value === void 0) return;
17
+ return typeof value === "number" ? `${value}px` : value;
18
+ }
19
+ function alphaOf(color, alphaValue) {
20
+ return `rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, ${alphaValue})`;
21
+ }
22
+ function completionIconMask(body) {
23
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="#000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${body}</svg>`;
24
+ return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
25
+ }
26
+ const COMPLETION_ICON_FALLBACK = completionIconMask("<circle cx=\"8\" cy=\"8\" r=\"2.5\"/>");
27
+ const COMPLETION_ICONS = {
28
+ keyword: completionIconMask("<path d=\"M5.5 5 2.5 8l3 3\"/><path d=\"m10.5 5 3 3-3 3\"/>"),
29
+ function: completionIconMask("<path d=\"M10.8 3.4c-2-.4-2.9.7-3.2 2.5L6.4 12c-.3 1.7-1.2 2.2-2.6 1.8\"/><path d=\"M4.6 7.5h5\"/>"),
30
+ method: completionIconMask("<path d=\"M10.8 3.4c-2-.4-2.9.7-3.2 2.5L6.4 12c-.3 1.7-1.2 2.2-2.6 1.8\"/><path d=\"M4.6 7.5h5\"/>"),
31
+ variable: completionIconMask("<path d=\"M4 5c2.2 0 5.3 6 8 6\"/><path d=\"M12 5c-2.7 0-5.8 6-8 6\"/>"),
32
+ constant: completionIconMask("<path d=\"M3.5 5.2h9\"/><path d=\"M6.2 5.2v6.3\"/><path d=\"M10.2 5.2v4.4c0 1.3.6 1.9 1.7 1.9\"/>"),
33
+ property: completionIconMask("<circle cx=\"4.9\" cy=\"11.1\" r=\"1.8\"/><circle cx=\"11.1\" cy=\"4.9\" r=\"1.8\"/><path d=\"M6.4 9.6l3.2-3.2\"/>"),
34
+ class: completionIconMask("<rect x=\"3.5\" y=\"3.5\" width=\"9\" height=\"9\" rx=\"2.2\"/>"),
35
+ interface: completionIconMask("<circle cx=\"8\" cy=\"8\" r=\"4.5\"/>"),
36
+ type: completionIconMask("<path d=\"M4 4.5h8\"/><path d=\"M8 4.5V12\"/>"),
37
+ enum: completionIconMask("<path d=\"M6.5 4.5h6\"/><path d=\"M6.5 8h6\"/><path d=\"M6.5 11.5h6\"/><path d=\"M3.4 4.5h.01\"/><path d=\"M3.4 8h.01\"/><path d=\"M3.4 11.5h.01\"/>"),
38
+ namespace: completionIconMask("<path d=\"M6.2 3.5c-1.3 0-1.9.6-1.9 1.9v1.2c0 .9-.4 1.4-1.3 1.4.9 0 1.3.5 1.3 1.4v1.2c0 1.3.6 1.9 1.9 1.9\"/><path d=\"M9.8 3.5c1.3 0 1.9.6 1.9 1.9v1.2c0 .9.4 1.4 1.3 1.4-.9 0-1.3.5-1.3 1.4v1.2c0 1.3-.6 1.9-1.9 1.9\"/>"),
39
+ text: completionIconMask("<path d=\"M3.5 5h9\"/><path d=\"M3.5 8h9\"/><path d=\"M3.5 11h5.5\"/>")
40
+ };
41
+ function completionIconRules() {
42
+ const rules = {};
43
+ for (const [kind, mask] of Object.entries(COMPLETION_ICONS)) rules[`.cm-completionIcon-${kind}:after`] = {
44
+ maskImage: mask,
45
+ WebkitMaskImage: mask
46
+ };
47
+ return rules;
48
+ }
49
+ function chromeTheme(dark) {
50
+ const { colors } = dark ? _catppuccin_palette.flavors.macchiato : _catppuccin_palette.flavors.latte;
51
+ const hairline = `1px solid ${alphaOf(colors.overlay0, .35)}`;
52
+ const frame = {
53
+ border: hairline,
54
+ borderRadius: "8px",
55
+ boxShadow: dark ? "0 8px 24px rgba(0, 0, 0, 0.5)" : "0 8px 24px rgba(0, 0, 0, 0.12)"
56
+ };
57
+ return _codemirror_view.EditorView.theme({
58
+ "&": { colorScheme: dark ? "dark" : "light" },
59
+ ".cm-tooltip.cm-tooltip-autocomplete": {
60
+ ...frame,
61
+ "& > ul": {
62
+ borderRadius: "7px",
63
+ padding: "4px",
64
+ maxHeight: "16em"
65
+ },
66
+ "& > ul > li": {
67
+ display: "flex",
68
+ alignItems: "center",
69
+ padding: "3px 8px",
70
+ borderRadius: "5px",
71
+ lineHeight: "1.45"
72
+ }
73
+ },
74
+ ".cm-completionLabel": {
75
+ minWidth: "0",
76
+ overflow: "hidden",
77
+ textOverflow: "ellipsis"
78
+ },
79
+ ".cm-completionMatchedText": {
80
+ textDecoration: "none",
81
+ fontWeight: "600",
82
+ color: colors.blue.hex
83
+ },
84
+ ".cm-completionDetail": {
85
+ marginLeft: "auto",
86
+ paddingLeft: "12px",
87
+ fontStyle: "normal",
88
+ fontSize: "85%",
89
+ color: colors.overlay1.hex
90
+ },
91
+ ".cm-completionIcon": {
92
+ width: "14px",
93
+ paddingRight: "8px",
94
+ opacity: "1",
95
+ color: colors.overlay1.hex
96
+ },
97
+ ".cm-completionIcon:after": {
98
+ content: "''",
99
+ display: "block",
100
+ width: "12px",
101
+ height: "12px",
102
+ backgroundColor: "currentColor",
103
+ maskImage: COMPLETION_ICON_FALLBACK,
104
+ WebkitMaskImage: COMPLETION_ICON_FALLBACK,
105
+ maskRepeat: "no-repeat",
106
+ WebkitMaskRepeat: "no-repeat",
107
+ maskPosition: "center",
108
+ WebkitMaskPosition: "center",
109
+ maskSize: "contain",
110
+ WebkitMaskSize: "contain"
111
+ },
112
+ ...completionIconRules(),
113
+ ".cm-completionIcon-keyword": { color: colors.mauve.hex },
114
+ ".cm-completionIcon-function, .cm-completionIcon-method": { color: colors.blue.hex },
115
+ ".cm-completionIcon-variable": { color: colors.peach.hex },
116
+ ".cm-completionIcon-constant": { color: colors.sky.hex },
117
+ ".cm-completionIcon-property": { color: colors.teal.hex },
118
+ ".cm-completionIcon-namespace": { color: colors.sapphire.hex },
119
+ ".cm-completionIcon-text": { color: colors.overlay1.hex },
120
+ ".cm-completionIcon-class, .cm-completionIcon-type, .cm-completionIcon-interface, .cm-completionIcon-enum": { color: colors.yellow.hex },
121
+ ".cm-tooltip.cm-completionInfo": {
122
+ ...frame,
123
+ padding: "8px 10px",
124
+ maxWidth: "26em"
125
+ },
126
+ "&.cm-focused": { outline: "none" },
127
+ ".cm-activeLine": { backgroundColor: alphaOf(colors.overlay0, .12) },
128
+ ".cm-activeLineGutter": { backgroundColor: alphaOf(colors.overlay0, .12) },
129
+ "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": { backgroundColor: alphaOf(colors.blue, dark ? .35 : .25) },
130
+ ".cm-selectionBackground": { backgroundColor: alphaOf(colors.blue, dark ? .2 : .14) },
131
+ ".cm-content ::selection": { backgroundColor: alphaOf(colors.blue, dark ? .35 : .25) },
132
+ ".cm-scroller, .cm-tooltip-autocomplete > ul, .cm-tooltip.cm-completionInfo": {
133
+ scrollbarWidth: "thin",
134
+ scrollbarColor: `${alphaOf(colors.overlay0, .55)} transparent`
135
+ },
136
+ ".cm-panels.cm-panels-top": { borderBottom: hairline },
137
+ ".cm-panels.cm-panels-bottom": { borderTop: hairline },
138
+ ".cm-panel.cm-search, .cm-panel.cm-gotoLine": {
139
+ position: "relative",
140
+ display: "flex",
141
+ flexWrap: "wrap",
142
+ alignItems: "center",
143
+ columnGap: "8px",
144
+ rowGap: "8px",
145
+ margin: "0",
146
+ padding: "10px 44px 10px 14px"
147
+ },
148
+ ".cm-panel.cm-search br": {
149
+ flexBasis: "100%",
150
+ height: "0",
151
+ margin: "0",
152
+ border: "none"
153
+ },
154
+ ".cm-panel.cm-search label, .cm-panel.cm-gotoLine label": {
155
+ display: "inline-flex",
156
+ alignItems: "center",
157
+ gap: "6px",
158
+ margin: "0",
159
+ fontSize: "12.5px",
160
+ color: colors.subtext0.hex,
161
+ cursor: "pointer",
162
+ userSelect: "none"
163
+ },
164
+ ".cm-panel.cm-search input[type=checkbox]": {
165
+ margin: "0",
166
+ width: "14px",
167
+ height: "14px",
168
+ accentColor: colors.blue.hex
169
+ },
170
+ ".cm-panel.cm-search button[name=close]": {
171
+ position: "absolute",
172
+ top: "10px",
173
+ right: "10px",
174
+ boxSizing: "border-box",
175
+ width: "28px",
176
+ height: "28px",
177
+ padding: "0",
178
+ display: "inline-flex",
179
+ alignItems: "center",
180
+ justifyContent: "center",
181
+ fontSize: "16px",
182
+ lineHeight: "1",
183
+ color: colors.overlay1.hex,
184
+ background: "transparent",
185
+ border: "none",
186
+ borderRadius: "6px",
187
+ cursor: "pointer"
188
+ },
189
+ ".cm-panel.cm-search button[name=close]:hover": {
190
+ backgroundColor: colors.surface0.hex,
191
+ color: colors.text.hex
192
+ },
193
+ ".cm-panel .cm-textfield": {
194
+ boxSizing: "border-box",
195
+ height: "28px",
196
+ width: "240px",
197
+ maxWidth: "100%",
198
+ margin: "0",
199
+ padding: "0 10px",
200
+ fontSize: "12.5px",
201
+ color: colors.text.hex,
202
+ backgroundColor: colors.base.hex,
203
+ border: hairline,
204
+ borderRadius: "6px"
205
+ },
206
+ ".cm-panel .cm-textfield:focus": {
207
+ outline: "none",
208
+ borderColor: alphaOf(colors.blue, .6),
209
+ boxShadow: `0 0 0 2px ${alphaOf(colors.blue, .2)}`
210
+ },
211
+ ".cm-panel .cm-button": {
212
+ boxSizing: "border-box",
213
+ height: "28px",
214
+ margin: "0",
215
+ padding: "0 12px",
216
+ fontSize: "12.5px",
217
+ color: colors.text.hex,
218
+ backgroundImage: "none",
219
+ backgroundColor: colors.surface0.hex,
220
+ border: hairline,
221
+ borderRadius: "6px",
222
+ cursor: "pointer"
223
+ },
224
+ ".cm-panel .cm-button:hover": { backgroundColor: colors.surface1.hex },
225
+ ".cm-panel .cm-button:active": {
226
+ backgroundImage: "none",
227
+ backgroundColor: colors.surface1.hex
228
+ }
229
+ }, { dark });
230
+ }
231
+ function defaultThemeExtensions(dark, height, width, fontSize, lineHeight) {
232
+ const extensions = [_codemirror_state.Prec.low(chromeTheme(dark)), _codemirror_state.Prec.low(dark ? _catppuccin_codemirror.catppuccinMacchiato : _catppuccin_codemirror.catppuccinLatte)];
233
+ const cssHeight = toCssSize(height);
234
+ const cssWidth = toCssSize(width);
235
+ if (cssHeight !== void 0 || cssWidth !== void 0 || fontSize !== void 0 || lineHeight !== void 0) {
236
+ const root = {};
237
+ if (cssHeight !== void 0) root.height = cssHeight;
238
+ if (cssWidth !== void 0) root.width = cssWidth;
239
+ if (fontSize !== void 0) root.fontSize = `${fontSize}px`;
240
+ const spec = { "&": root };
241
+ const scroller = {};
242
+ if (cssHeight !== void 0) scroller.overflow = "auto";
243
+ if (lineHeight !== void 0) scroller.lineHeight = lineHeight < 8 ? String(lineHeight) : `${lineHeight}px`;
244
+ if (Object.keys(scroller).length > 0) spec[".cm-scroller"] = scroller;
245
+ extensions.push(_codemirror_view.EditorView.theme(spec));
246
+ }
247
+ return extensions;
248
+ }
249
+ function CodeMirrorEditor({ value, onChange, language, context, placeholder, lineWrapping = false, showLineNumbers = false, folding = false, spellcheck = false, search = false, extensions, dynamicExtensions, dark = false, locale = "en", height, width, fontSize = 14, lineHeight = 1.6, tabSize, className, onMount, onFocus, onBlur }) {
250
+ const containerRef = (0, react.useRef)(null);
251
+ const hostRef = (0, react.useRef)(null);
252
+ const onChangeRef = (0, react.useRef)(onChange);
253
+ onChangeRef.current = onChange;
254
+ const onMountRef = (0, react.useRef)(onMount);
255
+ onMountRef.current = onMount;
256
+ const onFocusRef = (0, react.useRef)(onFocus);
257
+ onFocusRef.current = onFocus;
258
+ const onBlurRef = (0, react.useRef)(onBlur);
259
+ onBlurRef.current = onBlur;
260
+ const lastContextRef = (0, react.useRef)(null);
261
+ const [languageExtension, setLanguageExtension] = (0, react.useState)([]);
262
+ (0, react.useEffect)(() => {
263
+ if (!language) {
264
+ setLanguageExtension([]);
265
+ return;
266
+ }
267
+ let cancelled = false;
268
+ (0, _coldsmirk_inkstone_codemirror.loadLanguage)(language).then((extension) => {
269
+ if (!cancelled) setLanguageExtension([extension]);
270
+ }).catch((error) => {
271
+ console.error("inkstone: failed to load CodeMirror language", language, error);
272
+ });
273
+ return () => {
274
+ cancelled = true;
275
+ };
276
+ }, [language]);
277
+ const dynamic = (0, react.useMemo)(() => [
278
+ ...defaultThemeExtensions(dark, height, width, fontSize, lineHeight),
279
+ ...tabSize === void 0 ? [] : [_codemirror_state.EditorState.tabSize.of(tabSize), _codemirror_language.indentUnit.of(" ".repeat(tabSize))],
280
+ ...locale === "zh-cn" ? [_coldsmirk_inkstone_codemirror.searchPhrasesZhCn] : [],
281
+ ...languageExtension,
282
+ ...dynamicExtensions ?? []
283
+ ], [
284
+ dark,
285
+ height,
286
+ width,
287
+ fontSize,
288
+ lineHeight,
289
+ tabSize,
290
+ locale,
291
+ languageExtension,
292
+ dynamicExtensions
293
+ ]);
294
+ const contextSchema = (0, react.useMemo)(() => context ? (0, _coldsmirk_inkstone_codemirror.normalizeContext)(context) : null, [context]);
295
+ (0, react.useEffect)(() => {
296
+ const container = containerRef.current;
297
+ if (!container) return;
298
+ const host = new _coldsmirk_inkstone_codemirror.ControlledEditorHost({
299
+ parent: container,
300
+ doc: value,
301
+ onChange: (next) => onChangeRef.current?.(next),
302
+ extensions: [
303
+ ...(0, _coldsmirk_inkstone_codemirror.documentExtensions)({
304
+ placeholder,
305
+ lineWrapping,
306
+ showLineNumbers,
307
+ folding,
308
+ spellcheck,
309
+ search
310
+ }),
311
+ _codemirror_view.EditorView.updateListener.of((update) => {
312
+ if (update.focusChanged) if (update.view.hasFocus) onFocusRef.current?.();
313
+ else onBlurRef.current?.();
314
+ }),
315
+ ...extensions ?? []
316
+ ],
317
+ dynamicExtensions: dynamic
318
+ });
319
+ hostRef.current = host;
320
+ onMountRef.current?.(host.view);
321
+ return () => {
322
+ host.destroy();
323
+ hostRef.current = null;
324
+ };
325
+ }, []);
326
+ (0, react.useEffect)(() => {
327
+ hostRef.current?.setValue(value);
328
+ }, [value]);
329
+ (0, react.useEffect)(() => {
330
+ hostRef.current?.reconfigure(dynamic);
331
+ }, [dynamic]);
332
+ (0, react.useEffect)(() => {
333
+ const host = hostRef.current;
334
+ if (!host) return;
335
+ const key = contextSchema ? JSON.stringify(contextSchema) : "";
336
+ const last = lastContextRef.current;
337
+ if (last && last.key === key && last.language === languageExtension) return;
338
+ lastContextRef.current = {
339
+ key,
340
+ language: languageExtension
341
+ };
342
+ if (key === "" && (!last || last.key === "")) return;
343
+ host.view.dispatch({ effects: _coldsmirk_inkstone_codemirror.setMinijinjaContext.of(contextSchema) });
344
+ }, [contextSchema, languageExtension]);
345
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
346
+ ref: containerRef,
347
+ className
348
+ });
349
+ }
350
+ //#endregion
351
+ //#region src/use-shiki-highlighter.ts
352
+ function useShikiHighlighter() {
353
+ const [state, setState] = (0, react.useState)("loading");
354
+ const highlighterRef = (0, react.useRef)(null);
355
+ (0, react.useEffect)(() => {
356
+ let cancelled = false;
357
+ (0, _coldsmirk_inkstone_core.getHighlighter)().then((highlighter) => {
358
+ if (cancelled) return;
359
+ highlighterRef.current = highlighter;
360
+ setState("ready");
361
+ }).catch((error) => {
362
+ console.error("inkstone: shiki highlighter failed to load", error);
363
+ if (!cancelled) setState("failed");
364
+ });
365
+ return () => {
366
+ cancelled = true;
367
+ };
368
+ }, []);
369
+ return {
370
+ state,
371
+ highlighterRef
372
+ };
373
+ }
374
+ //#endregion
375
+ //#region src/monaco-editor.tsx
376
+ const DEFAULT_MONACO_OPTIONS = {
377
+ find: { addExtraSpaceOnTop: false },
378
+ minimap: { enabled: false },
379
+ fontSize: 14,
380
+ lineHeight: 1.6,
381
+ fontFamily: "var(--inkstone-font-family-code, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace)",
382
+ scrollBeyondLastLine: false,
383
+ automaticLayout: true,
384
+ renderLineHighlight: "line",
385
+ smoothScrolling: true,
386
+ padding: {
387
+ top: 12,
388
+ bottom: 12
389
+ },
390
+ fixedOverflowWidgets: true,
391
+ quickSuggestions: {
392
+ other: true,
393
+ comments: false,
394
+ strings: true
395
+ },
396
+ wordBasedSuggestions: "off",
397
+ tabCompletion: "on",
398
+ scrollbar: {
399
+ alwaysConsumeMouseWheel: false,
400
+ verticalScrollbarSize: 8,
401
+ horizontalScrollbarSize: 8
402
+ }
403
+ };
404
+ function documentOptions({ placeholder, lineWrapping, showLineNumbers, folding, disableContextMenu, fontSize, lineHeight, tabSize }) {
405
+ const resolved = {
406
+ placeholder,
407
+ wordWrap: lineWrapping ? "on" : "off",
408
+ lineNumbers: showLineNumbers ? "on" : "off",
409
+ folding,
410
+ contextmenu: !disableContextMenu
411
+ };
412
+ if (fontSize !== void 0) resolved.fontSize = fontSize;
413
+ if (lineHeight !== void 0) resolved.lineHeight = lineHeight;
414
+ if (tabSize !== void 0) resolved.tabSize = tabSize;
415
+ return resolved;
416
+ }
417
+ let loaderConfigured = false;
418
+ function configureLoader(monaco) {
419
+ if (loaderConfigured) return;
420
+ loaderConfigured = true;
421
+ _monaco_editor_react.loader.config({ monaco });
422
+ }
423
+ const DISABLED_KEYBINDINGS_CONTEXT_KEY = "inkstoneKeybindingsDisabled";
424
+ function disabledKeybindings(monaco, disableFind, disableCommandPalette) {
425
+ const { KeyCode, KeyMod } = monaco;
426
+ return [...disableFind ? [
427
+ KeyMod.CtrlCmd | KeyCode.KeyF,
428
+ KeyMod.CtrlCmd | KeyCode.KeyH,
429
+ KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyF
430
+ ] : [], ...disableCommandPalette ? [KeyCode.F1] : []];
431
+ }
432
+ let shikiRegistered = false;
433
+ function registerShikiHighlighting(monaco, highlighter) {
434
+ if (shikiRegistered) return;
435
+ shikiRegistered = true;
436
+ const canonical = new Set(highlighter.getLoadedLanguages().map((id) => highlighter.getLanguage(id).name));
437
+ const registered = new Set(monaco.languages.getLanguages().map((registeredLanguage) => registeredLanguage.id));
438
+ for (const lang of canonical) if (!registered.has(lang)) monaco.languages.register({ id: lang });
439
+ (0, _shikijs_monaco.shikiToMonaco)(highlighter, monaco);
440
+ }
441
+ function MonacoEditor({ value, onChange, language = "javascript", dark = false, locale, placeholder, lineWrapping = false, showLineNumbers = false, folding = false, disableContextMenu = false, disableFind = false, disableCommandPalette = false, fontSize, lineHeight, tabSize = 2, path, height = "100%", width, className, options, hostOptions, loading = "Loading editor…", failure = "Editor failed to load", beforeMount, onMount, onFocus, onBlur }) {
442
+ const [hostState, setHostState] = (0, react.useState)("loading");
443
+ const { state: shikiState, highlighterRef } = useShikiHighlighter();
444
+ const hostOptionsRef = (0, react.useRef)(hostOptions);
445
+ const localeRef = (0, react.useRef)(locale);
446
+ const beforeMountRef = (0, react.useRef)(beforeMount);
447
+ beforeMountRef.current = beforeMount;
448
+ const onMountRef = (0, react.useRef)(onMount);
449
+ onMountRef.current = onMount;
450
+ const onFocusRef = (0, react.useRef)(onFocus);
451
+ onFocusRef.current = onFocus;
452
+ const onBlurRef = (0, react.useRef)(onBlur);
453
+ onBlurRef.current = onBlur;
454
+ (0, react.useEffect)(() => {
455
+ let cancelled = false;
456
+ (0, _coldsmirk_inkstone_monaco.ensureMonacoHost)({
457
+ ...hostOptionsRef.current,
458
+ locale: localeRef.current ?? hostOptionsRef.current?.locale
459
+ }).then((monaco) => {
460
+ configureLoader(monaco);
461
+ if (!cancelled) setHostState("ready");
462
+ }).catch((error) => {
463
+ console.error("inkstone: monaco host failed to load", error);
464
+ if (!cancelled) setHostState("failed");
465
+ });
466
+ return () => {
467
+ cancelled = true;
468
+ };
469
+ }, []);
470
+ if (hostState === "failed") return failure;
471
+ if (hostState === "loading" || shikiState === "loading") return loading;
472
+ const handleBeforeMount = (monaco) => {
473
+ const highlighter = highlighterRef.current;
474
+ if (highlighter) registerShikiHighlighting(monaco, highlighter);
475
+ beforeMountRef.current?.(monaco);
476
+ };
477
+ const handleMount = (editorInstance, monaco) => {
478
+ const swallowed = disabledKeybindings(monaco, disableFind, disableCommandPalette);
479
+ if (swallowed.length > 0) {
480
+ editorInstance.createContextKey(DISABLED_KEYBINDINGS_CONTEXT_KEY, true);
481
+ for (const keybinding of swallowed) editorInstance.addCommand(keybinding, () => void 0, DISABLED_KEYBINDINGS_CONTEXT_KEY);
482
+ }
483
+ editorInstance.onDidFocusEditorText(() => onFocusRef.current?.());
484
+ editorInstance.onDidBlurEditorText(() => onBlurRef.current?.());
485
+ onMountRef.current?.(editorInstance, monaco);
486
+ };
487
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_monaco_editor_react.Editor, {
488
+ beforeMount: handleBeforeMount,
489
+ className,
490
+ height,
491
+ language,
492
+ loading,
493
+ path,
494
+ theme: shikiState === "ready" ? (0, _coldsmirk_inkstone_core.shikiThemeId)(dark) : dark ? "vs-dark" : "vs",
495
+ value,
496
+ width,
497
+ options: {
498
+ ...DEFAULT_MONACO_OPTIONS,
499
+ ...documentOptions({
500
+ placeholder,
501
+ lineWrapping,
502
+ showLineNumbers,
503
+ folding,
504
+ disableContextMenu,
505
+ fontSize,
506
+ lineHeight,
507
+ tabSize
508
+ }),
509
+ ...options
510
+ },
511
+ onChange: (next) => onChange?.(next ?? ""),
512
+ onMount: handleMount
513
+ });
514
+ }
515
+ //#endregion
516
+ exports.CodeMirrorEditor = CodeMirrorEditor;
517
+ exports.DEFAULT_MONACO_OPTIONS = DEFAULT_MONACO_OPTIONS;
518
+ exports.MonacoEditor = MonacoEditor;
519
+ Object.defineProperty(exports, "codeMirrorLanguages", {
520
+ enumerable: true,
521
+ get: function() {
522
+ return _coldsmirk_inkstone_codemirror.codeMirrorLanguages;
523
+ }
524
+ });
525
+ Object.defineProperty(exports, "normalizeContext", {
526
+ enumerable: true,
527
+ get: function() {
528
+ return _coldsmirk_inkstone_codemirror.normalizeContext;
529
+ }
530
+ });
531
+ Object.defineProperty(exports, "searchPhrasesZhCn", {
532
+ enumerable: true,
533
+ get: function() {
534
+ return _coldsmirk_inkstone_codemirror.searchPhrasesZhCn;
535
+ }
536
+ });
537
+ exports.useShikiHighlighter = useShikiHighlighter;