@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 +65 -0
- package/dist/index.cjs +537 -0
- package/dist/index.d.cts +390 -0
- package/dist/index.d.ts +390 -0
- package/dist/index.js +515 -0
- package/package.json +86 -0
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;
|