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

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
@@ -34556,6 +34556,39 @@ function parseExpression(value, pillEveryToken = false) {
34556
34556
  pushText(tail.slice(last));
34557
34557
  return segments;
34558
34558
  }
34559
+ function callAt(raw, path) {
34560
+ let current = parseCall(raw);
34561
+ for (const index of path) {
34562
+ if (!current) return void 0;
34563
+ const arg = current.args[index];
34564
+ if (arg === void 0) return void 0;
34565
+ current = parseCall(arg);
34566
+ }
34567
+ return current;
34568
+ }
34569
+ function formatNestedCall(name, args) {
34570
+ return `${name}(${args.filter((a) => a.trim() !== "").join(", ")})`;
34571
+ }
34572
+ function replaceArgument(raw, path, next) {
34573
+ if (path.length === 0) return next;
34574
+ const [index, ...rest] = path;
34575
+ const call = parseCall(raw);
34576
+ if (!call || index === void 0 || index > call.args.length) return raw;
34577
+ const args = [...call.args];
34578
+ if (args[index] === void 0) args[index] = "";
34579
+ args[index] = rest.length === 0 ? next : replaceArgument(args[index], rest, next);
34580
+ const wrapped = raw.trim().startsWith("{{");
34581
+ return wrapped ? formatCall(call.name, args) : formatNestedCall(call.name, args);
34582
+ }
34583
+ function argumentKind(arg) {
34584
+ const trimmed = arg.trim();
34585
+ if (!trimmed) return "literal";
34586
+ if (parseCall(trimmed)) return "call";
34587
+ if (trimmed.startsWith("{{") || trimmed.startsWith("ods://") || trimmed.startsWith("${")) {
34588
+ return "reference";
34589
+ }
34590
+ return "literal";
34591
+ }
34559
34592
 
34560
34593
  // src/components/ExpressionField/ExpressionField.tsx
34561
34594
  import { jsx as jsx147, jsxs as jsxs141 } from "react/jsx-runtime";
@@ -34614,8 +34647,14 @@ function insideCallArguments(textBeforeCaret) {
34614
34647
  }
34615
34648
  return depth > 0;
34616
34649
  }
34617
- function argSummary(arg) {
34650
+ function argSummary(arg, nameOf, depth = 2) {
34618
34651
  const trimmed = arg.trim();
34652
+ const call = parseCall(trimmed);
34653
+ if (call) {
34654
+ if (depth <= 0) return `${nameOf(call.name)}(\u2026)`;
34655
+ const inner = call.args.map((a) => argSummary(a, nameOf, depth - 1)).filter(Boolean).join(", ");
34656
+ return `${nameOf(call.name)}(${inner})`;
34657
+ }
34619
34658
  if (trimmed.startsWith("{{") || trimmed.startsWith("ods://") || trimmed.startsWith("${")) {
34620
34659
  return shortReferenceLabel(trimmed.startsWith("ods://") ? `{{${trimmed}}}` : trimmed);
34621
34660
  }
@@ -34653,7 +34692,7 @@ function ExpressionField({
34653
34692
  );
34654
34693
  const chipText = useCallback50(
34655
34694
  (name, args) => {
34656
- const summary = args.map(argSummary).filter(Boolean).join(", ");
34695
+ const summary = args.map((a) => argSummary(a, callLabel)).filter(Boolean).join(", ");
34657
34696
  return `${callLabel(name)}(${summary})`;
34658
34697
  },
34659
34698
  [callLabel]
@@ -34672,8 +34711,18 @@ function ExpressionField({
34672
34711
  } else {
34673
34712
  el.className = CHIP;
34674
34713
  el.setAttribute("data-call", segment.name);
34675
- el.textContent = chipText(segment.name, segment.args);
34676
34714
  el.title = segment.raw;
34715
+ const text = document.createElement("span");
34716
+ text.className = "ods-expression-field__chip-text";
34717
+ text.textContent = chipText(segment.name, segment.args);
34718
+ el.appendChild(text);
34719
+ const open = document.createElement("span");
34720
+ open.className = "ods-expression-field__chip-open";
34721
+ open.setAttribute("data-open", "true");
34722
+ open.setAttribute("role", "button");
34723
+ open.setAttribute("aria-label", "Edit arguments");
34724
+ open.textContent = "\u2304";
34725
+ el.appendChild(open);
34677
34726
  }
34678
34727
  return el;
34679
34728
  },
@@ -34779,27 +34828,28 @@ function ExpressionField({
34779
34828
  }, []);
34780
34829
  const openEditor = useCallback50((chip) => {
34781
34830
  const raw = chip.getAttribute("data-value") ?? "";
34782
- const parsed = parseCall(raw);
34783
- if (!parsed) return;
34831
+ if (!parseCall(raw)) return;
34784
34832
  const box = chip.getBoundingClientRect();
34785
34833
  const host = fieldRef.current?.getBoundingClientRect();
34786
34834
  setEditing({
34787
34835
  el: chip,
34788
- name: parsed.name,
34789
- args: parsed.args.length ? parsed.args : [""],
34836
+ raw,
34790
34837
  rect: {
34791
34838
  top: box.bottom - (host?.top ?? 0) + 4,
34792
34839
  left: Math.max(0, box.left - (host?.left ?? 0)),
34793
- width: Math.max(260, box.width)
34840
+ width: Math.max(300, box.width)
34794
34841
  }
34795
34842
  });
34796
34843
  }, []);
34797
34844
  const applyEditor = useCallback50(
34798
- (args) => {
34845
+ (raw) => {
34799
34846
  if (!editing) return;
34800
- const raw = formatCall(editing.name, args);
34847
+ const call = parseCall(raw);
34848
+ if (!call) return;
34801
34849
  editing.el.setAttribute("data-value", raw);
34802
- editing.el.textContent = chipText(editing.name, args);
34850
+ const text = editing.el.querySelector(".ods-expression-field__chip-text");
34851
+ if (text) text.textContent = chipText(call.name, call.args);
34852
+ else editing.el.textContent = chipText(call.name, call.args);
34803
34853
  editing.el.title = raw;
34804
34854
  setEditing(null);
34805
34855
  commit();
@@ -34809,12 +34859,13 @@ function ExpressionField({
34809
34859
  );
34810
34860
  const handleClick = useCallback50(
34811
34861
  (e) => {
34812
- const target = e.target.closest(`.${PILL}, .${CHIP}`);
34862
+ const node = e.target;
34863
+ const target = node.closest(`.${PILL}, .${CHIP}`);
34813
34864
  if (!target) return;
34814
- selectToken(target);
34815
- if (!disabled && target.classList.contains(CHIP) && e.detail >= 2) {
34816
- openEditor(target);
34817
- }
34865
+ const chipEl = target;
34866
+ const wantsEditor = chipEl.classList.contains(CHIP) && (!!node.closest("[data-open]") || e.detail >= 2);
34867
+ selectToken(chipEl);
34868
+ if (!disabled && wantsEditor) openEditor(chipEl);
34818
34869
  },
34819
34870
  [disabled, openEditor, selectToken]
34820
34871
  );
@@ -34933,11 +34984,11 @@ function ExpressionField({
34933
34984
  editing && /* @__PURE__ */ jsx147(
34934
34985
  CallEditor,
34935
34986
  {
34936
- name: editing.name,
34937
- label: callLabel(editing.name),
34938
- initialArgs: editing.args,
34939
- signature: resolveCall?.(editing.name),
34987
+ raw: editing.raw,
34940
34988
  rect: editing.rect,
34989
+ nameOf: callLabel,
34990
+ labelFor,
34991
+ resolveCall,
34941
34992
  onCancel: () => setEditing(null),
34942
34993
  onApply: applyEditor
34943
34994
  }
@@ -34947,36 +34998,44 @@ function ExpressionField({
34947
34998
  ] });
34948
34999
  }
34949
35000
  function CallEditor({
34950
- name,
34951
- label,
34952
- initialArgs,
34953
- signature,
35001
+ raw,
34954
35002
  rect,
35003
+ nameOf,
35004
+ labelFor,
35005
+ resolveCall,
34955
35006
  onApply,
34956
35007
  onCancel
34957
35008
  }) {
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);
35009
+ const [draft, setDraft] = useState71(raw);
35010
+ const [path, setPath] = useState71([]);
35011
+ const [rawMode, setRawMode] = useState71(false);
34965
35012
  const panelRef = useRef50(null);
34966
35013
  useEffect56(() => {
34967
- panelRef.current?.querySelector("input")?.focus();
34968
35014
  const onKey = (e) => {
34969
35015
  if (e.key === "Escape") onCancel();
34970
35016
  };
34971
35017
  document.addEventListener("keydown", onKey);
34972
35018
  return () => document.removeEventListener("keydown", onKey);
34973
35019
  }, [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;
35020
+ const current = callAt(draft, path);
35021
+ const signature = current ? resolveCall?.(current.name) : void 0;
35022
+ const names = signature?.args ?? [];
35023
+ const args = current ? current.args.length ? current.args : [""] : [];
35024
+ const crumbs = [];
35025
+ for (let i = 0; i <= path.length; i++) {
35026
+ const at = callAt(draft, path.slice(0, i));
35027
+ if (at) crumbs.push({ label: nameOf(at.name), path: path.slice(0, i) });
35028
+ }
35029
+ const setArg = (index, next) => setDraft((d) => replaceArgument(d, [...path, index], next));
35030
+ const dropInto = (index) => (e) => {
35031
+ const dropped = e.dataTransfer.getData("application/x-fx-insert");
35032
+ if (!dropped) return;
34978
35033
  e.preventDefault();
34979
- setArg(idx, ref);
35034
+ e.stopPropagation();
35035
+ const kind = e.dataTransfer.getData("application/x-fx-type");
35036
+ const bare = dropped.startsWith("{{") && dropped.endsWith("}}") ? dropped.slice(2, -2).trim() : dropped;
35037
+ setDraft((d) => replaceArgument(d, [...path, index], bare));
35038
+ if (kind === "function" || parseCall(bare)) setPath((pth) => [...pth, index]);
34980
35039
  };
34981
35040
  return /* @__PURE__ */ jsxs141(
34982
35041
  "div",
@@ -34985,49 +35044,122 @@ function CallEditor({
34985
35044
  className: "ods-expression-field__editor",
34986
35045
  style: { top: rect.top, left: rect.left, width: rect.width },
34987
35046
  role: "dialog",
34988
- "aria-label": `Edit ${label}`,
35047
+ "aria-label": `Edit ${crumbs[0]?.label ?? "function"}`,
34989
35048
  children: [
34990
35049
  /* @__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
- )),
35050
+ /* @__PURE__ */ jsx147("nav", { className: "ods-expression-field__editor-crumbs", "aria-label": "Nested functions", children: crumbs.map((crumb, i) => /* @__PURE__ */ jsxs141("span", { children: [
35051
+ i > 0 && /* @__PURE__ */ jsx147("span", { "aria-hidden": "true", children: " \u203A " }),
35052
+ /* @__PURE__ */ jsx147(
35053
+ "button",
35054
+ {
35055
+ type: "button",
35056
+ disabled: i === crumbs.length - 1,
35057
+ onClick: () => setPath(crumb.path),
35058
+ children: crumb.label
35059
+ }
35060
+ )
35061
+ ] }, crumb.path.join(".") || "root")) }),
35013
35062
  /* @__PURE__ */ jsx147(
35014
35063
  "button",
35015
35064
  {
35016
35065
  type: "button",
35017
35066
  className: "ods-expression-field__editor-link",
35018
- onClick: () => setArgs((prev) => [...prev, ""]),
35019
- children: "+ argument"
35067
+ onClick: () => setRawMode((r) => !r),
35068
+ children: rawMode ? "Back to fields" : "Edit as text"
35020
35069
  }
35021
35070
  )
35022
- ] }) : /* @__PURE__ */ jsx147(
35071
+ ] }),
35072
+ rawMode ? /* @__PURE__ */ jsx147(
35023
35073
  "textarea",
35024
35074
  {
35025
35075
  className: "ods-expression-field__editor-raw",
35026
- value: raw,
35076
+ value: draft,
35027
35077
  rows: 3,
35028
- onChange: (e) => setRaw(e.target.value)
35078
+ "aria-label": "Expression",
35079
+ onChange: (e) => setDraft(e.target.value)
35029
35080
  }
35030
- ),
35081
+ ) : /* @__PURE__ */ jsxs141("div", { className: "ods-expression-field__editor-rows", children: [
35082
+ args.map((arg, index) => {
35083
+ const kind = argumentKind(arg);
35084
+ const rowLabel = names[index] ?? `argument ${index + 1}`;
35085
+ const nested = kind === "call" ? parseCall(arg) : void 0;
35086
+ return (
35087
+ // eslint-disable-next-line react/no-array-index-key
35088
+ /* @__PURE__ */ jsxs141("div", { className: "ods-expression-field__editor-row", children: [
35089
+ /* @__PURE__ */ jsx147("span", { id: `arg-${index}-label`, children: rowLabel }),
35090
+ nested ? /* @__PURE__ */ jsxs141(
35091
+ "div",
35092
+ {
35093
+ className: "ods-expression-field__editor-nested",
35094
+ onDragOver: (e) => {
35095
+ if (e.dataTransfer.types.includes("application/x-fx-insert")) e.preventDefault();
35096
+ },
35097
+ onDrop: dropInto(index),
35098
+ children: [
35099
+ /* @__PURE__ */ jsx147("code", { children: argSummary(arg, nameOf) }),
35100
+ /* @__PURE__ */ jsxs141("span", { children: [
35101
+ /* @__PURE__ */ jsx147("button", { type: "button", onClick: () => setPath([...path, index]), children: "Open" }),
35102
+ /* @__PURE__ */ jsx147("button", { type: "button", onClick: () => setArg(index, ""), "aria-label": `Clear ${rowLabel}`, children: "Clear" })
35103
+ ] })
35104
+ ]
35105
+ }
35106
+ ) : kind === "reference" ? (
35107
+ // A reference is a value from somewhere else — named, never
35108
+ // its raw `ods://…` path, here as much as in the field.
35109
+ /* @__PURE__ */ jsxs141(
35110
+ "div",
35111
+ {
35112
+ className: "ods-expression-field__editor-ref",
35113
+ onDragOver: (e) => {
35114
+ if (e.dataTransfer.types.includes("application/x-fx-insert")) e.preventDefault();
35115
+ },
35116
+ onDrop: dropInto(index),
35117
+ children: [
35118
+ /* @__PURE__ */ jsx147(
35119
+ "span",
35120
+ {
35121
+ className: "ods-expression-field__pill",
35122
+ title: arg,
35123
+ "aria-labelledby": `arg-${index}-label`,
35124
+ children: labelFor(arg.startsWith("ods://") ? `{{${arg}}}` : arg)
35125
+ }
35126
+ ),
35127
+ /* @__PURE__ */ jsx147("button", { type: "button", onClick: () => setArg(index, ""), "aria-label": `Clear ${rowLabel}`, children: "Clear" })
35128
+ ]
35129
+ }
35130
+ )
35131
+ ) : /* @__PURE__ */ jsx147(
35132
+ "input",
35133
+ {
35134
+ "aria-labelledby": `arg-${index}-label`,
35135
+ value: arg,
35136
+ placeholder: 'a value, "text", or drag one in',
35137
+ "data-kind": kind,
35138
+ onChange: (e) => setArg(index, e.target.value),
35139
+ onDragOver: (e) => {
35140
+ if (e.dataTransfer.types.includes("application/x-fx-insert")) e.preventDefault();
35141
+ },
35142
+ onDrop: dropInto(index)
35143
+ }
35144
+ )
35145
+ ] }, index)
35146
+ );
35147
+ }),
35148
+ /* @__PURE__ */ jsx147(
35149
+ "button",
35150
+ {
35151
+ type: "button",
35152
+ className: "ods-expression-field__editor-link",
35153
+ onClick: () => setDraft((d) => {
35154
+ const at = callAt(d, path);
35155
+ if (!at) return d;
35156
+ const next = [...at.args, '""'];
35157
+ return path.length === 0 ? formatCall(at.name, next) : replaceArgument(d, path, `${at.name}(${next.join(", ")})`);
35158
+ }),
35159
+ children: "+ argument"
35160
+ }
35161
+ )
35162
+ ] }),
35031
35163
  /* @__PURE__ */ jsxs141("div", { className: "ods-expression-field__editor-foot", children: [
35032
35164
  /* @__PURE__ */ jsx147("button", { type: "button", onClick: onCancel, children: "Cancel" }),
35033
35165
  /* @__PURE__ */ jsx147(
@@ -35035,11 +35167,7 @@ function CallEditor({
35035
35167
  {
35036
35168
  type: "button",
35037
35169
  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
- },
35170
+ onClick: () => onApply(draft),
35043
35171
  children: "Apply"
35044
35172
  }
35045
35173
  )