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

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]
@@ -34779,27 +34818,26 @@ function ExpressionField({
34779
34818
  }, []);
34780
34819
  const openEditor = useCallback50((chip) => {
34781
34820
  const raw = chip.getAttribute("data-value") ?? "";
34782
- const parsed = parseCall(raw);
34783
- if (!parsed) return;
34821
+ if (!parseCall(raw)) return;
34784
34822
  const box = chip.getBoundingClientRect();
34785
34823
  const host = fieldRef.current?.getBoundingClientRect();
34786
34824
  setEditing({
34787
34825
  el: chip,
34788
- name: parsed.name,
34789
- args: parsed.args.length ? parsed.args : [""],
34826
+ raw,
34790
34827
  rect: {
34791
34828
  top: box.bottom - (host?.top ?? 0) + 4,
34792
34829
  left: Math.max(0, box.left - (host?.left ?? 0)),
34793
- width: Math.max(260, box.width)
34830
+ width: Math.max(300, box.width)
34794
34831
  }
34795
34832
  });
34796
34833
  }, []);
34797
34834
  const applyEditor = useCallback50(
34798
- (args) => {
34835
+ (raw) => {
34799
34836
  if (!editing) return;
34800
- const raw = formatCall(editing.name, args);
34837
+ const call = parseCall(raw);
34838
+ if (!call) return;
34801
34839
  editing.el.setAttribute("data-value", raw);
34802
- editing.el.textContent = chipText(editing.name, args);
34840
+ editing.el.textContent = chipText(call.name, call.args);
34803
34841
  editing.el.title = raw;
34804
34842
  setEditing(null);
34805
34843
  commit();
@@ -34933,11 +34971,11 @@ function ExpressionField({
34933
34971
  editing && /* @__PURE__ */ jsx147(
34934
34972
  CallEditor,
34935
34973
  {
34936
- name: editing.name,
34937
- label: callLabel(editing.name),
34938
- initialArgs: editing.args,
34939
- signature: resolveCall?.(editing.name),
34974
+ raw: editing.raw,
34940
34975
  rect: editing.rect,
34976
+ nameOf: callLabel,
34977
+ labelFor,
34978
+ resolveCall,
34941
34979
  onCancel: () => setEditing(null),
34942
34980
  onApply: applyEditor
34943
34981
  }
@@ -34947,36 +34985,44 @@ function ExpressionField({
34947
34985
  ] });
34948
34986
  }
34949
34987
  function CallEditor({
34950
- name,
34951
- label,
34952
- initialArgs,
34953
- signature,
34988
+ raw,
34954
34989
  rect,
34990
+ nameOf,
34991
+ labelFor,
34992
+ resolveCall,
34955
34993
  onApply,
34956
34994
  onCancel
34957
34995
  }) {
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);
34996
+ const [draft, setDraft] = useState71(raw);
34997
+ const [path, setPath] = useState71([]);
34998
+ const [rawMode, setRawMode] = useState71(false);
34965
34999
  const panelRef = useRef50(null);
34966
35000
  useEffect56(() => {
34967
- panelRef.current?.querySelector("input")?.focus();
34968
35001
  const onKey = (e) => {
34969
35002
  if (e.key === "Escape") onCancel();
34970
35003
  };
34971
35004
  document.addEventListener("keydown", onKey);
34972
35005
  return () => document.removeEventListener("keydown", onKey);
34973
35006
  }, [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;
35007
+ const current = callAt(draft, path);
35008
+ const signature = current ? resolveCall?.(current.name) : void 0;
35009
+ const names = signature?.args ?? [];
35010
+ const args = current ? current.args.length ? current.args : [""] : [];
35011
+ const crumbs = [];
35012
+ for (let i = 0; i <= path.length; i++) {
35013
+ const at = callAt(draft, path.slice(0, i));
35014
+ if (at) crumbs.push({ label: nameOf(at.name), path: path.slice(0, i) });
35015
+ }
35016
+ const setArg = (index, next) => setDraft((d) => replaceArgument(d, [...path, index], next));
35017
+ const dropInto = (index) => (e) => {
35018
+ const dropped = e.dataTransfer.getData("application/x-fx-insert");
35019
+ if (!dropped) return;
34978
35020
  e.preventDefault();
34979
- setArg(idx, ref);
35021
+ e.stopPropagation();
35022
+ const kind = e.dataTransfer.getData("application/x-fx-type");
35023
+ const bare = dropped.startsWith("{{") && dropped.endsWith("}}") ? dropped.slice(2, -2).trim() : dropped;
35024
+ setDraft((d) => replaceArgument(d, [...path, index], bare));
35025
+ if (kind === "function" || parseCall(bare)) setPath((pth) => [...pth, index]);
34980
35026
  };
34981
35027
  return /* @__PURE__ */ jsxs141(
34982
35028
  "div",
@@ -34985,49 +35031,122 @@ function CallEditor({
34985
35031
  className: "ods-expression-field__editor",
34986
35032
  style: { top: rect.top, left: rect.left, width: rect.width },
34987
35033
  role: "dialog",
34988
- "aria-label": `Edit ${label}`,
35034
+ "aria-label": `Edit ${crumbs[0]?.label ?? "function"}`,
34989
35035
  children: [
34990
35036
  /* @__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
- )),
35037
+ /* @__PURE__ */ jsx147("nav", { className: "ods-expression-field__editor-crumbs", "aria-label": "Nested functions", children: crumbs.map((crumb, i) => /* @__PURE__ */ jsxs141("span", { children: [
35038
+ i > 0 && /* @__PURE__ */ jsx147("span", { "aria-hidden": "true", children: " \u203A " }),
35039
+ /* @__PURE__ */ jsx147(
35040
+ "button",
35041
+ {
35042
+ type: "button",
35043
+ disabled: i === crumbs.length - 1,
35044
+ onClick: () => setPath(crumb.path),
35045
+ children: crumb.label
35046
+ }
35047
+ )
35048
+ ] }, crumb.path.join(".") || "root")) }),
35013
35049
  /* @__PURE__ */ jsx147(
35014
35050
  "button",
35015
35051
  {
35016
35052
  type: "button",
35017
35053
  className: "ods-expression-field__editor-link",
35018
- onClick: () => setArgs((prev) => [...prev, ""]),
35019
- children: "+ argument"
35054
+ onClick: () => setRawMode((r) => !r),
35055
+ children: rawMode ? "Back to fields" : "Edit as text"
35020
35056
  }
35021
35057
  )
35022
- ] }) : /* @__PURE__ */ jsx147(
35058
+ ] }),
35059
+ rawMode ? /* @__PURE__ */ jsx147(
35023
35060
  "textarea",
35024
35061
  {
35025
35062
  className: "ods-expression-field__editor-raw",
35026
- value: raw,
35063
+ value: draft,
35027
35064
  rows: 3,
35028
- onChange: (e) => setRaw(e.target.value)
35065
+ "aria-label": "Expression",
35066
+ onChange: (e) => setDraft(e.target.value)
35029
35067
  }
35030
- ),
35068
+ ) : /* @__PURE__ */ jsxs141("div", { className: "ods-expression-field__editor-rows", children: [
35069
+ args.map((arg, index) => {
35070
+ const kind = argumentKind(arg);
35071
+ const rowLabel = names[index] ?? `argument ${index + 1}`;
35072
+ const nested = kind === "call" ? parseCall(arg) : void 0;
35073
+ return (
35074
+ // eslint-disable-next-line react/no-array-index-key
35075
+ /* @__PURE__ */ jsxs141("div", { className: "ods-expression-field__editor-row", children: [
35076
+ /* @__PURE__ */ jsx147("span", { id: `arg-${index}-label`, children: rowLabel }),
35077
+ nested ? /* @__PURE__ */ jsxs141(
35078
+ "div",
35079
+ {
35080
+ className: "ods-expression-field__editor-nested",
35081
+ onDragOver: (e) => {
35082
+ if (e.dataTransfer.types.includes("application/x-fx-insert")) e.preventDefault();
35083
+ },
35084
+ onDrop: dropInto(index),
35085
+ children: [
35086
+ /* @__PURE__ */ jsx147("code", { children: argSummary(arg, nameOf) }),
35087
+ /* @__PURE__ */ jsxs141("span", { children: [
35088
+ /* @__PURE__ */ jsx147("button", { type: "button", onClick: () => setPath([...path, index]), children: "Open" }),
35089
+ /* @__PURE__ */ jsx147("button", { type: "button", onClick: () => setArg(index, ""), "aria-label": `Clear ${rowLabel}`, children: "Clear" })
35090
+ ] })
35091
+ ]
35092
+ }
35093
+ ) : kind === "reference" ? (
35094
+ // A reference is a value from somewhere else — named, never
35095
+ // its raw `ods://…` path, here as much as in the field.
35096
+ /* @__PURE__ */ jsxs141(
35097
+ "div",
35098
+ {
35099
+ className: "ods-expression-field__editor-ref",
35100
+ onDragOver: (e) => {
35101
+ if (e.dataTransfer.types.includes("application/x-fx-insert")) e.preventDefault();
35102
+ },
35103
+ onDrop: dropInto(index),
35104
+ children: [
35105
+ /* @__PURE__ */ jsx147(
35106
+ "span",
35107
+ {
35108
+ className: "ods-expression-field__pill",
35109
+ title: arg,
35110
+ "aria-labelledby": `arg-${index}-label`,
35111
+ children: labelFor(arg.startsWith("ods://") ? `{{${arg}}}` : arg)
35112
+ }
35113
+ ),
35114
+ /* @__PURE__ */ jsx147("button", { type: "button", onClick: () => setArg(index, ""), "aria-label": `Clear ${rowLabel}`, children: "Clear" })
35115
+ ]
35116
+ }
35117
+ )
35118
+ ) : /* @__PURE__ */ jsx147(
35119
+ "input",
35120
+ {
35121
+ "aria-labelledby": `arg-${index}-label`,
35122
+ value: arg,
35123
+ placeholder: 'a value, "text", or drag one in',
35124
+ "data-kind": kind,
35125
+ onChange: (e) => setArg(index, e.target.value),
35126
+ onDragOver: (e) => {
35127
+ if (e.dataTransfer.types.includes("application/x-fx-insert")) e.preventDefault();
35128
+ },
35129
+ onDrop: dropInto(index)
35130
+ }
35131
+ )
35132
+ ] }, index)
35133
+ );
35134
+ }),
35135
+ /* @__PURE__ */ jsx147(
35136
+ "button",
35137
+ {
35138
+ type: "button",
35139
+ className: "ods-expression-field__editor-link",
35140
+ onClick: () => setDraft((d) => {
35141
+ const at = callAt(d, path);
35142
+ if (!at) return d;
35143
+ const next = [...at.args, '""'];
35144
+ return path.length === 0 ? formatCall(at.name, next) : replaceArgument(d, path, `${at.name}(${next.join(", ")})`);
35145
+ }),
35146
+ children: "+ argument"
35147
+ }
35148
+ )
35149
+ ] }),
35031
35150
  /* @__PURE__ */ jsxs141("div", { className: "ods-expression-field__editor-foot", children: [
35032
35151
  /* @__PURE__ */ jsx147("button", { type: "button", onClick: onCancel, children: "Cancel" }),
35033
35152
  /* @__PURE__ */ jsx147(
@@ -35035,11 +35154,7 @@ function CallEditor({
35035
35154
  {
35036
35155
  type: "button",
35037
35156
  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
- },
35157
+ onClick: () => onApply(draft),
35043
35158
  children: "Apply"
35044
35159
  }
35045
35160
  )