@keboola/design 8.0.0 → 9.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.
- package/dist/editors.cjs +366 -186
- package/dist/editors.cjs.map +1 -1
- package/dist/editors.d.cts +56 -6
- package/dist/editors.d.ts +56 -6
- package/dist/editors.js +370 -190
- package/dist/editors.js.map +1 -1
- package/dist/index.cjs +89 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -4
- package/dist/index.d.ts +29 -4
- package/dist/index.js +89 -13
- package/dist/index.js.map +1 -1
- package/dist/rjsf.cjs +394 -39
- package/dist/rjsf.cjs.map +1 -1
- package/dist/rjsf.js +396 -41
- package/dist/rjsf.js.map +1 -1
- package/package.json +5 -5
package/dist/rjsf.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import React26, { createContext, use, useCallback, useMemo, useRef, useState, useEffect, useContext, isValidElement,
|
|
1
|
+
import React26, { createContext, use, useCallback, useMemo, useRef, useState, useEffect, useContext, isValidElement, useLayoutEffect, useId, useSyncExternalStore, Children, Fragment as Fragment$1 } from 'react';
|
|
2
2
|
import Form2 from '@rjsf/core';
|
|
3
3
|
import validator from '@rjsf/validator-ajv8';
|
|
4
4
|
import { completeAnyWord, autocompletion } from '@codemirror/autocomplete';
|
|
5
5
|
import { jsonParseLinter, json } from '@codemirror/lang-json';
|
|
6
6
|
import { linter } from '@codemirror/lint';
|
|
7
7
|
import { unifiedMergeView } from '@codemirror/merge';
|
|
8
|
-
import { EditorView, keymap, placeholder } from '@codemirror/view';
|
|
8
|
+
import { EditorView, Decoration, ViewPlugin, keymap, placeholder, tooltips } from '@codemirror/view';
|
|
9
9
|
import CodeMirror, { EditorState } from '@uiw/react-codemirror';
|
|
10
10
|
import { clsx } from 'clsx';
|
|
11
11
|
import { extendTailwindMerge } from 'tailwind-merge';
|
|
@@ -23,9 +23,10 @@ import { r } from '@codemirror/legacy-modes/mode/r';
|
|
|
23
23
|
import { shell } from '@codemirror/legacy-modes/mode/shell';
|
|
24
24
|
import { toml } from '@codemirror/legacy-modes/mode/toml';
|
|
25
25
|
import { githubLightInit } from '@uiw/codemirror-theme-github';
|
|
26
|
-
import {
|
|
26
|
+
import { getSqlDialect } from '@keboola/codemirror-lang-sql';
|
|
27
27
|
import colors from '@keboola/tailwind-config/colors';
|
|
28
28
|
import { simpleMode } from '@codemirror/legacy-modes/mode/simple-mode';
|
|
29
|
+
import { StateEffect, StateField, Compartment } from '@codemirror/state';
|
|
29
30
|
import DOMPurify from 'dompurify';
|
|
30
31
|
import { Root as Root$2, CollapsibleTrigger, Content as Content$1 } from '@radix-ui/react-collapsible';
|
|
31
32
|
import * as RadixTabs from '@radix-ui/react-tabs';
|
|
@@ -35,6 +36,7 @@ import TextareaAutosize from 'react-textarea-autosize';
|
|
|
35
36
|
import { Root, Indicator } from '@radix-ui/react-checkbox';
|
|
36
37
|
import { Root as Root$3, Trigger as Trigger$1, Close, Portal as Portal$1, Content as Content$2 } from '@radix-ui/react-popover';
|
|
37
38
|
import { Combobox as Combobox$1 } from '@base-ui/react/combobox';
|
|
39
|
+
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
38
40
|
import * as RRadioGroup from '@radix-ui/react-radio-group';
|
|
39
41
|
import { Item, Indicator as Indicator$1 } from '@radix-ui/react-radio-group';
|
|
40
42
|
import * as RSeparator from '@radix-ui/react-separator';
|
|
@@ -352,6 +354,9 @@ Tooltip.Trigger = TooltipTrigger;
|
|
|
352
354
|
// src/utils/createPrefixedString.ts
|
|
353
355
|
var createPrefixedString = (prefix2) => (literals) => literals.reduce((acc, literal) => acc + literal, `${prefix2}-`);
|
|
354
356
|
|
|
357
|
+
// src/utils/escapeStringRegexp.ts
|
|
358
|
+
var escapeStringRegexp = (string) => string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
|
|
359
|
+
|
|
355
360
|
// src/utils/mergeRefs.ts
|
|
356
361
|
function setRef(ref, value) {
|
|
357
362
|
if (typeof ref === "function") {
|
|
@@ -363,6 +368,21 @@ function setRef(ref, value) {
|
|
|
363
368
|
var mergeRefs = (...refs) => {
|
|
364
369
|
return (node) => refs.forEach((ref) => setRef(ref, node));
|
|
365
370
|
};
|
|
371
|
+
|
|
372
|
+
// src/utils/isMac.ts
|
|
373
|
+
var getIsMac = () => {
|
|
374
|
+
if (typeof navigator !== "undefined" && "userAgentData" in navigator) {
|
|
375
|
+
const platform = navigator.userAgentData.platform;
|
|
376
|
+
if (typeof platform === "string") {
|
|
377
|
+
return platform.toLowerCase() === "macos";
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (typeof navigator !== "undefined" && typeof navigator.platform === "string") {
|
|
381
|
+
return navigator.platform.toUpperCase().indexOf("MAC") >= 0;
|
|
382
|
+
}
|
|
383
|
+
return false;
|
|
384
|
+
};
|
|
385
|
+
var isMac = getIsMac();
|
|
366
386
|
var helpVariants = cva(["tw:m-0", "tw:text-xs"], {
|
|
367
387
|
defaultVariants: {
|
|
368
388
|
state: "default"
|
|
@@ -813,8 +833,6 @@ var ButtonToolbar = ({ value, children }) => {
|
|
|
813
833
|
};
|
|
814
834
|
var COMMANDS = [
|
|
815
835
|
"run-operation",
|
|
816
|
-
"source freshness",
|
|
817
|
-
"docs generate",
|
|
818
836
|
"run",
|
|
819
837
|
"test",
|
|
820
838
|
"seed",
|
|
@@ -823,9 +841,13 @@ var COMMANDS = [
|
|
|
823
841
|
"ls",
|
|
824
842
|
"compile",
|
|
825
843
|
"build",
|
|
826
|
-
"deps"
|
|
844
|
+
"deps",
|
|
845
|
+
"docs",
|
|
846
|
+
"source"
|
|
827
847
|
];
|
|
848
|
+
var SUBCOMMANDS = { docs: ["generate"], source: ["freshness"] };
|
|
828
849
|
var ARGUMENTS = ["--select", "--exclude", "--selector", "--defer", "--resource-type", "-s"];
|
|
850
|
+
var SELECTORS = ["--select", "--exclude", "-s"];
|
|
829
851
|
var METHODS = [
|
|
830
852
|
"path:",
|
|
831
853
|
"source:",
|
|
@@ -852,13 +874,59 @@ var FLAGS = [
|
|
|
852
874
|
"--profile",
|
|
853
875
|
"--full-refresh"
|
|
854
876
|
];
|
|
855
|
-
var
|
|
856
|
-
|
|
877
|
+
var COMMAND_PHRASES = COMMANDS.flatMap((command) => {
|
|
878
|
+
const subcommands = SUBCOMMANDS[command];
|
|
879
|
+
return subcommands ? subcommands.map((subcommand) => `${command} ${subcommand}`) : [command];
|
|
880
|
+
});
|
|
881
|
+
var alternation = (list) => [...list].sort((a, b) => b.length - a.length).join("|");
|
|
882
|
+
var word = (list) => new RegExp(`^\\s+(?:${alternation(list)})\\b`, "i");
|
|
883
|
+
var prefix = (list) => new RegExp(`^\\s+(?:${alternation(list)})`, "i");
|
|
884
|
+
var WORD = /[\w:-]*$/;
|
|
885
|
+
var asOptions = (list, type) => list.map((label) => ({ label, type }));
|
|
886
|
+
var DBT = [{ label: "dbt", type: "keyword" }];
|
|
887
|
+
var COMMAND_OPTIONS = asOptions(COMMANDS, "keyword");
|
|
888
|
+
var SUBCOMMAND_OPTIONS = Object.fromEntries(
|
|
889
|
+
Object.entries(SUBCOMMANDS).map(([command, subcommands]) => [
|
|
890
|
+
command,
|
|
891
|
+
asOptions(subcommands, "keyword")
|
|
892
|
+
])
|
|
893
|
+
);
|
|
894
|
+
var ARGUMENT_OPTIONS = asOptions(ARGUMENTS, "variable");
|
|
895
|
+
var FLAG_OPTIONS = asOptions(FLAGS, "constant");
|
|
896
|
+
var METHOD_OPTIONS = asOptions(METHODS, "property");
|
|
897
|
+
var optionsAfter = (words) => {
|
|
898
|
+
const [step, command, ...rest] = words;
|
|
899
|
+
if (step === void 0) return DBT;
|
|
900
|
+
if (step.toLowerCase() !== "dbt") return [];
|
|
901
|
+
if (command === void 0) return COMMAND_OPTIONS;
|
|
902
|
+
const subcommands = SUBCOMMAND_OPTIONS[command.toLowerCase()];
|
|
903
|
+
if (subcommands && rest.length === 0) return subcommands;
|
|
904
|
+
const lastArgument = rest.filter((it) => it.startsWith("-")).at(-1);
|
|
905
|
+
const selecting = lastArgument !== void 0 && SELECTORS.includes(lastArgument.toLowerCase());
|
|
906
|
+
return selecting ? [...ARGUMENT_OPTIONS, ...FLAG_OPTIONS, ...METHOD_OPTIONS] : [...ARGUMENT_OPTIONS, ...FLAG_OPTIONS];
|
|
907
|
+
};
|
|
908
|
+
var complete = (context) => {
|
|
909
|
+
const typed = context.matchBefore(WORD);
|
|
910
|
+
if (!typed?.text && !context.explicit) return null;
|
|
911
|
+
const from = typed?.from ?? context.pos;
|
|
912
|
+
const line = context.state.doc.lineAt(from);
|
|
913
|
+
const options = optionsAfter(
|
|
914
|
+
line.text.slice(0, from - line.from).split(/\s+/).filter(Boolean)
|
|
915
|
+
);
|
|
916
|
+
return options.length > 0 ? { from, options, validFor: WORD } : null;
|
|
917
|
+
};
|
|
857
918
|
var dbt = simpleMode({
|
|
919
|
+
languageData: {
|
|
920
|
+
// `toggleComment` is a no-op without these, so Mod-/ silently does nothing.
|
|
921
|
+
commentTokens: { line: "--", block: { open: "/*", close: "*/" } },
|
|
922
|
+
autocomplete: complete
|
|
923
|
+
},
|
|
858
924
|
start: [
|
|
859
925
|
{ regex: /"(?:[^\\]|\\.)*?(?:"|$)/, token: "string" },
|
|
860
|
-
|
|
861
|
-
|
|
926
|
+
// Word-bounded, so `dbtX` is not a dbt step whose first word happens to
|
|
927
|
+
// colour — the same thing the completion source asks of it.
|
|
928
|
+
{ regex: /dbt\b/, token: "keyword", sol: true },
|
|
929
|
+
{ regex: word(COMMAND_PHRASES), token: "keyword" },
|
|
862
930
|
{ regex: word(ARGUMENTS), token: "variableName" },
|
|
863
931
|
{ regex: prefix(METHODS), token: "propertyName" },
|
|
864
932
|
{ regex: prefix(FLAGS), token: "attributeName" },
|
|
@@ -906,6 +974,8 @@ var KEYWORDS = {
|
|
|
906
974
|
};
|
|
907
975
|
var jsonnet = {
|
|
908
976
|
name: "jsonnet",
|
|
977
|
+
// `toggleComment` is a no-op without these, so Mod-/ silently does nothing.
|
|
978
|
+
languageData: { commentTokens: { line: "//", block: { open: "/*", close: "*/" } } },
|
|
909
979
|
startState: () => ({
|
|
910
980
|
cComment: false,
|
|
911
981
|
textBlock: false,
|
|
@@ -1038,6 +1108,7 @@ var LANGUAGE_MODES = [
|
|
|
1038
1108
|
"text/x-mysql",
|
|
1039
1109
|
"text/x-pgsql",
|
|
1040
1110
|
"text/x-mssql",
|
|
1111
|
+
// Snowflake, under a historical name — these strings are a published prop contract.
|
|
1041
1112
|
"text/x-sfsql",
|
|
1042
1113
|
"text/x-plsql",
|
|
1043
1114
|
"text/x-rsrc",
|
|
@@ -1099,8 +1170,6 @@ var ALWAYS_OFF = {
|
|
|
1099
1170
|
var buildBasicSetup = (readOnly) => ({
|
|
1100
1171
|
...ALWAYS_OFF,
|
|
1101
1172
|
...readOnly ? {
|
|
1102
|
-
history: false,
|
|
1103
|
-
historyKeymap: false,
|
|
1104
1173
|
closeBrackets: false,
|
|
1105
1174
|
closeBracketsKeymap: false,
|
|
1106
1175
|
lintKeymap: false,
|
|
@@ -1138,7 +1207,7 @@ var getLanguageMode = (mode) => {
|
|
|
1138
1207
|
case "text/x-mssql":
|
|
1139
1208
|
return sql({ upperCaseKeywords: true, dialect: MSSQL });
|
|
1140
1209
|
case "text/x-sfsql":
|
|
1141
|
-
return
|
|
1210
|
+
return sql({ upperCaseKeywords: true, dialect: getSqlDialect("snowflake") });
|
|
1142
1211
|
case "text/x-plsql":
|
|
1143
1212
|
return sql({ upperCaseKeywords: true, dialect: PLSQL });
|
|
1144
1213
|
case "text/x-rsrc":
|
|
@@ -1163,6 +1232,112 @@ var getLanguageMode = (mode) => {
|
|
|
1163
1232
|
}
|
|
1164
1233
|
};
|
|
1165
1234
|
var shouldLintJson = (mode, readOnly) => mode === "application/json" && !readOnly;
|
|
1235
|
+
var setSearchEffect = StateEffect.define();
|
|
1236
|
+
function findMatches(doc, query) {
|
|
1237
|
+
if (!query) return [];
|
|
1238
|
+
const text = doc.toString();
|
|
1239
|
+
try {
|
|
1240
|
+
const regex = new RegExp(escapeStringRegexp(query), "gi");
|
|
1241
|
+
return Array.from(text.matchAll(regex)).map((match2, index) => {
|
|
1242
|
+
const from = match2.index;
|
|
1243
|
+
const to = from + match2[0].length;
|
|
1244
|
+
return { from, to, text: match2[0], localIndex: index };
|
|
1245
|
+
});
|
|
1246
|
+
} catch (e) {
|
|
1247
|
+
console.warn("Invalid search regex:", e);
|
|
1248
|
+
return [];
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
function createDecorations(matches, selectedLocalIndex) {
|
|
1252
|
+
const decorations = matches.map((match2) => {
|
|
1253
|
+
const isSelected = match2.localIndex === selectedLocalIndex;
|
|
1254
|
+
const decoration = Decoration.mark({
|
|
1255
|
+
class: isSelected ? "cm-searchMatch-selected" : "cm-searchMatch"
|
|
1256
|
+
});
|
|
1257
|
+
return decoration.range(match2.from, match2.to);
|
|
1258
|
+
});
|
|
1259
|
+
return Decoration.set(decorations, true);
|
|
1260
|
+
}
|
|
1261
|
+
var searchField = StateField.define({
|
|
1262
|
+
create() {
|
|
1263
|
+
return {
|
|
1264
|
+
query: "",
|
|
1265
|
+
selectedMatchIndex: void 0,
|
|
1266
|
+
matches: [],
|
|
1267
|
+
decorations: Decoration.none
|
|
1268
|
+
};
|
|
1269
|
+
},
|
|
1270
|
+
update(state, tr) {
|
|
1271
|
+
let newState = state;
|
|
1272
|
+
for (const effect of tr.effects) {
|
|
1273
|
+
if (!effect.is(setSearchEffect)) {
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
const { query, selectedMatchIndex } = effect.value;
|
|
1277
|
+
const matches = findMatches(tr.state.doc, query);
|
|
1278
|
+
const decorations = createDecorations(matches, selectedMatchIndex);
|
|
1279
|
+
newState = {
|
|
1280
|
+
query,
|
|
1281
|
+
selectedMatchIndex,
|
|
1282
|
+
matches,
|
|
1283
|
+
decorations
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
if (tr.docChanged && newState.query) {
|
|
1287
|
+
const matches = findMatches(tr.state.doc, newState.query);
|
|
1288
|
+
const decorations = createDecorations(matches, newState.selectedMatchIndex);
|
|
1289
|
+
newState = {
|
|
1290
|
+
...newState,
|
|
1291
|
+
matches,
|
|
1292
|
+
decorations
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
return newState;
|
|
1296
|
+
},
|
|
1297
|
+
provide: (field) => EditorView.decorations.from(field, (state) => state.decorations)
|
|
1298
|
+
});
|
|
1299
|
+
var searchViewPlugin = ViewPlugin.fromClass(
|
|
1300
|
+
class {
|
|
1301
|
+
lastSelectedIndex;
|
|
1302
|
+
update(update) {
|
|
1303
|
+
const state = update.state.field(searchField);
|
|
1304
|
+
if (state.selectedMatchIndex !== void 0 && state.selectedMatchIndex !== this.lastSelectedIndex) {
|
|
1305
|
+
const match2 = state.matches[state.selectedMatchIndex];
|
|
1306
|
+
if (match2) {
|
|
1307
|
+
requestAnimationFrame(() => {
|
|
1308
|
+
update.view.dispatch({
|
|
1309
|
+
effects: EditorView.scrollIntoView(match2.from, { y: "center" })
|
|
1310
|
+
});
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
this.lastSelectedIndex = state.selectedMatchIndex;
|
|
1314
|
+
}
|
|
1315
|
+
if (state.selectedMatchIndex === void 0) {
|
|
1316
|
+
this.lastSelectedIndex = void 0;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
);
|
|
1321
|
+
var searchTheme = EditorView.baseTheme({
|
|
1322
|
+
".cm-searchMatch": {
|
|
1323
|
+
backgroundColor: colors.blue[100]
|
|
1324
|
+
},
|
|
1325
|
+
".cm-searchMatch > span": {
|
|
1326
|
+
color: `${colors.blue[600]} !important`
|
|
1327
|
+
},
|
|
1328
|
+
".cm-searchMatch-selected": {
|
|
1329
|
+
backgroundColor: colors.blue[600]
|
|
1330
|
+
},
|
|
1331
|
+
".cm-searchMatch-selected > span": {
|
|
1332
|
+
color: `${colors.white} !important`
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
function searchExtension() {
|
|
1336
|
+
return [searchField, searchViewPlugin, searchTheme];
|
|
1337
|
+
}
|
|
1338
|
+
function updateSearch(view, query, selectedMatchIndex) {
|
|
1339
|
+
view.dispatch({ effects: setSearchEffect.of({ query, selectedMatchIndex }) });
|
|
1340
|
+
}
|
|
1166
1341
|
var KbdSeparator = ({ className, ...props }) => /* @__PURE__ */ jsx("span", { ...props, className: cn("tw:text-xs tw:text-muted-foreground", className) });
|
|
1167
1342
|
var KbdGroup = ({ ref, className, children, separator, ...props }) => {
|
|
1168
1343
|
return /* @__PURE__ */ jsx(
|
|
@@ -1222,12 +1397,79 @@ Kbd.Group = KbdGroup;
|
|
|
1222
1397
|
Kbd.Separator = KbdSeparator;
|
|
1223
1398
|
var ToggleComponentHelp = () => /* @__PURE__ */ jsxs("div", { children: [
|
|
1224
1399
|
"Use ",
|
|
1225
|
-
/* @__PURE__ */ jsx(Kbd, { children: "Ctrl+/" }),
|
|
1226
|
-
" or ",
|
|
1227
|
-
/* @__PURE__ */ jsx(Kbd, { children: "Command+/" }),
|
|
1400
|
+
/* @__PURE__ */ jsx(Kbd, { children: isMac ? "Command+/" : "Ctrl+/" }),
|
|
1228
1401
|
" to toggle comments."
|
|
1229
1402
|
] });
|
|
1230
|
-
var
|
|
1403
|
+
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);
|
|
1404
|
+
var crops = (style) => style.overflowX !== "visible" || style.overflowY !== "visible" || style.maskImage !== "none" || style.contentVisibility !== "visible" || /\b(paint|strict|content)\b/.test(style.contain);
|
|
1405
|
+
var findTooltipHost = (from) => {
|
|
1406
|
+
let outermostCropper = null;
|
|
1407
|
+
for (let node = from.parentElement; node && node !== document.body; node = node.parentElement) {
|
|
1408
|
+
const style = getComputedStyle(node);
|
|
1409
|
+
if (crops(style)) {
|
|
1410
|
+
outermostCropper = node;
|
|
1411
|
+
} else if (establishesFixedContainingBlock(style)) {
|
|
1412
|
+
return outermostCropper?.parentElement ?? node;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
return outermostCropper?.parentElement ?? null;
|
|
1416
|
+
};
|
|
1417
|
+
var tooltipsExtension = () => {
|
|
1418
|
+
const layer = new Compartment();
|
|
1419
|
+
return [
|
|
1420
|
+
layer.of(tooltips()),
|
|
1421
|
+
ViewPlugin.define((view) => {
|
|
1422
|
+
const scheduled = setTimeout(() => {
|
|
1423
|
+
if (!view.dom.isConnected) return;
|
|
1424
|
+
const parent = findTooltipHost(view.dom);
|
|
1425
|
+
if (parent) view.dispatch({ effects: layer.reconfigure(tooltips({ parent })) });
|
|
1426
|
+
});
|
|
1427
|
+
return { destroy: () => clearTimeout(scheduled) };
|
|
1428
|
+
})
|
|
1429
|
+
];
|
|
1430
|
+
};
|
|
1431
|
+
var useTooltipsExtension = () => useMemo(tooltipsExtension, []);
|
|
1432
|
+
|
|
1433
|
+
// src/components/CodeEditor/variableCompletion.ts
|
|
1434
|
+
var BOOST = 2e3;
|
|
1435
|
+
var NAME = String.raw`[\w-]*`;
|
|
1436
|
+
var AT_CARET = new RegExp(String.raw`(?:\{{1,2} ?)?${NAME}$`);
|
|
1437
|
+
var NAME_AT_END = new RegExp(`${NAME}$`);
|
|
1438
|
+
var OPENING = /\{{1,2} ?$/;
|
|
1439
|
+
var CLOSING = /^ ?\}{1,2}/;
|
|
1440
|
+
var completeVariables = (variables = []) => {
|
|
1441
|
+
if (variables.length === 0) return () => null;
|
|
1442
|
+
const options = variables.map(({ name, value }) => ({
|
|
1443
|
+
// The bare name: `SqlEditor` filters prefix-only while `CodeEditor` filters
|
|
1444
|
+
// fuzzily, and this is the only spelling both accept whether or not the caret
|
|
1445
|
+
// has typed the braces. The braces are `displayLabel`, which is what renders.
|
|
1446
|
+
label: name,
|
|
1447
|
+
displayLabel: `{{${name}}}`,
|
|
1448
|
+
detail: value,
|
|
1449
|
+
type: "variable",
|
|
1450
|
+
boost: BOOST,
|
|
1451
|
+
apply: (view, completion, from, to) => {
|
|
1452
|
+
const { doc } = view.state;
|
|
1453
|
+
const line = doc.lineAt(from);
|
|
1454
|
+
const insert = `{{${completion.label}}}`;
|
|
1455
|
+
const opening = OPENING.exec(doc.sliceString(line.from, from));
|
|
1456
|
+
const closing = CLOSING.exec(doc.sliceString(to, line.to));
|
|
1457
|
+
const start = opening ? from - opening[0].length : from;
|
|
1458
|
+
const end = closing ? to + closing[0].length : to;
|
|
1459
|
+
view.dispatch({
|
|
1460
|
+
changes: { from: start, to: end, insert },
|
|
1461
|
+
selection: { anchor: start + insert.length }
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
}));
|
|
1465
|
+
return (context) => {
|
|
1466
|
+
const match2 = context.matchBefore(AT_CARET);
|
|
1467
|
+
if (!match2?.text) return null;
|
|
1468
|
+
const [name = ""] = NAME_AT_END.exec(match2.text) ?? [];
|
|
1469
|
+
return { from: match2.to - name.length, options };
|
|
1470
|
+
};
|
|
1471
|
+
};
|
|
1472
|
+
var useVariableCompletion = (variables = []) => useStableCallback(completeVariables(variables));
|
|
1231
1473
|
var emptyObject = {};
|
|
1232
1474
|
var CodeEditor = ({
|
|
1233
1475
|
value,
|
|
@@ -1236,8 +1478,13 @@ var CodeEditor = ({
|
|
|
1236
1478
|
mode = "application/json",
|
|
1237
1479
|
placeholder: placeholder$1 = "",
|
|
1238
1480
|
ariaLabel,
|
|
1481
|
+
searchQuery,
|
|
1482
|
+
searchMatchIndex,
|
|
1239
1483
|
readOnly = false,
|
|
1240
|
-
|
|
1484
|
+
inputLocked = false,
|
|
1485
|
+
autocompletions,
|
|
1486
|
+
completionSource,
|
|
1487
|
+
variables,
|
|
1241
1488
|
extraKeys = emptyObject,
|
|
1242
1489
|
withToggleCommentHint = false,
|
|
1243
1490
|
withAutofocus = false,
|
|
@@ -1252,49 +1499,86 @@ var CodeEditor = ({
|
|
|
1252
1499
|
className
|
|
1253
1500
|
}) => {
|
|
1254
1501
|
const handleChange = useStableCallback((next) => onChange?.(next));
|
|
1502
|
+
const withSearch = searchQuery !== void 0;
|
|
1503
|
+
const editorRef = useRef(null);
|
|
1504
|
+
useEffect(() => {
|
|
1505
|
+
const view = editorRef.current?.view;
|
|
1506
|
+
if (view && withSearch) updateSearch(view, searchQuery, searchMatchIndex);
|
|
1507
|
+
}, [withSearch, searchQuery, searchMatchIndex]);
|
|
1508
|
+
const handleCreateEditor = useStableCallback((view) => {
|
|
1509
|
+
if (searchQuery) updateSearch(view, searchQuery, searchMatchIndex);
|
|
1510
|
+
});
|
|
1511
|
+
const extraKeysRef = useRef(extraKeys);
|
|
1512
|
+
useLayoutEffect(() => {
|
|
1513
|
+
extraKeysRef.current = extraKeys;
|
|
1514
|
+
});
|
|
1515
|
+
const boundKeysSignature = Object.keys(extraKeys).join("\n");
|
|
1516
|
+
const boundKeys = useMemo(
|
|
1517
|
+
() => boundKeysSignature.split("\n").filter(Boolean),
|
|
1518
|
+
[boundKeysSignature]
|
|
1519
|
+
);
|
|
1520
|
+
const tooltipLayer = useTooltipsExtension();
|
|
1521
|
+
const variableCompletion = useVariableCompletion(variables);
|
|
1255
1522
|
const customTheme = useThemedExtension("editor:syntax", buildCustomTheme);
|
|
1256
1523
|
const baseTheme = useThemedExtension(
|
|
1257
1524
|
"code-editor:chrome",
|
|
1258
1525
|
() => EditorView.theme(buildBaseThemeStyles())
|
|
1259
1526
|
);
|
|
1260
1527
|
const commonExtensions = useMemo(() => {
|
|
1528
|
+
const callerCompletions = completionSource ?? (completeAny ? completeAnyWord : autocompletions?.length ? autocompletions : null);
|
|
1529
|
+
const completionEntries = [
|
|
1530
|
+
...callerCompletions ? [{ autocomplete: callerCompletions }] : [],
|
|
1531
|
+
{ autocomplete: variableCompletion }
|
|
1532
|
+
];
|
|
1261
1533
|
return [
|
|
1262
1534
|
getLanguageMode(mode),
|
|
1263
1535
|
shouldLintJson(mode, readOnly) ? linter(jsonParseLinter()) : [],
|
|
1264
1536
|
withLineWrapping ? EditorView.lineWrapping : [],
|
|
1265
|
-
EditorState.languageData.of(() =>
|
|
1266
|
-
{ autocomplete: completeAny ? completeAnyWord : autocompletions }
|
|
1267
|
-
]),
|
|
1537
|
+
EditorState.languageData.of(() => completionEntries),
|
|
1268
1538
|
keymap.of(
|
|
1269
|
-
|
|
1539
|
+
boundKeys.map((key) => ({
|
|
1270
1540
|
key,
|
|
1271
1541
|
run: () => {
|
|
1272
|
-
|
|
1273
|
-
run();
|
|
1542
|
+
extraKeysRef.current[key]?.();
|
|
1274
1543
|
return true;
|
|
1275
1544
|
}
|
|
1276
1545
|
}))
|
|
1277
1546
|
),
|
|
1278
|
-
readOnly ? [] :
|
|
1547
|
+
readOnly ? [] : [
|
|
1548
|
+
// CodeMirror's own default. Gating it on the props hid completions the
|
|
1549
|
+
// *language* supplies — dbt's vocabulary, Snowflake's keywords — behind
|
|
1550
|
+
// a shortcut, and macOS leaves no usable one. With nothing to offer, no
|
|
1551
|
+
// popup opens anyway.
|
|
1552
|
+
autocompletion()
|
|
1553
|
+
],
|
|
1279
1554
|
ariaLabel ? EditorView.contentAttributes.of({ "aria-label": ariaLabel }) : [],
|
|
1555
|
+
withSearch ? searchExtension() : [],
|
|
1556
|
+
tooltipLayer,
|
|
1280
1557
|
baseTheme
|
|
1281
1558
|
];
|
|
1282
1559
|
}, [
|
|
1283
1560
|
autocompletions,
|
|
1561
|
+
completionSource,
|
|
1562
|
+
variableCompletion,
|
|
1284
1563
|
completeAny,
|
|
1285
|
-
|
|
1564
|
+
boundKeys,
|
|
1286
1565
|
mode,
|
|
1287
1566
|
readOnly,
|
|
1288
1567
|
withLineWrapping,
|
|
1289
1568
|
baseTheme,
|
|
1290
|
-
ariaLabel
|
|
1569
|
+
ariaLabel,
|
|
1570
|
+
withSearch,
|
|
1571
|
+
tooltipLayer
|
|
1291
1572
|
]);
|
|
1292
1573
|
const extensions = useMemo(() => {
|
|
1293
1574
|
return [
|
|
1294
1575
|
...commonExtensions,
|
|
1295
1576
|
placeholder(placeholder$1),
|
|
1296
1577
|
EditorView.editable.of(!readOnly),
|
|
1297
|
-
|
|
1578
|
+
// The lock is this facet and nothing else. `contenteditable` is what the
|
|
1579
|
+
// read-only wash keys off, and `basicSetup` is what holds the undo history,
|
|
1580
|
+
// so leaving both alone is what makes the lock invisible.
|
|
1581
|
+
EditorState.readOnly.of(readOnly || inputLocked),
|
|
1298
1582
|
previousValue ? unifiedMergeView({
|
|
1299
1583
|
original: previousValue,
|
|
1300
1584
|
mergeControls: false,
|
|
@@ -1302,13 +1586,16 @@ var CodeEditor = ({
|
|
|
1302
1586
|
collapseUnchanged: withCollapseUnchanged ? {} : void 0
|
|
1303
1587
|
}) : []
|
|
1304
1588
|
];
|
|
1305
|
-
}, [commonExtensions, placeholder$1, readOnly, previousValue, withCollapseUnchanged]);
|
|
1589
|
+
}, [commonExtensions, placeholder$1, readOnly, inputLocked, previousValue, withCollapseUnchanged]);
|
|
1306
1590
|
const config = useMemo(() => {
|
|
1307
1591
|
return {
|
|
1308
1592
|
...buildBasicSetup(readOnly),
|
|
1309
|
-
lineNumbers: withLineNumbers
|
|
1593
|
+
lineNumbers: withLineNumbers,
|
|
1594
|
+
// Mod-f opens CodeMirror's own panel, which would run a second query and a
|
|
1595
|
+
// second match count over the same document beside the caller's input.
|
|
1596
|
+
...withSearch ? { searchKeymap: false } : null
|
|
1310
1597
|
};
|
|
1311
|
-
}, [withLineNumbers, readOnly]);
|
|
1598
|
+
}, [withLineNumbers, readOnly, withSearch]);
|
|
1312
1599
|
const additionalButtons = renderAdditionalButtons?.(value);
|
|
1313
1600
|
const hasToolbar = withCopyButton && !!value || !!additionalButtons;
|
|
1314
1601
|
return (
|
|
@@ -1331,6 +1618,8 @@ var CodeEditor = ({
|
|
|
1331
1618
|
/* @__PURE__ */ jsx(
|
|
1332
1619
|
CodeMirror,
|
|
1333
1620
|
{
|
|
1621
|
+
ref: editorRef,
|
|
1622
|
+
onCreateEditor: handleCreateEditor,
|
|
1334
1623
|
theme: customTheme,
|
|
1335
1624
|
value,
|
|
1336
1625
|
onChange: handleChange,
|
|
@@ -8914,9 +9203,9 @@ var ComboboxItemIndicator = ({ ref, className, variant = "check", ...props }) =>
|
|
|
8914
9203
|
"tw:absolute",
|
|
8915
9204
|
"tw:right-2",
|
|
8916
9205
|
"tw:group-data-[variant=default]/combobox-content:right-4",
|
|
8917
|
-
//
|
|
8918
|
-
//
|
|
8919
|
-
|
|
9206
|
+
// `render={<Check />}` makes the icon the indicator element itself (not a descendant), so a
|
|
9207
|
+
// `[&_svg]` rule never matches it — size it directly or Lucide renders at its 24px default.
|
|
9208
|
+
"tw:size-4",
|
|
8920
9209
|
"tw:group-data-[highlighted]/combobox-item:text-accent-foreground",
|
|
8921
9210
|
"tw:group-data-[variant=default]/combobox-content:group-data-[highlighted]/combobox-item:text-foreground",
|
|
8922
9211
|
className
|
|
@@ -9041,6 +9330,9 @@ var ComboboxGroupLabel = ({ ref, className, ...props }) => /* @__PURE__ */ jsx(
|
|
|
9041
9330
|
}
|
|
9042
9331
|
);
|
|
9043
9332
|
var ComboboxCollection = ({ ...props }) => /* @__PURE__ */ jsx(Combobox$1.Collection, { "data-slot": "combobox-collection", ...props });
|
|
9333
|
+
var VIRTUALIZE_THRESHOLD = 30;
|
|
9334
|
+
var ESTIMATED_ROW_HEIGHT = 40;
|
|
9335
|
+
var ESTIMATED_LIST_HEIGHT = 320;
|
|
9044
9336
|
var Combobox = ({
|
|
9045
9337
|
// root props
|
|
9046
9338
|
items: _itemsProp,
|
|
@@ -9067,6 +9359,7 @@ var Combobox = ({
|
|
|
9067
9359
|
empty = "No items",
|
|
9068
9360
|
indicator,
|
|
9069
9361
|
prefix: prefix2,
|
|
9362
|
+
virtualized = false,
|
|
9070
9363
|
// creatable
|
|
9071
9364
|
allowCreate,
|
|
9072
9365
|
queryToCreateValue,
|
|
@@ -9090,7 +9383,9 @@ var Combobox = ({
|
|
|
9090
9383
|
const label = value === null ? "" : itemToStringLabelProp?.(value) ?? toLabel(value);
|
|
9091
9384
|
setInputValue((prev) => prev === label ? prev : label);
|
|
9092
9385
|
}, [value]);
|
|
9093
|
-
const
|
|
9386
|
+
const selectedLabel = value == null ? void 0 : itemToStringLabelProp?.(value) ?? toLabel(value);
|
|
9387
|
+
const isShowingSelection = selectedLabel !== void 0 && inputValue === selectedLabel;
|
|
9388
|
+
const query = (isShowingSelection ? "" : inputValue).trim();
|
|
9094
9389
|
const queryLower = query.toLowerCase();
|
|
9095
9390
|
const itemsProp = _itemsProp ?? [];
|
|
9096
9391
|
const sampleItem = itemsProp.flatMap((item) => isGroupItem(item) ? item.items : [item])[0];
|
|
@@ -9115,7 +9410,7 @@ var Combobox = ({
|
|
|
9115
9410
|
if (isCreatableItem(value2)) return value2.value;
|
|
9116
9411
|
return itemToStringLabelProp?.(value2) ?? toLabel(value2);
|
|
9117
9412
|
};
|
|
9118
|
-
const wrappedFilter = !allowCreate || !filter ? filter : (item, ...args) => isCreatableItem(item) || filter(item, ...args);
|
|
9413
|
+
const wrappedFilter = isShowingSelection ? () => true : !allowCreate || !filter ? filter : (item, ...args) => isCreatableItem(item) || filter(item, ...args);
|
|
9119
9414
|
const wrappedItemToStringValue = !allowCreate || !itemToStringValue ? itemToStringValue : (item) => isCreatableItem(item) ? item.value : itemToStringValue(item);
|
|
9120
9415
|
const isItemEqualToValue = (a, b) => {
|
|
9121
9416
|
if (isCreatableItem(a) || isCreatableItem(b)) return toValueKey(a) === toValueKey(b);
|
|
@@ -9150,10 +9445,34 @@ var Combobox = ({
|
|
|
9150
9445
|
closeReasonRef.current = "item-press";
|
|
9151
9446
|
onValueChange?.(next, event);
|
|
9152
9447
|
};
|
|
9153
|
-
const
|
|
9448
|
+
const [listElement, setListElement] = useState(null);
|
|
9449
|
+
const isGrouped = isGroupItems(allItems);
|
|
9450
|
+
const shouldVirtualize = !isGrouped && (virtualized === true || virtualized === "auto" && allItems.length > VIRTUALIZE_THRESHOLD);
|
|
9451
|
+
const matchesQuery = (item) => {
|
|
9452
|
+
if (isCreatableItem(item)) return true;
|
|
9453
|
+
if (!query) return true;
|
|
9454
|
+
if (filter) return !!filter(item, query, (value2) => itemToStringLabel(value2) ?? toLabel(value2));
|
|
9455
|
+
return (itemToStringLabel(item) ?? toLabel(item)).toLowerCase().includes(queryLower);
|
|
9456
|
+
};
|
|
9457
|
+
const virtualItems = shouldVirtualize ? items.filter(matchesQuery) : [];
|
|
9458
|
+
const virtualizer = useVirtualizer({
|
|
9459
|
+
count: virtualItems.length,
|
|
9460
|
+
getScrollElement: () => listElement,
|
|
9461
|
+
estimateSize: () => ESTIMATED_ROW_HEIGHT,
|
|
9462
|
+
overscan: 12,
|
|
9463
|
+
enabled: shouldVirtualize,
|
|
9464
|
+
// Seed a viewport height so rows paint on the first frame. Without it the list measures 0 until
|
|
9465
|
+
// the ResizeObserver fires (a frame later), which renders an empty popup on open.
|
|
9466
|
+
initialRect: { width: 0, height: ESTIMATED_LIST_HEIGHT }
|
|
9467
|
+
});
|
|
9468
|
+
const renderItem = (item, index) => {
|
|
9469
|
+
if (isCreatableItem(item)) {
|
|
9470
|
+
return /* @__PURE__ */ jsx(Combobox.Item, { index, value: item, icon: /* @__PURE__ */ jsx(Plus, {}), children: queryToCreateLabel(item.value) });
|
|
9471
|
+
}
|
|
9154
9472
|
return /* @__PURE__ */ jsx(
|
|
9155
9473
|
Combobox.Item,
|
|
9156
9474
|
{
|
|
9475
|
+
index,
|
|
9157
9476
|
value: item,
|
|
9158
9477
|
indicator,
|
|
9159
9478
|
icon: itemToIcon(item),
|
|
@@ -9162,13 +9481,20 @@ var Combobox = ({
|
|
|
9162
9481
|
}
|
|
9163
9482
|
);
|
|
9164
9483
|
};
|
|
9484
|
+
const rowStyle = (offset) => ({
|
|
9485
|
+
position: "absolute",
|
|
9486
|
+
top: 0,
|
|
9487
|
+
left: 0,
|
|
9488
|
+
width: "100%",
|
|
9489
|
+
transform: `translateY(${offset}px)`
|
|
9490
|
+
});
|
|
9165
9491
|
return /* @__PURE__ */ jsxs(
|
|
9166
9492
|
Combobox$1.Root,
|
|
9167
9493
|
{
|
|
9168
|
-
items,
|
|
9494
|
+
items: shouldVirtualize ? virtualItems : items,
|
|
9169
9495
|
value,
|
|
9170
9496
|
defaultValue,
|
|
9171
|
-
filter: wrappedFilter,
|
|
9497
|
+
filter: shouldVirtualize ? null : wrappedFilter,
|
|
9172
9498
|
onValueChange: handleOnValueChange,
|
|
9173
9499
|
itemToStringLabel,
|
|
9174
9500
|
isItemEqualToValue,
|
|
@@ -9178,6 +9504,12 @@ var Combobox = ({
|
|
|
9178
9504
|
inputRef,
|
|
9179
9505
|
inputValue,
|
|
9180
9506
|
onInputValueChange: handleInputValueChange,
|
|
9507
|
+
virtualized: shouldVirtualize,
|
|
9508
|
+
onItemHighlighted: (_, details) => {
|
|
9509
|
+
if (shouldVirtualize && details.reason === "keyboard") {
|
|
9510
|
+
virtualizer.scrollToIndex(details.index);
|
|
9511
|
+
}
|
|
9512
|
+
},
|
|
9181
9513
|
children: [
|
|
9182
9514
|
/* @__PURE__ */ jsxs(
|
|
9183
9515
|
ComboboxInputGroup,
|
|
@@ -9199,7 +9531,30 @@ var Combobox = ({
|
|
|
9199
9531
|
),
|
|
9200
9532
|
/* @__PURE__ */ jsxs(ComboboxContent, { children: [
|
|
9201
9533
|
/* @__PURE__ */ jsx(ComboboxEmpty, { children: empty }),
|
|
9202
|
-
/* @__PURE__ */ jsx(ComboboxList, { children:
|
|
9534
|
+
shouldVirtualize ? /* @__PURE__ */ jsx(ComboboxList, { ref: setListElement, children: /* @__PURE__ */ jsx(
|
|
9535
|
+
"div",
|
|
9536
|
+
{
|
|
9537
|
+
style: {
|
|
9538
|
+
position: "relative",
|
|
9539
|
+
width: "100%",
|
|
9540
|
+
height: `${virtualizer.getTotalSize()}px`
|
|
9541
|
+
},
|
|
9542
|
+
children: virtualizer.getVirtualItems().map((row) => {
|
|
9543
|
+
const item = virtualItems[row.index];
|
|
9544
|
+
if (item === void 0) return null;
|
|
9545
|
+
return /* @__PURE__ */ jsx(
|
|
9546
|
+
"div",
|
|
9547
|
+
{
|
|
9548
|
+
"data-index": row.index,
|
|
9549
|
+
ref: virtualizer.measureElement,
|
|
9550
|
+
style: rowStyle(row.start),
|
|
9551
|
+
children: renderItem(item, row.index)
|
|
9552
|
+
},
|
|
9553
|
+
row.key
|
|
9554
|
+
);
|
|
9555
|
+
})
|
|
9556
|
+
}
|
|
9557
|
+
) }) : /* @__PURE__ */ jsx(ComboboxList, { children: (itemOrGroup) => {
|
|
9203
9558
|
if (isCreatableItem(itemOrGroup))
|
|
9204
9559
|
return /* @__PURE__ */ jsx(Combobox.Item, { value: itemOrGroup, icon: /* @__PURE__ */ jsx(Plus, {}), children: queryToCreateLabel(itemOrGroup.value) });
|
|
9205
9560
|
if (isGroupItem(itemOrGroup)) {
|
|
@@ -9210,7 +9565,7 @@ var Combobox = ({
|
|
|
9210
9565
|
const groupLabel = groupToLabel(itemOrGroup);
|
|
9211
9566
|
return /* @__PURE__ */ jsxs(Combobox.Group, { items: itemOrGroup.items, children: [
|
|
9212
9567
|
groupLabel && /* @__PURE__ */ jsx(Combobox.GroupLabel, { children: groupLabel }),
|
|
9213
|
-
/* @__PURE__ */ jsx(Combobox.GroupList, { children: renderItem })
|
|
9568
|
+
/* @__PURE__ */ jsx(Combobox.GroupList, { children: (item) => renderItem(item) })
|
|
9214
9569
|
] });
|
|
9215
9570
|
}
|
|
9216
9571
|
return renderItem(itemOrGroup);
|