@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.js CHANGED
@@ -34425,13 +34425,143 @@ import {
34425
34425
  useCallback as useCallback50,
34426
34426
  useEffect as useEffect56,
34427
34427
  useId as useId61,
34428
- useRef as useRef50
34428
+ useRef as useRef50,
34429
+ useState as useState71
34429
34430
  } from "react";
34431
+
34432
+ // src/components/ExpressionField/parseExpression.ts
34433
+ var CALL_NAME_SOURCE = "(?:[A-Z_][A-Z0-9_]*|user\\.[A-Za-z_][A-Za-z0-9_]*)";
34434
+ var CALL_HEAD = new RegExp(`^(${CALL_NAME_SOURCE})\\s*\\(`);
34435
+ function wrappedRegions(value) {
34436
+ const out = [];
34437
+ let i = 0;
34438
+ while (i < value.length) {
34439
+ const open = value.indexOf("{{", i);
34440
+ if (open === -1) break;
34441
+ let depth = 0;
34442
+ let quote = null;
34443
+ let close = -1;
34444
+ for (let j = open; j < value.length; j++) {
34445
+ const ch = value[j];
34446
+ if (quote) {
34447
+ if (ch === quote) quote = null;
34448
+ continue;
34449
+ }
34450
+ if (ch === '"' || ch === "'") {
34451
+ quote = ch;
34452
+ continue;
34453
+ }
34454
+ if (ch === "{" && value[j + 1] === "{") {
34455
+ depth++;
34456
+ j++;
34457
+ continue;
34458
+ }
34459
+ if (ch === "}" && value[j + 1] === "}") {
34460
+ depth--;
34461
+ j++;
34462
+ if (depth === 0) {
34463
+ close = j;
34464
+ break;
34465
+ }
34466
+ }
34467
+ }
34468
+ if (close === -1) break;
34469
+ out.push({ start: open, end: close });
34470
+ i = close + 1;
34471
+ }
34472
+ return out;
34473
+ }
34474
+ function splitArguments(argsText) {
34475
+ const trimmed = argsText.trim();
34476
+ if (!trimmed) return [];
34477
+ const args = [];
34478
+ let current = "";
34479
+ let depth = 0;
34480
+ let braces = 0;
34481
+ let quote = null;
34482
+ for (let i = 0; i < trimmed.length; i++) {
34483
+ const ch = trimmed[i];
34484
+ if (quote) {
34485
+ current += ch;
34486
+ if (ch === quote) quote = null;
34487
+ continue;
34488
+ }
34489
+ if (ch === '"' || ch === "'") {
34490
+ quote = ch;
34491
+ current += ch;
34492
+ continue;
34493
+ }
34494
+ if (ch === "{" && trimmed[i + 1] === "{") {
34495
+ braces++;
34496
+ current += "{{";
34497
+ i++;
34498
+ continue;
34499
+ }
34500
+ if (ch === "}" && trimmed[i + 1] === "}") {
34501
+ braces--;
34502
+ current += "}}";
34503
+ i++;
34504
+ continue;
34505
+ }
34506
+ if (ch === "(") depth++;
34507
+ if (ch === ")") depth--;
34508
+ if (ch === "," && depth === 0 && braces === 0) {
34509
+ args.push(current.trim());
34510
+ current = "";
34511
+ continue;
34512
+ }
34513
+ current += ch;
34514
+ }
34515
+ if (current.trim()) args.push(current.trim());
34516
+ return args;
34517
+ }
34518
+ function parseCall(raw) {
34519
+ const inner = raw.startsWith("{{") && raw.endsWith("}}") ? raw.slice(2, -2).trim() : raw.trim();
34520
+ const head = CALL_HEAD.exec(inner);
34521
+ if (!head) return void 0;
34522
+ const open = inner.indexOf("(");
34523
+ const close = inner.lastIndexOf(")");
34524
+ if (open === -1 || close < open) return void 0;
34525
+ return { name: head[1], args: splitArguments(inner.slice(open + 1, close)) };
34526
+ }
34527
+ function formatCall(name, args) {
34528
+ return `{{${name}(${args.filter((a) => a.trim() !== "").join(", ")})}}`;
34529
+ }
34530
+ function parseExpression(value, pillEveryToken = false) {
34531
+ const segments = [];
34532
+ let cursor = 0;
34533
+ const pushText = (raw) => {
34534
+ if (raw) segments.push({ kind: "text", raw });
34535
+ };
34536
+ for (const { start, end } of wrappedRegions(value)) {
34537
+ pushText(value.slice(cursor, start));
34538
+ const raw = value.slice(start, end + 1);
34539
+ const call = parseCall(raw);
34540
+ if (call) {
34541
+ segments.push({ kind: "call", raw, name: call.name, args: call.args });
34542
+ } else if (raw.startsWith("{{ods://") || pillEveryToken) {
34543
+ segments.push({ kind: "ref", raw });
34544
+ } else {
34545
+ pushText(raw);
34546
+ }
34547
+ cursor = end + 1;
34548
+ }
34549
+ const tail = value.slice(cursor);
34550
+ let last = 0;
34551
+ for (const m of tail.matchAll(/\$\{[^}]+\}/g)) {
34552
+ pushText(tail.slice(last, m.index));
34553
+ segments.push({ kind: "ref", raw: m[0] });
34554
+ last = (m.index ?? 0) + m[0].length;
34555
+ }
34556
+ pushText(tail.slice(last));
34557
+ return segments;
34558
+ }
34559
+
34560
+ // src/components/ExpressionField/ExpressionField.tsx
34430
34561
  import { jsx as jsx147, jsxs as jsxs141 } from "react/jsx-runtime";
34431
- var REF_TOKEN = /(\{\{ods:\/\/[^}]+\}\}|\$\{[^}]+\})/g;
34432
- var ANY_TOKEN = /(\{\{[^}]+\}\}|\$\{[^}]+\})/g;
34433
- var CALL_TOKEN = /^\{\{\s*(?:[A-Z_][A-Z0-9_]*|user\.[A-Za-z_][A-Za-z0-9_]*)\s*\(/;
34434
34562
  var ZERO_WIDTH = "\u200B";
34563
+ var PILL = "ods-expression-field__pill";
34564
+ var CHIP = "ods-expression-field__chip";
34435
34565
  function shortReferenceLabel(token) {
34436
34566
  if (token.startsWith("{{ods://")) {
34437
34567
  const path = token.slice(8, -2);
@@ -34453,7 +34583,7 @@ function serialize(el) {
34453
34583
  }
34454
34584
  if (node.nodeType !== Node.ELEMENT_NODE) return;
34455
34585
  const elem = node;
34456
- if (elem.classList.contains("ods-expression-field__pill")) {
34586
+ if (elem.classList.contains(PILL) || elem.classList.contains(CHIP)) {
34457
34587
  out += elem.getAttribute("data-value") || "";
34458
34588
  } else if (elem.tagName === "BR") {
34459
34589
  out += "\n";
@@ -34484,6 +34614,13 @@ function insideCallArguments(textBeforeCaret) {
34484
34614
  }
34485
34615
  return depth > 0;
34486
34616
  }
34617
+ function argSummary(arg) {
34618
+ const trimmed = arg.trim();
34619
+ if (trimmed.startsWith("{{") || trimmed.startsWith("ods://") || trimmed.startsWith("${")) {
34620
+ return shortReferenceLabel(trimmed.startsWith("ods://") ? `{{${trimmed}}}` : trimmed);
34621
+ }
34622
+ return trimmed;
34623
+ }
34487
34624
  function ExpressionField({
34488
34625
  value,
34489
34626
  onChange,
@@ -34496,6 +34633,7 @@ function ExpressionField({
34496
34633
  className,
34497
34634
  style,
34498
34635
  resolveLabel,
34636
+ resolveCall,
34499
34637
  pillEveryToken = false,
34500
34638
  "aria-label": ariaLabel
34501
34639
  }) {
@@ -34504,28 +34642,42 @@ function ExpressionField({
34504
34642
  const generatedId = useId61();
34505
34643
  const fieldId = id ?? generatedId;
34506
34644
  const helperId = `${fieldId}-helper`;
34507
- const splitPattern = pillEveryToken ? ANY_TOKEN : REF_TOKEN;
34508
- const isPillToken = useCallback50(
34509
- (part) => {
34510
- if (CALL_TOKEN.test(part)) return false;
34511
- return pillEveryToken ? /^(\{\{[^}]+\}\}|\$\{[^}]+\})$/.test(part) : /^(\{\{ods:\/\/[^}]+\}\}|\$\{[^}]+\})$/.test(part);
34512
- },
34513
- [pillEveryToken]
34514
- );
34645
+ const [editing, setEditing] = useState71(null);
34515
34646
  const labelFor = useCallback50(
34516
34647
  (token) => resolveLabel?.(token) ?? shortReferenceLabel(token),
34517
34648
  [resolveLabel]
34518
34649
  );
34519
- const makePill = useCallback50(
34520
- (token) => {
34521
- const pill = document.createElement("span");
34522
- pill.className = "ods-expression-field__pill";
34523
- pill.contentEditable = "false";
34524
- pill.setAttribute("data-value", token);
34525
- pill.textContent = labelFor(token);
34526
- return pill;
34650
+ const callLabel = useCallback50(
34651
+ (name) => resolveCall?.(name)?.label ?? (name.startsWith("user.") ? name.slice(5) : name),
34652
+ [resolveCall]
34653
+ );
34654
+ const chipText = useCallback50(
34655
+ (name, args) => {
34656
+ const summary = args.map(argSummary).filter(Boolean).join(", ");
34657
+ return `${callLabel(name)}(${summary})`;
34658
+ },
34659
+ [callLabel]
34660
+ );
34661
+ const makeToken = useCallback50(
34662
+ (segment) => {
34663
+ if (segment.kind === "text") return null;
34664
+ const el = document.createElement("span");
34665
+ el.contentEditable = "false";
34666
+ el.setAttribute("data-value", segment.raw);
34667
+ el.tabIndex = -1;
34668
+ if (segment.kind === "ref") {
34669
+ el.className = PILL;
34670
+ el.textContent = labelFor(segment.raw);
34671
+ el.title = segment.raw;
34672
+ } else {
34673
+ el.className = CHIP;
34674
+ el.setAttribute("data-call", segment.name);
34675
+ el.textContent = chipText(segment.name, segment.args);
34676
+ el.title = segment.raw;
34677
+ }
34678
+ return el;
34527
34679
  },
34528
- [labelFor]
34680
+ [labelFor, chipText]
34529
34681
  );
34530
34682
  const render = useCallback50(
34531
34683
  (raw) => {
@@ -34533,17 +34685,19 @@ function ExpressionField({
34533
34685
  if (!el) return;
34534
34686
  el.innerHTML = "";
34535
34687
  if (!raw) return;
34536
- raw.split(new RegExp(splitPattern.source, "g")).forEach((part) => {
34537
- if (!part) return;
34538
- if (isPillToken(part)) {
34539
- el.appendChild(makePill(part));
34688
+ for (const segment of parseExpression(raw, pillEveryToken)) {
34689
+ if (segment.kind === "text") {
34690
+ el.appendChild(document.createTextNode(segment.raw));
34691
+ continue;
34692
+ }
34693
+ const token = makeToken(segment);
34694
+ if (token) {
34695
+ el.appendChild(token);
34540
34696
  el.appendChild(document.createTextNode(ZERO_WIDTH));
34541
- } else {
34542
- el.appendChild(document.createTextNode(part));
34543
34697
  }
34544
- });
34698
+ }
34545
34699
  },
34546
- [splitPattern, isPillToken, makePill]
34700
+ [pillEveryToken, makeToken]
34547
34701
  );
34548
34702
  useEffect56(() => {
34549
34703
  if (fromSelf.current) {
@@ -34564,9 +34718,8 @@ function ExpressionField({
34564
34718
  if (!el || !sel || sel.rangeCount === 0 || !el.contains(sel.anchorNode)) return "";
34565
34719
  const range = sel.getRangeAt(0).cloneRange();
34566
34720
  range.setStart(el, 0);
34567
- const fragment = range.cloneContents();
34568
34721
  const holder = document.createElement("div");
34569
- holder.appendChild(fragment);
34722
+ holder.appendChild(range.cloneContents());
34570
34723
  return serialize(holder);
34571
34724
  }, []);
34572
34725
  const insert = useCallback50(
@@ -34576,9 +34729,19 @@ function ExpressionField({
34576
34729
  const sel = window.getSelection();
34577
34730
  const hasCaret = !!sel && sel.rangeCount > 0 && el.contains(sel.anchorNode);
34578
34731
  const asArgument = hasCaret && insideCallArguments(textBeforeCaret());
34579
- const isCall = kind === "function" || /\(\s*\)?\s*$/.test(raw.trim());
34580
- const text = isCall ? raw : asArgument ? raw : raw.startsWith("ods://") ? `{{${raw}}}` : raw;
34581
- const node = !isCall && !asArgument && (text.startsWith("{{") || text.startsWith("${")) ? makePill(text) : document.createTextNode(text);
34732
+ const call = parseCall(raw);
34733
+ const isCall = kind === "function" || !!call;
34734
+ const text = isCall || asArgument ? raw : raw.startsWith("ods://") ? `{{${raw}}}` : raw;
34735
+ let node;
34736
+ if (asArgument) {
34737
+ node = document.createTextNode(text);
34738
+ } else if (isCall && call) {
34739
+ node = makeToken({ kind: "call", raw: text, name: call.name, args: call.args });
34740
+ } else if (text.startsWith("{{") || text.startsWith("${")) {
34741
+ node = makeToken({ kind: "ref", raw: text });
34742
+ } else {
34743
+ node = document.createTextNode(text);
34744
+ }
34582
34745
  const range = hasCaret && sel ? sel.getRangeAt(0) : (() => {
34583
34746
  const r = document.createRange();
34584
34747
  r.selectNodeContents(el);
@@ -34590,27 +34753,114 @@ function ExpressionField({
34590
34753
  const after = document.createTextNode(ZERO_WIDTH);
34591
34754
  range.setStartAfter(node);
34592
34755
  range.insertNode(after);
34593
- if (node.nodeType === Node.TEXT_NODE && isCall) {
34594
- const open = text.lastIndexOf("(");
34595
- const close = text.lastIndexOf(")");
34596
- if (open !== -1 && close === open + 1) {
34597
- range.setStart(node, open + 1);
34598
- range.collapse(true);
34599
- } else {
34600
- range.setStartAfter(after);
34601
- range.collapse(true);
34602
- }
34603
- } else {
34604
- range.setStartAfter(after);
34605
- range.collapse(true);
34606
- }
34756
+ range.setStartAfter(after);
34757
+ range.collapse(true);
34607
34758
  el.focus();
34608
34759
  const selection = window.getSelection();
34609
34760
  selection?.removeAllRanges();
34610
34761
  selection?.addRange(range);
34611
34762
  commit();
34763
+ if (isCall && call && node instanceof HTMLElement && call.args.length === 0) {
34764
+ openEditor(node);
34765
+ }
34766
+ },
34767
+ // eslint-disable-next-line react-hooks/exhaustive-deps
34768
+ [commit, disabled, makeToken, textBeforeCaret]
34769
+ );
34770
+ const selected = useRef50(null);
34771
+ const selectToken = useCallback50((token) => {
34772
+ selected.current = token;
34773
+ const range = document.createRange();
34774
+ range.selectNode(token);
34775
+ const sel = window.getSelection();
34776
+ sel?.removeAllRanges();
34777
+ sel?.addRange(range);
34778
+ fieldRef.current?.focus();
34779
+ }, []);
34780
+ const openEditor = useCallback50((chip) => {
34781
+ const raw = chip.getAttribute("data-value") ?? "";
34782
+ const parsed = parseCall(raw);
34783
+ if (!parsed) return;
34784
+ const box = chip.getBoundingClientRect();
34785
+ const host = fieldRef.current?.getBoundingClientRect();
34786
+ setEditing({
34787
+ el: chip,
34788
+ name: parsed.name,
34789
+ args: parsed.args.length ? parsed.args : [""],
34790
+ rect: {
34791
+ top: box.bottom - (host?.top ?? 0) + 4,
34792
+ left: Math.max(0, box.left - (host?.left ?? 0)),
34793
+ width: Math.max(260, box.width)
34794
+ }
34795
+ });
34796
+ }, []);
34797
+ const applyEditor = useCallback50(
34798
+ (args) => {
34799
+ if (!editing) return;
34800
+ const raw = formatCall(editing.name, args);
34801
+ editing.el.setAttribute("data-value", raw);
34802
+ editing.el.textContent = chipText(editing.name, args);
34803
+ editing.el.title = raw;
34804
+ setEditing(null);
34805
+ commit();
34806
+ fieldRef.current?.focus();
34807
+ },
34808
+ [editing, chipText, commit]
34809
+ );
34810
+ const handleClick = useCallback50(
34811
+ (e) => {
34812
+ const target = e.target.closest(`.${PILL}, .${CHIP}`);
34813
+ if (!target) return;
34814
+ selectToken(target);
34815
+ if (!disabled && target.classList.contains(CHIP) && e.detail >= 2) {
34816
+ openEditor(target);
34817
+ }
34818
+ },
34819
+ [disabled, openEditor, selectToken]
34820
+ );
34821
+ const handleCopy = useCallback50(
34822
+ (e, cut = false) => {
34823
+ const sel = window.getSelection();
34824
+ if (!sel || sel.rangeCount === 0) return;
34825
+ const holder = document.createElement("div");
34826
+ holder.appendChild(sel.getRangeAt(0).cloneContents());
34827
+ const token = selected.current;
34828
+ const text = serialize(holder) || (token && fieldRef.current?.contains(token) && sel.containsNode(token, true) ? token.getAttribute("data-value") ?? "" : "");
34829
+ if (!text) return;
34830
+ e.preventDefault();
34831
+ e.clipboardData.setData("text/plain", text);
34832
+ if (cut && !disabled) {
34833
+ if (serialize(holder)) sel.getRangeAt(0).deleteContents();
34834
+ else token?.remove();
34835
+ commit();
34836
+ }
34837
+ },
34838
+ [commit, disabled]
34839
+ );
34840
+ const handlePaste = useCallback50(
34841
+ (e) => {
34842
+ e.preventDefault();
34843
+ const text = e.clipboardData.getData("text/plain");
34844
+ if (!text) return;
34845
+ document.execCommand("insertText", false, text);
34846
+ const next = serialize(fieldRef.current);
34847
+ fromSelf.current = false;
34848
+ onChange(next);
34849
+ },
34850
+ [onChange]
34851
+ );
34852
+ const handleKeyDown = useCallback50(
34853
+ (e) => {
34854
+ if (e.key !== "Enter" || !e.metaKey) return;
34855
+ const sel = window.getSelection();
34856
+ const node = sel?.anchorNode;
34857
+ const chip = node instanceof HTMLElement ? node.closest(`.${CHIP}`) : node?.parentElement?.closest(`.${CHIP}`) ?? null;
34858
+ if (chip) {
34859
+ e.preventDefault();
34860
+ openEditor(chip);
34861
+ }
34612
34862
  },
34613
- [commit, disabled, makePill, textBeforeCaret]
34863
+ [openEditor]
34614
34864
  );
34615
34865
  const handleDragOver = useCallback50(
34616
34866
  (e) => {
@@ -34637,42 +34887,167 @@ function ExpressionField({
34637
34887
  },
34638
34888
  [disabled, insert]
34639
34889
  );
34640
- const handlePaste = useCallback50((e) => {
34641
- e.preventDefault();
34642
- const text = e.clipboardData.getData("text/plain");
34643
- document.execCommand("insertText", false, text);
34644
- }, []);
34890
+ const handleBlur = useCallback50(() => {
34891
+ const el = fieldRef.current;
34892
+ if (!el) return;
34893
+ const next = serialize(el);
34894
+ if (next !== value) {
34895
+ fromSelf.current = true;
34896
+ onChange(next);
34897
+ }
34898
+ if (!editing) render(next);
34899
+ }, [editing, onChange, render, value]);
34645
34900
  return /* @__PURE__ */ jsxs141("div", { className: cn("ods-expression-field", className), style, children: [
34646
34901
  label && /* @__PURE__ */ jsx147("label", { className: "ods-expression-field__label", htmlFor: fieldId, children: label }),
34647
- /* @__PURE__ */ jsx147(
34648
- "div",
34649
- {
34650
- id: fieldId,
34651
- ref: fieldRef,
34652
- role: "textbox",
34653
- "aria-multiline": "true",
34654
- "aria-label": ariaLabel ?? (typeof label === "string" ? label : void 0),
34655
- "aria-describedby": helperText ? helperId : void 0,
34656
- "aria-disabled": disabled || void 0,
34657
- contentEditable: !disabled,
34658
- suppressContentEditableWarning: true,
34659
- "data-placeholder": placeholder,
34660
- className: cn(
34661
- "ods-expression-field__input",
34662
- disabled && "ods-expression-field__input--disabled"
34663
- ),
34664
- style: { minHeight: `${Math.max(1, rows) * 1.5 + 1}rem` },
34665
- onInput: commit,
34666
- onBlur: commit,
34667
- onDragOver: handleDragOver,
34668
- onDragLeave: handleDragLeave,
34669
- onDrop: handleDrop,
34670
- onPaste: handlePaste
34671
- }
34672
- ),
34902
+ /* @__PURE__ */ jsxs141("div", { className: "ods-expression-field__body", children: [
34903
+ /* @__PURE__ */ jsx147(
34904
+ "div",
34905
+ {
34906
+ id: fieldId,
34907
+ ref: fieldRef,
34908
+ role: "textbox",
34909
+ "aria-multiline": "true",
34910
+ "aria-label": ariaLabel ?? (typeof label === "string" ? label : void 0),
34911
+ "aria-describedby": helperText ? helperId : void 0,
34912
+ "aria-disabled": disabled || void 0,
34913
+ contentEditable: !disabled,
34914
+ suppressContentEditableWarning: true,
34915
+ "data-placeholder": placeholder,
34916
+ className: cn(
34917
+ "ods-expression-field__input",
34918
+ disabled && "ods-expression-field__input--disabled"
34919
+ ),
34920
+ style: { minHeight: `${Math.max(1, rows) * 1.5 + 1}rem` },
34921
+ onInput: commit,
34922
+ onBlur: handleBlur,
34923
+ onClick: handleClick,
34924
+ onKeyDown: handleKeyDown,
34925
+ onCopy: handleCopy,
34926
+ onCut: (e) => handleCopy(e, true),
34927
+ onPaste: handlePaste,
34928
+ onDragOver: handleDragOver,
34929
+ onDragLeave: handleDragLeave,
34930
+ onDrop: handleDrop
34931
+ }
34932
+ ),
34933
+ editing && /* @__PURE__ */ jsx147(
34934
+ CallEditor,
34935
+ {
34936
+ name: editing.name,
34937
+ label: callLabel(editing.name),
34938
+ initialArgs: editing.args,
34939
+ signature: resolveCall?.(editing.name),
34940
+ rect: editing.rect,
34941
+ onCancel: () => setEditing(null),
34942
+ onApply: applyEditor
34943
+ }
34944
+ )
34945
+ ] }),
34673
34946
  helperText && /* @__PURE__ */ jsx147("span", { id: helperId, className: "ods-expression-field__helper", children: helperText })
34674
34947
  ] });
34675
34948
  }
34949
+ function CallEditor({
34950
+ name,
34951
+ label,
34952
+ initialArgs,
34953
+ signature,
34954
+ rect,
34955
+ onApply,
34956
+ onCancel
34957
+ }) {
34958
+ const names = signature?.args ?? [];
34959
+ const [args, setArgs] = useState71(() => {
34960
+ const seeded = [...initialArgs];
34961
+ while (seeded.length < names.length) seeded.push("");
34962
+ return seeded.length ? seeded : [""];
34963
+ });
34964
+ const [raw, setRaw] = useState71(null);
34965
+ const panelRef = useRef50(null);
34966
+ useEffect56(() => {
34967
+ panelRef.current?.querySelector("input")?.focus();
34968
+ const onKey = (e) => {
34969
+ if (e.key === "Escape") onCancel();
34970
+ };
34971
+ document.addEventListener("keydown", onKey);
34972
+ return () => document.removeEventListener("keydown", onKey);
34973
+ }, [onCancel]);
34974
+ const setArg = (idx, next) => setArgs((prev) => prev.map((a, i) => i === idx ? next : a));
34975
+ const dropInto = (idx) => (e) => {
34976
+ const ref = e.dataTransfer.getData("application/x-fx-insert");
34977
+ if (!ref) return;
34978
+ e.preventDefault();
34979
+ setArg(idx, ref);
34980
+ };
34981
+ return /* @__PURE__ */ jsxs141(
34982
+ "div",
34983
+ {
34984
+ ref: panelRef,
34985
+ className: "ods-expression-field__editor",
34986
+ style: { top: rect.top, left: rect.left, width: rect.width },
34987
+ role: "dialog",
34988
+ "aria-label": `Edit ${label}`,
34989
+ children: [
34990
+ /* @__PURE__ */ jsxs141("div", { className: "ods-expression-field__editor-head", children: [
34991
+ /* @__PURE__ */ jsx147("code", { children: label }),
34992
+ /* @__PURE__ */ jsx147("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" })
34993
+ ] }),
34994
+ raw === null ? /* @__PURE__ */ jsxs141("div", { className: "ods-expression-field__editor-rows", children: [
34995
+ args.map((arg, idx) => (
34996
+ // eslint-disable-next-line react/no-array-index-key
34997
+ /* @__PURE__ */ jsxs141("label", { className: "ods-expression-field__editor-row", children: [
34998
+ /* @__PURE__ */ jsx147("span", { children: names[idx] ?? `argument ${idx + 1}` }),
34999
+ /* @__PURE__ */ jsx147(
35000
+ "input",
35001
+ {
35002
+ value: arg,
35003
+ placeholder: "value, or drag one in",
35004
+ onChange: (e) => setArg(idx, e.target.value),
35005
+ onDragOver: (e) => {
35006
+ if (e.dataTransfer.types.includes("application/x-fx-insert")) e.preventDefault();
35007
+ },
35008
+ onDrop: dropInto(idx)
35009
+ }
35010
+ )
35011
+ ] }, idx)
35012
+ )),
35013
+ /* @__PURE__ */ jsx147(
35014
+ "button",
35015
+ {
35016
+ type: "button",
35017
+ className: "ods-expression-field__editor-link",
35018
+ onClick: () => setArgs((prev) => [...prev, ""]),
35019
+ children: "+ argument"
35020
+ }
35021
+ )
35022
+ ] }) : /* @__PURE__ */ jsx147(
35023
+ "textarea",
35024
+ {
35025
+ className: "ods-expression-field__editor-raw",
35026
+ value: raw,
35027
+ rows: 3,
35028
+ onChange: (e) => setRaw(e.target.value)
35029
+ }
35030
+ ),
35031
+ /* @__PURE__ */ jsxs141("div", { className: "ods-expression-field__editor-foot", children: [
35032
+ /* @__PURE__ */ jsx147("button", { type: "button", onClick: onCancel, children: "Cancel" }),
35033
+ /* @__PURE__ */ jsx147(
35034
+ "button",
35035
+ {
35036
+ type: "button",
35037
+ className: "ods-expression-field__editor-primary",
35038
+ onClick: () => {
35039
+ if (raw === null) return onApply(args);
35040
+ const parsed = parseCall(raw);
35041
+ onApply(parsed ? parsed.args : splitArguments(raw));
35042
+ },
35043
+ children: "Apply"
35044
+ }
35045
+ )
35046
+ ] })
35047
+ ]
35048
+ }
35049
+ );
35050
+ }
34676
35051
 
34677
35052
  // src/components/TopBar/TopBar.tsx
34678
35053
  import { jsx as jsx148, jsxs as jsxs142 } from "react/jsx-runtime";
@@ -34761,7 +35136,7 @@ import {
34761
35136
  forwardRef as forwardRef97,
34762
35137
  useCallback as useCallback51,
34763
35138
  useId as useId62,
34764
- useState as useState71
35139
+ useState as useState72
34765
35140
  } from "react";
34766
35141
  import {
34767
35142
  BuildToolIcon,
@@ -34807,7 +35182,7 @@ var TraceStep = forwardRef97(
34807
35182
  const baseId = providedId ?? `ods-trace-step-${reactId}`;
34808
35183
  const headId = `${baseId}-head`;
34809
35184
  const contentId = `${baseId}-content`;
34810
- const [uncontrolledOpen, setUncontrolledOpen] = useState71(defaultOpen);
35185
+ const [uncontrolledOpen, setUncontrolledOpen] = useState72(defaultOpen);
34811
35186
  const isControlled = controlledOpen !== void 0;
34812
35187
  const open = isControlled ? controlledOpen : uncontrolledOpen;
34813
35188
  const canExpand = !!children;
@@ -35166,7 +35541,7 @@ var UserCard = forwardRef99(function UserCard2({
35166
35541
  UserCard.displayName = "UserCard";
35167
35542
 
35168
35543
  // src/components/WorkflowEditor/WorkflowEditor.tsx
35169
- import { useCallback as useCallback53, useMemo as useMemo39, useState as useState72 } from "react";
35544
+ import { useCallback as useCallback53, useMemo as useMemo39, useState as useState73 } from "react";
35170
35545
 
35171
35546
  // src/hooks/useWorkflow.ts
35172
35547
  import { useCallback as useCallback52, useMemo as useMemo38, useReducer } from "react";
@@ -35934,10 +36309,10 @@ function WorkflowEditor({
35934
36309
  }) {
35935
36310
  const wf = useWorkflow(options2);
35936
36311
  const { workflow, dispatch, canUndo, canRedo, undo, redo } = wf;
35937
- const [uncontrolledLock, setUncontrolledLock] = useState72(false);
35938
- const [uncontrolledDrawer, setUncontrolledDrawer] = useState72(!!drawer);
35939
- const [uncontrolledHistory, setUncontrolledHistory] = useState72(false);
35940
- const [uncontrolledDebug, setUncontrolledDebug] = useState72(false);
36312
+ const [uncontrolledLock, setUncontrolledLock] = useState73(false);
36313
+ const [uncontrolledDrawer, setUncontrolledDrawer] = useState73(!!drawer);
36314
+ const [uncontrolledHistory, setUncontrolledHistory] = useState73(false);
36315
+ const [uncontrolledDebug, setUncontrolledDebug] = useState73(false);
35941
36316
  const isLockMode = controlledLockMode ?? uncontrolledLock;
35942
36317
  const isDrawerOpen = controlledDrawerOpen ?? uncontrolledDrawer;
35943
36318
  const isHistoryOpen = controlledHistoryOpen ?? uncontrolledHistory;
@@ -36028,7 +36403,7 @@ function WorkflowEditor({
36028
36403
  },
36029
36404
  [wf]
36030
36405
  );
36031
- const [canvasSize, setCanvasSize] = useState72({ w: 0, h: 0 });
36406
+ const [canvasSize, setCanvasSize] = useState73({ w: 0, h: 0 });
36032
36407
  const handleCanvasRef = useCallback53((node) => {
36033
36408
  if (!node) return;
36034
36409
  setCanvasSize({ w: node.clientWidth, h: node.clientHeight });
@@ -36278,7 +36653,7 @@ import {
36278
36653
  } from "@octaviaflow/icons";
36279
36654
 
36280
36655
  // src/hooks/useWorkflowRuntime.ts
36281
- import { useEffect as useEffect57, useState as useState73 } from "react";
36656
+ import { useEffect as useEffect57, useState as useState74 } from "react";
36282
36657
  function toMs2(t) {
36283
36658
  if (t == null) return null;
36284
36659
  return typeof t === "number" ? t : t.getTime();
@@ -36292,7 +36667,7 @@ function formatWorkflowRuntime(ms) {
36292
36667
  return h > 0 ? `${h}:${pad2(m)}:${pad2(sec)}` : `${pad2(m)}:${pad2(sec)}`;
36293
36668
  }
36294
36669
  function useWorkflowRuntime(startedAt, active) {
36295
- const [, force] = useState73(0);
36670
+ const [, force] = useState74(0);
36296
36671
  useEffect57(() => {
36297
36672
  if (!active || startedAt == null) return;
36298
36673
  if (typeof window === "undefined") return;
@@ -36673,10 +37048,10 @@ function WorkflowHeaderExpanded({
36673
37048
  }
36674
37049
 
36675
37050
  // src/hooks/useBanner.ts
36676
- import { useCallback as useCallback54, useEffect as useEffect58, useRef as useRef51, useState as useState74 } from "react";
37051
+ import { useCallback as useCallback54, useEffect as useEffect58, useRef as useRef51, useState as useState75 } from "react";
36677
37052
  function useBanner(options2 = {}) {
36678
37053
  const { defaultOpen = false, autoDismissAfter, onDismiss } = options2;
36679
- const [open, setOpen] = useState74(defaultOpen);
37054
+ const [open, setOpen] = useState75(defaultOpen);
36680
37055
  const timerRef = useRef51(null);
36681
37056
  const onDismissRef = useRef51(onDismiss);
36682
37057
  useEffect58(() => {
@@ -36721,7 +37096,7 @@ function useBanner(options2 = {}) {
36721
37096
  }
36722
37097
 
36723
37098
  // src/hooks/useCallout.ts
36724
- import { useCallback as useCallback55, useEffect as useEffect59, useState as useState75 } from "react";
37099
+ import { useCallback as useCallback55, useEffect as useEffect59, useState as useState76 } from "react";
36725
37100
  var STORAGE_PREFIX = "ods:callout:dismissed:";
36726
37101
  function readPersisted(key) {
36727
37102
  if (!key || typeof window === "undefined") return false;
@@ -36744,7 +37119,7 @@ function writePersisted(key, value) {
36744
37119
  }
36745
37120
  function useCallout(options2 = {}) {
36746
37121
  const { defaultOpen = true, persistKey, onDismiss } = options2;
36747
- const [open, setOpen] = useState75(defaultOpen);
37122
+ const [open, setOpen] = useState76(defaultOpen);
36748
37123
  useEffect59(() => {
36749
37124
  if (persistKey && readPersisted(persistKey)) {
36750
37125
  setOpen(false);
@@ -36845,7 +37220,7 @@ function breakpointMinWidth(name) {
36845
37220
  }
36846
37221
 
36847
37222
  // src/hooks/useRating.ts
36848
- import { useCallback as useCallback56, useState as useState76 } from "react";
37223
+ import { useCallback as useCallback56, useState as useState77 } from "react";
36849
37224
  function useRating({
36850
37225
  value: controlled,
36851
37226
  defaultValue = 0,
@@ -36853,7 +37228,7 @@ function useRating({
36853
37228
  step = 1,
36854
37229
  onChange
36855
37230
  } = {}) {
36856
- const [internal, setInternal] = useState76(defaultValue);
37231
+ const [internal, setInternal] = useState77(defaultValue);
36857
37232
  const value = controlled ?? internal;
36858
37233
  const isControlled = controlled !== void 0;
36859
37234
  const clamp2 = useCallback56(
@@ -36882,7 +37257,7 @@ function useBinaryRating({
36882
37257
  defaultValue = null,
36883
37258
  onChange
36884
37259
  } = {}) {
36885
- const [internal, setInternal] = useState76(defaultValue);
37260
+ const [internal, setInternal] = useState77(defaultValue);
36886
37261
  const value = controlled ?? internal;
36887
37262
  const isControlled = controlled !== void 0;
36888
37263
  const set = useCallback56(
@@ -36904,13 +37279,13 @@ function useBinaryRating({
36904
37279
  }
36905
37280
 
36906
37281
  // src/hooks/useTimeline.ts
36907
- import { useCallback as useCallback57, useState as useState77 } from "react";
37282
+ import { useCallback as useCallback57, useState as useState78 } from "react";
36908
37283
  function useTimeline({
36909
37284
  items: controlled,
36910
37285
  defaultItems = [],
36911
37286
  onChange
36912
37287
  } = {}) {
36913
- const [internal, setInternal] = useState77(defaultItems);
37288
+ const [internal, setInternal] = useState78(defaultItems);
36914
37289
  const items = controlled ?? internal;
36915
37290
  const isControlled = controlled !== void 0;
36916
37291
  const commit = useCallback57(
@@ -36970,7 +37345,7 @@ function useTimeline({
36970
37345
  }
36971
37346
 
36972
37347
  // src/hooks/useTraceTimeline.ts
36973
- import { useCallback as useCallback58, useRef as useRef52, useState as useState78 } from "react";
37348
+ import { useCallback as useCallback58, useRef as useRef52, useState as useState79 } from "react";
36974
37349
  var GLOBAL_SEQ = 0;
36975
37350
  var generateId = () => `ods-trace-${Date.now().toString(36)}-${(GLOBAL_SEQ++).toString(36)}`;
36976
37351
  function useTraceTimeline({
@@ -36978,7 +37353,7 @@ function useTraceTimeline({
36978
37353
  defaultSteps = [],
36979
37354
  onChange
36980
37355
  } = {}) {
36981
- const [internal, setInternal] = useState78(defaultSteps);
37356
+ const [internal, setInternal] = useState79(defaultSteps);
36982
37357
  const steps = controlled ?? internal;
36983
37358
  const isControlled = controlled !== void 0;
36984
37359
  const stepsRef = useRef52(steps);
@@ -38141,7 +38516,7 @@ DeliveryFrequencyControl.displayName = "DeliveryFrequencyControl";
38141
38516
 
38142
38517
  // src/notifications/components/EventCatalogPicker/EventCatalogPicker.tsx
38143
38518
  import { SearchIcon as SearchIcon16 } from "@octaviaflow/icons";
38144
- import { useId as useId67, useMemo as useMemo41, useState as useState79 } from "react";
38519
+ import { useId as useId67, useMemo as useMemo41, useState as useState80 } from "react";
38145
38520
 
38146
38521
  // src/notifications/components/meta.tsx
38147
38522
  import {
@@ -38247,8 +38622,8 @@ function EventCatalogPicker({
38247
38622
  }) {
38248
38623
  const reactId = useId67();
38249
38624
  const baseId = id ?? `${reactId}-events`;
38250
- const [query, setQuery] = useState79("");
38251
- const [collapsed, setCollapsed] = useState79({});
38625
+ const [query, setQuery] = useState80("");
38626
+ const [collapsed, setCollapsed] = useState80({});
38252
38627
  const selected = useMemo41(() => new Set(value), [value]);
38253
38628
  const filtered = useMemo41(() => {
38254
38629
  const q2 = query.trim().toLowerCase();
@@ -38394,7 +38769,7 @@ EventCatalogPicker.displayName = "EventCatalogPicker";
38394
38769
 
38395
38770
  // src/notifications/components/NotificationBell/NotificationBell.tsx
38396
38771
  import { NotificationIcon as NotificationIcon2 } from "@octaviaflow/icons";
38397
- import { useCallback as useCallback59, useState as useState81 } from "react";
38772
+ import { useCallback as useCallback59, useState as useState82 } from "react";
38398
38773
 
38399
38774
  // src/notifications/engine/inbox.ts
38400
38775
  function resolveInboxAction(item, catalog) {
@@ -38549,7 +38924,7 @@ NotificationDetailDialog.displayName = "NotificationDetailDialog";
38549
38924
 
38550
38925
  // src/notifications/components/NotificationInbox/NotificationInbox.tsx
38551
38926
  import { ArchiveIcon as ArchiveIcon3, CheckmarkIcon as CheckmarkIcon16, NotificationOffIcon } from "@octaviaflow/icons";
38552
- import { useMemo as useMemo42, useState as useState80 } from "react";
38927
+ import { useMemo as useMemo42, useState as useState81 } from "react";
38553
38928
  import { Fragment as Fragment66, jsx as jsx161, jsxs as jsxs153 } from "react/jsx-runtime";
38554
38929
  var FILTER_OPTIONS = [
38555
38930
  { value: "all", label: "All" },
@@ -38576,13 +38951,13 @@ function NotificationInbox({
38576
38951
  now: now2,
38577
38952
  className
38578
38953
  }) {
38579
- const [internalFilter, setInternalFilter] = useState80(defaultFilter2);
38954
+ const [internalFilter, setInternalFilter] = useState81(defaultFilter2);
38580
38955
  const activeFilter = filter ?? internalFilter;
38581
38956
  const setFilter = (f) => {
38582
38957
  if (filter === void 0) setInternalFilter(f);
38583
38958
  onFilterChange?.(f);
38584
38959
  };
38585
- const [detailItem, setDetailItem] = useState80(null);
38960
+ const [detailItem, setDetailItem] = useState81(null);
38586
38961
  const unread = countUnread(items);
38587
38962
  const visible = useMemo42(() => {
38588
38963
  const list = filterInbox(items, activeFilter);
@@ -38749,7 +39124,7 @@ function NotificationBell({
38749
39124
  const unread = unreadCount ?? countUnread(items);
38750
39125
  const hasUnread = unread > 0;
38751
39126
  const countLabel = unread > 99 ? "99+" : String(unread);
38752
- const [internalOpen, setInternalOpen] = useState81(false);
39127
+ const [internalOpen, setInternalOpen] = useState82(false);
38753
39128
  const isOpen = open ?? internalOpen;
38754
39129
  const setOpen = useCallback59(
38755
39130
  (next) => {
@@ -38758,7 +39133,7 @@ function NotificationBell({
38758
39133
  },
38759
39134
  [open, onOpenChange]
38760
39135
  );
38761
- const [detailItem, setDetailItem] = useState81(null);
39136
+ const [detailItem, setDetailItem] = useState82(null);
38762
39137
  const handleOpenItem = (item, action) => {
38763
39138
  setOpen(false);
38764
39139
  if (action.kind === "modal") setDetailItem(item);
@@ -38958,7 +39333,7 @@ NotificationChannelCard.displayName = "NotificationChannelCard";
38958
39333
 
38959
39334
  // src/notifications/components/NotificationChannelDialog/NotificationChannelDialog.tsx
38960
39335
  import { SendIcon as SendIcon2 } from "@octaviaflow/icons";
38961
- import { useEffect as useEffect61, useMemo as useMemo43, useState as useState83 } from "react";
39336
+ import { useEffect as useEffect61, useMemo as useMemo43, useState as useState84 } from "react";
38962
39337
 
38963
39338
  // src/notifications/engine/channels.ts
38964
39339
  var CONNECTED_CHANNEL_TYPES = [
@@ -39064,7 +39439,7 @@ function draftFromChannel(channel, fallbackType = "slack") {
39064
39439
 
39065
39440
  // src/notifications/components/NotificationChannelForm/NotificationChannelForm.tsx
39066
39441
  import { AddIcon as AddIcon6, CheckmarkFilledIcon as CheckmarkFilledIcon3, LaunchIcon as LaunchIcon2 } from "@octaviaflow/icons";
39067
- import { useId as useId68, useState as useState82 } from "react";
39442
+ import { useId as useId68, useState as useState83 } from "react";
39068
39443
  import { jsx as jsx164, jsxs as jsxs156 } from "react/jsx-runtime";
39069
39444
  var DEFAULT_TYPES = [
39070
39445
  "slack",
@@ -39108,7 +39483,7 @@ function NotificationChannelForm({
39108
39483
  const baseId = id ?? `${reactId}-channel`;
39109
39484
  const typeMeta = CHANNEL_TYPE_META[value.type];
39110
39485
  const patch = (next) => onChange({ ...value, ...next });
39111
- const [touched, setTouched] = useState82({});
39486
+ const [touched, setTouched] = useState83({});
39112
39487
  const touch = (field) => setTouched((t) => t[field] ? t : { ...t, [field]: true });
39113
39488
  const errorFor = (field) => errors?.[field] && (showAllErrors || touched[field]) ? errors[field] : void 0;
39114
39489
  const errorText = (field) => {
@@ -39124,7 +39499,7 @@ function NotificationChannelForm({
39124
39499
  const targetLabel = targetOptions?.find((o) => o.value === value.target)?.label;
39125
39500
  const summary = describeChannelDraft(value, instances, targetLabel);
39126
39501
  const suggestedLabel = suggestChannelLabel(value, instances, targetLabel);
39127
- const [recipientDraft, setRecipientDraft] = useState82("");
39502
+ const [recipientDraft, setRecipientDraft] = useState83("");
39128
39503
  const recipients = value.recipients ?? [];
39129
39504
  const commitRecipient = () => {
39130
39505
  const parts = recipientDraft.split(/[,\s;]+/).map((s) => s.trim()).filter(Boolean);
@@ -39409,13 +39784,13 @@ function NotificationChannelDialog({
39409
39784
  className
39410
39785
  }) {
39411
39786
  const editing = Boolean(channel?.id);
39412
- const [draft, setDraft] = useState83(
39787
+ const [draft, setDraft] = useState84(
39413
39788
  () => draftFromChannel(channel, initialType)
39414
39789
  );
39415
- const [submitted, setSubmitted] = useState83(false);
39416
- const [testing, setTesting] = useState83(false);
39417
- const [testResult, setTestResult] = useState83(null);
39418
- const [submitError, setSubmitError] = useState83(null);
39790
+ const [submitted, setSubmitted] = useState84(false);
39791
+ const [testing, setTesting] = useState84(false);
39792
+ const [testResult, setTestResult] = useState84(null);
39793
+ const [submitError, setSubmitError] = useState84(null);
39419
39794
  useEffect61(() => {
39420
39795
  if (!open) return;
39421
39796
  setDraft(draftFromChannel(channel, initialType));
@@ -39598,7 +39973,7 @@ NotificationChannelList.displayName = "NotificationChannelList";
39598
39973
 
39599
39974
  // src/notifications/components/NotificationPreferenceMatrix/NotificationPreferenceMatrix.tsx
39600
39975
  import { LockedIcon as LockedIcon2 } from "@octaviaflow/icons";
39601
- import { useId as useId69, useMemo as useMemo44, useState as useState84 } from "react";
39976
+ import { useId as useId69, useMemo as useMemo44, useState as useState85 } from "react";
39602
39977
 
39603
39978
  // src/notifications/engine/preferences.ts
39604
39979
  var CRITICAL_CHANNELS = ["in_app", "email"];
@@ -39692,7 +40067,7 @@ function NotificationPreferenceMatrix({
39692
40067
  () => filterCatalog(catalog, personalAudiences(isAdmin)),
39693
40068
  [catalog, isAdmin]
39694
40069
  );
39695
- const [notice, setNotice] = useState84(null);
40070
+ const [notice, setNotice] = useState85(null);
39696
40071
  const toggle = (eventKey, channel, on) => {
39697
40072
  const r = setPreference(value, catalog, eventKey, channel, on);
39698
40073
  if (r.blocked) {