@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.js CHANGED
@@ -1,11 +1,11 @@
1
- import React26, { createContext, use, useCallback, useMemo, useRef, useState, useEffect, useContext, isValidElement, useId, useLayoutEffect, useSyncExternalStore, Children, Fragment as Fragment$1 } from 'react';
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
- import { completeAnyWord, autocompletion } from '@codemirror/autocomplete';
4
+ import { completeFromList, 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 { sfsql } from '@keboola/codemirror-lang-sfsql';
26
+ import { modeSpec, sfsql } from '@keboola/codemirror-lang-sfsql';
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';
@@ -352,6 +353,9 @@ Tooltip.Trigger = TooltipTrigger;
352
353
  // src/utils/createPrefixedString.ts
353
354
  var createPrefixedString = (prefix2) => (literals) => literals.reduce((acc, literal) => acc + literal, `${prefix2}-`);
354
355
 
356
+ // src/utils/escapeStringRegexp.ts
357
+ var escapeStringRegexp = (string) => string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
358
+
355
359
  // src/utils/mergeRefs.ts
356
360
  function setRef(ref, value) {
357
361
  if (typeof ref === "function") {
@@ -363,6 +367,21 @@ function setRef(ref, value) {
363
367
  var mergeRefs = (...refs) => {
364
368
  return (node) => refs.forEach((ref) => setRef(ref, node));
365
369
  };
370
+
371
+ // src/utils/isMac.ts
372
+ var getIsMac = () => {
373
+ if (typeof navigator !== "undefined" && "userAgentData" in navigator) {
374
+ const platform = navigator.userAgentData.platform;
375
+ if (typeof platform === "string") {
376
+ return platform.toLowerCase() === "macos";
377
+ }
378
+ }
379
+ if (typeof navigator !== "undefined" && typeof navigator.platform === "string") {
380
+ return navigator.platform.toUpperCase().indexOf("MAC") >= 0;
381
+ }
382
+ return false;
383
+ };
384
+ var isMac = getIsMac();
366
385
  var helpVariants = cva(["tw:m-0", "tw:text-xs"], {
367
386
  defaultVariants: {
368
387
  state: "default"
@@ -813,8 +832,6 @@ var ButtonToolbar = ({ value, children }) => {
813
832
  };
814
833
  var COMMANDS = [
815
834
  "run-operation",
816
- "source freshness",
817
- "docs generate",
818
835
  "run",
819
836
  "test",
820
837
  "seed",
@@ -823,9 +840,13 @@ var COMMANDS = [
823
840
  "ls",
824
841
  "compile",
825
842
  "build",
826
- "deps"
843
+ "deps",
844
+ "docs",
845
+ "source"
827
846
  ];
847
+ var SUBCOMMANDS = { docs: ["generate"], source: ["freshness"] };
828
848
  var ARGUMENTS = ["--select", "--exclude", "--selector", "--defer", "--resource-type", "-s"];
849
+ var SELECTORS = ["--select", "--exclude", "-s"];
829
850
  var METHODS = [
830
851
  "path:",
831
852
  "source:",
@@ -852,13 +873,59 @@ var FLAGS = [
852
873
  "--profile",
853
874
  "--full-refresh"
854
875
  ];
855
- var word = (list) => new RegExp(`^\\s+(?:${list.join("|")})\\b`, "i");
856
- var prefix = (list) => new RegExp(`^\\s+(?:${list.join("|")})`, "i");
876
+ var COMMAND_PHRASES = COMMANDS.flatMap((command) => {
877
+ const subcommands = SUBCOMMANDS[command];
878
+ return subcommands ? subcommands.map((subcommand) => `${command} ${subcommand}`) : [command];
879
+ });
880
+ var alternation = (list) => [...list].sort((a, b) => b.length - a.length).join("|");
881
+ var word = (list) => new RegExp(`^\\s+(?:${alternation(list)})\\b`, "i");
882
+ var prefix = (list) => new RegExp(`^\\s+(?:${alternation(list)})`, "i");
883
+ var WORD = /[\w:-]*$/;
884
+ var asOptions = (list, type) => list.map((label) => ({ label, type }));
885
+ var DBT = [{ label: "dbt", type: "keyword" }];
886
+ var COMMAND_OPTIONS = asOptions(COMMANDS, "keyword");
887
+ var SUBCOMMAND_OPTIONS = Object.fromEntries(
888
+ Object.entries(SUBCOMMANDS).map(([command, subcommands]) => [
889
+ command,
890
+ asOptions(subcommands, "keyword")
891
+ ])
892
+ );
893
+ var ARGUMENT_OPTIONS = asOptions(ARGUMENTS, "variable");
894
+ var FLAG_OPTIONS = asOptions(FLAGS, "constant");
895
+ var METHOD_OPTIONS = asOptions(METHODS, "property");
896
+ var optionsAfter = (words) => {
897
+ const [step, command, ...rest] = words;
898
+ if (step === void 0) return DBT;
899
+ if (step.toLowerCase() !== "dbt") return [];
900
+ if (command === void 0) return COMMAND_OPTIONS;
901
+ const subcommands = SUBCOMMAND_OPTIONS[command.toLowerCase()];
902
+ if (subcommands && rest.length === 0) return subcommands;
903
+ const lastArgument = rest.filter((it) => it.startsWith("-")).at(-1);
904
+ const selecting = lastArgument !== void 0 && SELECTORS.includes(lastArgument.toLowerCase());
905
+ return selecting ? [...ARGUMENT_OPTIONS, ...FLAG_OPTIONS, ...METHOD_OPTIONS] : [...ARGUMENT_OPTIONS, ...FLAG_OPTIONS];
906
+ };
907
+ var complete = (context) => {
908
+ const typed = context.matchBefore(WORD);
909
+ if (!typed?.text && !context.explicit) return null;
910
+ const from = typed?.from ?? context.pos;
911
+ const line = context.state.doc.lineAt(from);
912
+ const options = optionsAfter(
913
+ line.text.slice(0, from - line.from).split(/\s+/).filter(Boolean)
914
+ );
915
+ return options.length > 0 ? { from, options, validFor: WORD } : null;
916
+ };
857
917
  var dbt = simpleMode({
918
+ languageData: {
919
+ // `toggleComment` is a no-op without these, so Mod-/ silently does nothing.
920
+ commentTokens: { line: "--", block: { open: "/*", close: "*/" } },
921
+ autocomplete: complete
922
+ },
858
923
  start: [
859
924
  { regex: /"(?:[^\\]|\\.)*?(?:"|$)/, token: "string" },
860
- { regex: /dbt/, token: "keyword", sol: true },
861
- { regex: word(COMMANDS), token: "keyword" },
925
+ // Word-bounded, so `dbtX` is not a dbt step whose first word happens to
926
+ // colour — the same thing the completion source asks of it.
927
+ { regex: /dbt\b/, token: "keyword", sol: true },
928
+ { regex: word(COMMAND_PHRASES), token: "keyword" },
862
929
  { regex: word(ARGUMENTS), token: "variableName" },
863
930
  { regex: prefix(METHODS), token: "propertyName" },
864
931
  { regex: prefix(FLAGS), token: "attributeName" },
@@ -906,6 +973,8 @@ var KEYWORDS = {
906
973
  };
907
974
  var jsonnet = {
908
975
  name: "jsonnet",
976
+ // `toggleComment` is a no-op without these, so Mod-/ silently does nothing.
977
+ languageData: { commentTokens: { line: "//", block: { open: "/*", close: "*/" } } },
909
978
  startState: () => ({
910
979
  cComment: false,
911
980
  textBlock: false,
@@ -1122,6 +1191,20 @@ var buildCustomTheme = () => githubLightInit({
1122
1191
  selection: resolveToken("primary", "31 143 255")
1123
1192
  }
1124
1193
  });
1194
+ var sfsqlWithLanguageData = {
1195
+ ...sfsql,
1196
+ languageData: {
1197
+ ...sfsql.languageData,
1198
+ // `toggleComment` is a no-op without these, so Mod-/ silently does nothing.
1199
+ commentTokens: { line: "--", block: { open: "/*", close: "*/" } },
1200
+ autocomplete: completeFromList(
1201
+ Object.keys(modeSpec.keywords).map((label) => ({
1202
+ label: label.toUpperCase(),
1203
+ type: "keyword"
1204
+ }))
1205
+ )
1206
+ }
1207
+ };
1125
1208
  var getLanguageMode = (mode) => {
1126
1209
  switch (mode) {
1127
1210
  case void 0:
@@ -1138,7 +1221,7 @@ var getLanguageMode = (mode) => {
1138
1221
  case "text/x-mssql":
1139
1222
  return sql({ upperCaseKeywords: true, dialect: MSSQL });
1140
1223
  case "text/x-sfsql":
1141
- return StreamLanguage.define(sfsql);
1224
+ return StreamLanguage.define(sfsqlWithLanguageData);
1142
1225
  case "text/x-plsql":
1143
1226
  return sql({ upperCaseKeywords: true, dialect: PLSQL });
1144
1227
  case "text/x-rsrc":
@@ -1163,6 +1246,112 @@ var getLanguageMode = (mode) => {
1163
1246
  }
1164
1247
  };
1165
1248
  var shouldLintJson = (mode, readOnly) => mode === "application/json" && !readOnly;
1249
+ var setSearchEffect = StateEffect.define();
1250
+ function findMatches(doc, query) {
1251
+ if (!query) return [];
1252
+ const text = doc.toString();
1253
+ try {
1254
+ const regex = new RegExp(escapeStringRegexp(query), "gi");
1255
+ return Array.from(text.matchAll(regex)).map((match2, index) => {
1256
+ const from = match2.index;
1257
+ const to = from + match2[0].length;
1258
+ return { from, to, text: match2[0], localIndex: index };
1259
+ });
1260
+ } catch (e) {
1261
+ console.warn("Invalid search regex:", e);
1262
+ return [];
1263
+ }
1264
+ }
1265
+ function createDecorations(matches, selectedLocalIndex) {
1266
+ const decorations = matches.map((match2) => {
1267
+ const isSelected = match2.localIndex === selectedLocalIndex;
1268
+ const decoration = Decoration.mark({
1269
+ class: isSelected ? "cm-searchMatch-selected" : "cm-searchMatch"
1270
+ });
1271
+ return decoration.range(match2.from, match2.to);
1272
+ });
1273
+ return Decoration.set(decorations, true);
1274
+ }
1275
+ var searchField = StateField.define({
1276
+ create() {
1277
+ return {
1278
+ query: "",
1279
+ selectedMatchIndex: void 0,
1280
+ matches: [],
1281
+ decorations: Decoration.none
1282
+ };
1283
+ },
1284
+ update(state, tr) {
1285
+ let newState = state;
1286
+ for (const effect of tr.effects) {
1287
+ if (!effect.is(setSearchEffect)) {
1288
+ continue;
1289
+ }
1290
+ const { query, selectedMatchIndex } = effect.value;
1291
+ const matches = findMatches(tr.state.doc, query);
1292
+ const decorations = createDecorations(matches, selectedMatchIndex);
1293
+ newState = {
1294
+ query,
1295
+ selectedMatchIndex,
1296
+ matches,
1297
+ decorations
1298
+ };
1299
+ }
1300
+ if (tr.docChanged && newState.query) {
1301
+ const matches = findMatches(tr.state.doc, newState.query);
1302
+ const decorations = createDecorations(matches, newState.selectedMatchIndex);
1303
+ newState = {
1304
+ ...newState,
1305
+ matches,
1306
+ decorations
1307
+ };
1308
+ }
1309
+ return newState;
1310
+ },
1311
+ provide: (field) => EditorView.decorations.from(field, (state) => state.decorations)
1312
+ });
1313
+ var searchViewPlugin = ViewPlugin.fromClass(
1314
+ class {
1315
+ lastSelectedIndex;
1316
+ update(update) {
1317
+ const state = update.state.field(searchField);
1318
+ if (state.selectedMatchIndex !== void 0 && state.selectedMatchIndex !== this.lastSelectedIndex) {
1319
+ const match2 = state.matches[state.selectedMatchIndex];
1320
+ if (match2) {
1321
+ requestAnimationFrame(() => {
1322
+ update.view.dispatch({
1323
+ effects: EditorView.scrollIntoView(match2.from, { y: "center" })
1324
+ });
1325
+ });
1326
+ }
1327
+ this.lastSelectedIndex = state.selectedMatchIndex;
1328
+ }
1329
+ if (state.selectedMatchIndex === void 0) {
1330
+ this.lastSelectedIndex = void 0;
1331
+ }
1332
+ }
1333
+ }
1334
+ );
1335
+ var searchTheme = EditorView.baseTheme({
1336
+ ".cm-searchMatch": {
1337
+ backgroundColor: colors.blue[100]
1338
+ },
1339
+ ".cm-searchMatch > span": {
1340
+ color: `${colors.blue[600]} !important`
1341
+ },
1342
+ ".cm-searchMatch-selected": {
1343
+ backgroundColor: colors.blue[600]
1344
+ },
1345
+ ".cm-searchMatch-selected > span": {
1346
+ color: `${colors.white} !important`
1347
+ }
1348
+ });
1349
+ function searchExtension() {
1350
+ return [searchField, searchViewPlugin, searchTheme];
1351
+ }
1352
+ function updateSearch(view, query, selectedMatchIndex) {
1353
+ view.dispatch({ effects: setSearchEffect.of({ query, selectedMatchIndex }) });
1354
+ }
1166
1355
  var KbdSeparator = ({ className, ...props }) => /* @__PURE__ */ jsx("span", { ...props, className: cn("tw:text-xs tw:text-muted-foreground", className) });
1167
1356
  var KbdGroup = ({ ref, className, children, separator, ...props }) => {
1168
1357
  return /* @__PURE__ */ jsx(
@@ -1222,11 +1411,78 @@ Kbd.Group = KbdGroup;
1222
1411
  Kbd.Separator = KbdSeparator;
1223
1412
  var ToggleComponentHelp = () => /* @__PURE__ */ jsxs("div", { children: [
1224
1413
  "Use ",
1225
- /* @__PURE__ */ jsx(Kbd, { children: "Ctrl+/" }),
1226
- " or ",
1227
- /* @__PURE__ */ jsx(Kbd, { children: "Command+/" }),
1414
+ /* @__PURE__ */ jsx(Kbd, { children: isMac ? "Command+/" : "Ctrl+/" }),
1228
1415
  " to toggle comments."
1229
1416
  ] });
1417
+ 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);
1418
+ var crops = (style) => style.overflowX !== "visible" || style.overflowY !== "visible" || style.maskImage !== "none" || style.contentVisibility !== "visible" || /\b(paint|strict|content)\b/.test(style.contain);
1419
+ var findTooltipHost = (from) => {
1420
+ let outermostCropper = null;
1421
+ for (let node = from.parentElement; node && node !== document.body; node = node.parentElement) {
1422
+ const style = getComputedStyle(node);
1423
+ if (crops(style)) {
1424
+ outermostCropper = node;
1425
+ } else if (establishesFixedContainingBlock(style)) {
1426
+ return outermostCropper?.parentElement ?? node;
1427
+ }
1428
+ }
1429
+ return outermostCropper?.parentElement ?? null;
1430
+ };
1431
+ var tooltipsExtension = () => {
1432
+ const layer = new Compartment();
1433
+ return [
1434
+ layer.of(tooltips()),
1435
+ ViewPlugin.define((view) => {
1436
+ const scheduled = setTimeout(() => {
1437
+ if (!view.dom.isConnected) return;
1438
+ const parent = findTooltipHost(view.dom);
1439
+ if (parent) view.dispatch({ effects: layer.reconfigure(tooltips({ parent })) });
1440
+ });
1441
+ return { destroy: () => clearTimeout(scheduled) };
1442
+ })
1443
+ ];
1444
+ };
1445
+ var useTooltipsExtension = () => useMemo(tooltipsExtension, []);
1446
+
1447
+ // src/components/CodeEditor/variableCompletion.ts
1448
+ var BOOST = 2e3;
1449
+ var NAME = String.raw`[\w-]*`;
1450
+ var AT_CARET = new RegExp(String.raw`(?:\{{1,2} ?)?${NAME}$`);
1451
+ var NAME_AT_END = new RegExp(`${NAME}$`);
1452
+ var OPENING = /\{{1,2} ?$/;
1453
+ var CLOSING = /^ ?\}{1,2}/;
1454
+ var completeVariables = (variables = []) => {
1455
+ if (variables.length === 0) return () => null;
1456
+ const options = variables.map(({ name, value }) => ({
1457
+ // The bare name: `SqlEditor` filters prefix-only while `CodeEditor` filters
1458
+ // fuzzily, and this is the only spelling both accept whether or not the caret
1459
+ // has typed the braces. The braces are `displayLabel`, which is what renders.
1460
+ label: name,
1461
+ displayLabel: `{{${name}}}`,
1462
+ detail: value,
1463
+ type: "variable",
1464
+ boost: BOOST,
1465
+ apply: (view, completion, from, to) => {
1466
+ const { doc } = view.state;
1467
+ const line = doc.lineAt(from);
1468
+ const insert = `{{${completion.label}}}`;
1469
+ const opening = OPENING.exec(doc.sliceString(line.from, from));
1470
+ const closing = CLOSING.exec(doc.sliceString(to, line.to));
1471
+ const start = opening ? from - opening[0].length : from;
1472
+ const end = closing ? to + closing[0].length : to;
1473
+ view.dispatch({
1474
+ changes: { from: start, to: end, insert },
1475
+ selection: { anchor: start + insert.length }
1476
+ });
1477
+ }
1478
+ }));
1479
+ return (context) => {
1480
+ const match2 = context.matchBefore(AT_CARET);
1481
+ if (!match2?.text) return null;
1482
+ const [name = ""] = NAME_AT_END.exec(match2.text) ?? [];
1483
+ return { from: match2.to - name.length, options };
1484
+ };
1485
+ };
1230
1486
  var emptyArray = [];
1231
1487
  var emptyObject = {};
1232
1488
  var CodeEditor = ({
@@ -1236,8 +1492,12 @@ var CodeEditor = ({
1236
1492
  mode = "application/json",
1237
1493
  placeholder: placeholder$1 = "",
1238
1494
  ariaLabel,
1495
+ searchQuery,
1496
+ searchMatchIndex,
1239
1497
  readOnly = false,
1240
1498
  autocompletions = emptyArray,
1499
+ completionSource,
1500
+ variables,
1241
1501
  extraKeys = emptyObject,
1242
1502
  withToggleCommentHint = false,
1243
1503
  withAutofocus = false,
@@ -1252,6 +1512,26 @@ var CodeEditor = ({
1252
1512
  className
1253
1513
  }) => {
1254
1514
  const handleChange = useStableCallback((next) => onChange?.(next));
1515
+ const withSearch = searchQuery !== void 0;
1516
+ const editorRef = useRef(null);
1517
+ useEffect(() => {
1518
+ const view = editorRef.current?.view;
1519
+ if (view && withSearch) updateSearch(view, searchQuery, searchMatchIndex);
1520
+ }, [withSearch, searchQuery, searchMatchIndex]);
1521
+ const handleCreateEditor = useStableCallback((view) => {
1522
+ if (searchQuery) updateSearch(view, searchQuery, searchMatchIndex);
1523
+ });
1524
+ const extraKeysRef = useRef(extraKeys);
1525
+ useLayoutEffect(() => {
1526
+ extraKeysRef.current = extraKeys;
1527
+ });
1528
+ const boundKeysSignature = Object.keys(extraKeys).join("\n");
1529
+ const boundKeys = useMemo(
1530
+ () => boundKeysSignature.split("\n").filter(Boolean),
1531
+ [boundKeysSignature]
1532
+ );
1533
+ const tooltipLayer = useTooltipsExtension();
1534
+ const variableCompletion = useMemo(() => completeVariables(variables), [variables]);
1255
1535
  const customTheme = useThemedExtension("editor:syntax", buildCustomTheme);
1256
1536
  const baseTheme = useThemedExtension(
1257
1537
  "code-editor:chrome",
@@ -1263,31 +1543,43 @@ var CodeEditor = ({
1263
1543
  shouldLintJson(mode, readOnly) ? linter(jsonParseLinter()) : [],
1264
1544
  withLineWrapping ? EditorView.lineWrapping : [],
1265
1545
  EditorState.languageData.of(() => [
1266
- { autocomplete: completeAny ? completeAnyWord : autocompletions }
1546
+ { autocomplete: completionSource ?? (completeAny ? completeAnyWord : autocompletions) }
1267
1547
  ]),
1548
+ EditorState.languageData.of(() => [{ autocomplete: variableCompletion }]),
1268
1549
  keymap.of(
1269
- Object.entries(extraKeys).map(([key, run]) => ({
1550
+ boundKeys.map((key) => ({
1270
1551
  key,
1271
1552
  run: () => {
1272
- if (typeof run === "string") return run;
1273
- run();
1553
+ extraKeysRef.current[key]?.();
1274
1554
  return true;
1275
1555
  }
1276
1556
  }))
1277
1557
  ),
1278
- readOnly ? [] : autocompletion({ activateOnTyping: completeAny }),
1558
+ readOnly ? [] : [
1559
+ // CodeMirror's own default. Gating it on the props hid completions the
1560
+ // *language* supplies — dbt's vocabulary, Snowflake's keywords — behind
1561
+ // a shortcut, and macOS leaves no usable one. With nothing to offer, no
1562
+ // popup opens anyway.
1563
+ autocompletion()
1564
+ ],
1279
1565
  ariaLabel ? EditorView.contentAttributes.of({ "aria-label": ariaLabel }) : [],
1566
+ withSearch ? searchExtension() : [],
1567
+ tooltipLayer,
1280
1568
  baseTheme
1281
1569
  ];
1282
1570
  }, [
1283
1571
  autocompletions,
1572
+ completionSource,
1573
+ variableCompletion,
1284
1574
  completeAny,
1285
- extraKeys,
1575
+ boundKeys,
1286
1576
  mode,
1287
1577
  readOnly,
1288
1578
  withLineWrapping,
1289
1579
  baseTheme,
1290
- ariaLabel
1580
+ ariaLabel,
1581
+ withSearch,
1582
+ tooltipLayer
1291
1583
  ]);
1292
1584
  const extensions = useMemo(() => {
1293
1585
  return [
@@ -1306,9 +1598,12 @@ var CodeEditor = ({
1306
1598
  const config = useMemo(() => {
1307
1599
  return {
1308
1600
  ...buildBasicSetup(readOnly),
1309
- lineNumbers: withLineNumbers
1601
+ lineNumbers: withLineNumbers,
1602
+ // Mod-f opens CodeMirror's own panel, which would run a second query and a
1603
+ // second match count over the same document beside the caller's input.
1604
+ ...withSearch ? { searchKeymap: false } : null
1310
1605
  };
1311
- }, [withLineNumbers, readOnly]);
1606
+ }, [withLineNumbers, readOnly, withSearch]);
1312
1607
  const additionalButtons = renderAdditionalButtons?.(value);
1313
1608
  const hasToolbar = withCopyButton && !!value || !!additionalButtons;
1314
1609
  return (
@@ -1331,6 +1626,8 @@ var CodeEditor = ({
1331
1626
  /* @__PURE__ */ jsx(
1332
1627
  CodeMirror,
1333
1628
  {
1629
+ ref: editorRef,
1630
+ onCreateEditor: handleCreateEditor,
1334
1631
  theme: customTheme,
1335
1632
  value,
1336
1633
  onChange: handleChange,