@octaviaflow/core 3.1.0-beta.102 → 3.1.0-beta.103

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/index.cjs CHANGED
@@ -41339,11 +41339,140 @@ ToolCard.displayName = "ToolCard";
41339
41339
 
41340
41340
  // src/components/ExpressionField/ExpressionField.tsx
41341
41341
  var import_react188 = require("react");
41342
+
41343
+ // src/components/ExpressionField/parseExpression.ts
41344
+ var CALL_NAME_SOURCE = "(?:[A-Z_][A-Z0-9_]*|user\\.[A-Za-z_][A-Za-z0-9_]*)";
41345
+ var CALL_HEAD = new RegExp(`^(${CALL_NAME_SOURCE})\\s*\\(`);
41346
+ function wrappedRegions(value) {
41347
+ const out = [];
41348
+ let i = 0;
41349
+ while (i < value.length) {
41350
+ const open = value.indexOf("{{", i);
41351
+ if (open === -1) break;
41352
+ let depth = 0;
41353
+ let quote = null;
41354
+ let close = -1;
41355
+ for (let j = open; j < value.length; j++) {
41356
+ const ch = value[j];
41357
+ if (quote) {
41358
+ if (ch === quote) quote = null;
41359
+ continue;
41360
+ }
41361
+ if (ch === '"' || ch === "'") {
41362
+ quote = ch;
41363
+ continue;
41364
+ }
41365
+ if (ch === "{" && value[j + 1] === "{") {
41366
+ depth++;
41367
+ j++;
41368
+ continue;
41369
+ }
41370
+ if (ch === "}" && value[j + 1] === "}") {
41371
+ depth--;
41372
+ j++;
41373
+ if (depth === 0) {
41374
+ close = j;
41375
+ break;
41376
+ }
41377
+ }
41378
+ }
41379
+ if (close === -1) break;
41380
+ out.push({ start: open, end: close });
41381
+ i = close + 1;
41382
+ }
41383
+ return out;
41384
+ }
41385
+ function splitArguments(argsText) {
41386
+ const trimmed = argsText.trim();
41387
+ if (!trimmed) return [];
41388
+ const args = [];
41389
+ let current = "";
41390
+ let depth = 0;
41391
+ let braces = 0;
41392
+ let quote = null;
41393
+ for (let i = 0; i < trimmed.length; i++) {
41394
+ const ch = trimmed[i];
41395
+ if (quote) {
41396
+ current += ch;
41397
+ if (ch === quote) quote = null;
41398
+ continue;
41399
+ }
41400
+ if (ch === '"' || ch === "'") {
41401
+ quote = ch;
41402
+ current += ch;
41403
+ continue;
41404
+ }
41405
+ if (ch === "{" && trimmed[i + 1] === "{") {
41406
+ braces++;
41407
+ current += "{{";
41408
+ i++;
41409
+ continue;
41410
+ }
41411
+ if (ch === "}" && trimmed[i + 1] === "}") {
41412
+ braces--;
41413
+ current += "}}";
41414
+ i++;
41415
+ continue;
41416
+ }
41417
+ if (ch === "(") depth++;
41418
+ if (ch === ")") depth--;
41419
+ if (ch === "," && depth === 0 && braces === 0) {
41420
+ args.push(current.trim());
41421
+ current = "";
41422
+ continue;
41423
+ }
41424
+ current += ch;
41425
+ }
41426
+ if (current.trim()) args.push(current.trim());
41427
+ return args;
41428
+ }
41429
+ function parseCall(raw) {
41430
+ const inner = raw.startsWith("{{") && raw.endsWith("}}") ? raw.slice(2, -2).trim() : raw.trim();
41431
+ const head = CALL_HEAD.exec(inner);
41432
+ if (!head) return void 0;
41433
+ const open = inner.indexOf("(");
41434
+ const close = inner.lastIndexOf(")");
41435
+ if (open === -1 || close < open) return void 0;
41436
+ return { name: head[1], args: splitArguments(inner.slice(open + 1, close)) };
41437
+ }
41438
+ function formatCall(name, args) {
41439
+ return `{{${name}(${args.filter((a) => a.trim() !== "").join(", ")})}}`;
41440
+ }
41441
+ function parseExpression(value, pillEveryToken = false) {
41442
+ const segments = [];
41443
+ let cursor = 0;
41444
+ const pushText = (raw) => {
41445
+ if (raw) segments.push({ kind: "text", raw });
41446
+ };
41447
+ for (const { start, end } of wrappedRegions(value)) {
41448
+ pushText(value.slice(cursor, start));
41449
+ const raw = value.slice(start, end + 1);
41450
+ const call = parseCall(raw);
41451
+ if (call) {
41452
+ segments.push({ kind: "call", raw, name: call.name, args: call.args });
41453
+ } else if (raw.startsWith("{{ods://") || pillEveryToken) {
41454
+ segments.push({ kind: "ref", raw });
41455
+ } else {
41456
+ pushText(raw);
41457
+ }
41458
+ cursor = end + 1;
41459
+ }
41460
+ const tail = value.slice(cursor);
41461
+ let last = 0;
41462
+ for (const m of tail.matchAll(/\$\{[^}]+\}/g)) {
41463
+ pushText(tail.slice(last, m.index));
41464
+ segments.push({ kind: "ref", raw: m[0] });
41465
+ last = (m.index ?? 0) + m[0].length;
41466
+ }
41467
+ pushText(tail.slice(last));
41468
+ return segments;
41469
+ }
41470
+
41471
+ // src/components/ExpressionField/ExpressionField.tsx
41342
41472
  var import_jsx_runtime174 = require("react/jsx-runtime");
41343
- var REF_TOKEN = /(\{\{ods:\/\/[^}]+\}\}|\$\{[^}]+\})/g;
41344
- var ANY_TOKEN = /(\{\{[^}]+\}\}|\$\{[^}]+\})/g;
41345
- var CALL_TOKEN = /^\{\{\s*(?:[A-Z_][A-Z0-9_]*|user\.[A-Za-z_][A-Za-z0-9_]*)\s*\(/;
41346
41473
  var ZERO_WIDTH = "\u200B";
41474
+ var PILL = "ods-expression-field__pill";
41475
+ var CHIP = "ods-expression-field__chip";
41347
41476
  function shortReferenceLabel(token) {
41348
41477
  if (token.startsWith("{{ods://")) {
41349
41478
  const path = token.slice(8, -2);
@@ -41365,7 +41494,7 @@ function serialize(el) {
41365
41494
  }
41366
41495
  if (node.nodeType !== Node.ELEMENT_NODE) return;
41367
41496
  const elem = node;
41368
- if (elem.classList.contains("ods-expression-field__pill")) {
41497
+ if (elem.classList.contains(PILL) || elem.classList.contains(CHIP)) {
41369
41498
  out += elem.getAttribute("data-value") || "";
41370
41499
  } else if (elem.tagName === "BR") {
41371
41500
  out += "\n";
@@ -41396,6 +41525,13 @@ function insideCallArguments(textBeforeCaret) {
41396
41525
  }
41397
41526
  return depth > 0;
41398
41527
  }
41528
+ function argSummary(arg) {
41529
+ const trimmed = arg.trim();
41530
+ if (trimmed.startsWith("{{") || trimmed.startsWith("ods://") || trimmed.startsWith("${")) {
41531
+ return shortReferenceLabel(trimmed.startsWith("ods://") ? `{{${trimmed}}}` : trimmed);
41532
+ }
41533
+ return trimmed;
41534
+ }
41399
41535
  function ExpressionField({
41400
41536
  value,
41401
41537
  onChange,
@@ -41408,6 +41544,7 @@ function ExpressionField({
41408
41544
  className,
41409
41545
  style,
41410
41546
  resolveLabel,
41547
+ resolveCall,
41411
41548
  pillEveryToken = false,
41412
41549
  "aria-label": ariaLabel
41413
41550
  }) {
@@ -41416,28 +41553,42 @@ function ExpressionField({
41416
41553
  const generatedId = (0, import_react188.useId)();
41417
41554
  const fieldId = id ?? generatedId;
41418
41555
  const helperId = `${fieldId}-helper`;
41419
- const splitPattern = pillEveryToken ? ANY_TOKEN : REF_TOKEN;
41420
- const isPillToken = (0, import_react188.useCallback)(
41421
- (part) => {
41422
- if (CALL_TOKEN.test(part)) return false;
41423
- return pillEveryToken ? /^(\{\{[^}]+\}\}|\$\{[^}]+\})$/.test(part) : /^(\{\{ods:\/\/[^}]+\}\}|\$\{[^}]+\})$/.test(part);
41424
- },
41425
- [pillEveryToken]
41426
- );
41556
+ const [editing, setEditing] = (0, import_react188.useState)(null);
41427
41557
  const labelFor = (0, import_react188.useCallback)(
41428
41558
  (token) => resolveLabel?.(token) ?? shortReferenceLabel(token),
41429
41559
  [resolveLabel]
41430
41560
  );
41431
- const makePill = (0, import_react188.useCallback)(
41432
- (token) => {
41433
- const pill = document.createElement("span");
41434
- pill.className = "ods-expression-field__pill";
41435
- pill.contentEditable = "false";
41436
- pill.setAttribute("data-value", token);
41437
- pill.textContent = labelFor(token);
41438
- return pill;
41561
+ const callLabel = (0, import_react188.useCallback)(
41562
+ (name) => resolveCall?.(name)?.label ?? (name.startsWith("user.") ? name.slice(5) : name),
41563
+ [resolveCall]
41564
+ );
41565
+ const chipText = (0, import_react188.useCallback)(
41566
+ (name, args) => {
41567
+ const summary = args.map(argSummary).filter(Boolean).join(", ");
41568
+ return `${callLabel(name)}(${summary})`;
41439
41569
  },
41440
- [labelFor]
41570
+ [callLabel]
41571
+ );
41572
+ const makeToken = (0, import_react188.useCallback)(
41573
+ (segment) => {
41574
+ if (segment.kind === "text") return null;
41575
+ const el = document.createElement("span");
41576
+ el.contentEditable = "false";
41577
+ el.setAttribute("data-value", segment.raw);
41578
+ el.tabIndex = -1;
41579
+ if (segment.kind === "ref") {
41580
+ el.className = PILL;
41581
+ el.textContent = labelFor(segment.raw);
41582
+ el.title = segment.raw;
41583
+ } else {
41584
+ el.className = CHIP;
41585
+ el.setAttribute("data-call", segment.name);
41586
+ el.textContent = chipText(segment.name, segment.args);
41587
+ el.title = segment.raw;
41588
+ }
41589
+ return el;
41590
+ },
41591
+ [labelFor, chipText]
41441
41592
  );
41442
41593
  const render = (0, import_react188.useCallback)(
41443
41594
  (raw) => {
@@ -41445,17 +41596,19 @@ function ExpressionField({
41445
41596
  if (!el) return;
41446
41597
  el.innerHTML = "";
41447
41598
  if (!raw) return;
41448
- raw.split(new RegExp(splitPattern.source, "g")).forEach((part) => {
41449
- if (!part) return;
41450
- if (isPillToken(part)) {
41451
- el.appendChild(makePill(part));
41599
+ for (const segment of parseExpression(raw, pillEveryToken)) {
41600
+ if (segment.kind === "text") {
41601
+ el.appendChild(document.createTextNode(segment.raw));
41602
+ continue;
41603
+ }
41604
+ const token = makeToken(segment);
41605
+ if (token) {
41606
+ el.appendChild(token);
41452
41607
  el.appendChild(document.createTextNode(ZERO_WIDTH));
41453
- } else {
41454
- el.appendChild(document.createTextNode(part));
41455
41608
  }
41456
- });
41609
+ }
41457
41610
  },
41458
- [splitPattern, isPillToken, makePill]
41611
+ [pillEveryToken, makeToken]
41459
41612
  );
41460
41613
  (0, import_react188.useEffect)(() => {
41461
41614
  if (fromSelf.current) {
@@ -41476,9 +41629,8 @@ function ExpressionField({
41476
41629
  if (!el || !sel || sel.rangeCount === 0 || !el.contains(sel.anchorNode)) return "";
41477
41630
  const range = sel.getRangeAt(0).cloneRange();
41478
41631
  range.setStart(el, 0);
41479
- const fragment = range.cloneContents();
41480
41632
  const holder = document.createElement("div");
41481
- holder.appendChild(fragment);
41633
+ holder.appendChild(range.cloneContents());
41482
41634
  return serialize(holder);
41483
41635
  }, []);
41484
41636
  const insert = (0, import_react188.useCallback)(
@@ -41488,9 +41640,19 @@ function ExpressionField({
41488
41640
  const sel = window.getSelection();
41489
41641
  const hasCaret = !!sel && sel.rangeCount > 0 && el.contains(sel.anchorNode);
41490
41642
  const asArgument = hasCaret && insideCallArguments(textBeforeCaret());
41491
- const isCall = kind === "function" || /\(\s*\)?\s*$/.test(raw.trim());
41492
- const text = isCall ? raw : asArgument ? raw : raw.startsWith("ods://") ? `{{${raw}}}` : raw;
41493
- const node = !isCall && !asArgument && (text.startsWith("{{") || text.startsWith("${")) ? makePill(text) : document.createTextNode(text);
41643
+ const call = parseCall(raw);
41644
+ const isCall = kind === "function" || !!call;
41645
+ const text = isCall || asArgument ? raw : raw.startsWith("ods://") ? `{{${raw}}}` : raw;
41646
+ let node;
41647
+ if (asArgument) {
41648
+ node = document.createTextNode(text);
41649
+ } else if (isCall && call) {
41650
+ node = makeToken({ kind: "call", raw: text, name: call.name, args: call.args });
41651
+ } else if (text.startsWith("{{") || text.startsWith("${")) {
41652
+ node = makeToken({ kind: "ref", raw: text });
41653
+ } else {
41654
+ node = document.createTextNode(text);
41655
+ }
41494
41656
  const range = hasCaret && sel ? sel.getRangeAt(0) : (() => {
41495
41657
  const r = document.createRange();
41496
41658
  r.selectNodeContents(el);
@@ -41502,27 +41664,114 @@ function ExpressionField({
41502
41664
  const after = document.createTextNode(ZERO_WIDTH);
41503
41665
  range.setStartAfter(node);
41504
41666
  range.insertNode(after);
41505
- if (node.nodeType === Node.TEXT_NODE && isCall) {
41506
- const open = text.lastIndexOf("(");
41507
- const close = text.lastIndexOf(")");
41508
- if (open !== -1 && close === open + 1) {
41509
- range.setStart(node, open + 1);
41510
- range.collapse(true);
41511
- } else {
41512
- range.setStartAfter(after);
41513
- range.collapse(true);
41514
- }
41515
- } else {
41516
- range.setStartAfter(after);
41517
- range.collapse(true);
41518
- }
41667
+ range.setStartAfter(after);
41668
+ range.collapse(true);
41519
41669
  el.focus();
41520
41670
  const selection = window.getSelection();
41521
41671
  selection?.removeAllRanges();
41522
41672
  selection?.addRange(range);
41523
41673
  commit();
41674
+ if (isCall && call && node instanceof HTMLElement && call.args.length === 0) {
41675
+ openEditor(node);
41676
+ }
41677
+ },
41678
+ // eslint-disable-next-line react-hooks/exhaustive-deps
41679
+ [commit, disabled, makeToken, textBeforeCaret]
41680
+ );
41681
+ const selected = (0, import_react188.useRef)(null);
41682
+ const selectToken = (0, import_react188.useCallback)((token) => {
41683
+ selected.current = token;
41684
+ const range = document.createRange();
41685
+ range.selectNode(token);
41686
+ const sel = window.getSelection();
41687
+ sel?.removeAllRanges();
41688
+ sel?.addRange(range);
41689
+ fieldRef.current?.focus();
41690
+ }, []);
41691
+ const openEditor = (0, import_react188.useCallback)((chip) => {
41692
+ const raw = chip.getAttribute("data-value") ?? "";
41693
+ const parsed = parseCall(raw);
41694
+ if (!parsed) return;
41695
+ const box = chip.getBoundingClientRect();
41696
+ const host = fieldRef.current?.getBoundingClientRect();
41697
+ setEditing({
41698
+ el: chip,
41699
+ name: parsed.name,
41700
+ args: parsed.args.length ? parsed.args : [""],
41701
+ rect: {
41702
+ top: box.bottom - (host?.top ?? 0) + 4,
41703
+ left: Math.max(0, box.left - (host?.left ?? 0)),
41704
+ width: Math.max(260, box.width)
41705
+ }
41706
+ });
41707
+ }, []);
41708
+ const applyEditor = (0, import_react188.useCallback)(
41709
+ (args) => {
41710
+ if (!editing) return;
41711
+ const raw = formatCall(editing.name, args);
41712
+ editing.el.setAttribute("data-value", raw);
41713
+ editing.el.textContent = chipText(editing.name, args);
41714
+ editing.el.title = raw;
41715
+ setEditing(null);
41716
+ commit();
41717
+ fieldRef.current?.focus();
41718
+ },
41719
+ [editing, chipText, commit]
41720
+ );
41721
+ const handleClick = (0, import_react188.useCallback)(
41722
+ (e) => {
41723
+ const target = e.target.closest(`.${PILL}, .${CHIP}`);
41724
+ if (!target) return;
41725
+ selectToken(target);
41726
+ if (!disabled && target.classList.contains(CHIP) && e.detail >= 2) {
41727
+ openEditor(target);
41728
+ }
41524
41729
  },
41525
- [commit, disabled, makePill, textBeforeCaret]
41730
+ [disabled, openEditor, selectToken]
41731
+ );
41732
+ const handleCopy = (0, import_react188.useCallback)(
41733
+ (e, cut = false) => {
41734
+ const sel = window.getSelection();
41735
+ if (!sel || sel.rangeCount === 0) return;
41736
+ const holder = document.createElement("div");
41737
+ holder.appendChild(sel.getRangeAt(0).cloneContents());
41738
+ const token = selected.current;
41739
+ const text = serialize(holder) || (token && fieldRef.current?.contains(token) && sel.containsNode(token, true) ? token.getAttribute("data-value") ?? "" : "");
41740
+ if (!text) return;
41741
+ e.preventDefault();
41742
+ e.clipboardData.setData("text/plain", text);
41743
+ if (cut && !disabled) {
41744
+ if (serialize(holder)) sel.getRangeAt(0).deleteContents();
41745
+ else token?.remove();
41746
+ commit();
41747
+ }
41748
+ },
41749
+ [commit, disabled]
41750
+ );
41751
+ const handlePaste = (0, import_react188.useCallback)(
41752
+ (e) => {
41753
+ e.preventDefault();
41754
+ const text = e.clipboardData.getData("text/plain");
41755
+ if (!text) return;
41756
+ document.execCommand("insertText", false, text);
41757
+ const next = serialize(fieldRef.current);
41758
+ fromSelf.current = false;
41759
+ onChange(next);
41760
+ },
41761
+ [onChange]
41762
+ );
41763
+ const handleKeyDown = (0, import_react188.useCallback)(
41764
+ (e) => {
41765
+ if (e.key !== "Enter" || !e.metaKey) return;
41766
+ const sel = window.getSelection();
41767
+ const node = sel?.anchorNode;
41768
+ const chip = node instanceof HTMLElement ? node.closest(`.${CHIP}`) : node?.parentElement?.closest(`.${CHIP}`) ?? null;
41769
+ if (chip) {
41770
+ e.preventDefault();
41771
+ openEditor(chip);
41772
+ }
41773
+ },
41774
+ [openEditor]
41526
41775
  );
41527
41776
  const handleDragOver = (0, import_react188.useCallback)(
41528
41777
  (e) => {
@@ -41549,42 +41798,167 @@ function ExpressionField({
41549
41798
  },
41550
41799
  [disabled, insert]
41551
41800
  );
41552
- const handlePaste = (0, import_react188.useCallback)((e) => {
41553
- e.preventDefault();
41554
- const text = e.clipboardData.getData("text/plain");
41555
- document.execCommand("insertText", false, text);
41556
- }, []);
41801
+ const handleBlur = (0, import_react188.useCallback)(() => {
41802
+ const el = fieldRef.current;
41803
+ if (!el) return;
41804
+ const next = serialize(el);
41805
+ if (next !== value) {
41806
+ fromSelf.current = true;
41807
+ onChange(next);
41808
+ }
41809
+ if (!editing) render(next);
41810
+ }, [editing, onChange, render, value]);
41557
41811
  return /* @__PURE__ */ (0, import_jsx_runtime174.jsxs)("div", { className: cn("ods-expression-field", className), style, children: [
41558
41812
  label && /* @__PURE__ */ (0, import_jsx_runtime174.jsx)("label", { className: "ods-expression-field__label", htmlFor: fieldId, children: label }),
41559
- /* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
41560
- "div",
41561
- {
41562
- id: fieldId,
41563
- ref: fieldRef,
41564
- role: "textbox",
41565
- "aria-multiline": "true",
41566
- "aria-label": ariaLabel ?? (typeof label === "string" ? label : void 0),
41567
- "aria-describedby": helperText ? helperId : void 0,
41568
- "aria-disabled": disabled || void 0,
41569
- contentEditable: !disabled,
41570
- suppressContentEditableWarning: true,
41571
- "data-placeholder": placeholder,
41572
- className: cn(
41573
- "ods-expression-field__input",
41574
- disabled && "ods-expression-field__input--disabled"
41575
- ),
41576
- style: { minHeight: `${Math.max(1, rows) * 1.5 + 1}rem` },
41577
- onInput: commit,
41578
- onBlur: commit,
41579
- onDragOver: handleDragOver,
41580
- onDragLeave: handleDragLeave,
41581
- onDrop: handleDrop,
41582
- onPaste: handlePaste
41583
- }
41584
- ),
41813
+ /* @__PURE__ */ (0, import_jsx_runtime174.jsxs)("div", { className: "ods-expression-field__body", children: [
41814
+ /* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
41815
+ "div",
41816
+ {
41817
+ id: fieldId,
41818
+ ref: fieldRef,
41819
+ role: "textbox",
41820
+ "aria-multiline": "true",
41821
+ "aria-label": ariaLabel ?? (typeof label === "string" ? label : void 0),
41822
+ "aria-describedby": helperText ? helperId : void 0,
41823
+ "aria-disabled": disabled || void 0,
41824
+ contentEditable: !disabled,
41825
+ suppressContentEditableWarning: true,
41826
+ "data-placeholder": placeholder,
41827
+ className: cn(
41828
+ "ods-expression-field__input",
41829
+ disabled && "ods-expression-field__input--disabled"
41830
+ ),
41831
+ style: { minHeight: `${Math.max(1, rows) * 1.5 + 1}rem` },
41832
+ onInput: commit,
41833
+ onBlur: handleBlur,
41834
+ onClick: handleClick,
41835
+ onKeyDown: handleKeyDown,
41836
+ onCopy: handleCopy,
41837
+ onCut: (e) => handleCopy(e, true),
41838
+ onPaste: handlePaste,
41839
+ onDragOver: handleDragOver,
41840
+ onDragLeave: handleDragLeave,
41841
+ onDrop: handleDrop
41842
+ }
41843
+ ),
41844
+ editing && /* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
41845
+ CallEditor,
41846
+ {
41847
+ name: editing.name,
41848
+ label: callLabel(editing.name),
41849
+ initialArgs: editing.args,
41850
+ signature: resolveCall?.(editing.name),
41851
+ rect: editing.rect,
41852
+ onCancel: () => setEditing(null),
41853
+ onApply: applyEditor
41854
+ }
41855
+ )
41856
+ ] }),
41585
41857
  helperText && /* @__PURE__ */ (0, import_jsx_runtime174.jsx)("span", { id: helperId, className: "ods-expression-field__helper", children: helperText })
41586
41858
  ] });
41587
41859
  }
41860
+ function CallEditor({
41861
+ name,
41862
+ label,
41863
+ initialArgs,
41864
+ signature,
41865
+ rect,
41866
+ onApply,
41867
+ onCancel
41868
+ }) {
41869
+ const names = signature?.args ?? [];
41870
+ const [args, setArgs] = (0, import_react188.useState)(() => {
41871
+ const seeded = [...initialArgs];
41872
+ while (seeded.length < names.length) seeded.push("");
41873
+ return seeded.length ? seeded : [""];
41874
+ });
41875
+ const [raw, setRaw] = (0, import_react188.useState)(null);
41876
+ const panelRef = (0, import_react188.useRef)(null);
41877
+ (0, import_react188.useEffect)(() => {
41878
+ panelRef.current?.querySelector("input")?.focus();
41879
+ const onKey = (e) => {
41880
+ if (e.key === "Escape") onCancel();
41881
+ };
41882
+ document.addEventListener("keydown", onKey);
41883
+ return () => document.removeEventListener("keydown", onKey);
41884
+ }, [onCancel]);
41885
+ const setArg = (idx, next) => setArgs((prev) => prev.map((a, i) => i === idx ? next : a));
41886
+ const dropInto = (idx) => (e) => {
41887
+ const ref = e.dataTransfer.getData("application/x-fx-insert");
41888
+ if (!ref) return;
41889
+ e.preventDefault();
41890
+ setArg(idx, ref);
41891
+ };
41892
+ return /* @__PURE__ */ (0, import_jsx_runtime174.jsxs)(
41893
+ "div",
41894
+ {
41895
+ ref: panelRef,
41896
+ className: "ods-expression-field__editor",
41897
+ style: { top: rect.top, left: rect.left, width: rect.width },
41898
+ role: "dialog",
41899
+ "aria-label": `Edit ${label}`,
41900
+ children: [
41901
+ /* @__PURE__ */ (0, import_jsx_runtime174.jsxs)("div", { className: "ods-expression-field__editor-head", children: [
41902
+ /* @__PURE__ */ (0, import_jsx_runtime174.jsx)("code", { children: label }),
41903
+ /* @__PURE__ */ (0, import_jsx_runtime174.jsx)("button", { type: "button", className: "ods-expression-field__editor-link", onClick: () => setRaw(raw === null ? formatCall(name, args) : null), children: raw === null ? "Edit as text" : "Back to fields" })
41904
+ ] }),
41905
+ raw === null ? /* @__PURE__ */ (0, import_jsx_runtime174.jsxs)("div", { className: "ods-expression-field__editor-rows", children: [
41906
+ args.map((arg, idx) => (
41907
+ // eslint-disable-next-line react/no-array-index-key
41908
+ /* @__PURE__ */ (0, import_jsx_runtime174.jsxs)("label", { className: "ods-expression-field__editor-row", children: [
41909
+ /* @__PURE__ */ (0, import_jsx_runtime174.jsx)("span", { children: names[idx] ?? `argument ${idx + 1}` }),
41910
+ /* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
41911
+ "input",
41912
+ {
41913
+ value: arg,
41914
+ placeholder: "value, or drag one in",
41915
+ onChange: (e) => setArg(idx, e.target.value),
41916
+ onDragOver: (e) => {
41917
+ if (e.dataTransfer.types.includes("application/x-fx-insert")) e.preventDefault();
41918
+ },
41919
+ onDrop: dropInto(idx)
41920
+ }
41921
+ )
41922
+ ] }, idx)
41923
+ )),
41924
+ /* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
41925
+ "button",
41926
+ {
41927
+ type: "button",
41928
+ className: "ods-expression-field__editor-link",
41929
+ onClick: () => setArgs((prev) => [...prev, ""]),
41930
+ children: "+ argument"
41931
+ }
41932
+ )
41933
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
41934
+ "textarea",
41935
+ {
41936
+ className: "ods-expression-field__editor-raw",
41937
+ value: raw,
41938
+ rows: 3,
41939
+ onChange: (e) => setRaw(e.target.value)
41940
+ }
41941
+ ),
41942
+ /* @__PURE__ */ (0, import_jsx_runtime174.jsxs)("div", { className: "ods-expression-field__editor-foot", children: [
41943
+ /* @__PURE__ */ (0, import_jsx_runtime174.jsx)("button", { type: "button", onClick: onCancel, children: "Cancel" }),
41944
+ /* @__PURE__ */ (0, import_jsx_runtime174.jsx)(
41945
+ "button",
41946
+ {
41947
+ type: "button",
41948
+ className: "ods-expression-field__editor-primary",
41949
+ onClick: () => {
41950
+ if (raw === null) return onApply(args);
41951
+ const parsed = parseCall(raw);
41952
+ onApply(parsed ? parsed.args : splitArguments(raw));
41953
+ },
41954
+ children: "Apply"
41955
+ }
41956
+ )
41957
+ ] })
41958
+ ]
41959
+ }
41960
+ );
41961
+ }
41588
41962
 
41589
41963
  // src/components/TopBar/TopBar.tsx
41590
41964
  var import_jsx_runtime175 = require("react/jsx-runtime");