@coldsmirk/inkstone-react 0.11.0 → 0.13.0

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.
@@ -1,604 +0,0 @@
1
- import { n as useControlledReconcileSignal, r as CODE_FONT_FAMILY, t as useLatest } from "./use-latest-BhihMdOp.js";
2
- import { flavors } from "@catppuccin/palette";
3
- import { EditorState, Prec } from "@codemirror/state";
4
- import { EditorView, lineNumbers } from "@codemirror/view";
5
- import { ControlledEditorHost, ControlledMergeHost, codeMirrorLanguages, documentExtensions, loadLanguage, normalizeContext, normalizeContext as normalizeContext$1, searchPhrasesZhCn, searchPhrasesZhCn as searchPhrasesZhCn$1, setMinijinjaContext, setSqlSchema } from "@coldsmirk/inkstone-codemirror";
6
- import { useEffect, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
7
- import { catppuccinLatte, catppuccinMacchiato } from "@catppuccin/codemirror";
8
- import { jsx } from "react/jsx-runtime";
9
- import { indentUnit } from "@codemirror/language";
10
- //#region src/codemirror-theme.ts
11
- function toCssSize(value) {
12
- if (value === void 0) return;
13
- return typeof value === "number" ? `${value}px` : value;
14
- }
15
- function alphaOf(color, alphaValue) {
16
- return `rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, ${alphaValue})`;
17
- }
18
- function completionIconMask(body) {
19
- 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>`;
20
- return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
21
- }
22
- const COMPLETION_ICON_FALLBACK = completionIconMask("<circle cx=\"8\" cy=\"8\" r=\"2.5\"/>");
23
- const COMPLETION_ICONS = {
24
- keyword: completionIconMask("<path d=\"M5.5 5 2.5 8l3 3\"/><path d=\"m10.5 5 3 3-3 3\"/>"),
25
- 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\"/>"),
26
- 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\"/>"),
27
- 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\"/>"),
28
- 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\"/>"),
29
- 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\"/>"),
30
- class: completionIconMask("<rect x=\"3.5\" y=\"3.5\" width=\"9\" height=\"9\" rx=\"2.2\"/>"),
31
- interface: completionIconMask("<circle cx=\"8\" cy=\"8\" r=\"4.5\"/>"),
32
- type: completionIconMask("<path d=\"M4 4.5h8\"/><path d=\"M8 4.5V12\"/>"),
33
- 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\"/>"),
34
- 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\"/>"),
35
- text: completionIconMask("<path d=\"M3.5 5h9\"/><path d=\"M3.5 8h9\"/><path d=\"M3.5 11h5.5\"/>")
36
- };
37
- function completionIconRules() {
38
- const rules = {};
39
- for (const [kind, mask] of Object.entries(COMPLETION_ICONS)) rules[`.cm-completionIcon-${kind}:after`] = {
40
- maskImage: mask,
41
- WebkitMaskImage: mask
42
- };
43
- return rules;
44
- }
45
- function chromeTheme(dark) {
46
- const { colors } = dark ? flavors.macchiato : flavors.latte;
47
- const hairline = `1px solid ${alphaOf(colors.overlay0, .35)}`;
48
- const frame = {
49
- border: hairline,
50
- borderRadius: "8px",
51
- boxShadow: dark ? "0 8px 24px rgba(0, 0, 0, 0.5)" : "0 8px 24px rgba(0, 0, 0, 0.12)",
52
- color: colors.text.hex
53
- };
54
- return EditorView.theme({
55
- "&": { colorScheme: dark ? "dark" : "light" },
56
- ".cm-scroller": { fontFamily: CODE_FONT_FAMILY },
57
- ".cm-tooltip": frame,
58
- ".cm-tooltip.cm-tooltip-autocomplete": {
59
- ...frame,
60
- "& > ul": {
61
- borderRadius: "7px",
62
- padding: "4px",
63
- maxHeight: "16em"
64
- },
65
- "& > ul > li": {
66
- display: "flex",
67
- alignItems: "center",
68
- padding: "3px 8px",
69
- borderRadius: "5px",
70
- lineHeight: "1.45"
71
- }
72
- },
73
- ".cm-completionLabel": {
74
- minWidth: "0",
75
- overflow: "hidden",
76
- textOverflow: "ellipsis"
77
- },
78
- ".cm-completionMatchedText": {
79
- textDecoration: "none",
80
- fontWeight: "600",
81
- color: colors.blue.hex
82
- },
83
- ".cm-completionDetail": {
84
- marginLeft: "auto",
85
- paddingLeft: "12px",
86
- fontStyle: "normal",
87
- fontSize: "85%",
88
- color: colors.overlay1.hex
89
- },
90
- ".cm-completionIcon": {
91
- width: "14px",
92
- paddingRight: "8px",
93
- opacity: "1",
94
- color: colors.overlay1.hex
95
- },
96
- ".cm-completionIcon:after": {
97
- content: "''",
98
- display: "block",
99
- width: "12px",
100
- height: "12px",
101
- backgroundColor: "currentColor",
102
- maskImage: COMPLETION_ICON_FALLBACK,
103
- WebkitMaskImage: COMPLETION_ICON_FALLBACK,
104
- maskRepeat: "no-repeat",
105
- WebkitMaskRepeat: "no-repeat",
106
- maskPosition: "center",
107
- WebkitMaskPosition: "center",
108
- maskSize: "contain",
109
- WebkitMaskSize: "contain"
110
- },
111
- ...completionIconRules(),
112
- ".cm-completionIcon-keyword": { color: colors.mauve.hex },
113
- ".cm-completionIcon-function, .cm-completionIcon-method": { color: colors.blue.hex },
114
- ".cm-completionIcon-variable": { color: colors.peach.hex },
115
- ".cm-completionIcon-constant": { color: colors.sky.hex },
116
- ".cm-completionIcon-property": { color: colors.teal.hex },
117
- ".cm-completionIcon-namespace": { color: colors.sapphire.hex },
118
- ".cm-completionIcon-text": { color: colors.overlay1.hex },
119
- ".cm-completionIcon-class, .cm-completionIcon-type, .cm-completionIcon-interface, .cm-completionIcon-enum": { color: colors.yellow.hex },
120
- ".cm-tooltip.cm-completionInfo": {
121
- ...frame,
122
- padding: "8px 10px",
123
- maxWidth: "26em"
124
- },
125
- ".cm-tooltip-lint": {
126
- padding: "4px",
127
- maxWidth: "26em",
128
- overflowWrap: "anywhere"
129
- },
130
- ".cm-diagnostic": {
131
- margin: "0",
132
- padding: "3px 8px",
133
- borderLeftWidth: "3px",
134
- borderRadius: "5px",
135
- lineHeight: "1.45"
136
- },
137
- ".cm-diagnostic-error": { borderLeftColor: colors.red.hex },
138
- ".cm-diagnostic-warning": { borderLeftColor: colors.yellow.hex },
139
- ".cm-diagnostic-info": { borderLeftColor: colors.overlay1.hex },
140
- ".cm-diagnostic-hint": { borderLeftColor: colors.blue.hex },
141
- ".cm-diagnosticAction": {
142
- padding: "2px 6px",
143
- backgroundColor: colors.surface1.hex,
144
- color: colors.text.hex,
145
- borderRadius: "4px"
146
- },
147
- ".cm-diagnosticSource": {
148
- fontSize: "85%",
149
- color: colors.overlay1.hex,
150
- opacity: "1"
151
- },
152
- "&.cm-focused": { outline: "none" },
153
- ".cm-activeLine": { backgroundColor: alphaOf(colors.overlay0, .12) },
154
- ".cm-activeLineGutter": { backgroundColor: alphaOf(colors.overlay0, .12) },
155
- "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": { backgroundColor: alphaOf(colors.blue, dark ? .35 : .25) },
156
- ".cm-selectionBackground": { backgroundColor: alphaOf(colors.blue, dark ? .2 : .14) },
157
- ".cm-content ::selection": { backgroundColor: alphaOf(colors.blue, dark ? .35 : .25) },
158
- ".cm-scroller, .cm-tooltip-autocomplete > ul, .cm-tooltip.cm-completionInfo": {
159
- scrollbarWidth: "thin",
160
- scrollbarColor: `${alphaOf(colors.overlay0, .55)} transparent`
161
- },
162
- ".cm-panels.cm-panels-top": { borderBottom: hairline },
163
- ".cm-panels.cm-panels-bottom": { borderTop: hairline },
164
- ".cm-panel.cm-search, .cm-panel.cm-gotoLine": {
165
- position: "relative",
166
- display: "flex",
167
- flexWrap: "wrap",
168
- alignItems: "center",
169
- columnGap: "8px",
170
- rowGap: "8px",
171
- margin: "0",
172
- padding: "10px 44px 10px 14px"
173
- },
174
- ".cm-panel.cm-search br": {
175
- flexBasis: "100%",
176
- height: "0",
177
- margin: "0",
178
- border: "none"
179
- },
180
- ".cm-panel.cm-search label, .cm-panel.cm-gotoLine label": {
181
- display: "inline-flex",
182
- alignItems: "center",
183
- gap: "6px",
184
- margin: "0",
185
- fontSize: "12.5px",
186
- color: colors.subtext0.hex,
187
- cursor: "pointer",
188
- userSelect: "none"
189
- },
190
- ".cm-panel.cm-search input[type=checkbox]": {
191
- margin: "0",
192
- width: "14px",
193
- height: "14px",
194
- accentColor: colors.blue.hex
195
- },
196
- ".cm-panel.cm-search button[name=close]": {
197
- position: "absolute",
198
- top: "10px",
199
- right: "10px",
200
- boxSizing: "border-box",
201
- width: "28px",
202
- height: "28px",
203
- padding: "0",
204
- display: "inline-flex",
205
- alignItems: "center",
206
- justifyContent: "center",
207
- fontSize: "16px",
208
- lineHeight: "1",
209
- color: colors.overlay1.hex,
210
- background: "transparent",
211
- border: "none",
212
- borderRadius: "6px",
213
- cursor: "pointer"
214
- },
215
- ".cm-panel.cm-search button[name=close]:hover": {
216
- backgroundColor: colors.surface0.hex,
217
- color: colors.text.hex
218
- },
219
- ".cm-panel .cm-textfield": {
220
- boxSizing: "border-box",
221
- height: "28px",
222
- width: "240px",
223
- maxWidth: "100%",
224
- margin: "0",
225
- padding: "0 10px",
226
- fontSize: "12.5px",
227
- color: colors.text.hex,
228
- backgroundColor: colors.base.hex,
229
- border: hairline,
230
- borderRadius: "6px"
231
- },
232
- ".cm-panel .cm-textfield:focus": {
233
- outline: "none",
234
- borderColor: alphaOf(colors.blue, .6),
235
- boxShadow: `0 0 0 2px ${alphaOf(colors.blue, .2)}`
236
- },
237
- ".cm-panel .cm-button": {
238
- boxSizing: "border-box",
239
- height: "28px",
240
- margin: "0",
241
- padding: "0 12px",
242
- fontSize: "12.5px",
243
- color: colors.text.hex,
244
- backgroundImage: "none",
245
- backgroundColor: colors.surface0.hex,
246
- border: hairline,
247
- borderRadius: "6px",
248
- cursor: "pointer"
249
- },
250
- ".cm-panel .cm-button:hover": { backgroundColor: colors.surface1.hex },
251
- ".cm-panel .cm-button:active": {
252
- backgroundImage: "none",
253
- backgroundColor: colors.surface1.hex
254
- }
255
- }, { dark });
256
- }
257
- function defaultThemeExtensions(dark, height, width, fontSize, lineHeight) {
258
- const extensions = [Prec.low(chromeTheme(dark)), Prec.low(dark ? catppuccinMacchiato : catppuccinLatte)];
259
- const cssHeight = toCssSize(height);
260
- const cssWidth = toCssSize(width);
261
- if (cssHeight !== void 0 || cssWidth !== void 0 || fontSize !== void 0 || lineHeight !== void 0) {
262
- const root = {};
263
- if (cssHeight !== void 0) root.height = cssHeight;
264
- if (cssWidth !== void 0) root.width = cssWidth;
265
- if (fontSize !== void 0) root.fontSize = `${fontSize}px`;
266
- const spec = { "&": root };
267
- const scroller = {};
268
- if (cssHeight !== void 0) scroller.overflow = "auto";
269
- if (lineHeight !== void 0) scroller.lineHeight = lineHeight < 8 ? String(lineHeight) : `${lineHeight}px`;
270
- if (Object.keys(scroller).length > 0) spec[".cm-scroller"] = scroller;
271
- extensions.push(EditorView.theme(spec));
272
- }
273
- return extensions;
274
- }
275
- //#endregion
276
- //#region src/codemirror-diff-editor.tsx
277
- function mergeTheme(dark) {
278
- const { colors } = dark ? flavors.macchiato : flavors.latte;
279
- const removed = colors.red;
280
- const added = colors.green;
281
- const underline = (color) => `linear-gradient(${alphaOf(color, .5)}, ${alphaOf(color, .5)}) bottom/100% 2px no-repeat`;
282
- return EditorView.theme({
283
- "&.cm-merge-a .cm-changedLine": { backgroundColor: alphaOf(removed, dark ? .14 : .09) },
284
- "&.cm-merge-b .cm-changedLine": { backgroundColor: alphaOf(added, dark ? .14 : .09) },
285
- "&.cm-merge-a .cm-changedText": { background: underline(removed) },
286
- "&.cm-merge-b .cm-changedText": { background: underline(added) },
287
- "&.cm-merge-a .cm-changedLineGutter": { background: removed.hex },
288
- "&.cm-merge-b .cm-changedLineGutter": { background: added.hex },
289
- ".cm-collapsedLines": {
290
- color: colors.subtext0.hex,
291
- background: `linear-gradient(to bottom, transparent 0, ${colors.surface0.hex} 30%, ${colors.surface0.hex} 70%, transparent 100%)`
292
- }
293
- }, { dark });
294
- }
295
- const DIFF_CHROME_STYLE_SELECTOR = "style[data-inkstone=\"codemirror-diff\"]";
296
- function installDiffChrome(root) {
297
- if (root.querySelector(DIFF_CHROME_STYLE_SELECTOR)) return;
298
- const style = (root.nodeType === 9 ? root : root.host.ownerDocument).createElement("style");
299
- style.dataset.inkstone = "codemirror-diff";
300
- style.textContent = [
301
- "[data-inkstone-diff] > .cm-mergeView { height: 100%; }",
302
- "[data-inkstone-diff] .cm-merge-revert button { color: inherit; border-radius: 4px; }",
303
- "[data-inkstone-diff] .cm-merge-revert button:hover { background-color: color-mix(in srgb, currentColor 15%, transparent); }"
304
- ].join("\n");
305
- if (root.nodeType === 9) root.head.append(style);
306
- else root.append(style);
307
- }
308
- function editorRoot(container) {
309
- const root = container.getRootNode();
310
- if (root.nodeType === 9 || root.nodeType === 11 && "host" in root) return root;
311
- return container.ownerDocument;
312
- }
313
- function eventElement(event) {
314
- const target = event.target;
315
- return target?.nodeType === 1 ? target : target?.parentElement ?? null;
316
- }
317
- function installDiffControlKeyboardSupport(container) {
318
- const enhanceControls = () => {
319
- for (const control of container.querySelectorAll(":scope .cm-collapsedLines")) {
320
- control.setAttribute("role", "button");
321
- control.tabIndex = 0;
322
- }
323
- for (const button of container.querySelectorAll(":scope .cm-merge-revert button")) button.type = "button";
324
- };
325
- const handleClick = (event) => {
326
- if (event.detail !== 0) return;
327
- const button = eventElement(event)?.closest(".cm-merge-revert button");
328
- if (!button || !container.contains(button)) return;
329
- event.preventDefault();
330
- const MouseEventConstructor = container.ownerDocument.defaultView?.MouseEvent ?? MouseEvent;
331
- button.dispatchEvent(new MouseEventConstructor("mousedown", {
332
- bubbles: true,
333
- button: 0,
334
- cancelable: true
335
- }));
336
- };
337
- const handleKeyDown = (event) => {
338
- if (event.key !== "Enter" && event.key !== " ") return;
339
- const control = eventElement(event)?.closest(".cm-collapsedLines");
340
- if (!control || !container.contains(control)) return;
341
- event.preventDefault();
342
- event.stopPropagation();
343
- control.click();
344
- };
345
- enhanceControls();
346
- const observer = new MutationObserver(enhanceControls);
347
- observer.observe(container, {
348
- childList: true,
349
- subtree: true
350
- });
351
- container.addEventListener("click", handleClick);
352
- container.addEventListener("keydown", handleKeyDown, { capture: true });
353
- return () => {
354
- observer.disconnect();
355
- container.removeEventListener("click", handleClick);
356
- container.removeEventListener("keydown", handleKeyDown, { capture: true });
357
- };
358
- }
359
- function CodeMirrorDiffEditor({ original, modified, onChange, language, readOnly = false, revertControls = false, collapseUnchanged = false, lineWrapping = false, showLineNumbers = true, extensions, dynamicExtensions, dark = false, height, width, fontSize = 14, lineHeight = 1.6, className, originalAriaLabel = "Original content", modifiedAriaLabel = "Modified content", onMount }) {
360
- const containerRef = useRef(null);
361
- const hostRef = useRef(null);
362
- const modifiedTransitionRef = useRef(false);
363
- const [reconcileRevision, requestControlledReconcile] = useControlledReconcileSignal();
364
- const onChangeRef = useLatest(onChange);
365
- const onMountRef = useLatest(onMount);
366
- const [loadedLanguage, setLoadedLanguage] = useState(null);
367
- const languageExtension = useMemo(() => loadedLanguage && loadedLanguage.id === language ? [loadedLanguage.extension] : [], [language, loadedLanguage]);
368
- const activeRevertControls = revertControls && !readOnly;
369
- useEffect(() => {
370
- if (!language) return;
371
- let cancelled = false;
372
- loadLanguage(language).then((extension) => {
373
- if (!cancelled) setLoadedLanguage({
374
- id: language,
375
- extension
376
- });
377
- }).catch((error) => {
378
- console.error("inkstone: failed to load CodeMirror language", language, error);
379
- if (!cancelled) setLoadedLanguage(null);
380
- });
381
- return () => {
382
- cancelled = true;
383
- };
384
- }, [language]);
385
- const dynamic = useMemo(() => [
386
- ...defaultThemeExtensions(dark, void 0, void 0, fontSize, lineHeight),
387
- mergeTheme(dark),
388
- ...showLineNumbers ? [lineNumbers()] : [],
389
- ...lineWrapping ? [EditorView.lineWrapping] : [],
390
- ...readOnly ? [EditorState.readOnly.of(true), EditorView.editable.of(false)] : [],
391
- ...languageExtension,
392
- ...dynamicExtensions ?? []
393
- ], [
394
- dark,
395
- fontSize,
396
- lineHeight,
397
- showLineNumbers,
398
- lineWrapping,
399
- readOnly,
400
- languageExtension,
401
- dynamicExtensions
402
- ]);
403
- useEffect(() => {
404
- const container = containerRef.current;
405
- if (!container) return;
406
- const root = editorRoot(container);
407
- installDiffChrome(root);
408
- const host = new ControlledMergeHost({
409
- parent: container,
410
- root,
411
- original,
412
- modified,
413
- onModifiedChange: (next) => {
414
- if (modifiedTransitionRef.current) return;
415
- onChangeRef.current?.(next);
416
- requestControlledReconcile();
417
- },
418
- extensions: [...documentExtensions(), ...extensions ?? []],
419
- originalExtensions: [
420
- EditorState.readOnly.of(true),
421
- EditorView.editable.of(false),
422
- EditorView.contentAttributes.of({ "aria-label": originalAriaLabel })
423
- ],
424
- modifiedExtensions: [EditorView.contentAttributes.of({ "aria-label": modifiedAriaLabel })],
425
- dynamicExtensions: dynamic,
426
- revertControls: activeRevertControls ? "a-to-b" : void 0,
427
- collapseUnchanged: collapseUnchanged ? {} : void 0
428
- });
429
- hostRef.current = host;
430
- const removeDiffControlKeyboardSupport = installDiffControlKeyboardSupport(container);
431
- onMountRef.current?.(host.view);
432
- return () => {
433
- removeDiffControlKeyboardSupport();
434
- host.destroy();
435
- hostRef.current = null;
436
- };
437
- }, []);
438
- useInsertionEffect(() => {
439
- const host = hostRef.current;
440
- modifiedTransitionRef.current = host !== null && host.view.b.state.doc.toString() !== modified;
441
- }, [modified, reconcileRevision]);
442
- useLayoutEffect(() => {
443
- hostRef.current?.setOriginal(original);
444
- }, [original]);
445
- useLayoutEffect(() => {
446
- try {
447
- hostRef.current?.setModified(modified);
448
- } finally {
449
- modifiedTransitionRef.current = false;
450
- }
451
- }, [modified, reconcileRevision]);
452
- useEffect(() => {
453
- hostRef.current?.reconfigure(dynamic);
454
- }, [dynamic]);
455
- useEffect(() => {
456
- hostRef.current?.view.reconfigure({
457
- revertControls: activeRevertControls ? "a-to-b" : void 0,
458
- collapseUnchanged: collapseUnchanged ? {} : void 0
459
- });
460
- }, [activeRevertControls, collapseUnchanged]);
461
- return /* @__PURE__ */ jsx("div", {
462
- ref: containerRef,
463
- className,
464
- "data-inkstone-diff": "",
465
- style: {
466
- height: toCssSize(height),
467
- width: toCssSize(width),
468
- color: (dark ? flavors.macchiato : flavors.latte).colors.overlay1.hex
469
- }
470
- });
471
- }
472
- //#endregion
473
- //#region src/codemirror-editor.tsx
474
- function CodeMirrorEditor({ value, onChange, language, context, sqlSchema, 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, ariaLabel = "Editor content" }) {
475
- const containerRef = useRef(null);
476
- const hostRef = useRef(null);
477
- const valueTransitionRef = useRef(false);
478
- const [reconcileRevision, requestControlledReconcile] = useControlledReconcileSignal();
479
- const onChangeRef = useLatest(onChange);
480
- const onMountRef = useLatest(onMount);
481
- const onFocusRef = useLatest(onFocus);
482
- const onBlurRef = useLatest(onBlur);
483
- const lastContextKeyRef = useRef(null);
484
- const lastSqlSchemaKeyRef = useRef(null);
485
- const [loadedLanguage, setLoadedLanguage] = useState(null);
486
- const languageExtension = useMemo(() => loadedLanguage && loadedLanguage.id === language ? [loadedLanguage.extension] : [], [language, loadedLanguage]);
487
- useEffect(() => {
488
- if (!language) return;
489
- let cancelled = false;
490
- loadLanguage(language).then((extension) => {
491
- if (!cancelled) setLoadedLanguage({
492
- id: language,
493
- extension
494
- });
495
- }).catch((error) => {
496
- console.error("inkstone: failed to load CodeMirror language", language, error);
497
- if (!cancelled) setLoadedLanguage(null);
498
- });
499
- return () => {
500
- cancelled = true;
501
- };
502
- }, [language]);
503
- const dynamic = useMemo(() => [
504
- ...defaultThemeExtensions(dark, height, width, fontSize, lineHeight),
505
- ...tabSize === void 0 ? [] : [EditorState.tabSize.of(tabSize), indentUnit.of(" ".repeat(tabSize))],
506
- ...locale === "zh-cn" ? [searchPhrasesZhCn] : [],
507
- ...languageExtension,
508
- EditorView.contentAttributes.of({ "aria-label": ariaLabel }),
509
- ...dynamicExtensions ?? []
510
- ], [
511
- dark,
512
- height,
513
- width,
514
- fontSize,
515
- lineHeight,
516
- tabSize,
517
- locale,
518
- languageExtension,
519
- ariaLabel,
520
- dynamicExtensions
521
- ]);
522
- const contextSchema = useMemo(() => context ? normalizeContext(context) : null, [context]);
523
- useEffect(() => {
524
- const container = containerRef.current;
525
- if (!container) return;
526
- const host = new ControlledEditorHost({
527
- parent: container,
528
- doc: value,
529
- onChange: (next) => {
530
- if (valueTransitionRef.current) return;
531
- onChangeRef.current?.(next);
532
- requestControlledReconcile();
533
- },
534
- extensions: [
535
- ...documentExtensions({
536
- placeholder,
537
- lineWrapping,
538
- showLineNumbers,
539
- folding,
540
- spellcheck,
541
- search
542
- }),
543
- EditorView.updateListener.of((update) => {
544
- if (update.focusChanged) if (update.view.hasFocus) onFocusRef.current?.();
545
- else onBlurRef.current?.();
546
- }),
547
- ...extensions ?? []
548
- ],
549
- dynamicExtensions: dynamic
550
- });
551
- hostRef.current = host;
552
- onMountRef.current?.(host.view);
553
- return () => {
554
- host.destroy();
555
- hostRef.current = null;
556
- };
557
- }, []);
558
- useInsertionEffect(() => {
559
- const host = hostRef.current;
560
- valueTransitionRef.current = host !== null && host.view.state.doc.toString() !== value;
561
- }, [reconcileRevision, value]);
562
- useLayoutEffect(() => {
563
- try {
564
- hostRef.current?.setValue(value);
565
- } finally {
566
- valueTransitionRef.current = false;
567
- }
568
- }, [reconcileRevision, value]);
569
- useEffect(() => {
570
- hostRef.current?.reconfigure(dynamic);
571
- }, [dynamic]);
572
- useEffect(() => {
573
- const host = hostRef.current;
574
- if (!host) return;
575
- const key = contextSchema ? JSON.stringify(contextSchema) : "";
576
- const last = lastContextKeyRef.current;
577
- if (last?.host === host && last.key === key) return;
578
- lastContextKeyRef.current = {
579
- host,
580
- key
581
- };
582
- if (key === "" && last?.host !== host) return;
583
- host.view.dispatch({ effects: setMinijinjaContext.of(contextSchema) });
584
- }, [contextSchema]);
585
- useEffect(() => {
586
- const host = hostRef.current;
587
- if (!host) return;
588
- const key = sqlSchema ? JSON.stringify(sqlSchema) : "";
589
- const last = lastSqlSchemaKeyRef.current;
590
- if (last?.host === host && last.key === key) return;
591
- lastSqlSchemaKeyRef.current = {
592
- host,
593
- key
594
- };
595
- if (key === "" && last?.host !== host) return;
596
- host.view.dispatch({ effects: setSqlSchema.of(sqlSchema ?? null) });
597
- }, [sqlSchema]);
598
- return /* @__PURE__ */ jsx("div", {
599
- ref: containerRef,
600
- className
601
- });
602
- }
603
- //#endregion
604
- export { CodeMirrorDiffEditor as a, CodeMirrorEditor as i, normalizeContext$1 as n, searchPhrasesZhCn$1 as r, codeMirrorLanguages as t };