@keboola/design 9.1.0 → 10.1.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.
Files changed (84) hide show
  1. package/AGENTS.md +117 -40
  2. package/COMPONENT-API.md +31 -9
  3. package/README.md +3 -3
  4. package/dist/MermaidDiagram-EOKJHQQV.js +10 -0
  5. package/dist/MermaidDiagram-EOKJHQQV.js.map +1 -0
  6. package/dist/charts.d.ts +31 -2
  7. package/dist/charts.js +6 -9
  8. package/dist/charts.js.map +1 -1
  9. package/dist/chunk-63NSN2VL.js +274 -0
  10. package/dist/chunk-63NSN2VL.js.map +1 -0
  11. package/dist/{rjsf.cjs → chunk-6FPEI4EM.js} +1491 -5701
  12. package/dist/chunk-6FPEI4EM.js.map +1 -0
  13. package/dist/chunk-7PUUHEJQ.js +1313 -0
  14. package/dist/chunk-7PUUHEJQ.js.map +1 -0
  15. package/dist/chunk-BOHNXTMM.js +606 -0
  16. package/dist/chunk-BOHNXTMM.js.map +1 -0
  17. package/dist/{testing.cjs → chunk-C3L3TBBT.js} +33 -164
  18. package/dist/chunk-C3L3TBBT.js.map +1 -0
  19. package/dist/chunk-CTCFYWJC.js +407 -0
  20. package/dist/chunk-CTCFYWJC.js.map +1 -0
  21. package/dist/chunk-ESBL3IPF.js +23 -0
  22. package/dist/chunk-ESBL3IPF.js.map +1 -0
  23. package/dist/chunk-IJTHFNNJ.js +261 -0
  24. package/dist/chunk-IJTHFNNJ.js.map +1 -0
  25. package/dist/chunk-JP7PNHLH.js +40 -0
  26. package/dist/chunk-JP7PNHLH.js.map +1 -0
  27. package/dist/chunk-KPNAW5MU.js +229 -0
  28. package/dist/chunk-KPNAW5MU.js.map +1 -0
  29. package/dist/chunk-L37JJ6MR.js +152 -0
  30. package/dist/chunk-L37JJ6MR.js.map +1 -0
  31. package/dist/chunk-Q7NWXLCT.js +90 -0
  32. package/dist/chunk-Q7NWXLCT.js.map +1 -0
  33. package/dist/chunk-SYIEF644.js +1016 -0
  34. package/dist/chunk-SYIEF644.js.map +1 -0
  35. package/dist/chunk-VMFSRV5H.js +16 -0
  36. package/dist/chunk-VMFSRV5H.js.map +1 -0
  37. package/dist/chunk-VPPM7UUQ.js +47 -0
  38. package/dist/chunk-VPPM7UUQ.js.map +1 -0
  39. package/dist/chunk-XV3UUOYF.js +845 -0
  40. package/dist/chunk-XV3UUOYF.js.map +1 -0
  41. package/dist/editors.d.ts +27 -3
  42. package/dist/editors.js +85 -2081
  43. package/dist/editors.js.map +1 -1
  44. package/dist/icons.d.ts +29 -8
  45. package/dist/icons.js +2 -361
  46. package/dist/icons.js.map +1 -1
  47. package/dist/index.d.ts +709 -425
  48. package/dist/index.js +5042 -13885
  49. package/dist/index.js.map +1 -1
  50. package/dist/markdown-editor.js +41 -3426
  51. package/dist/markdown-editor.js.map +1 -1
  52. package/dist/mermaid.js +8 -1129
  53. package/dist/mermaid.js.map +1 -1
  54. package/dist/rjsf.js +180 -9381
  55. package/dist/rjsf.js.map +1 -1
  56. package/dist/styles.css +1 -1
  57. package/dist/testing.js +5 -357
  58. package/dist/testing.js.map +1 -1
  59. package/package.json +28 -73
  60. package/src/styles.css +60 -3
  61. package/src/theme.css +1 -0
  62. package/dist/charts.cjs +0 -268
  63. package/dist/charts.cjs.map +0 -1
  64. package/dist/charts.d.cts +0 -181
  65. package/dist/constants-D3gaMY-W.d.cts +0 -39
  66. package/dist/editors.cjs +0 -2440
  67. package/dist/editors.cjs.map +0 -1
  68. package/dist/editors.d.cts +0 -165
  69. package/dist/icons.cjs +0 -23888
  70. package/dist/icons.cjs.map +0 -1
  71. package/dist/icons.d.cts +0 -20
  72. package/dist/index.cjs +0 -24894
  73. package/dist/index.cjs.map +0 -1
  74. package/dist/index.d.cts +0 -10438
  75. package/dist/markdown-editor.cjs +0 -4636
  76. package/dist/markdown-editor.cjs.map +0 -1
  77. package/dist/markdown-editor.d.cts +0 -75
  78. package/dist/mermaid.cjs +0 -1138
  79. package/dist/mermaid.cjs.map +0 -1
  80. package/dist/mermaid.d.cts +0 -15
  81. package/dist/rjsf.cjs.map +0 -1
  82. package/dist/rjsf.d.cts +0 -55
  83. package/dist/testing.cjs.map +0 -1
  84. package/dist/testing.d.cts +0 -25
@@ -0,0 +1,1313 @@
1
+ import { escapeStringRegexp, useStableCallback, Kbd, isMac } from './chunk-Q7NWXLCT.js';
2
+ import { ClipboardButton } from './chunk-L37JJ6MR.js';
3
+ import { HelpBlock } from './chunk-ESBL3IPF.js';
4
+ import { useArgoDesign, keyCodes } from './chunk-63NSN2VL.js';
5
+ import { cn } from './chunk-JP7PNHLH.js';
6
+ import { javascript } from '@codemirror/lang-javascript';
7
+ import { jsonParseLinter, json } from '@codemirror/lang-json';
8
+ import { python } from '@codemirror/lang-python';
9
+ import { PLSQL, MSSQL, PostgreSQL, MySQL, StandardSQL } from '@codemirror/lang-sql';
10
+ import { StreamLanguage } from '@codemirror/language';
11
+ import { r } from '@codemirror/legacy-modes/mode/r';
12
+ import { shell } from '@codemirror/legacy-modes/mode/shell';
13
+ import { toml } from '@codemirror/legacy-modes/mode/toml';
14
+ import { tags } from '@lezer/highlight';
15
+ import { githubLightInit } from '@uiw/codemirror-theme-github';
16
+ import { installExtensions, getSqlLanguageSupport } from '@keboola/codemirror-lang-sql';
17
+ import colors from '@keboola/tailwind-config/colors';
18
+ import { simpleMode } from '@codemirror/legacy-modes/mode/simple-mode';
19
+ import { useRef, useEffect, useLayoutEffect, useMemo, useSyncExternalStore } from 'react';
20
+ import { completeAnyWord, autocompletion } from '@codemirror/autocomplete';
21
+ import { linter } from '@codemirror/lint';
22
+ import { unifiedMergeView } from '@codemirror/merge';
23
+ import { EditorView, Decoration, ViewPlugin, gutter, GutterMarker, keymap as keymap$1, placeholder, tooltips } from '@codemirror/view';
24
+ import CodeMirror, { keymap, EditorState, EditorView as EditorView$1 } from '@uiw/react-codemirror';
25
+ import { jsxs, jsx } from 'react/jsx-runtime';
26
+ import { StateEffect, StateField, RangeSet, Compartment } from '@codemirror/state';
27
+ import { defaultKeymap } from '@codemirror/commands';
28
+
29
+ var COMMANDS = [
30
+ "run-operation",
31
+ "run",
32
+ "test",
33
+ "seed",
34
+ "snapshot",
35
+ "list",
36
+ "ls",
37
+ "compile",
38
+ "build",
39
+ "deps",
40
+ "docs",
41
+ "source"
42
+ ];
43
+ var SUBCOMMANDS = { docs: ["generate"], source: ["freshness"] };
44
+ var ARGUMENTS = ["--select", "--exclude", "--selector", "--defer", "--resource-type", "-s"];
45
+ var SELECTORS = ["--select", "--exclude", "-s"];
46
+ var METHODS = [
47
+ "path:",
48
+ "source:",
49
+ "tag:",
50
+ "package:",
51
+ "config:",
52
+ "test_type:",
53
+ "test_name:",
54
+ "state:",
55
+ "exposure:",
56
+ "metric:",
57
+ "result:",
58
+ "source_status:",
59
+ "column_name:"
60
+ ];
61
+ var FLAGS = [
62
+ "--fail-fast",
63
+ "--warn-error",
64
+ "--favor-state",
65
+ "--vars",
66
+ "--models",
67
+ "--threads",
68
+ "--profiles-dir",
69
+ "--profile",
70
+ "--full-refresh"
71
+ ];
72
+ var COMMAND_PHRASES = COMMANDS.flatMap((command) => {
73
+ const subcommands = SUBCOMMANDS[command];
74
+ return subcommands ? subcommands.map((subcommand) => `${command} ${subcommand}`) : [command];
75
+ });
76
+ var alternation = (list) => [...list].sort((a, b) => b.length - a.length).join("|");
77
+ var word = (list) => new RegExp(`^\\s+(?:${alternation(list)})\\b`, "i");
78
+ var prefix = (list) => new RegExp(`^\\s+(?:${alternation(list)})`, "i");
79
+ var WORD = /[\w:-]*$/;
80
+ var asOptions = (list, type) => list.map((label) => ({ label, type }));
81
+ var DBT = [{ label: "dbt", type: "keyword" }];
82
+ var COMMAND_OPTIONS = asOptions(COMMANDS, "keyword");
83
+ var SUBCOMMAND_OPTIONS = Object.fromEntries(
84
+ Object.entries(SUBCOMMANDS).map(([command, subcommands]) => [
85
+ command,
86
+ asOptions(subcommands, "keyword")
87
+ ])
88
+ );
89
+ var ARGUMENT_OPTIONS = asOptions(ARGUMENTS, "variable");
90
+ var FLAG_OPTIONS = asOptions(FLAGS, "constant");
91
+ var METHOD_OPTIONS = asOptions(METHODS, "property");
92
+ var optionsAfter = (words) => {
93
+ const [step, command, ...rest] = words;
94
+ if (step === void 0) return DBT;
95
+ if (step.toLowerCase() !== "dbt") return [];
96
+ if (command === void 0) return COMMAND_OPTIONS;
97
+ const subcommands = SUBCOMMAND_OPTIONS[command.toLowerCase()];
98
+ if (subcommands && rest.length === 0) return subcommands;
99
+ const lastArgument = rest.filter((it) => it.startsWith("-")).at(-1);
100
+ const selecting = lastArgument !== void 0 && SELECTORS.includes(lastArgument.toLowerCase());
101
+ return selecting ? [...ARGUMENT_OPTIONS, ...FLAG_OPTIONS, ...METHOD_OPTIONS] : [...ARGUMENT_OPTIONS, ...FLAG_OPTIONS];
102
+ };
103
+ var complete = (context) => {
104
+ const typed = context.matchBefore(WORD);
105
+ if (!typed?.text && !context.explicit) return null;
106
+ const from = typed?.from ?? context.pos;
107
+ const line = context.state.doc.lineAt(from);
108
+ const options = optionsAfter(
109
+ line.text.slice(0, from - line.from).split(/\s+/).filter(Boolean)
110
+ );
111
+ return options.length > 0 ? { from, options, validFor: WORD } : null;
112
+ };
113
+ var dbt = simpleMode({
114
+ languageData: {
115
+ // `toggleComment` is a no-op without these, so Mod-/ silently does nothing.
116
+ commentTokens: { line: "--", block: { open: "/*", close: "*/" } },
117
+ autocomplete: complete
118
+ },
119
+ start: [
120
+ { regex: /"(?:[^\\]|\\.)*?(?:"|$)/, token: "string" },
121
+ // Word-bounded, so `dbtX` is not a dbt step whose first word happens to
122
+ // colour — the same thing the completion source asks of it.
123
+ { regex: /dbt\b/, token: "keyword", sol: true },
124
+ { regex: word(COMMAND_PHRASES), token: "keyword" },
125
+ { regex: word(ARGUMENTS), token: "variableName" },
126
+ { regex: prefix(METHODS), token: "propertyName" },
127
+ { regex: prefix(FLAGS), token: "attributeName" },
128
+ { regex: /(?:@|\*|,|\+\d+|\d+\+|\+)/, token: "operator" },
129
+ { regex: /'.*'/, token: "string" },
130
+ { regex: /true|false/, token: "atom" }
131
+ ]
132
+ });
133
+
134
+ // src/components/CodeEditor/modes/jsonnet.ts
135
+ var COMMENT_END = /^\*\//;
136
+ var COMMENT_START = /^\/\/|^#/;
137
+ var C_COMMENT_START = /^\/\*/;
138
+ var TEXT_BLOCK = /^\|\|\|/;
139
+ var TEXT_BLOCK_END = /^\s*\|\|\|/;
140
+ var RAW_DOUBLE = /^""/;
141
+ var RAW_SINGLE = /^''/;
142
+ var CLOSE_DOUBLE = /^"/;
143
+ var CLOSE_SINGLE = /^'/;
144
+ var ESCAPE = /^\\[\\"'/bfnrt0]/;
145
+ var UNICODE_ESCAPE = /^\\u[0-9a-fA-F]{4}/;
146
+ var BACKSLASH = /^\\/;
147
+ var IMPORT = /^import(?:str)?\s*(@?)(["'])/;
148
+ var QUOTE = /^(@?)(["'])/;
149
+ var ROOT = /^\$/;
150
+ var NUMBER = /^(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i;
151
+ var OPERATOR = /^[-+/*=<>!&~^|$%]+/;
152
+ var IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*/;
153
+ var KEYWORDS = {
154
+ local: "keyword",
155
+ self: "keyword",
156
+ super: "keyword",
157
+ assert: "keyword",
158
+ function: "keyword",
159
+ if: "keyword",
160
+ then: "keyword",
161
+ else: "keyword",
162
+ for: "keyword",
163
+ in: "keyword",
164
+ tailstrict: "keyword",
165
+ error: "keyword",
166
+ true: "atom",
167
+ false: "atom",
168
+ null: "atom"
169
+ };
170
+ var jsonnet = {
171
+ name: "jsonnet",
172
+ // `toggleComment` is a no-op without these, so Mod-/ silently does nothing.
173
+ languageData: { commentTokens: { line: "//", block: { open: "/*", close: "*/" } } },
174
+ startState: () => ({
175
+ cComment: false,
176
+ textBlock: false,
177
+ textBlockIndent: null,
178
+ string: false,
179
+ importString: false,
180
+ stringSingle: false,
181
+ stringRaw: false
182
+ }),
183
+ token: (stream, state) => {
184
+ if (state.cComment) {
185
+ if (stream.match(COMMENT_END)) {
186
+ state.cComment = false;
187
+ return "comment";
188
+ }
189
+ stream.next();
190
+ return "comment";
191
+ }
192
+ if (state.textBlock) {
193
+ if (state.textBlockIndent == null) {
194
+ if (stream.match(TEXT_BLOCK_END)) {
195
+ state.textBlock = false;
196
+ return "string";
197
+ }
198
+ state.textBlockIndent = stream.indentation();
199
+ }
200
+ if (stream.indentation() >= state.textBlockIndent) {
201
+ stream.skipToEnd();
202
+ return "string";
203
+ }
204
+ if (stream.match(TEXT_BLOCK_END)) {
205
+ state.textBlock = false;
206
+ return "string";
207
+ }
208
+ stream.next();
209
+ return "invalid";
210
+ }
211
+ if (state.string || state.importString) {
212
+ const style = state.string ? "string" : "meta";
213
+ if (state.stringRaw) {
214
+ if (stream.match(state.stringSingle ? RAW_SINGLE : RAW_DOUBLE)) return "escape";
215
+ } else {
216
+ if (stream.match(ESCAPE)) return "escape";
217
+ if (stream.match(UNICODE_ESCAPE)) return "escape";
218
+ if (stream.match(BACKSLASH)) return "invalid";
219
+ }
220
+ if (stream.match(state.stringSingle ? CLOSE_SINGLE : CLOSE_DOUBLE)) {
221
+ state.string = false;
222
+ state.importString = false;
223
+ return style;
224
+ }
225
+ stream.next();
226
+ return style;
227
+ }
228
+ if (stream.match(COMMENT_START)) {
229
+ stream.skipToEnd();
230
+ return "comment";
231
+ }
232
+ if (stream.match(C_COMMENT_START)) {
233
+ state.cComment = true;
234
+ return "comment";
235
+ }
236
+ const imported = stream.match(IMPORT);
237
+ if (Array.isArray(imported)) {
238
+ state.importString = true;
239
+ state.stringRaw = imported[1] === "@";
240
+ state.stringSingle = imported[2] === "'";
241
+ return "meta";
242
+ }
243
+ const quoted = stream.match(QUOTE);
244
+ if (Array.isArray(quoted)) {
245
+ state.string = true;
246
+ state.stringRaw = quoted[1] === "@";
247
+ state.stringSingle = quoted[2] === "'";
248
+ return "string";
249
+ }
250
+ if (stream.match(TEXT_BLOCK)) {
251
+ state.textBlock = true;
252
+ state.textBlockIndent = null;
253
+ return "string";
254
+ }
255
+ if (stream.match(ROOT)) return "keyword";
256
+ if (stream.match(NUMBER)) return "number";
257
+ if (stream.match(OPERATOR)) return "operator";
258
+ const identifier = stream.match(IDENTIFIER);
259
+ if (Array.isArray(identifier)) return KEYWORDS[identifier[0]] ?? null;
260
+ stream.next();
261
+ return null;
262
+ }
263
+ };
264
+ var resolveToken = (token, fallbackChannels, alpha) => {
265
+ const channels = typeof document === "undefined" ? fallbackChannels : getComputedStyle(document.documentElement).getPropertyValue(`--${token}`).trim() || fallbackChannels;
266
+ return alpha === void 0 ? `rgb(${channels})` : `rgb(${channels} / ${alpha})`;
267
+ };
268
+ var isDarkScheme = () => typeof document !== "undefined" && document.documentElement.getAttribute("data-theme") === "dark";
269
+ var SSR_SIGNAL = "ssr";
270
+ var readThemeSignal = () => {
271
+ if (typeof document === "undefined") return SSR_SIGNAL;
272
+ const root = document.documentElement;
273
+ return `${root.getAttribute("data-theme") ?? ""}:${root.getAttribute("data-brand") ?? ""}`;
274
+ };
275
+ var subscribeThemeSignal = (onChange) => {
276
+ if (typeof document === "undefined") return () => {
277
+ };
278
+ const observer = new MutationObserver(onChange);
279
+ observer.observe(document.documentElement, {
280
+ attributes: true,
281
+ attributeFilter: ["data-theme", "data-brand"]
282
+ });
283
+ return () => observer.disconnect();
284
+ };
285
+ var useThemeSignal = () => useSyncExternalStore(subscribeThemeSignal, readThemeSignal, () => SSR_SIGNAL);
286
+ var themeCache = /* @__PURE__ */ new Map();
287
+ var useThemedExtension = (cacheKey, build) => {
288
+ const themeSignal = useThemeSignal();
289
+ const key = `${themeSignal} ${cacheKey}`;
290
+ let cached = themeCache.get(key);
291
+ if (!cached) {
292
+ cached = build();
293
+ themeCache.set(key, cached);
294
+ }
295
+ return cached;
296
+ };
297
+
298
+ // src/components/CodeEditor/helpers.ts
299
+ var LANGUAGE_MODES = [
300
+ "application/json",
301
+ "text/plain",
302
+ "text/x-python",
303
+ "text/x-sql",
304
+ "text/x-mysql",
305
+ "text/x-pgsql",
306
+ "text/x-mssql",
307
+ // Snowflake, under a historical name — these strings are a published prop contract.
308
+ "text/x-sfsql",
309
+ "text/x-bqsql",
310
+ "text/x-plsql",
311
+ "text/x-rsrc",
312
+ "text/x-sh",
313
+ "text/x-toml",
314
+ "text/jsonnet",
315
+ "text/x-dbt",
316
+ "text/javascript",
317
+ "text/typescript"
318
+ ];
319
+ var isLanguageMode = (mode) => LANGUAGE_MODES.includes(mode);
320
+ var READ_ONLY_EDITOR = '&.cm-editor:has(.cm-content[contenteditable="false"])';
321
+ var EDITABLE_EDITOR = '&.cm-editor:has(.cm-content[contenteditable="true"])';
322
+ var buildBaseThemeStyles = () => ({
323
+ "*": { lineHeight: "20px" },
324
+ "*::selection": {
325
+ backgroundColor: resolveToken("primary", "31 143 255"),
326
+ color: `${resolveToken("primary-foreground", "255 255 255")} !important`
327
+ },
328
+ [READ_ONLY_EDITOR]: {
329
+ backgroundColor: resolveToken("secondary", "226 232 240")
330
+ },
331
+ [`${READ_ONLY_EDITOR} .cm-gutters`]: {
332
+ backgroundColor: resolveToken("border", "229 231 235")
333
+ },
334
+ "&.cm-editor .cm-scroller": { overflowAnchor: "none" },
335
+ "&.cm-editor, &.cm-editor .cm-scroller": {
336
+ minHeight: "100%",
337
+ flexGrow: 1,
338
+ alignItems: "stretch !important",
339
+ outline: "none"
340
+ },
341
+ ".cm-gutter.cm-foldGutter": { display: "none !important" },
342
+ ".cm-gutter.cm-lineNumbers": { paddingLeft: "20px", paddingRight: "6px" },
343
+ ".cm-gutters": { height: "auto" },
344
+ // merge (diff) view
345
+ '&[class*="cm-merge-"] .cm-deletedLine': { backgroundColor: colors.error[100] },
346
+ '&[class*="cm-merge-"] .cm-changedLine': { backgroundColor: colors.green[100] },
347
+ '&[class*="cm-merge-"] .cm-deletedLineGutter': { backgroundColor: colors.error[200] },
348
+ '&[class*="cm-merge-"] .cm-changedLineGutter': { backgroundColor: colors.green[200] },
349
+ '&[class*="cm-merge-"] .cm-deletedText': { background: colors.error[200] },
350
+ '&[class*="cm-merge-"] .cm-changedText': { background: colors.green[200] },
351
+ '&[class*="cm-merge-"]': {
352
+ backgroundColor: `${colors.white} !important`
353
+ },
354
+ '&[class*="cm-merge-"] .cm-gutters': {
355
+ backgroundColor: `${colors.white} !important`
356
+ },
357
+ ".cm-changeGutter": { position: "absolute", zIndex: -1, width: "100%", paddingLeft: 0 },
358
+ '&[class*="cm-merge-"] .cm-changedLine,&[class*="cm-merge-"] .cm-line, &[class*="cm-merge-"] .cm-deletedChunk': { paddingLeft: 0 }
359
+ });
360
+ var ALWAYS_OFF = {
361
+ drawSelection: false,
362
+ foldGutter: false,
363
+ foldKeymap: false,
364
+ highlightActiveLine: false,
365
+ highlightActiveLineGutter: false
366
+ };
367
+ var buildBasicSetup = (readOnly) => ({
368
+ ...ALWAYS_OFF,
369
+ ...readOnly ? {
370
+ closeBrackets: false,
371
+ closeBracketsKeymap: false,
372
+ lintKeymap: false,
373
+ indentOnInput: false,
374
+ rectangularSelection: false,
375
+ dropCursor: false,
376
+ highlightSelectionMatches: false
377
+ } : null
378
+ });
379
+ var buildCustomTheme = () => {
380
+ const foreground = resolveToken("foreground", "30 41 59");
381
+ return githubLightInit({
382
+ settings: {
383
+ fontFamily: `'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New'`,
384
+ background: resolveToken("background", "249 250 251"),
385
+ foreground,
386
+ gutterBackground: resolveToken("muted", "241 245 249"),
387
+ gutterForeground: resolveToken("muted-foreground", "100 116 139"),
388
+ gutterBorder: "transparent",
389
+ lineHighlight: "transparent",
390
+ selection: resolveToken("primary", "31 143 255")
391
+ },
392
+ styles: [
393
+ { tag: [tags.name, tags.special(tags.string), tags.bracket], color: foreground },
394
+ { tag: tags.standard(tags.name), color: colors.purple[600] },
395
+ { tag: tags.special(tags.name), color: colors.cyan[700] },
396
+ { tag: tags.string, color: colors.warning[700] }
397
+ ]
398
+ });
399
+ };
400
+ var getLanguageMode = (mode) => {
401
+ switch (mode) {
402
+ case void 0:
403
+ case "application/json":
404
+ return json();
405
+ case "text/x-python":
406
+ return python();
407
+ case "text/x-sql":
408
+ return getSqlLanguageSupport(StandardSQL);
409
+ case "text/x-mysql":
410
+ return getSqlLanguageSupport(MySQL);
411
+ case "text/x-pgsql":
412
+ return getSqlLanguageSupport(PostgreSQL);
413
+ case "text/x-mssql":
414
+ return getSqlLanguageSupport(MSSQL);
415
+ case "text/x-sfsql":
416
+ return getSqlLanguageSupport("snowflake");
417
+ case "text/x-bqsql":
418
+ return getSqlLanguageSupport("bigquery");
419
+ case "text/x-plsql":
420
+ return getSqlLanguageSupport(PLSQL);
421
+ case "text/x-rsrc":
422
+ return StreamLanguage.define(r);
423
+ case "text/x-sh":
424
+ return StreamLanguage.define(shell);
425
+ case "text/x-toml":
426
+ return StreamLanguage.define(toml);
427
+ case "text/jsonnet":
428
+ return StreamLanguage.define(jsonnet);
429
+ case "text/x-dbt":
430
+ return StreamLanguage.define(dbt);
431
+ case "text/javascript":
432
+ return javascript({ jsx: true });
433
+ case "text/typescript":
434
+ return javascript({ jsx: true, typescript: true });
435
+ // No language — `text/plain` lands here, and so does any mode added to
436
+ // LANGUAGE_MODES without an arm above. Unstyled text is the safe degradation;
437
+ // a `json()` default would colour unrelated content as if it were JSON.
438
+ default:
439
+ return [];
440
+ }
441
+ };
442
+ var shouldLintJson = (mode, readOnly) => mode === "application/json" && !readOnly;
443
+ var ButtonToolbar = ({ value, children }) => {
444
+ if (!value && !children) return null;
445
+ return /* @__PURE__ */ jsxs("div", { className: "tw:absolute tw:right-3 tw:top-1 tw:z-10 tw:flex tw:gap-0.5 tw:overflow-visible tw:rounded tw:border tw:border-solid tw:border-border tw:!bg-background tw:p-px tw:shadow-sm", children: [
446
+ !!value && /* @__PURE__ */ jsx(ClipboardButton, { size: "icon-xs", variant: "invisible", text: value }),
447
+ children
448
+ ] });
449
+ };
450
+ var buildInputChrome = (isArgo) => ({
451
+ surface: isArgo ? resolveToken("muted", "241 245 249") : colors.neutral[100],
452
+ borderColor: isArgo ? resolveToken("input", "203 213 225") : colors.neutral[100],
453
+ readOnlySurface: isArgo ? resolveToken("muted", "241 245 249") : colors.neutral[150],
454
+ readOnlyBorderColor: isArgo ? resolveToken("input", "203 213 225") : colors.neutral[150],
455
+ hoverBorderColor: null,
456
+ focusBorderColor: isArgo ? resolveToken("primary", "37 99 235") : resolveToken("ring", "31 143 255"),
457
+ focusRing: `0 0 0 4px ${resolveToken("ring", "31 143 255", isArgo ? 0.5 : 0.3)}`,
458
+ radius: isArgo ? "var(--radius-lg, 0.25rem)" : "0.25rem",
459
+ // 2px border + 2×this + `.cm-content`'s own 4px + a 20px line, against the
460
+ // height a one-line `TextInput` has: argo forces `sizing: 'medium'` (36px),
461
+ // legacy leaves it at `default` (40px).
462
+ paddingY: isArgo ? "3px" : "5px",
463
+ // Tops `.cm-line`'s 6px/2px up to the 16px an input's text is inset by.
464
+ paddingX: { left: "10px", right: "14px" }
465
+ });
466
+ var buildCanvasChrome = () => ({
467
+ surface: resolveToken("card", "255 255 255"),
468
+ borderColor: "transparent",
469
+ readOnlySurface: resolveToken("muted", "241 245 249"),
470
+ readOnlyBorderColor: "transparent",
471
+ hoverBorderColor: resolveToken("ring", "31 143 255"),
472
+ focusBorderColor: resolveToken("ring", "31 143 255"),
473
+ focusRing: `0 0 0 4px ${resolveToken("ring", "31 143 255", 0.3)}`,
474
+ radius: "8px",
475
+ paddingY: "8px",
476
+ paddingX: null
477
+ });
478
+ var buildChromeStyles = (chrome) => ({
479
+ "&.cm-editor": {
480
+ border: `solid 1px ${chrome.borderColor}`,
481
+ borderRadius: chrome.radius,
482
+ backgroundColor: chrome.surface,
483
+ paddingTop: chrome.paddingY,
484
+ paddingBottom: chrome.paddingY
485
+ },
486
+ "&.cm-editor :is(.cm-gutters,.cm-scroller)": { backgroundColor: chrome.surface },
487
+ ...chrome.paddingX ? {
488
+ // Only without a gutter: there the editor reads as a one-line input and
489
+ // its text should start where a neighbouring `TextInput`'s does. With a
490
+ // gutter, the gutter owns the left edge.
491
+ "&.cm-editor:not(:has(.cm-gutters)) .cm-content": {
492
+ paddingLeft: chrome.paddingX.left,
493
+ paddingRight: chrome.paddingX.right
494
+ }
495
+ } : null,
496
+ ...chrome.hoverBorderColor ? { [`${EDITABLE_EDITOR}:hover`]: { borderColor: chrome.hoverBorderColor } } : null,
497
+ [`${EDITABLE_EDITOR}:focus-within`]: {
498
+ borderColor: chrome.focusBorderColor,
499
+ boxShadow: chrome.focusRing
500
+ },
501
+ [READ_ONLY_EDITOR]: {
502
+ backgroundColor: chrome.readOnlySurface,
503
+ borderColor: chrome.readOnlyBorderColor
504
+ },
505
+ [`${READ_ONLY_EDITOR} .cm-gutters`]: { backgroundColor: chrome.readOnlySurface },
506
+ [`${READ_ONLY_EDITOR} .cm-scroller`]: { backgroundColor: chrome.readOnlySurface }
507
+ });
508
+ var setSearchEffect = StateEffect.define();
509
+ function findMatches(doc, query) {
510
+ if (!query) return [];
511
+ const text = doc.toString();
512
+ try {
513
+ const regex = new RegExp(escapeStringRegexp(query), "gi");
514
+ return Array.from(text.matchAll(regex)).map((match, index) => {
515
+ const from = match.index;
516
+ const to = from + match[0].length;
517
+ return { from, to, text: match[0], localIndex: index };
518
+ });
519
+ } catch (e) {
520
+ console.warn("Invalid search regex:", e);
521
+ return [];
522
+ }
523
+ }
524
+ function createDecorations(matches, selectedLocalIndex) {
525
+ const decorations = matches.map((match) => {
526
+ const isSelected = match.localIndex === selectedLocalIndex;
527
+ const decoration = Decoration.mark({
528
+ class: isSelected ? "cm-searchMatch-selected" : "cm-searchMatch"
529
+ });
530
+ return decoration.range(match.from, match.to);
531
+ });
532
+ return Decoration.set(decorations, true);
533
+ }
534
+ var searchField = StateField.define({
535
+ create() {
536
+ return {
537
+ query: "",
538
+ selectedMatchIndex: void 0,
539
+ matches: [],
540
+ decorations: Decoration.none
541
+ };
542
+ },
543
+ update(state, tr) {
544
+ let newState = state;
545
+ for (const effect of tr.effects) {
546
+ if (!effect.is(setSearchEffect)) {
547
+ continue;
548
+ }
549
+ const { query, selectedMatchIndex } = effect.value;
550
+ const matches = findMatches(tr.state.doc, query);
551
+ const decorations = createDecorations(matches, selectedMatchIndex);
552
+ newState = {
553
+ query,
554
+ selectedMatchIndex,
555
+ matches,
556
+ decorations
557
+ };
558
+ }
559
+ if (tr.docChanged && newState.query) {
560
+ const matches = findMatches(tr.state.doc, newState.query);
561
+ const decorations = createDecorations(matches, newState.selectedMatchIndex);
562
+ newState = {
563
+ ...newState,
564
+ matches,
565
+ decorations
566
+ };
567
+ }
568
+ return newState;
569
+ },
570
+ provide: (field) => EditorView.decorations.from(field, (state) => state.decorations)
571
+ });
572
+ var searchViewPlugin = ViewPlugin.fromClass(
573
+ class {
574
+ lastSelectedIndex;
575
+ update(update) {
576
+ const state = update.state.field(searchField);
577
+ if (state.selectedMatchIndex !== void 0 && state.selectedMatchIndex !== this.lastSelectedIndex) {
578
+ const match = state.matches[state.selectedMatchIndex];
579
+ if (match) {
580
+ requestAnimationFrame(() => {
581
+ update.view.dispatch({
582
+ effects: EditorView.scrollIntoView(match.from, { y: "center" })
583
+ });
584
+ });
585
+ }
586
+ this.lastSelectedIndex = state.selectedMatchIndex;
587
+ }
588
+ if (state.selectedMatchIndex === void 0) {
589
+ this.lastSelectedIndex = void 0;
590
+ }
591
+ }
592
+ }
593
+ );
594
+ var searchTheme = EditorView.baseTheme({
595
+ ".cm-searchMatch": {
596
+ backgroundColor: colors.blue[100]
597
+ },
598
+ ".cm-searchMatch > span": {
599
+ color: `${colors.blue[600]} !important`
600
+ },
601
+ ".cm-searchMatch-selected": {
602
+ backgroundColor: colors.blue[600]
603
+ },
604
+ ".cm-searchMatch-selected > span": {
605
+ color: `${colors.white} !important`
606
+ }
607
+ });
608
+ function searchExtension() {
609
+ return [searchField, searchViewPlugin, searchTheme];
610
+ }
611
+ function updateSearch(view, query, selectedMatchIndex) {
612
+ view.dispatch({ effects: setSearchEffect.of({ query, selectedMatchIndex }) });
613
+ }
614
+ var ToggleComponentHelp = () => /* @__PURE__ */ jsxs("div", { children: [
615
+ "Use ",
616
+ /* @__PURE__ */ jsx(Kbd, { children: isMac ? "Command+/" : "Ctrl+/" }),
617
+ " to toggle comments."
618
+ ] });
619
+ var establishesFixedContainingBlock = (style) => style.transform !== "none" || style.translate !== "none" || style.rotate !== "none" || style.scale !== "none" || style.perspective !== "none" || style.filter !== "none" || style.backdropFilter !== "none" || /\b(paint|layout|strict|content)\b/.test(style.contain) || /\b(transform|perspective|filter|contain)\b/.test(style.willChange);
620
+ var crops = (style) => style.overflowX !== "visible" || style.overflowY !== "visible" || style.maskImage !== "none" || style.contentVisibility !== "visible" || /\b(paint|strict|content)\b/.test(style.contain);
621
+ var findTooltipHost = (from) => {
622
+ let outermostCropper = null;
623
+ for (let node = from.parentElement; node && node !== document.body; node = node.parentElement) {
624
+ const style = getComputedStyle(node);
625
+ if (crops(style)) {
626
+ outermostCropper = node;
627
+ } else if (establishesFixedContainingBlock(style)) {
628
+ return outermostCropper?.parentElement ?? node;
629
+ }
630
+ }
631
+ return outermostCropper?.parentElement ?? null;
632
+ };
633
+ var tooltipsExtension = () => {
634
+ const layer = new Compartment();
635
+ return [
636
+ layer.of(tooltips()),
637
+ ViewPlugin.define((view) => {
638
+ const scheduled = setTimeout(() => {
639
+ if (!view.dom.isConnected) return;
640
+ const parent = findTooltipHost(view.dom);
641
+ if (parent) view.dispatch({ effects: layer.reconfigure(tooltips({ parent })) });
642
+ });
643
+ return { destroy: () => clearTimeout(scheduled) };
644
+ })
645
+ ];
646
+ };
647
+ var useTooltipsExtension = () => useMemo(tooltipsExtension, []);
648
+
649
+ // src/components/CodeEditor/variableCompletion.ts
650
+ var BOOST = 2e3;
651
+ var NAME = String.raw`[\w-]*`;
652
+ var AT_CARET = new RegExp(String.raw`(?:\{{1,2} ?)?${NAME}$`);
653
+ var NAME_AT_END = new RegExp(`${NAME}$`);
654
+ var OPENING = /\{{1,2} ?$/;
655
+ var CLOSING = /^ ?\}{1,2}/;
656
+ var completeVariables = (variables = []) => {
657
+ if (variables.length === 0) return () => null;
658
+ const options = variables.map(({ name, value }) => ({
659
+ // The bare name: `SqlEditor` filters prefix-only while `CodeEditor` filters
660
+ // fuzzily, and this is the only spelling both accept whether or not the caret
661
+ // has typed the braces. The braces are `displayLabel`, which is what renders.
662
+ label: name,
663
+ displayLabel: `{{${name}}}`,
664
+ detail: value,
665
+ type: "variable",
666
+ boost: BOOST,
667
+ apply: (view, completion, from, to) => {
668
+ const { doc } = view.state;
669
+ const line = doc.lineAt(from);
670
+ const insert = `{{${completion.label}}}`;
671
+ const opening = OPENING.exec(doc.sliceString(line.from, from));
672
+ const closing = CLOSING.exec(doc.sliceString(to, line.to));
673
+ const start = opening ? from - opening[0].length : from;
674
+ const end = closing ? to + closing[0].length : to;
675
+ view.dispatch({
676
+ changes: { from: start, to: end, insert },
677
+ selection: { anchor: start + insert.length }
678
+ });
679
+ }
680
+ }));
681
+ return (context) => {
682
+ const match = context.matchBefore(AT_CARET);
683
+ if (!match?.text) return null;
684
+ const [name = ""] = NAME_AT_END.exec(match.text) ?? [];
685
+ return { from: match.to - name.length, options };
686
+ };
687
+ };
688
+ var useVariableCompletion = (variables = []) => useStableCallback(completeVariables(variables));
689
+ var emptyObject = {};
690
+ var CodeEditor = ({
691
+ value,
692
+ previousValue,
693
+ onChange,
694
+ variant = "plain",
695
+ mode = "application/json",
696
+ placeholder: placeholder$1 = "",
697
+ ariaLabel,
698
+ searchQuery,
699
+ searchMatchIndex,
700
+ readOnly = false,
701
+ inputLocked = false,
702
+ autocompletions,
703
+ completionSource,
704
+ variables,
705
+ extraKeys = emptyObject,
706
+ withToggleCommentHint = false,
707
+ withAutofocus = false,
708
+ withLineNumbers = true,
709
+ withLineWrapping = true,
710
+ withCollapseUnchanged = false,
711
+ withCopyButton = false,
712
+ completeAny = false,
713
+ help = null,
714
+ helpPostfix = null,
715
+ renderAdditionalButtons,
716
+ className
717
+ }) => {
718
+ const handleChange = useStableCallback((next) => onChange?.(next));
719
+ const withSearch = searchQuery !== void 0;
720
+ const editorRef = useRef(null);
721
+ useEffect(() => {
722
+ const view = editorRef.current?.view;
723
+ if (view && withSearch) updateSearch(view, searchQuery, searchMatchIndex);
724
+ }, [withSearch, searchQuery, searchMatchIndex]);
725
+ const handleCreateEditor = useStableCallback((view) => {
726
+ if (searchQuery) updateSearch(view, searchQuery, searchMatchIndex);
727
+ });
728
+ const extraKeysRef = useRef(extraKeys);
729
+ useLayoutEffect(() => {
730
+ extraKeysRef.current = extraKeys;
731
+ });
732
+ const boundKeysSignature = Object.keys(extraKeys).join("\n");
733
+ const boundKeys = useMemo(
734
+ () => boundKeysSignature.split("\n").filter(Boolean),
735
+ [boundKeysSignature]
736
+ );
737
+ const tooltipLayer = useTooltipsExtension();
738
+ const variableCompletion = useVariableCompletion(variables);
739
+ const customTheme = useThemedExtension("editor:syntax", buildCustomTheme);
740
+ const isArgo = useArgoDesign();
741
+ const isField = variant === "field";
742
+ const baseTheme = useThemedExtension(
743
+ isField ? `code-editor:field:${isArgo ? "argo" : "legacy"}` : "code-editor:chrome",
744
+ () => EditorView.theme({
745
+ ...buildBaseThemeStyles(),
746
+ ...isField ? buildChromeStyles(buildInputChrome(isArgo)) : null
747
+ })
748
+ );
749
+ const commonExtensions = useMemo(() => {
750
+ const callerCompletions = completionSource ?? (completeAny ? completeAnyWord : autocompletions?.length ? autocompletions : null);
751
+ const completionEntries = [
752
+ ...callerCompletions ? [{ autocomplete: callerCompletions }] : [],
753
+ { autocomplete: variableCompletion }
754
+ ];
755
+ return [
756
+ getLanguageMode(mode),
757
+ shouldLintJson(mode, readOnly) ? linter(jsonParseLinter()) : [],
758
+ withLineWrapping ? EditorView.lineWrapping : [],
759
+ EditorState.languageData.of(() => completionEntries),
760
+ keymap$1.of(
761
+ boundKeys.map((key) => ({
762
+ key,
763
+ run: () => {
764
+ extraKeysRef.current[key]?.();
765
+ return true;
766
+ }
767
+ }))
768
+ ),
769
+ readOnly ? [] : [
770
+ // CodeMirror's own default. Gating it on the props hid completions the
771
+ // *language* supplies — dbt's vocabulary, Snowflake's keywords — behind
772
+ // a shortcut, and macOS leaves no usable one. With nothing to offer, no
773
+ // popup opens anyway.
774
+ autocompletion()
775
+ ],
776
+ ariaLabel ? EditorView.contentAttributes.of({ "aria-label": ariaLabel }) : [],
777
+ withSearch ? searchExtension() : [],
778
+ tooltipLayer,
779
+ baseTheme
780
+ ];
781
+ }, [
782
+ autocompletions,
783
+ completionSource,
784
+ variableCompletion,
785
+ completeAny,
786
+ boundKeys,
787
+ mode,
788
+ readOnly,
789
+ withLineWrapping,
790
+ baseTheme,
791
+ ariaLabel,
792
+ withSearch,
793
+ tooltipLayer
794
+ ]);
795
+ const extensions = useMemo(() => {
796
+ return [
797
+ ...commonExtensions,
798
+ placeholder(placeholder$1),
799
+ EditorView.editable.of(!readOnly),
800
+ // The lock is this facet and nothing else. `contenteditable` is what the
801
+ // read-only wash keys off, and `basicSetup` is what holds the undo history,
802
+ // so leaving both alone is what makes the lock invisible.
803
+ EditorState.readOnly.of(readOnly || inputLocked),
804
+ previousValue ? unifiedMergeView({
805
+ original: previousValue,
806
+ mergeControls: false,
807
+ highlightChanges: false,
808
+ collapseUnchanged: withCollapseUnchanged ? {} : void 0
809
+ }) : []
810
+ ];
811
+ }, [commonExtensions, placeholder$1, readOnly, inputLocked, previousValue, withCollapseUnchanged]);
812
+ const config = useMemo(() => {
813
+ return {
814
+ ...buildBasicSetup(readOnly),
815
+ lineNumbers: withLineNumbers,
816
+ // Mod-f opens CodeMirror's own panel, which would run a second query and a
817
+ // second match count over the same document beside the caller's input.
818
+ ...withSearch ? { searchKeymap: false } : null
819
+ };
820
+ }, [withLineNumbers, readOnly, withSearch]);
821
+ return (
822
+ // oxlint-disable-next-line jsx-a11y/no-static-element-interactions
823
+ /* @__PURE__ */ jsxs(
824
+ "div",
825
+ {
826
+ "data-variant": variant,
827
+ className: cn(
828
+ // Scoped to the box CodeMirror renders into rather than to every child, so
829
+ // the toolbar below does not have to undo all three in `!important`.
830
+ "tw:relative tw:flex tw:min-h-0 tw:flex-col tw:[&_.cm-mergeViewEditor]:overflow-visible tw:[&_.cm-mergeViewEditors]:h-full tw:[&_.cm-mergeView]:h-full tw:[&_>.cm-theme]:h-full",
831
+ // A field's `.cm-editor` is rounded, so the square box behind it would show
832
+ // at the corners, and `overflow-auto` would crop the focus ring — a shadow
833
+ // painted outside the editor's own box.
834
+ isField ? "tw:[&_>.cm-theme]:overflow-visible tw:[&_>.cm-theme]:bg-transparent" : "tw:[&_>.cm-theme]:overflow-auto tw:[&_>.cm-theme]:bg-background",
835
+ className
836
+ ),
837
+ onKeyDown: (event) => {
838
+ if (event.key === keyCodes.ESCAPE) {
839
+ event.stopPropagation();
840
+ }
841
+ },
842
+ children: [
843
+ /* @__PURE__ */ jsx(ButtonToolbar, { value: withCopyButton ? value : void 0, children: renderAdditionalButtons?.(value) }),
844
+ /* @__PURE__ */ jsx(
845
+ CodeMirror,
846
+ {
847
+ ref: editorRef,
848
+ onCreateEditor: handleCreateEditor,
849
+ theme: customTheme,
850
+ value,
851
+ onChange: handleChange,
852
+ extensions,
853
+ autoFocus: withAutofocus,
854
+ basicSetup: config
855
+ }
856
+ ),
857
+ !readOnly && (withToggleCommentHint || help || helpPostfix) && /* @__PURE__ */ jsxs(HelpBlock, { className: "tw:mt-2 tw:flex tw:flex-wrap tw:gap-1", children: [
858
+ help,
859
+ withToggleCommentHint ? /* @__PURE__ */ jsx(ToggleComponentHelp, {}) : null,
860
+ helpPostfix
861
+ ] })
862
+ ]
863
+ }
864
+ )
865
+ );
866
+ };
867
+ var activeQueryFirstLine = Decoration.line({ class: "cm-active-query cm-active-query-first" });
868
+ var activeQueryMiddleLine = Decoration.line({ class: "cm-active-query" });
869
+ var activeQueryLastLine = Decoration.line({ class: "cm-active-query cm-active-query-last" });
870
+ var activeQuerySingleLine = Decoration.line({
871
+ class: "cm-active-query cm-active-query-first cm-active-query-last"
872
+ });
873
+ var findActiveQueryRange = (doc, cursorPos, boundaries) => {
874
+ if (boundaries.length === 0) return null;
875
+ let match = boundaries[boundaries.length - 1];
876
+ for (const boundary of boundaries) {
877
+ if (cursorPos <= boundary.to) {
878
+ match = boundary;
879
+ break;
880
+ }
881
+ }
882
+ if (!match) return null;
883
+ const queryText = doc.slice(match.from, match.to);
884
+ const leadingWhitespace = queryText.length - queryText.trimStart().length;
885
+ const trailingWhitespace = queryText.length - queryText.trimEnd().length;
886
+ const trimmedStart = match.from + leadingWhitespace;
887
+ const trimmedEnd = match.to - trailingWhitespace;
888
+ if (trimmedStart >= trimmedEnd) return null;
889
+ return { from: trimmedStart, to: trimmedEnd };
890
+ };
891
+ var computeBoundaries = (queries) => {
892
+ const boundaries = [];
893
+ let offset = 0;
894
+ for (const query of queries) {
895
+ boundaries.push({ from: offset, to: offset + query.length });
896
+ offset += query.length;
897
+ }
898
+ return boundaries;
899
+ };
900
+ var mapBoundariesThroughChanges = (boundaries, changes) => {
901
+ const prevLen = changes.length;
902
+ const mapped = [];
903
+ for (const b of boundaries) {
904
+ const from = changes.mapPos(Math.max(0, Math.min(b.from, prevLen)), -1);
905
+ const to = changes.mapPos(Math.max(0, Math.min(b.to, prevLen)), 1);
906
+ if (from < to) mapped.push({ from, to });
907
+ }
908
+ return mapped;
909
+ };
910
+ var buildDecorations = (view, range) => {
911
+ const startLine = view.state.doc.lineAt(range.from).number;
912
+ const endLine = view.state.doc.lineAt(range.to).number;
913
+ const decorations = [];
914
+ for (let lineNum = startLine; lineNum <= endLine; lineNum++) {
915
+ const lineStart = view.state.doc.line(lineNum).from;
916
+ if (startLine === endLine) {
917
+ decorations.push(activeQuerySingleLine.range(lineStart));
918
+ } else if (lineNum === startLine) {
919
+ decorations.push(activeQueryFirstLine.range(lineStart));
920
+ } else if (lineNum === endLine) {
921
+ decorations.push(activeQueryLastLine.range(lineStart));
922
+ } else {
923
+ decorations.push(activeQueryMiddleLine.range(lineStart));
924
+ }
925
+ }
926
+ return Decoration.set(decorations);
927
+ };
928
+ var activeQueryHighlight = (splitQueries) => {
929
+ return [
930
+ ViewPlugin.fromClass(
931
+ class {
932
+ decorations = Decoration.none;
933
+ debounceTimer = null;
934
+ seq = 0;
935
+ cachedDoc = "";
936
+ cachedBoundaries = [];
937
+ destroyed = false;
938
+ constructor(view) {
939
+ this.scheduleDocParse(view);
940
+ }
941
+ update(update) {
942
+ if (update.focusChanged && !update.view.hasFocus) {
943
+ this.decorations = Decoration.none;
944
+ return;
945
+ }
946
+ if (update.docChanged) {
947
+ if (this.cachedBoundaries.length > 0) {
948
+ this.cachedBoundaries = mapBoundariesThroughChanges(
949
+ this.cachedBoundaries,
950
+ update.changes
951
+ );
952
+ this.cachedDoc = update.state.doc.toString();
953
+ this.updateFromCache(update.view);
954
+ }
955
+ this.scheduleDocParse(update.view);
956
+ } else if (update.selectionSet || update.focusChanged && update.view.hasFocus) {
957
+ this.updateFromCache(update.view);
958
+ }
959
+ }
960
+ destroy() {
961
+ this.destroyed = true;
962
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
963
+ }
964
+ /**
965
+ * Update decorations using cached query boundaries.
966
+ * Only mutates this.decorations — does NOT call view.dispatch().
967
+ * When called from update() (sync), CodeMirror picks up the new
968
+ * decorations via the accessor. When called from the async
969
+ * scheduleDocParse callback, the caller must dispatch separately.
970
+ */
971
+ updateFromCache(view) {
972
+ if (!view.hasFocus) {
973
+ this.decorations = Decoration.none;
974
+ return;
975
+ }
976
+ const { main } = view.state.selection;
977
+ if (main.from !== main.to) {
978
+ this.decorations = Decoration.none;
979
+ return;
980
+ }
981
+ const doc = view.state.doc.toString();
982
+ if (doc !== this.cachedDoc) return;
983
+ if (this.cachedBoundaries.length === 0) {
984
+ this.decorations = Decoration.none;
985
+ return;
986
+ }
987
+ const range = findActiveQueryRange(doc, main.head, this.cachedBoundaries);
988
+ this.decorations = range ? buildDecorations(view, range) : Decoration.none;
989
+ }
990
+ /** Parse queries async (debounced) and update decorations. */
991
+ scheduleDocParse(view) {
992
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
993
+ this.debounceTimer = setTimeout(async () => {
994
+ const seq = ++this.seq;
995
+ const doc = view.state.doc.toString();
996
+ if (!doc.trim()) {
997
+ this.cachedDoc = doc;
998
+ this.cachedBoundaries = [];
999
+ if (this.decorations !== Decoration.none) {
1000
+ this.decorations = Decoration.none;
1001
+ if (!this.destroyed) view.dispatch();
1002
+ }
1003
+ return;
1004
+ }
1005
+ let queries;
1006
+ try {
1007
+ queries = await splitQueries(doc);
1008
+ } catch {
1009
+ return;
1010
+ }
1011
+ if (this.destroyed || seq !== this.seq) return;
1012
+ this.cachedDoc = doc;
1013
+ this.cachedBoundaries = computeBoundaries(queries);
1014
+ this.updateFromCache(view);
1015
+ if (!this.destroyed) view.dispatch();
1016
+ }, 100);
1017
+ }
1018
+ },
1019
+ { decorations: (v) => v.decorations }
1020
+ ),
1021
+ EditorView.baseTheme({
1022
+ ".cm-active-query": {
1023
+ boxShadow: `inset -1px 0 0 0 ${colors.blue[300]}, inset 1px 0 0 0 ${colors.blue[300]}`,
1024
+ marginRight: "8px"
1025
+ },
1026
+ ".cm-active-query-first": {
1027
+ boxShadow: `inset -1px 0 0 0 ${colors.blue[300]}, inset 1px 0 0 0 ${colors.blue[300]}, inset 0 1px 0 0 ${colors.blue[300]}`,
1028
+ borderRadius: "4px 4px 0 0"
1029
+ },
1030
+ ".cm-active-query-last": {
1031
+ boxShadow: `inset -1px 0 0 0 ${colors.blue[300]}, inset 1px 0 0 0 ${colors.blue[300]}, inset 0 -1px 0 0 ${colors.blue[300]}`,
1032
+ borderRadius: "0 0 4px 4px"
1033
+ },
1034
+ ".cm-active-query-first.cm-active-query-last": {
1035
+ boxShadow: `inset 0 0 0 1px ${colors.blue[300]}`,
1036
+ borderRadius: "4px"
1037
+ }
1038
+ })
1039
+ ];
1040
+ };
1041
+ var HighlightMarker = class extends GutterMarker {
1042
+ toDOM() {
1043
+ const marker = document.createElement("div");
1044
+ marker.className = "cm-highlight-marker";
1045
+ return marker;
1046
+ }
1047
+ };
1048
+ var highlightMarker = new HighlightMarker();
1049
+ var forEachLineInRange = (doc, from, to, callback) => {
1050
+ const results = [];
1051
+ const fromLine = Math.max(1, from + 1);
1052
+ const toLine = Math.min(doc.lines, to + 1);
1053
+ for (let i = fromLine; i <= toLine; i++) {
1054
+ const line = doc.line(i);
1055
+ results.push(callback(line.from));
1056
+ }
1057
+ return results;
1058
+ };
1059
+ var setHighlightedLinesEffect = StateEffect.define();
1060
+ var highlightedLinesField = StateField.define({
1061
+ create() {
1062
+ return null;
1063
+ },
1064
+ update(value, transaction) {
1065
+ for (const effect of transaction.effects) {
1066
+ if (effect.is(setHighlightedLinesEffect)) {
1067
+ return effect.value;
1068
+ }
1069
+ }
1070
+ return value;
1071
+ }
1072
+ });
1073
+ var highlightGutter = gutter({
1074
+ class: "cm-highlight-gutter",
1075
+ markers: (view) => {
1076
+ const highlightInfo = view.state.field(highlightedLinesField, false);
1077
+ if (!highlightInfo) {
1078
+ return RangeSet.empty;
1079
+ }
1080
+ const markers = forEachLineInRange(
1081
+ view.state.doc,
1082
+ highlightInfo.from,
1083
+ highlightInfo.to,
1084
+ (lineFrom) => highlightMarker.range(lineFrom)
1085
+ );
1086
+ return RangeSet.of(markers);
1087
+ }
1088
+ });
1089
+ var errorLineDecorations = StateField.define({
1090
+ create() {
1091
+ return Decoration.none;
1092
+ },
1093
+ update(decorations, transaction) {
1094
+ decorations = decorations.map(transaction.changes);
1095
+ for (const effect of transaction.effects) {
1096
+ if (!effect.is(setHighlightedLinesEffect)) {
1097
+ continue;
1098
+ }
1099
+ if (!effect.value || !effect.value.error) {
1100
+ return Decoration.none;
1101
+ }
1102
+ const decorationArray = forEachLineInRange(
1103
+ transaction.state.doc,
1104
+ effect.value.error.from,
1105
+ effect.value.error.to,
1106
+ (lineFrom) => Decoration.line({ class: "cm-error-line" }).range(lineFrom)
1107
+ );
1108
+ return Decoration.set(decorationArray);
1109
+ }
1110
+ return decorations;
1111
+ },
1112
+ provide: (f) => EditorView.decorations.from(f)
1113
+ });
1114
+ var processingLineDecorations = StateField.define({
1115
+ create() {
1116
+ return Decoration.none;
1117
+ },
1118
+ update(decorations, transaction) {
1119
+ decorations = decorations.map(transaction.changes);
1120
+ for (const effect of transaction.effects) {
1121
+ if (!effect.is(setHighlightedLinesEffect)) {
1122
+ continue;
1123
+ }
1124
+ if (!effect.value || !effect.value.processing) {
1125
+ return Decoration.none;
1126
+ }
1127
+ const decorationArray = forEachLineInRange(
1128
+ transaction.state.doc,
1129
+ effect.value.processing.from,
1130
+ effect.value.processing.to,
1131
+ (lineFrom) => Decoration.line({ class: "cm-processing-line" }).range(lineFrom)
1132
+ );
1133
+ return Decoration.set(decorationArray);
1134
+ }
1135
+ return decorations;
1136
+ },
1137
+ provide: (f) => EditorView.decorations.from(f)
1138
+ });
1139
+ var highlightedLinesTheme = EditorView.baseTheme({
1140
+ ".cm-highlight-gutter": {
1141
+ width: "2px",
1142
+ marginRight: "2px"
1143
+ },
1144
+ ".cm-highlight-marker": {
1145
+ width: "2px",
1146
+ height: "100%",
1147
+ backgroundColor: colors.blue[500]
1148
+ },
1149
+ ".cm-error-line": {
1150
+ backgroundColor: `${colors.error[100]} !important`
1151
+ },
1152
+ ".cm-processing-line": {
1153
+ backgroundColor: `${colors.blue[100]} !important`
1154
+ }
1155
+ });
1156
+ function highlightingExtension() {
1157
+ return [
1158
+ highlightedLinesField,
1159
+ highlightGutter,
1160
+ errorLineDecorations,
1161
+ processingLineDecorations,
1162
+ highlightedLinesTheme
1163
+ ];
1164
+ }
1165
+ function updateHighlighting(view, lines) {
1166
+ view.dispatch({ effects: setHighlightedLinesEffect.of(lines ?? null) });
1167
+ }
1168
+ var OWN_KEYMAPS = {
1169
+ defaultKeymap: false,
1170
+ completionKeymap: false,
1171
+ searchKeymap: false
1172
+ };
1173
+ var CUSTOM_KEYMAP = [
1174
+ {
1175
+ key: "Escape",
1176
+ stopPropagation: true,
1177
+ run: (view) => {
1178
+ view.contentDOM.blur();
1179
+ return true;
1180
+ }
1181
+ }
1182
+ ];
1183
+ var OWN_KEYMAP_EXTENSION = keymap.of([...defaultKeymap, ...CUSTOM_KEYMAP]);
1184
+ var buildTheme = () => ({
1185
+ ...buildBaseThemeStyles(),
1186
+ ...buildChromeStyles(buildCanvasChrome())
1187
+ });
1188
+ var SqlEditor = ({
1189
+ value,
1190
+ onChange,
1191
+ onSelectionChange,
1192
+ onCreateEditor,
1193
+ onDestroyEditor,
1194
+ onBlurEditor,
1195
+ onFocusEditor,
1196
+ placeholder = "",
1197
+ searchQuery = "",
1198
+ searchMatchIndex,
1199
+ readOnly = false,
1200
+ queryParser,
1201
+ autoFocus = false,
1202
+ lineNumbers = true,
1203
+ className,
1204
+ schema,
1205
+ completionSource,
1206
+ variables,
1207
+ defaultDatabase,
1208
+ defaultSchema,
1209
+ highlightLines,
1210
+ dialect
1211
+ }) => {
1212
+ const editorRef = useRef(null);
1213
+ const handleChange = useStableCallback((next) => onChange?.(next));
1214
+ const parseQueries = useStableCallback(async (sql) => await queryParser?.(sql) ?? []);
1215
+ const handleBlur = useStableCallback((view) => onBlurEditor?.(view));
1216
+ const handleFocus = useStableCallback((view) => onFocusEditor?.(view));
1217
+ const handleUpdate = useStableCallback((update) => {
1218
+ if (update.selectionSet) {
1219
+ const { main } = update.state.selection;
1220
+ onSelectionChange?.(update.state.sliceDoc(main.from, main.to));
1221
+ }
1222
+ });
1223
+ const variableCompletion = useVariableCompletion(variables);
1224
+ const withQueryParser = queryParser !== void 0;
1225
+ const tooltipLayer = useTooltipsExtension();
1226
+ const customTheme = useThemedExtension("editor:syntax", buildCustomTheme);
1227
+ const themeExtension = useThemedExtension(
1228
+ "sql-editor:chrome",
1229
+ () => EditorView$1.theme(buildTheme())
1230
+ );
1231
+ const extensions = useMemo(() => {
1232
+ return [
1233
+ EditorView$1.lineWrapping,
1234
+ OWN_KEYMAP_EXTENSION,
1235
+ ...installExtensions({
1236
+ schema,
1237
+ defaultDatabase,
1238
+ defaultSchema,
1239
+ variableCompletion,
1240
+ completionSource,
1241
+ dialect
1242
+ }),
1243
+ searchExtension(),
1244
+ highlightingExtension(),
1245
+ withQueryParser ? activeQueryHighlight(parseQueries) : [],
1246
+ tooltipLayer,
1247
+ themeExtension,
1248
+ EditorView$1.domEventHandlers({
1249
+ blur: (_, view) => handleBlur(view),
1250
+ focus: (_, view) => handleFocus(view)
1251
+ })
1252
+ ];
1253
+ }, [
1254
+ withQueryParser,
1255
+ parseQueries,
1256
+ schema,
1257
+ completionSource,
1258
+ defaultDatabase,
1259
+ defaultSchema,
1260
+ variableCompletion,
1261
+ dialect,
1262
+ handleBlur,
1263
+ handleFocus,
1264
+ themeExtension,
1265
+ tooltipLayer
1266
+ ]);
1267
+ const defaultSetup = useMemo(() => {
1268
+ return { ...buildBasicSetup(readOnly), ...OWN_KEYMAPS, lineNumbers };
1269
+ }, [readOnly, lineNumbers]);
1270
+ useEffect(() => {
1271
+ if (editorRef.current?.view) {
1272
+ updateHighlighting(editorRef.current.view, highlightLines);
1273
+ }
1274
+ }, [highlightLines]);
1275
+ useEffect(() => {
1276
+ if (editorRef.current?.view) {
1277
+ updateSearch(editorRef.current.view, searchQuery, searchMatchIndex);
1278
+ }
1279
+ }, [searchQuery, searchMatchIndex]);
1280
+ const handleDestroy = useStableCallback(() => onDestroyEditor?.());
1281
+ useEffect(() => handleDestroy, [handleDestroy]);
1282
+ const handleCreate = useStableCallback((view, state) => {
1283
+ onCreateEditor?.(view, state);
1284
+ if (highlightLines) {
1285
+ updateHighlighting(view, highlightLines);
1286
+ }
1287
+ if (searchQuery) {
1288
+ updateSearch(view, searchQuery, searchMatchIndex);
1289
+ }
1290
+ });
1291
+ return /* @__PURE__ */ jsx(
1292
+ CodeMirror,
1293
+ {
1294
+ ref: editorRef,
1295
+ theme: customTheme,
1296
+ extensions,
1297
+ value,
1298
+ onChange: handleChange,
1299
+ onUpdate: handleUpdate,
1300
+ onCreateEditor: handleCreate,
1301
+ autoFocus,
1302
+ placeholder,
1303
+ className,
1304
+ readOnly,
1305
+ editable: !readOnly,
1306
+ basicSetup: defaultSetup
1307
+ }
1308
+ );
1309
+ };
1310
+
1311
+ export { CodeEditor, LANGUAGE_MODES, SqlEditor, buildBasicSetup, isDarkScheme, isLanguageMode, resolveToken, useThemedExtension };
1312
+ //# sourceMappingURL=chunk-7PUUHEJQ.js.map
1313
+ //# sourceMappingURL=chunk-7PUUHEJQ.js.map