@keboola/design 8.0.0 → 9.0.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/rjsf.cjs CHANGED
@@ -28,6 +28,7 @@ var codemirrorThemeGithub = require('@uiw/codemirror-theme-github');
28
28
  var codemirrorLangSfsql = require('@keboola/codemirror-lang-sfsql');
29
29
  var colors = require('@keboola/tailwind-config/colors');
30
30
  var simpleMode = require('@codemirror/legacy-modes/mode/simple-mode');
31
+ var state = require('@codemirror/state');
31
32
  var DOMPurify = require('dompurify');
32
33
  var reactCollapsible = require('@radix-ui/react-collapsible');
33
34
  var RadixTabs = require('@radix-ui/react-tabs');
@@ -386,6 +387,9 @@ Tooltip.Trigger = TooltipTrigger;
386
387
  // src/utils/createPrefixedString.ts
387
388
  var createPrefixedString = (prefix2) => (literals) => literals.reduce((acc, literal) => acc + literal, `${prefix2}-`);
388
389
 
390
+ // src/utils/escapeStringRegexp.ts
391
+ var escapeStringRegexp = (string) => string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
392
+
389
393
  // src/utils/mergeRefs.ts
390
394
  function setRef(ref, value) {
391
395
  if (typeof ref === "function") {
@@ -397,6 +401,21 @@ function setRef(ref, value) {
397
401
  var mergeRefs = (...refs) => {
398
402
  return (node) => refs.forEach((ref) => setRef(ref, node));
399
403
  };
404
+
405
+ // src/utils/isMac.ts
406
+ var getIsMac = () => {
407
+ if (typeof navigator !== "undefined" && "userAgentData" in navigator) {
408
+ const platform = navigator.userAgentData.platform;
409
+ if (typeof platform === "string") {
410
+ return platform.toLowerCase() === "macos";
411
+ }
412
+ }
413
+ if (typeof navigator !== "undefined" && typeof navigator.platform === "string") {
414
+ return navigator.platform.toUpperCase().indexOf("MAC") >= 0;
415
+ }
416
+ return false;
417
+ };
418
+ var isMac = getIsMac();
400
419
  var helpVariants = classVarianceAuthority.cva(["tw:m-0", "tw:text-xs"], {
401
420
  defaultVariants: {
402
421
  state: "default"
@@ -847,8 +866,6 @@ var ButtonToolbar = ({ value, children }) => {
847
866
  };
848
867
  var COMMANDS = [
849
868
  "run-operation",
850
- "source freshness",
851
- "docs generate",
852
869
  "run",
853
870
  "test",
854
871
  "seed",
@@ -857,9 +874,13 @@ var COMMANDS = [
857
874
  "ls",
858
875
  "compile",
859
876
  "build",
860
- "deps"
877
+ "deps",
878
+ "docs",
879
+ "source"
861
880
  ];
881
+ var SUBCOMMANDS = { docs: ["generate"], source: ["freshness"] };
862
882
  var ARGUMENTS = ["--select", "--exclude", "--selector", "--defer", "--resource-type", "-s"];
883
+ var SELECTORS = ["--select", "--exclude", "-s"];
863
884
  var METHODS = [
864
885
  "path:",
865
886
  "source:",
@@ -886,13 +907,59 @@ var FLAGS = [
886
907
  "--profile",
887
908
  "--full-refresh"
888
909
  ];
889
- var word = (list) => new RegExp(`^\\s+(?:${list.join("|")})\\b`, "i");
890
- var prefix = (list) => new RegExp(`^\\s+(?:${list.join("|")})`, "i");
910
+ var COMMAND_PHRASES = COMMANDS.flatMap((command) => {
911
+ const subcommands = SUBCOMMANDS[command];
912
+ return subcommands ? subcommands.map((subcommand) => `${command} ${subcommand}`) : [command];
913
+ });
914
+ var alternation = (list) => [...list].sort((a, b) => b.length - a.length).join("|");
915
+ var word = (list) => new RegExp(`^\\s+(?:${alternation(list)})\\b`, "i");
916
+ var prefix = (list) => new RegExp(`^\\s+(?:${alternation(list)})`, "i");
917
+ var WORD = /[\w:-]*$/;
918
+ var asOptions = (list, type) => list.map((label) => ({ label, type }));
919
+ var DBT = [{ label: "dbt", type: "keyword" }];
920
+ var COMMAND_OPTIONS = asOptions(COMMANDS, "keyword");
921
+ var SUBCOMMAND_OPTIONS = Object.fromEntries(
922
+ Object.entries(SUBCOMMANDS).map(([command, subcommands]) => [
923
+ command,
924
+ asOptions(subcommands, "keyword")
925
+ ])
926
+ );
927
+ var ARGUMENT_OPTIONS = asOptions(ARGUMENTS, "variable");
928
+ var FLAG_OPTIONS = asOptions(FLAGS, "constant");
929
+ var METHOD_OPTIONS = asOptions(METHODS, "property");
930
+ var optionsAfter = (words) => {
931
+ const [step, command, ...rest] = words;
932
+ if (step === void 0) return DBT;
933
+ if (step.toLowerCase() !== "dbt") return [];
934
+ if (command === void 0) return COMMAND_OPTIONS;
935
+ const subcommands = SUBCOMMAND_OPTIONS[command.toLowerCase()];
936
+ if (subcommands && rest.length === 0) return subcommands;
937
+ const lastArgument = rest.filter((it) => it.startsWith("-")).at(-1);
938
+ const selecting = lastArgument !== void 0 && SELECTORS.includes(lastArgument.toLowerCase());
939
+ return selecting ? [...ARGUMENT_OPTIONS, ...FLAG_OPTIONS, ...METHOD_OPTIONS] : [...ARGUMENT_OPTIONS, ...FLAG_OPTIONS];
940
+ };
941
+ var complete = (context) => {
942
+ const typed = context.matchBefore(WORD);
943
+ if (!typed?.text && !context.explicit) return null;
944
+ const from = typed?.from ?? context.pos;
945
+ const line = context.state.doc.lineAt(from);
946
+ const options = optionsAfter(
947
+ line.text.slice(0, from - line.from).split(/\s+/).filter(Boolean)
948
+ );
949
+ return options.length > 0 ? { from, options, validFor: WORD } : null;
950
+ };
891
951
  var dbt = simpleMode.simpleMode({
952
+ languageData: {
953
+ // `toggleComment` is a no-op without these, so Mod-/ silently does nothing.
954
+ commentTokens: { line: "--", block: { open: "/*", close: "*/" } },
955
+ autocomplete: complete
956
+ },
892
957
  start: [
893
958
  { regex: /"(?:[^\\]|\\.)*?(?:"|$)/, token: "string" },
894
- { regex: /dbt/, token: "keyword", sol: true },
895
- { regex: word(COMMANDS), token: "keyword" },
959
+ // Word-bounded, so `dbtX` is not a dbt step whose first word happens to
960
+ // colour — the same thing the completion source asks of it.
961
+ { regex: /dbt\b/, token: "keyword", sol: true },
962
+ { regex: word(COMMAND_PHRASES), token: "keyword" },
896
963
  { regex: word(ARGUMENTS), token: "variableName" },
897
964
  { regex: prefix(METHODS), token: "propertyName" },
898
965
  { regex: prefix(FLAGS), token: "attributeName" },
@@ -940,6 +1007,8 @@ var KEYWORDS = {
940
1007
  };
941
1008
  var jsonnet = {
942
1009
  name: "jsonnet",
1010
+ // `toggleComment` is a no-op without these, so Mod-/ silently does nothing.
1011
+ languageData: { commentTokens: { line: "//", block: { open: "/*", close: "*/" } } },
943
1012
  startState: () => ({
944
1013
  cComment: false,
945
1014
  textBlock: false,
@@ -1156,6 +1225,20 @@ var buildCustomTheme = () => codemirrorThemeGithub.githubLightInit({
1156
1225
  selection: resolveToken("primary", "31 143 255")
1157
1226
  }
1158
1227
  });
1228
+ var sfsqlWithLanguageData = {
1229
+ ...codemirrorLangSfsql.sfsql,
1230
+ languageData: {
1231
+ ...codemirrorLangSfsql.sfsql.languageData,
1232
+ // `toggleComment` is a no-op without these, so Mod-/ silently does nothing.
1233
+ commentTokens: { line: "--", block: { open: "/*", close: "*/" } },
1234
+ autocomplete: autocomplete.completeFromList(
1235
+ Object.keys(codemirrorLangSfsql.modeSpec.keywords).map((label) => ({
1236
+ label: label.toUpperCase(),
1237
+ type: "keyword"
1238
+ }))
1239
+ )
1240
+ }
1241
+ };
1159
1242
  var getLanguageMode = (mode) => {
1160
1243
  switch (mode) {
1161
1244
  case void 0:
@@ -1172,7 +1255,7 @@ var getLanguageMode = (mode) => {
1172
1255
  case "text/x-mssql":
1173
1256
  return langSql.sql({ upperCaseKeywords: true, dialect: langSql.MSSQL });
1174
1257
  case "text/x-sfsql":
1175
- return language.StreamLanguage.define(codemirrorLangSfsql.sfsql);
1258
+ return language.StreamLanguage.define(sfsqlWithLanguageData);
1176
1259
  case "text/x-plsql":
1177
1260
  return langSql.sql({ upperCaseKeywords: true, dialect: langSql.PLSQL });
1178
1261
  case "text/x-rsrc":
@@ -1197,6 +1280,112 @@ var getLanguageMode = (mode) => {
1197
1280
  }
1198
1281
  };
1199
1282
  var shouldLintJson = (mode, readOnly) => mode === "application/json" && !readOnly;
1283
+ var setSearchEffect = state.StateEffect.define();
1284
+ function findMatches(doc, query) {
1285
+ if (!query) return [];
1286
+ const text = doc.toString();
1287
+ try {
1288
+ const regex = new RegExp(escapeStringRegexp(query), "gi");
1289
+ return Array.from(text.matchAll(regex)).map((match2, index) => {
1290
+ const from = match2.index;
1291
+ const to = from + match2[0].length;
1292
+ return { from, to, text: match2[0], localIndex: index };
1293
+ });
1294
+ } catch (e) {
1295
+ console.warn("Invalid search regex:", e);
1296
+ return [];
1297
+ }
1298
+ }
1299
+ function createDecorations(matches, selectedLocalIndex) {
1300
+ const decorations = matches.map((match2) => {
1301
+ const isSelected = match2.localIndex === selectedLocalIndex;
1302
+ const decoration = view.Decoration.mark({
1303
+ class: isSelected ? "cm-searchMatch-selected" : "cm-searchMatch"
1304
+ });
1305
+ return decoration.range(match2.from, match2.to);
1306
+ });
1307
+ return view.Decoration.set(decorations, true);
1308
+ }
1309
+ var searchField = state.StateField.define({
1310
+ create() {
1311
+ return {
1312
+ query: "",
1313
+ selectedMatchIndex: void 0,
1314
+ matches: [],
1315
+ decorations: view.Decoration.none
1316
+ };
1317
+ },
1318
+ update(state, tr) {
1319
+ let newState = state;
1320
+ for (const effect of tr.effects) {
1321
+ if (!effect.is(setSearchEffect)) {
1322
+ continue;
1323
+ }
1324
+ const { query, selectedMatchIndex } = effect.value;
1325
+ const matches = findMatches(tr.state.doc, query);
1326
+ const decorations = createDecorations(matches, selectedMatchIndex);
1327
+ newState = {
1328
+ query,
1329
+ selectedMatchIndex,
1330
+ matches,
1331
+ decorations
1332
+ };
1333
+ }
1334
+ if (tr.docChanged && newState.query) {
1335
+ const matches = findMatches(tr.state.doc, newState.query);
1336
+ const decorations = createDecorations(matches, newState.selectedMatchIndex);
1337
+ newState = {
1338
+ ...newState,
1339
+ matches,
1340
+ decorations
1341
+ };
1342
+ }
1343
+ return newState;
1344
+ },
1345
+ provide: (field) => view.EditorView.decorations.from(field, (state) => state.decorations)
1346
+ });
1347
+ var searchViewPlugin = view.ViewPlugin.fromClass(
1348
+ class {
1349
+ lastSelectedIndex;
1350
+ update(update) {
1351
+ const state = update.state.field(searchField);
1352
+ if (state.selectedMatchIndex !== void 0 && state.selectedMatchIndex !== this.lastSelectedIndex) {
1353
+ const match2 = state.matches[state.selectedMatchIndex];
1354
+ if (match2) {
1355
+ requestAnimationFrame(() => {
1356
+ update.view.dispatch({
1357
+ effects: view.EditorView.scrollIntoView(match2.from, { y: "center" })
1358
+ });
1359
+ });
1360
+ }
1361
+ this.lastSelectedIndex = state.selectedMatchIndex;
1362
+ }
1363
+ if (state.selectedMatchIndex === void 0) {
1364
+ this.lastSelectedIndex = void 0;
1365
+ }
1366
+ }
1367
+ }
1368
+ );
1369
+ var searchTheme = view.EditorView.baseTheme({
1370
+ ".cm-searchMatch": {
1371
+ backgroundColor: colors__default.default.blue[100]
1372
+ },
1373
+ ".cm-searchMatch > span": {
1374
+ color: `${colors__default.default.blue[600]} !important`
1375
+ },
1376
+ ".cm-searchMatch-selected": {
1377
+ backgroundColor: colors__default.default.blue[600]
1378
+ },
1379
+ ".cm-searchMatch-selected > span": {
1380
+ color: `${colors__default.default.white} !important`
1381
+ }
1382
+ });
1383
+ function searchExtension() {
1384
+ return [searchField, searchViewPlugin, searchTheme];
1385
+ }
1386
+ function updateSearch(view, query, selectedMatchIndex) {
1387
+ view.dispatch({ effects: setSearchEffect.of({ query, selectedMatchIndex }) });
1388
+ }
1200
1389
  var KbdSeparator = ({ className, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("span", { ...props, className: cn("tw:text-xs tw:text-muted-foreground", className) });
1201
1390
  var KbdGroup = ({ ref, className, children, separator, ...props }) => {
1202
1391
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -1256,11 +1445,78 @@ Kbd.Group = KbdGroup;
1256
1445
  Kbd.Separator = KbdSeparator;
1257
1446
  var ToggleComponentHelp = () => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1258
1447
  "Use ",
1259
- /* @__PURE__ */ jsxRuntime.jsx(Kbd, { children: "Ctrl+/" }),
1260
- " or ",
1261
- /* @__PURE__ */ jsxRuntime.jsx(Kbd, { children: "Command+/" }),
1448
+ /* @__PURE__ */ jsxRuntime.jsx(Kbd, { children: isMac ? "Command+/" : "Ctrl+/" }),
1262
1449
  " to toggle comments."
1263
1450
  ] });
1451
+ 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);
1452
+ var crops = (style) => style.overflowX !== "visible" || style.overflowY !== "visible" || style.maskImage !== "none" || style.contentVisibility !== "visible" || /\b(paint|strict|content)\b/.test(style.contain);
1453
+ var findTooltipHost = (from) => {
1454
+ let outermostCropper = null;
1455
+ for (let node = from.parentElement; node && node !== document.body; node = node.parentElement) {
1456
+ const style = getComputedStyle(node);
1457
+ if (crops(style)) {
1458
+ outermostCropper = node;
1459
+ } else if (establishesFixedContainingBlock(style)) {
1460
+ return outermostCropper?.parentElement ?? node;
1461
+ }
1462
+ }
1463
+ return outermostCropper?.parentElement ?? null;
1464
+ };
1465
+ var tooltipsExtension = () => {
1466
+ const layer = new state.Compartment();
1467
+ return [
1468
+ layer.of(view.tooltips()),
1469
+ view.ViewPlugin.define((view$1) => {
1470
+ const scheduled = setTimeout(() => {
1471
+ if (!view$1.dom.isConnected) return;
1472
+ const parent = findTooltipHost(view$1.dom);
1473
+ if (parent) view$1.dispatch({ effects: layer.reconfigure(view.tooltips({ parent })) });
1474
+ });
1475
+ return { destroy: () => clearTimeout(scheduled) };
1476
+ })
1477
+ ];
1478
+ };
1479
+ var useTooltipsExtension = () => React26.useMemo(tooltipsExtension, []);
1480
+
1481
+ // src/components/CodeEditor/variableCompletion.ts
1482
+ var BOOST = 2e3;
1483
+ var NAME = String.raw`[\w-]*`;
1484
+ var AT_CARET = new RegExp(String.raw`(?:\{{1,2} ?)?${NAME}$`);
1485
+ var NAME_AT_END = new RegExp(`${NAME}$`);
1486
+ var OPENING = /\{{1,2} ?$/;
1487
+ var CLOSING = /^ ?\}{1,2}/;
1488
+ var completeVariables = (variables = []) => {
1489
+ if (variables.length === 0) return () => null;
1490
+ const options = variables.map(({ name, value }) => ({
1491
+ // The bare name: `SqlEditor` filters prefix-only while `CodeEditor` filters
1492
+ // fuzzily, and this is the only spelling both accept whether or not the caret
1493
+ // has typed the braces. The braces are `displayLabel`, which is what renders.
1494
+ label: name,
1495
+ displayLabel: `{{${name}}}`,
1496
+ detail: value,
1497
+ type: "variable",
1498
+ boost: BOOST,
1499
+ apply: (view, completion, from, to) => {
1500
+ const { doc } = view.state;
1501
+ const line = doc.lineAt(from);
1502
+ const insert = `{{${completion.label}}}`;
1503
+ const opening = OPENING.exec(doc.sliceString(line.from, from));
1504
+ const closing = CLOSING.exec(doc.sliceString(to, line.to));
1505
+ const start = opening ? from - opening[0].length : from;
1506
+ const end = closing ? to + closing[0].length : to;
1507
+ view.dispatch({
1508
+ changes: { from: start, to: end, insert },
1509
+ selection: { anchor: start + insert.length }
1510
+ });
1511
+ }
1512
+ }));
1513
+ return (context) => {
1514
+ const match2 = context.matchBefore(AT_CARET);
1515
+ if (!match2?.text) return null;
1516
+ const [name = ""] = NAME_AT_END.exec(match2.text) ?? [];
1517
+ return { from: match2.to - name.length, options };
1518
+ };
1519
+ };
1264
1520
  var emptyArray = [];
1265
1521
  var emptyObject = {};
1266
1522
  var CodeEditor = ({
@@ -1270,8 +1526,12 @@ var CodeEditor = ({
1270
1526
  mode = "application/json",
1271
1527
  placeholder = "",
1272
1528
  ariaLabel,
1529
+ searchQuery,
1530
+ searchMatchIndex,
1273
1531
  readOnly = false,
1274
1532
  autocompletions = emptyArray,
1533
+ completionSource,
1534
+ variables,
1275
1535
  extraKeys = emptyObject,
1276
1536
  withToggleCommentHint = false,
1277
1537
  withAutofocus = false,
@@ -1286,6 +1546,26 @@ var CodeEditor = ({
1286
1546
  className
1287
1547
  }) => {
1288
1548
  const handleChange = useStableCallback((next) => onChange?.(next));
1549
+ const withSearch = searchQuery !== void 0;
1550
+ const editorRef = React26.useRef(null);
1551
+ React26.useEffect(() => {
1552
+ const view = editorRef.current?.view;
1553
+ if (view && withSearch) updateSearch(view, searchQuery, searchMatchIndex);
1554
+ }, [withSearch, searchQuery, searchMatchIndex]);
1555
+ const handleCreateEditor = useStableCallback((view) => {
1556
+ if (searchQuery) updateSearch(view, searchQuery, searchMatchIndex);
1557
+ });
1558
+ const extraKeysRef = React26.useRef(extraKeys);
1559
+ React26.useLayoutEffect(() => {
1560
+ extraKeysRef.current = extraKeys;
1561
+ });
1562
+ const boundKeysSignature = Object.keys(extraKeys).join("\n");
1563
+ const boundKeys = React26.useMemo(
1564
+ () => boundKeysSignature.split("\n").filter(Boolean),
1565
+ [boundKeysSignature]
1566
+ );
1567
+ const tooltipLayer = useTooltipsExtension();
1568
+ const variableCompletion = React26.useMemo(() => completeVariables(variables), [variables]);
1289
1569
  const customTheme = useThemedExtension("editor:syntax", buildCustomTheme);
1290
1570
  const baseTheme = useThemedExtension(
1291
1571
  "code-editor:chrome",
@@ -1297,31 +1577,43 @@ var CodeEditor = ({
1297
1577
  shouldLintJson(mode, readOnly) ? lint.linter(langJson.jsonParseLinter()) : [],
1298
1578
  withLineWrapping ? view.EditorView.lineWrapping : [],
1299
1579
  CodeMirror.EditorState.languageData.of(() => [
1300
- { autocomplete: completeAny ? autocomplete.completeAnyWord : autocompletions }
1580
+ { autocomplete: completionSource ?? (completeAny ? autocomplete.completeAnyWord : autocompletions) }
1301
1581
  ]),
1582
+ CodeMirror.EditorState.languageData.of(() => [{ autocomplete: variableCompletion }]),
1302
1583
  view.keymap.of(
1303
- Object.entries(extraKeys).map(([key, run]) => ({
1584
+ boundKeys.map((key) => ({
1304
1585
  key,
1305
1586
  run: () => {
1306
- if (typeof run === "string") return run;
1307
- run();
1587
+ extraKeysRef.current[key]?.();
1308
1588
  return true;
1309
1589
  }
1310
1590
  }))
1311
1591
  ),
1312
- readOnly ? [] : autocomplete.autocompletion({ activateOnTyping: completeAny }),
1592
+ readOnly ? [] : [
1593
+ // CodeMirror's own default. Gating it on the props hid completions the
1594
+ // *language* supplies — dbt's vocabulary, Snowflake's keywords — behind
1595
+ // a shortcut, and macOS leaves no usable one. With nothing to offer, no
1596
+ // popup opens anyway.
1597
+ autocomplete.autocompletion()
1598
+ ],
1313
1599
  ariaLabel ? view.EditorView.contentAttributes.of({ "aria-label": ariaLabel }) : [],
1600
+ withSearch ? searchExtension() : [],
1601
+ tooltipLayer,
1314
1602
  baseTheme
1315
1603
  ];
1316
1604
  }, [
1317
1605
  autocompletions,
1606
+ completionSource,
1607
+ variableCompletion,
1318
1608
  completeAny,
1319
- extraKeys,
1609
+ boundKeys,
1320
1610
  mode,
1321
1611
  readOnly,
1322
1612
  withLineWrapping,
1323
1613
  baseTheme,
1324
- ariaLabel
1614
+ ariaLabel,
1615
+ withSearch,
1616
+ tooltipLayer
1325
1617
  ]);
1326
1618
  const extensions = React26.useMemo(() => {
1327
1619
  return [
@@ -1340,9 +1632,12 @@ var CodeEditor = ({
1340
1632
  const config = React26.useMemo(() => {
1341
1633
  return {
1342
1634
  ...buildBasicSetup(readOnly),
1343
- lineNumbers: withLineNumbers
1635
+ lineNumbers: withLineNumbers,
1636
+ // Mod-f opens CodeMirror's own panel, which would run a second query and a
1637
+ // second match count over the same document beside the caller's input.
1638
+ ...withSearch ? { searchKeymap: false } : null
1344
1639
  };
1345
- }, [withLineNumbers, readOnly]);
1640
+ }, [withLineNumbers, readOnly, withSearch]);
1346
1641
  const additionalButtons = renderAdditionalButtons?.(value);
1347
1642
  const hasToolbar = withCopyButton && !!value || !!additionalButtons;
1348
1643
  return (
@@ -1365,6 +1660,8 @@ var CodeEditor = ({
1365
1660
  /* @__PURE__ */ jsxRuntime.jsx(
1366
1661
  CodeMirror__default.default,
1367
1662
  {
1663
+ ref: editorRef,
1664
+ onCreateEditor: handleCreateEditor,
1368
1665
  theme: customTheme,
1369
1666
  value,
1370
1667
  onChange: handleChange,