@octaviaflow/core 3.0.18-beta.9 → 3.1.0-beta.52

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.
Files changed (170) hide show
  1. package/dist/chunk-2ZIOFEIS.js +3001 -0
  2. package/dist/chunk-2ZIOFEIS.js.map +1 -0
  3. package/dist/chunk-3DWB2PUF.js +2991 -0
  4. package/dist/chunk-3DWB2PUF.js.map +1 -0
  5. package/dist/chunk-4ZALUTZS.js +2936 -0
  6. package/dist/chunk-4ZALUTZS.js.map +1 -0
  7. package/dist/chunk-5ARKSRED.js +2951 -0
  8. package/dist/chunk-5ARKSRED.js.map +1 -0
  9. package/dist/chunk-5OSGSJMM.js +2981 -0
  10. package/dist/chunk-5OSGSJMM.js.map +1 -0
  11. package/dist/chunk-5YQQMEF3.js +2981 -0
  12. package/dist/chunk-5YQQMEF3.js.map +1 -0
  13. package/dist/chunk-6QCJK7H7.js +2991 -0
  14. package/dist/chunk-6QCJK7H7.js.map +1 -0
  15. package/dist/chunk-6QDYERZD.js +2944 -0
  16. package/dist/chunk-6QDYERZD.js.map +1 -0
  17. package/dist/chunk-76TP67ED.js +2984 -0
  18. package/dist/chunk-76TP67ED.js.map +1 -0
  19. package/dist/chunk-A6KMO4JV.js +2949 -0
  20. package/dist/chunk-A6KMO4JV.js.map +1 -0
  21. package/dist/chunk-B7FTWSTM.js +2938 -0
  22. package/dist/chunk-B7FTWSTM.js.map +1 -0
  23. package/dist/chunk-BCO6M26F.js +2940 -0
  24. package/dist/chunk-BCO6M26F.js.map +1 -0
  25. package/dist/chunk-DBNSBJO7.js +2993 -0
  26. package/dist/chunk-DBNSBJO7.js.map +1 -0
  27. package/dist/chunk-ECIHUVU7.js +2986 -0
  28. package/dist/chunk-ECIHUVU7.js.map +1 -0
  29. package/dist/chunk-HDOTOZNA.js +2936 -0
  30. package/dist/chunk-HDOTOZNA.js.map +1 -0
  31. package/dist/chunk-IUIICQU5.js +2946 -0
  32. package/dist/chunk-IUIICQU5.js.map +1 -0
  33. package/dist/chunk-J2UYZI6D.js +2946 -0
  34. package/dist/chunk-J2UYZI6D.js.map +1 -0
  35. package/dist/chunk-K2H7JLQW.js +2952 -0
  36. package/dist/chunk-K2H7JLQW.js.map +1 -0
  37. package/dist/chunk-KOE4VS4I.js +2945 -0
  38. package/dist/chunk-KOE4VS4I.js.map +1 -0
  39. package/dist/chunk-KUXYBP66.js +2953 -0
  40. package/dist/chunk-KUXYBP66.js.map +1 -0
  41. package/dist/chunk-M2QXKYL2.js +2945 -0
  42. package/dist/chunk-M2QXKYL2.js.map +1 -0
  43. package/dist/chunk-MMXL343D.js +2974 -0
  44. package/dist/chunk-MMXL343D.js.map +1 -0
  45. package/dist/chunk-MXJL3EPE.js +2986 -0
  46. package/dist/chunk-MXJL3EPE.js.map +1 -0
  47. package/dist/chunk-MYZ25B2R.js +2995 -0
  48. package/dist/chunk-MYZ25B2R.js.map +1 -0
  49. package/dist/chunk-NTMEYB7B.js +2949 -0
  50. package/dist/chunk-NTMEYB7B.js.map +1 -0
  51. package/dist/chunk-Q6ERDPQR.js +2981 -0
  52. package/dist/chunk-Q6ERDPQR.js.map +1 -0
  53. package/dist/chunk-SLVDAZSX.js +2946 -0
  54. package/dist/chunk-SLVDAZSX.js.map +1 -0
  55. package/dist/chunk-UQBPYONV.js +2991 -0
  56. package/dist/chunk-UQBPYONV.js.map +1 -0
  57. package/dist/chunk-UXMNBS22.js +2955 -0
  58. package/dist/chunk-UXMNBS22.js.map +1 -0
  59. package/dist/chunk-WEGED7TA.js +2991 -0
  60. package/dist/chunk-WEGED7TA.js.map +1 -0
  61. package/dist/chunk-XSRJM2TR.js +2944 -0
  62. package/dist/chunk-XSRJM2TR.js.map +1 -0
  63. package/dist/chunk-ZAMJEU42.js +2992 -0
  64. package/dist/chunk-ZAMJEU42.js.map +1 -0
  65. package/dist/chunk-ZRAM6FXB.js +2949 -0
  66. package/dist/chunk-ZRAM6FXB.js.map +1 -0
  67. package/dist/components/ChatAttachment/ChatAttachment.d.ts +20 -0
  68. package/dist/components/ChatAttachment/ChatAttachment.d.ts.map +1 -0
  69. package/dist/components/ChatAttachment/index.d.ts +2 -0
  70. package/dist/components/ChatAttachment/index.d.ts.map +1 -0
  71. package/dist/components/ChatBubble/ChatBubble.d.ts +11 -1
  72. package/dist/components/ChatBubble/ChatBubble.d.ts.map +1 -1
  73. package/dist/components/ChatCodeBlock/ChatCodeBlock.d.ts +19 -0
  74. package/dist/components/ChatCodeBlock/ChatCodeBlock.d.ts.map +1 -0
  75. package/dist/components/ChatCodeBlock/index.d.ts +2 -0
  76. package/dist/components/ChatCodeBlock/index.d.ts.map +1 -0
  77. package/dist/components/ChatComposer/ChatComposer.d.ts +51 -0
  78. package/dist/components/ChatComposer/ChatComposer.d.ts.map +1 -0
  79. package/dist/components/ChatComposer/index.d.ts +2 -0
  80. package/dist/components/ChatComposer/index.d.ts.map +1 -0
  81. package/dist/components/ChatDayDivider/ChatDayDivider.d.ts +11 -0
  82. package/dist/components/ChatDayDivider/ChatDayDivider.d.ts.map +1 -0
  83. package/dist/components/ChatDayDivider/index.d.ts +2 -0
  84. package/dist/components/ChatDayDivider/index.d.ts.map +1 -0
  85. package/dist/components/ChatFeedback/ChatFeedback.d.ts +22 -0
  86. package/dist/components/ChatFeedback/ChatFeedback.d.ts.map +1 -0
  87. package/dist/components/ChatFeedback/index.d.ts +2 -0
  88. package/dist/components/ChatFeedback/index.d.ts.map +1 -0
  89. package/dist/components/ChatPanel/ChatPanel.d.ts +34 -0
  90. package/dist/components/ChatPanel/ChatPanel.d.ts.map +1 -0
  91. package/dist/components/ChatPanel/index.d.ts +2 -0
  92. package/dist/components/ChatPanel/index.d.ts.map +1 -0
  93. package/dist/components/ChatScrollToBottom/ChatScrollToBottom.d.ts +12 -0
  94. package/dist/components/ChatScrollToBottom/ChatScrollToBottom.d.ts.map +1 -0
  95. package/dist/components/ChatScrollToBottom/index.d.ts +2 -0
  96. package/dist/components/ChatScrollToBottom/index.d.ts.map +1 -0
  97. package/dist/components/ChatSources/ChatSources.d.ts +34 -0
  98. package/dist/components/ChatSources/ChatSources.d.ts.map +1 -0
  99. package/dist/components/ChatSources/index.d.ts +2 -0
  100. package/dist/components/ChatSources/index.d.ts.map +1 -0
  101. package/dist/components/ChatSuggestions/ChatSuggestions.d.ts +30 -0
  102. package/dist/components/ChatSuggestions/ChatSuggestions.d.ts.map +1 -0
  103. package/dist/components/ChatSuggestions/index.d.ts +2 -0
  104. package/dist/components/ChatSuggestions/index.d.ts.map +1 -0
  105. package/dist/components/ChatThinking/ChatThinking.d.ts +29 -0
  106. package/dist/components/ChatThinking/ChatThinking.d.ts.map +1 -0
  107. package/dist/components/ChatThinking/index.d.ts +2 -0
  108. package/dist/components/ChatThinking/index.d.ts.map +1 -0
  109. package/dist/components/ChatThread/ChatThread.d.ts +25 -0
  110. package/dist/components/ChatThread/ChatThread.d.ts.map +1 -0
  111. package/dist/components/ChatThread/index.d.ts +2 -0
  112. package/dist/components/ChatThread/index.d.ts.map +1 -0
  113. package/dist/components/ChatToolTrace/ChatToolTrace.d.ts +27 -0
  114. package/dist/components/ChatToolTrace/ChatToolTrace.d.ts.map +1 -0
  115. package/dist/components/ChatToolTrace/index.d.ts +2 -0
  116. package/dist/components/ChatToolTrace/index.d.ts.map +1 -0
  117. package/dist/components/ConditionBuilder/ConditionBuilder.d.ts +6 -0
  118. package/dist/components/ConditionBuilder/ConditionBuilder.d.ts.map +1 -1
  119. package/dist/components/DatePicker/DatePicker.d.ts.map +1 -1
  120. package/dist/components/ExecutionConsole/ExecutionConsole.d.ts +1 -1
  121. package/dist/components/ExecutionConsole/ExecutionConsole.d.ts.map +1 -1
  122. package/dist/components/FlowMinimap/FlowMinimap.d.ts.map +1 -1
  123. package/dist/components/FlowToolbar/FlowToolbar.d.ts.map +1 -1
  124. package/dist/components/Input/Input.d.ts +1 -1
  125. package/dist/components/Input/Input.d.ts.map +1 -1
  126. package/dist/components/MultiSelect/MultiSelect.d.ts +7 -0
  127. package/dist/components/MultiSelect/MultiSelect.d.ts.map +1 -1
  128. package/dist/components/NumberInput/NumberInput.d.ts +7 -0
  129. package/dist/components/NumberInput/NumberInput.d.ts.map +1 -1
  130. package/dist/components/PageHeader/PageHeader.d.ts +5 -1
  131. package/dist/components/PageHeader/PageHeader.d.ts.map +1 -1
  132. package/dist/components/Select/Select.d.ts.map +1 -1
  133. package/dist/components/TimePicker/TimePicker.d.ts.map +1 -1
  134. package/dist/index.cjs +5197 -3852
  135. package/dist/index.cjs.map +1 -1
  136. package/dist/index.d.ts +13 -0
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +4866 -3511
  139. package/dist/index.js.map +1 -1
  140. package/dist/styles.css +1 -1
  141. package/dist/utils/useAnchoredPopover.d.ts +63 -0
  142. package/dist/utils/useAnchoredPopover.d.ts.map +1 -0
  143. package/dist/workflow/components/ConfigPanel/ConfigPanel.d.ts +27 -9
  144. package/dist/workflow/components/ConfigPanel/ConfigPanel.d.ts.map +1 -1
  145. package/dist/workflow/components/FlowCanvas/FlowCanvas.d.ts +8 -0
  146. package/dist/workflow/components/FlowCanvas/FlowCanvas.d.ts.map +1 -1
  147. package/dist/workflow/components/FlowEdge/FlowEdge.d.ts.map +1 -1
  148. package/dist/workflow/components/FxPanel/DataRefButton.d.ts +9 -0
  149. package/dist/workflow/components/FxPanel/DataRefButton.d.ts.map +1 -0
  150. package/dist/workflow/components/FxPanel/FxPanel.d.ts +74 -0
  151. package/dist/workflow/components/FxPanel/FxPanel.d.ts.map +1 -0
  152. package/dist/workflow/components/FxPanel/FxToggleButton.d.ts +9 -0
  153. package/dist/workflow/components/FxPanel/FxToggleButton.d.ts.map +1 -0
  154. package/dist/workflow/components/Handle/Handle.d.ts +9 -1
  155. package/dist/workflow/components/Handle/Handle.d.ts.map +1 -1
  156. package/dist/workflow/components/Handle/handleRegistry.d.ts +19 -0
  157. package/dist/workflow/components/Handle/handleRegistry.d.ts.map +1 -1
  158. package/dist/workflow/components/kinds/index.d.ts.map +1 -1
  159. package/dist/workflow/index.d.ts +2 -0
  160. package/dist/workflow/index.d.ts.map +1 -1
  161. package/dist/workflow/layout/elk.d.ts.map +1 -1
  162. package/dist/workflow/types.d.ts +108 -0
  163. package/dist/workflow/types.d.ts.map +1 -1
  164. package/dist/workflow/utils/parenting.d.ts +5 -3
  165. package/dist/workflow/utils/parenting.d.ts.map +1 -1
  166. package/dist/workflow.cjs +678 -196
  167. package/dist/workflow.cjs.map +1 -1
  168. package/dist/workflow.js +539 -64
  169. package/dist/workflow.js.map +1 -1
  170. package/package.json +2 -2
package/dist/workflow.cjs CHANGED
@@ -26,11 +26,13 @@ __export(workflow_exports, {
26
26
  DEFAULT_NODE_HEIGHT: () => DEFAULT_NODE_HEIGHT,
27
27
  DEFAULT_NODE_KINDS: () => DEFAULT_NODE_KINDS,
28
28
  DEFAULT_NODE_WIDTH: () => DEFAULT_NODE_WIDTH,
29
+ DataRefButton: () => DataRefButton,
29
30
  ErrorNode: () => ErrorNode,
30
31
  FlowCanvas: () => FlowCanvas,
31
32
  FlowEdge: () => FlowEdge,
32
33
  FlowNode: () => FlowNode,
33
34
  ForEachNode: () => ForEachNode,
35
+ FxPanel: () => FxPanel,
34
36
  GroupNode: () => GroupNode,
35
37
  Handle: () => Handle,
36
38
  HttpRequestNode: () => HttpRequestNode,
@@ -560,6 +562,9 @@ function createElkEngine(elk, defaults = {}) {
560
562
  if (list) list.push(n);
561
563
  else childrenByParent.set(key, [n]);
562
564
  }
565
+ const engineOptions = Object.fromEntries(
566
+ Object.entries(options?.engine ?? {}).map(([k, v]) => [k, String(v)])
567
+ );
563
568
  const buildChildren = (parentId) => {
564
569
  const list = childrenByParent.get(parentId) ?? [];
565
570
  return list.map((n) => ({
@@ -571,7 +576,8 @@ function createElkEngine(elk, defaults = {}) {
571
576
  "elk.algorithm": algorithm,
572
577
  "elk.direction": ELK_DIRECTION[direction],
573
578
  "elk.spacing.nodeNode": String(nodeSpacing),
574
- "elk.layered.spacing.nodeNodeBetweenLayers": String(rankSpacing)
579
+ "elk.layered.spacing.nodeNodeBetweenLayers": String(rankSpacing),
580
+ ...engineOptions
575
581
  } : void 0
576
582
  }));
577
583
  };
@@ -726,26 +732,27 @@ var SAVE_STATE_DOT = {
726
732
  function ConfigPanel({
727
733
  open = true,
728
734
  title,
729
- kindLabel,
730
735
  icon,
736
+ headerActions,
731
737
  banner,
732
738
  tabs,
733
739
  activeTab: controlledActive,
734
740
  defaultActiveTab,
735
741
  onTabChange,
736
- saveState = "clean",
742
+ saveState,
737
743
  onTitleChange,
738
744
  onClose,
739
745
  onSave,
740
746
  onCancel,
747
+ padding = 12,
741
748
  resizable = true,
742
- defaultWidth = 360,
743
- minWidth = 280,
749
+ defaultWidth = 400,
750
+ minWidth = 400,
744
751
  maxWidth = 720,
745
752
  maxWidthContainerInset = 80,
746
753
  variant = "pinned",
747
754
  children,
748
- footer,
755
+ footerActions,
749
756
  className,
750
757
  style
751
758
  }) {
@@ -842,55 +849,53 @@ function ConfigPanel({
842
849
  "aria-hidden": "true"
843
850
  }
844
851
  ),
845
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "ods-flow-config-panel__header", children: [
846
- kindLabel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ods-flow-config-panel__breadcrumb", children: kindLabel }),
847
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ods-flow-config-panel__title-row", children: [
848
- icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ods-flow-config-panel__icon", children: icon }),
849
- editingTitle ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
850
- "input",
851
- {
852
- className: "ods-flow-config-panel__title-input",
853
- value: draftTitle,
854
- autoFocus: true,
855
- onChange: (e) => setDraftTitle(e.target.value),
856
- onBlur: () => commitTitle(draftTitle),
857
- onKeyDown: titleKeyDown,
858
- "aria-label": "Edit title"
859
- }
860
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
861
- "button",
862
- {
863
- type: "button",
864
- className: "ods-flow-config-panel__title",
865
- onDoubleClick: () => {
866
- if (!onTitleChange) return;
867
- setDraftTitle(title ?? "");
868
- setEditingTitle(true);
869
- },
870
- title: onTitleChange ? "Double-click to rename" : void 0,
871
- children: title ?? "Untitled"
872
- }
873
- ),
874
- onClose && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
875
- "button",
876
- {
877
- type: "button",
878
- className: "ods-flow-config-panel__close",
879
- onClick: onClose,
880
- "aria-label": "Close panel",
881
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
882
- "path",
883
- {
884
- d: "M3 3l8 8M11 3l-8 8",
885
- stroke: "currentColor",
886
- strokeWidth: "1.5",
887
- strokeLinecap: "round"
888
- }
889
- ) })
890
- }
891
- )
892
- ] })
893
- ] }),
852
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("header", { className: "ods-flow-config-panel__header", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ods-flow-config-panel__title-row", children: [
853
+ icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ods-flow-config-panel__icon", children: icon }),
854
+ editingTitle ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
855
+ "input",
856
+ {
857
+ className: "ods-flow-config-panel__title-input",
858
+ value: draftTitle,
859
+ autoFocus: true,
860
+ onChange: (e) => setDraftTitle(e.target.value),
861
+ onBlur: () => commitTitle(draftTitle),
862
+ onKeyDown: titleKeyDown,
863
+ "aria-label": "Edit title"
864
+ }
865
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
866
+ "button",
867
+ {
868
+ type: "button",
869
+ className: "ods-flow-config-panel__title",
870
+ onDoubleClick: () => {
871
+ if (!onTitleChange) return;
872
+ setDraftTitle(title ?? "");
873
+ setEditingTitle(true);
874
+ },
875
+ title: onTitleChange ? "Double-click to rename" : void 0,
876
+ children: title ?? "Untitled"
877
+ }
878
+ ),
879
+ headerActions && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ods-flow-config-panel__header-actions", children: headerActions }),
880
+ onClose && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
881
+ "button",
882
+ {
883
+ type: "button",
884
+ className: "ods-flow-config-panel__close",
885
+ onClick: onClose,
886
+ "aria-label": "Close panel",
887
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
888
+ "path",
889
+ {
890
+ d: "M3 3l8 8M11 3l-8 8",
891
+ stroke: "currentColor",
892
+ strokeWidth: "1.5",
893
+ strokeLinecap: "round"
894
+ }
895
+ ) })
896
+ }
897
+ )
898
+ ] }) }),
894
899
  tabs && tabs.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", { className: "ods-flow-config-panel__tabs", role: "tablist", children: tabs.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
895
900
  "button",
896
901
  {
@@ -911,9 +916,17 @@ function ConfigPanel({
911
916
  t.id
912
917
  )) }),
913
918
  banner && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ods-flow-config-panel__banner", children: banner }),
914
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ods-flow-config-panel__body", role: "tabpanel", children: tabs ? activeTab?.content : children }),
919
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
920
+ "div",
921
+ {
922
+ className: "ods-flow-config-panel__body",
923
+ role: "tabpanel",
924
+ style: { padding },
925
+ children: tabs ? activeTab?.content : children
926
+ }
927
+ ),
915
928
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", { className: "ods-flow-config-panel__footer", children: [
916
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
929
+ saveState && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
917
930
  "span",
918
931
  {
919
932
  className: cn(
@@ -933,7 +946,7 @@ function ConfigPanel({
933
946
  ]
934
947
  }
935
948
  ),
936
- footer ?? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ods-flow-config-panel__footer-actions", children: [
949
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ods-flow-config-panel__footer-actions", children: footerActions ?? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
937
950
  onCancel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
938
951
  "button",
939
952
  {
@@ -949,11 +962,11 @@ function ConfigPanel({
949
962
  type: "button",
950
963
  className: "ods-flow-config-panel__btn ods-flow-config-panel__btn--primary",
951
964
  onClick: onSave,
952
- disabled: saveState === "saving" || saveState === "clean",
965
+ disabled: saveState === "saving",
953
966
  children: saveState === "saving" ? "Saving\u2026" : "Save"
954
967
  }
955
968
  )
956
- ] })
969
+ ] }) })
957
970
  ] })
958
971
  ]
959
972
  }
@@ -1144,7 +1157,7 @@ function findContainingGroup(point, nodes, exclude = []) {
1144
1157
  for (let i = nodes.length - 1; i >= 0; i--) {
1145
1158
  const n = nodes[i];
1146
1159
  if (exclude.includes(n.id)) continue;
1147
- if (n.type !== "group") continue;
1160
+ if (n.type !== "group" && n.type !== "forEach") continue;
1148
1161
  if (n.data && typeof n.data === "object" && n.data.collapsed) {
1149
1162
  continue;
1150
1163
  }
@@ -1354,6 +1367,9 @@ function createHandleRegistry() {
1354
1367
  resolve(nodeId, type, handleId) {
1355
1368
  return map.get(key(nodeId, type, handleId));
1356
1369
  },
1370
+ all() {
1371
+ return Array.from(map.values());
1372
+ },
1357
1373
  subscribe(listener) {
1358
1374
  listeners.add(listener);
1359
1375
  return () => {
@@ -1394,21 +1410,23 @@ function FlowEdgeImpl({
1394
1410
  const targetDesc = registry.resolve(targetNode.id, "target", targetHandleId);
1395
1411
  const sourceSide = sourceDesc?.side ?? sourceNode.sourcePosition ?? "bottom";
1396
1412
  const targetSide = targetDesc?.side ?? targetNode.targetPosition ?? "top";
1413
+ const sourceRoute = sourceDesc?.routeSide ?? sourceSide;
1414
+ const targetRoute = targetDesc?.routeSide ?? targetSide;
1397
1415
  const sourceIndex = sourceDesc?.index ?? 0;
1398
1416
  const sourceTotal = sourceDesc?.total ?? 1;
1399
1417
  const targetIndex = targetDesc?.index ?? 0;
1400
1418
  const targetTotal = targetDesc?.total ?? 1;
1401
1419
  const rawStart = handleCentre(sourceNode, sourceSide, sourceIndex, sourceTotal);
1402
1420
  const rawEnd = handleCentre(targetNode, targetSide, targetIndex, targetTotal);
1403
- const HANDLE_GAP = 8;
1404
- const start = offsetAlongSide2(rawStart, sourceSide, HANDLE_GAP);
1405
- const end = offsetAlongSide2(rawEnd, targetSide, HANDLE_GAP);
1421
+ const HANDLE_GAP = 2;
1422
+ const start = offsetAlongSide2(rawStart, sourceRoute, HANDLE_GAP);
1423
+ const end = offsetAlongSide2(rawEnd, targetRoute, HANDLE_GAP);
1406
1424
  const routing = edge.routing ?? "bezier";
1407
- const { d, midX, midY } = buildEdgePath(routing, start, sourceSide, end, targetSide, {
1425
+ const { d, midX, midY } = buildEdgePath(routing, start, sourceRoute, end, targetRoute, {
1408
1426
  curvature,
1409
1427
  borderRadius
1410
1428
  });
1411
- const { d: hitD } = buildEdgePath(routing, rawStart, sourceSide, rawEnd, targetSide, {
1429
+ const { d: hitD } = buildEdgePath(routing, rawStart, sourceRoute, rawEnd, targetRoute, {
1412
1430
  curvature,
1413
1431
  borderRadius
1414
1432
  });
@@ -1455,6 +1473,12 @@ function FlowEdgeImpl({
1455
1473
  e.stopPropagation();
1456
1474
  onSelect?.(edge.id);
1457
1475
  },
1476
+ onDoubleClick: (e) => {
1477
+ if (!onLabelChange) return;
1478
+ e.stopPropagation();
1479
+ setDraftLabel(edge.label ?? "");
1480
+ setEditing(true);
1481
+ },
1458
1482
  children: [
1459
1483
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1460
1484
  "marker",
@@ -1726,6 +1750,7 @@ var DEFAULT_HANDLE_ID = "default";
1726
1750
  function Handle({
1727
1751
  type,
1728
1752
  position,
1753
+ routeSide,
1729
1754
  id = DEFAULT_HANDLE_ID,
1730
1755
  isConnectable = true,
1731
1756
  isConnectableStart,
@@ -1748,11 +1773,14 @@ function Handle({
1748
1773
  handleId: id,
1749
1774
  type,
1750
1775
  side: position,
1776
+ routeSide: routeSide ?? position,
1751
1777
  index,
1752
- total
1778
+ total,
1779
+ canStart,
1780
+ canEnd
1753
1781
  });
1754
1782
  return dispose;
1755
- }, [registry, node.id, id, type, position, index, total]);
1783
+ }, [registry, node.id, id, type, position, routeSide, index, total, canStart, canEnd]);
1756
1784
  const handlePointerDown = (e) => {
1757
1785
  if (!canStart) return;
1758
1786
  e.stopPropagation();
@@ -2168,92 +2196,35 @@ var ForEachNode = ({
2168
2196
  node
2169
2197
  }) => {
2170
2198
  const d = node.data ?? {};
2171
- const iteratorExpr = d.iterator ?? d.description ?? "items[]";
2172
- const collapsed = !!d.collapsed;
2173
- const disabled = !!d.disabled;
2174
- const hiddenCount = d.hiddenCount;
2175
- const bridge = useFlowNodeBridge();
2176
- const onChevronClick = (e) => {
2177
- e.stopPropagation();
2178
- bridge.toggleNodeCollapse(node.id);
2179
- };
2199
+ const branches = d.branches ?? [
2200
+ { id: "each", label: "Each" },
2201
+ { id: "done", label: "Done" }
2202
+ ];
2180
2203
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2181
- "div",
2204
+ BaseNode,
2182
2205
  {
2183
- className: "ods-flow-foreach",
2184
- "data-collapsed": collapsed ? "true" : "false",
2185
- "data-disabled": disabled ? "true" : void 0,
2186
- style: {
2187
- width: node.width ?? 420,
2188
- height: collapsed ? 40 : node.height ?? 260
2189
- },
2206
+ kind: d.kind ?? "FOR EACH",
2207
+ icon: d.icon,
2208
+ title: d.title ?? "For each",
2209
+ chip: d.chip ?? d.badge,
2210
+ description: d.description ?? d.subtitle ?? d.iterator,
2211
+ valueChip: d.valueChip,
2212
+ status: d.status,
2213
+ accent: "amber",
2190
2214
  children: [
2191
- !collapsed && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(NodeResizer, { minWidth: 240, minHeight: 120 }),
2192
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ods-flow-foreach__header", children: [
2193
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2194
- "button",
2195
- {
2196
- type: "button",
2197
- className: "ods-flow-foreach__chevron",
2198
- "data-flow-no-drag": "true",
2199
- "aria-label": collapsed ? "Expand iterator" : "Collapse iterator",
2200
- "aria-expanded": !collapsed,
2201
- onClick: onChevronClick,
2202
- onPointerDown: (e) => e.stopPropagation(),
2203
- children: collapsed ? "\u25B8" : "\u25BE"
2204
- }
2205
- ),
2206
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ods-flow-foreach__icon", "aria-hidden": "true", children: "\u21BB" }),
2207
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ods-flow-foreach__title", children: d.title ?? "For each" }),
2208
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("code", { className: "ods-flow-foreach__iterator", children: iteratorExpr }),
2209
- disabled && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2210
- "span",
2211
- {
2212
- className: "ods-flow-foreach__disabled-badge",
2213
- "aria-label": "Disabled subflow",
2214
- children: "Disabled"
2215
- }
2216
- ),
2217
- collapsed && hiddenCount !== void 0 && hiddenCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "ods-flow-foreach__count", "aria-label": `${hiddenCount} hidden steps`, children: [
2218
- hiddenCount,
2219
- " steps"
2220
- ] })
2221
- ] }),
2222
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top", id: "__group_in", label: !collapsed ? "in" : void 0 }),
2223
- collapsed ? (
2224
- // Collapsed: single bottom exit so the container behaves like a
2225
- // regular action in the chain.
2226
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom", id: "__group_out" })
2227
- ) : (
2228
- // Expanded: two bottom outputs spread across the bottom edge.
2229
- // Index/total tell the Handle to position them at 33% and 66%
2230
- // along the edge (per `handleSideStyle`). `each` is left of
2231
- // centre, `__group_out` (labelled "done") is right of centre.
2232
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2233
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2234
- Handle,
2235
- {
2236
- type: "source",
2237
- position: "bottom",
2238
- id: "each",
2239
- label: "each",
2240
- index: 0,
2241
- total: 2
2242
- }
2243
- ),
2244
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2245
- Handle,
2246
- {
2247
- type: "source",
2248
- position: "bottom",
2249
- id: "__group_out",
2250
- label: "done",
2251
- index: 1,
2252
- total: 2
2253
- }
2254
- )
2255
- ] })
2256
- )
2215
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2216
+ branches.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2217
+ Handle,
2218
+ {
2219
+ type: "source",
2220
+ position: "bottom",
2221
+ id: b.id,
2222
+ index: i,
2223
+ total: branches.length,
2224
+ label: b.label
2225
+ },
2226
+ b.id
2227
+ ))
2257
2228
  ]
2258
2229
  }
2259
2230
  );
@@ -2477,6 +2448,8 @@ function FlowCanvas(props) {
2477
2448
  reparentOnDrag = false,
2478
2449
  autoResizeContainers = false,
2479
2450
  containerAutoResizePadding = 32,
2451
+ containerMinWidth = 320,
2452
+ containerMinHeight = 200,
2480
2453
  height = "100%",
2481
2454
  width = "100%",
2482
2455
  className,
@@ -2613,7 +2586,9 @@ function FlowCanvas(props) {
2613
2586
  id: d.id,
2614
2587
  startPosition: d.position
2615
2588
  }));
2616
- const wantsReparent = altKey || reparentOnDragRef.current;
2589
+ const evaluateReparent = altKey || reparentOnDragRef.current;
2590
+ const hasParent = !!node.parentId;
2591
+ const skipClamp = altKey || reparentOnDragRef.current && !hasParent;
2617
2592
  dragRef.current = {
2618
2593
  pointerId,
2619
2594
  nodeId,
@@ -2621,7 +2596,8 @@ function FlowCanvas(props) {
2621
2596
  startClientY: clientY,
2622
2597
  startPosition: node.position,
2623
2598
  descendants: kids,
2624
- altDetach: wantsReparent,
2599
+ skipClamp,
2600
+ evaluateReparent,
2625
2601
  rafScheduled: false,
2626
2602
  nextDelta: null
2627
2603
  };
@@ -2681,6 +2657,8 @@ function FlowCanvas(props) {
2681
2657
  const reparentOnDragRef = (0, import_react15.useRef)(reparentOnDrag);
2682
2658
  const autoResizeContainersRef = (0, import_react15.useRef)(autoResizeContainers);
2683
2659
  const containerAutoResizePaddingRef = (0, import_react15.useRef)(containerAutoResizePadding);
2660
+ const containerMinWidthRef = (0, import_react15.useRef)(containerMinWidth);
2661
+ const containerMinHeightRef = (0, import_react15.useRef)(containerMinHeight);
2684
2662
  (0, import_react15.useEffect)(() => {
2685
2663
  edgesRef.current = edges;
2686
2664
  }, [edges]);
@@ -2714,6 +2692,12 @@ function FlowCanvas(props) {
2714
2692
  (0, import_react15.useEffect)(() => {
2715
2693
  containerAutoResizePaddingRef.current = containerAutoResizePadding;
2716
2694
  }, [containerAutoResizePadding]);
2695
+ (0, import_react15.useEffect)(() => {
2696
+ containerMinWidthRef.current = containerMinWidth;
2697
+ }, [containerMinWidth]);
2698
+ (0, import_react15.useEffect)(() => {
2699
+ containerMinHeightRef.current = containerMinHeight;
2700
+ }, [containerMinHeight]);
2717
2701
  const onNodesChangeRef = (0, import_react15.useRef)(onNodesChange);
2718
2702
  const onConnectRef = (0, import_react15.useRef)(onConnect);
2719
2703
  const onConnectEndRef = (0, import_react15.useRef)(onConnectEnd);
@@ -2758,7 +2742,7 @@ function FlowCanvas(props) {
2758
2742
  x: d.startPosition.x + delta.dx,
2759
2743
  y: d.startPosition.y + delta.dy
2760
2744
  };
2761
- const clamped = d.altDetach ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2745
+ const clamped = d.skipClamp ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2762
2746
  const isContainer = dragNode.type === "group" || dragNode.type === "forEach";
2763
2747
  const gap = isContainer ? subflowCollisionGapRef.current : nodeCollisionGapRef.current;
2764
2748
  const excludeIds = /* @__PURE__ */ new Set([d.nodeId, ...d.descendants.map((kid) => kid.id)]);
@@ -2810,7 +2794,7 @@ function FlowCanvas(props) {
2810
2794
  y: Math.round(proposed.y / g) * g
2811
2795
  };
2812
2796
  }
2813
- const clamped = drag.altDetach ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2797
+ const clamped = drag.skipClamp ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2814
2798
  const isContainer = dragNode.type === "group" || dragNode.type === "forEach";
2815
2799
  const gap = isContainer ? subflowCollisionGapRef.current : nodeCollisionGapRef.current;
2816
2800
  const excludeIds = /* @__PURE__ */ new Set([
@@ -2833,7 +2817,7 @@ function FlowCanvas(props) {
2833
2817
  )
2834
2818
  );
2835
2819
  }
2836
- if (drag.altDetach) {
2820
+ if (drag.evaluateReparent) {
2837
2821
  const targetGroup = findContainingGroup(
2838
2822
  {
2839
2823
  x: finalPos.x + (dragNode.width ?? 0) / 2,
@@ -2848,8 +2832,10 @@ function FlowCanvas(props) {
2848
2832
  ...dragNode,
2849
2833
  position: finalPos,
2850
2834
  parentId: nextParentId,
2851
- // Preserve extent only when staying in a group.
2852
- extent: nextParentId ? dragNode.extent : void 0
2835
+ // Adopting into a container always pins the child with
2836
+ // `extent: 'parent'` so it's clamped from the next drag
2837
+ // on. Detaching clears it.
2838
+ extent: nextParentId ? "parent" : void 0
2853
2839
  };
2854
2840
  changes.push(change.node.replace(drag.nodeId, updated));
2855
2841
  }
@@ -2860,7 +2846,9 @@ function FlowCanvas(props) {
2860
2846
  // Projected children: apply the position + reparent
2861
2847
  // changes we just built before measuring bboxes.
2862
2848
  changes,
2863
- containerAutoResizePaddingRef.current
2849
+ containerAutoResizePaddingRef.current,
2850
+ containerMinWidthRef.current,
2851
+ containerMinHeightRef.current
2864
2852
  );
2865
2853
  for (const c2 of containerChanges) changes.push(c2);
2866
2854
  }
@@ -2871,38 +2859,54 @@ function FlowCanvas(props) {
2871
2859
  }
2872
2860
  const c = connRef.current;
2873
2861
  if (c && c.pointerId === e.pointerId) {
2874
- const target = e.target;
2875
- const handleEl = target?.closest("[data-handle-id]");
2862
+ const rect = containerRef.current?.getBoundingClientRect();
2863
+ const flowPos = rect ? screenToFlow({ x: e.clientX - rect.left, y: e.clientY - rect.top }, vp) : { x: 0, y: 0 };
2864
+ const snapRadius = 26 / Math.max(0.1, vp.zoom);
2865
+ let bestDesc = null;
2866
+ let bestDist = snapRadius;
2867
+ for (const desc of handleRegistry.all()) {
2868
+ if (desc.nodeId === c.from.nodeId) continue;
2869
+ if (desc.type === c.from.handleType) continue;
2870
+ if (!desc.canEnd) continue;
2871
+ const targetNode = nodesRef.current.find((n) => n.id === desc.nodeId);
2872
+ if (!targetNode || targetNode.hidden) continue;
2873
+ const centre = handleCentre(targetNode, desc.side, desc.index, desc.total);
2874
+ const dist = Math.hypot(centre.x - flowPos.x, centre.y - flowPos.y);
2875
+ if (dist < bestDist) {
2876
+ bestDist = dist;
2877
+ bestDesc = desc;
2878
+ }
2879
+ }
2876
2880
  let connection = null;
2877
2881
  let connectedTo;
2878
- if (handleEl) {
2879
- const targetNodeId = handleEl.dataset.handleNodeId;
2880
- const targetHandleId = handleEl.dataset.handleId;
2881
- const targetType = handleEl.dataset.handleType;
2882
- const connectableEnd = handleEl.dataset.handleConnectableEnd === "true";
2883
- if (connectableEnd && (targetNodeId !== c.from.nodeId || targetHandleId !== c.from.handleId) && targetType !== c.from.handleType) {
2884
- const source = c.from.handleType === "source" ? c.from : { nodeId: targetNodeId, handleId: targetHandleId, handleType: "source" };
2885
- const target2 = c.from.handleType === "target" ? c.from : { nodeId: targetNodeId, handleId: targetHandleId, handleType: "target" };
2886
- connection = {
2887
- source: source.nodeId,
2888
- sourceHandle: source.handleId,
2889
- target: target2.nodeId,
2890
- targetHandle: target2.handleId
2891
- };
2892
- connectedTo = {
2893
- nodeId: targetNodeId,
2894
- handleId: targetHandleId,
2895
- handleType: targetType
2896
- };
2897
- const validator = isValidConnectionRef.current;
2898
- if (validator && !validator(connection)) {
2899
- connection = null;
2900
- connectedTo = void 0;
2901
- }
2882
+ if (bestDesc) {
2883
+ const source = c.from.handleType === "source" ? c.from : {
2884
+ nodeId: bestDesc.nodeId,
2885
+ handleId: bestDesc.handleId,
2886
+ handleType: "source"
2887
+ };
2888
+ const target2 = c.from.handleType === "target" ? c.from : {
2889
+ nodeId: bestDesc.nodeId,
2890
+ handleId: bestDesc.handleId,
2891
+ handleType: "target"
2892
+ };
2893
+ connection = {
2894
+ source: source.nodeId,
2895
+ sourceHandle: source.handleId,
2896
+ target: target2.nodeId,
2897
+ targetHandle: target2.handleId
2898
+ };
2899
+ connectedTo = {
2900
+ nodeId: bestDesc.nodeId,
2901
+ handleId: bestDesc.handleId,
2902
+ handleType: bestDesc.type
2903
+ };
2904
+ const validator = isValidConnectionRef.current;
2905
+ if (validator && !validator(connection)) {
2906
+ connection = null;
2907
+ connectedTo = void 0;
2902
2908
  }
2903
2909
  }
2904
- const rect = containerRef.current?.getBoundingClientRect();
2905
- const flowPos = rect ? screenToFlow({ x: e.clientX - rect.left, y: e.clientY - rect.top }, vp) : { x: 0, y: 0 };
2906
2910
  const endState = {
2907
2911
  cancelled: !connection,
2908
2912
  position: { x: e.clientX, y: e.clientY },
@@ -2935,6 +2939,25 @@ function FlowCanvas(props) {
2935
2939
  window.removeEventListener("pointercancel", onPointerCancel);
2936
2940
  };
2937
2941
  }, [store]);
2942
+ (0, import_react15.useEffect)(() => {
2943
+ if (!autoResizeContainers || draggingId) return;
2944
+ const resizeChanges = computeContainerAutoResize(
2945
+ nodes,
2946
+ [],
2947
+ containerAutoResizePadding,
2948
+ containerMinWidth,
2949
+ containerMinHeight
2950
+ );
2951
+ if (resizeChanges.length > 0) onNodesChange?.(resizeChanges);
2952
+ }, [
2953
+ nodes,
2954
+ draggingId,
2955
+ autoResizeContainers,
2956
+ containerAutoResizePadding,
2957
+ containerMinWidth,
2958
+ containerMinHeight,
2959
+ onNodesChange
2960
+ ]);
2938
2961
  const panRef = (0, import_react15.useRef)(null);
2939
2962
  const onCanvasPointerDown = (e) => {
2940
2963
  if (e.button !== 0) return;
@@ -3397,7 +3420,9 @@ function FlowCanvas(props) {
3397
3420
  function onEdgesChangeRef(id, cb) {
3398
3421
  cb?.([change.edge.remove(id)]);
3399
3422
  }
3400
- function computeContainerAutoResize(nodes, pending, padding) {
3423
+ var CONTAINER_DROP_HEADROOM_W = 220;
3424
+ var CONTAINER_DROP_HEADROOM_H = 2 * 128;
3425
+ function computeContainerAutoResize(nodes, pending, padding, minWidth, minHeight) {
3401
3426
  const projected = /* @__PURE__ */ new Map();
3402
3427
  for (const n of nodes) projected.set(n.id, n);
3403
3428
  for (const c of pending) {
@@ -3418,7 +3443,14 @@ function computeContainerAutoResize(nodes, pending, padding) {
3418
3443
  for (const n of projected.values()) {
3419
3444
  if (n.parentId === parent.id) children.push(n);
3420
3445
  }
3421
- if (children.length === 0) continue;
3446
+ if (children.length === 0) {
3447
+ const curW2 = parent.width ?? 480;
3448
+ const curH2 = parent.height ?? 240;
3449
+ if (curW2 !== minWidth || curH2 !== minHeight) {
3450
+ out.push(change.node.dimensions(parent.id, { width: minWidth, height: minHeight }));
3451
+ }
3452
+ continue;
3453
+ }
3422
3454
  let minX = Number.POSITIVE_INFINITY;
3423
3455
  let minY = Number.POSITIVE_INFINITY;
3424
3456
  let maxX = Number.NEGATIVE_INFINITY;
@@ -3433,12 +3465,12 @@ function computeContainerAutoResize(nodes, pending, padding) {
3433
3465
  }
3434
3466
  const targetX = minX - padding;
3435
3467
  const targetY = minY - padding - HEADER;
3436
- const targetW = maxX - minX + padding * 2;
3437
- const targetH = maxY - minY + padding * 2 + HEADER;
3438
3468
  const curW = parent.width ?? 480;
3439
3469
  const curH = parent.height ?? 240;
3440
- const nextW = Math.max(curW, targetW);
3441
- const nextH = Math.max(curH, targetH);
3470
+ const fitW = maxX - minX + padding * 2 + CONTAINER_DROP_HEADROOM_W;
3471
+ const fitH = maxY - minY + padding * 2 + HEADER + CONTAINER_DROP_HEADROOM_H;
3472
+ const nextW = Math.max(minWidth, fitW);
3473
+ const nextH = Math.max(minHeight, fitH);
3442
3474
  const positionDirty = parent.position.x !== targetX || parent.position.y !== targetY;
3443
3475
  const sizeDirty = curW !== nextW || curH !== nextH;
3444
3476
  if (positionDirty) {
@@ -3494,8 +3526,456 @@ var EdgesLayer = (0, import_react15.memo)(function EdgesLayer2({
3494
3526
  );
3495
3527
  });
3496
3528
 
3497
- // src/workflow/components/NodeToolbar/NodeToolbar.tsx
3529
+ // src/workflow/components/FxPanel/FxPanel.tsx
3530
+ var import_react16 = require("react");
3531
+ var import_icons2 = require("@octaviaflow/icons");
3498
3532
  var import_jsx_runtime9 = require("react/jsx-runtime");
3533
+ var KIND_ICON = {
3534
+ function: import_icons2.FunctionMathIcon,
3535
+ variable: import_icons2.ValueVariableIcon
3536
+ };
3537
+ function countLeaves(items) {
3538
+ let n = 0;
3539
+ for (const it of items) {
3540
+ if (it.children && it.children.length > 0) n += countLeaves(it.children);
3541
+ else n += 1;
3542
+ }
3543
+ return n;
3544
+ }
3545
+ function collectBranchIds(items, out) {
3546
+ for (const it of items) {
3547
+ if (it.children && it.children.length > 0) {
3548
+ out.add(it.id);
3549
+ collectBranchIds(it.children, out);
3550
+ }
3551
+ }
3552
+ }
3553
+ function flattenTree(items, depth, expandedItems, out) {
3554
+ for (const it of items) {
3555
+ const hasChildren = !!it.children && it.children.length > 0;
3556
+ const expanded = hasChildren && expandedItems.has(it.id);
3557
+ out.push({ item: it, depth, hasChildren, expanded });
3558
+ if (expanded) flattenTree(it.children, depth + 1, expandedItems, out);
3559
+ }
3560
+ }
3561
+ function collectHits(items, ancestors, q, out) {
3562
+ for (const it of items) {
3563
+ if (it.children && it.children.length > 0) {
3564
+ collectHits(it.children, [...ancestors, it.label], q, out);
3565
+ } else {
3566
+ const path = [...ancestors, it.label].join(" / ");
3567
+ const hay = `${it.label} ${it.description ?? ""} ${path} ${it.insertValue ?? ""}`.toLowerCase();
3568
+ if (hay.includes(q)) out.push({ item: it, ancestors });
3569
+ }
3570
+ }
3571
+ }
3572
+ function FxPanel({
3573
+ categories,
3574
+ title = "Data References",
3575
+ hint = "Drag onto a field, or click an item to insert it.",
3576
+ search: controlledSearch,
3577
+ defaultSearch = "",
3578
+ onSearchChange,
3579
+ expandedCategory: controlledExpanded,
3580
+ defaultExpandedCategory,
3581
+ onExpandedCategoryChange,
3582
+ onClose,
3583
+ onItemDragStart,
3584
+ onItemSelect,
3585
+ width = 300,
3586
+ emptyLabel = "No matches",
3587
+ searchPlaceholder = "Search fields, functions & variables\u2026",
3588
+ className,
3589
+ style
3590
+ }) {
3591
+ const [internalSearch, setInternalSearch] = (0, import_react16.useState)(defaultSearch);
3592
+ const search = controlledSearch ?? internalSearch;
3593
+ const query = search.trim().toLowerCase();
3594
+ const setSearch = (next) => {
3595
+ if (controlledSearch === void 0) setInternalSearch(next);
3596
+ onSearchChange?.(next);
3597
+ };
3598
+ const visibleCategories = (0, import_react16.useMemo)(
3599
+ () => categories.filter((c) => c.items.length > 0),
3600
+ [categories]
3601
+ );
3602
+ const [internalCat, setInternalCat] = (0, import_react16.useState)(
3603
+ defaultExpandedCategory !== void 0 ? defaultExpandedCategory : visibleCategories[0]?.id ?? null
3604
+ );
3605
+ const openCategoryId = controlledExpanded ?? internalCat;
3606
+ const setOpenCategory = (id) => {
3607
+ if (controlledExpanded === void 0) setInternalCat(id);
3608
+ onExpandedCategoryChange?.(id);
3609
+ };
3610
+ const [expandedItems, setExpandedItems] = (0, import_react16.useState)(() => {
3611
+ const ids = /* @__PURE__ */ new Set();
3612
+ for (const cat of categories) {
3613
+ for (const item of cat.items) {
3614
+ if (item.children && item.children.length > 0) ids.add(item.id);
3615
+ }
3616
+ }
3617
+ return ids;
3618
+ });
3619
+ const toggleItem = (id) => {
3620
+ setExpandedItems((prev) => {
3621
+ const next = new Set(prev);
3622
+ if (next.has(id)) next.delete(id);
3623
+ else next.add(id);
3624
+ return next;
3625
+ });
3626
+ };
3627
+ const setCategoryExpansion = (cat, expand) => {
3628
+ const branchIds = /* @__PURE__ */ new Set();
3629
+ collectBranchIds(cat.items, branchIds);
3630
+ setExpandedItems((prev) => {
3631
+ const next = new Set(prev);
3632
+ for (const id of branchIds) {
3633
+ if (expand) next.add(id);
3634
+ else next.delete(id);
3635
+ }
3636
+ return next;
3637
+ });
3638
+ };
3639
+ const [draggingId, setDraggingId] = (0, import_react16.useState)(null);
3640
+ const [focusedId, setFocusedId] = (0, import_react16.useState)(null);
3641
+ const hits = (0, import_react16.useMemo)(() => {
3642
+ if (!query) return null;
3643
+ const all = [];
3644
+ for (const cat of visibleCategories) {
3645
+ const out = [];
3646
+ collectHits(cat.items, [], query, out);
3647
+ for (const hit of out) all.push({ category: cat, hit });
3648
+ }
3649
+ return all;
3650
+ }, [query, visibleCategories]);
3651
+ const handleDragStart = (item, category) => (e) => {
3652
+ setDraggingId(item.id);
3653
+ if (onItemDragStart) {
3654
+ onItemDragStart(item, category, e);
3655
+ } else if (item.insertValue) {
3656
+ e.dataTransfer.setData("text/plain", item.insertValue);
3657
+ e.dataTransfer.setData("application/x-fx-insert", item.insertValue);
3658
+ e.dataTransfer.setData(
3659
+ "application/x-fx-type",
3660
+ category.kind ?? "function"
3661
+ );
3662
+ e.dataTransfer.effectAllowed = "copy";
3663
+ }
3664
+ };
3665
+ const listRef = (0, import_react16.useRef)(null);
3666
+ const focusRow = (id) => {
3667
+ setFocusedId(id);
3668
+ requestAnimationFrame(() => {
3669
+ listRef.current?.querySelector(`[data-fx-row="${CSS.escape(id)}"]`)?.focus();
3670
+ });
3671
+ };
3672
+ const onRowKeyDown = (e, rows, row, category) => {
3673
+ const idx = rows.findIndex((r) => r.item.id === row.item.id);
3674
+ if (idx < 0) return;
3675
+ switch (e.key) {
3676
+ case "ArrowDown":
3677
+ e.preventDefault();
3678
+ if (idx < rows.length - 1) focusRow(rows[idx + 1].item.id);
3679
+ break;
3680
+ case "ArrowUp":
3681
+ e.preventDefault();
3682
+ if (idx > 0) focusRow(rows[idx - 1].item.id);
3683
+ break;
3684
+ case "ArrowRight":
3685
+ if (row.hasChildren) {
3686
+ e.preventDefault();
3687
+ if (!row.expanded) toggleItem(row.item.id);
3688
+ else if (idx < rows.length - 1) focusRow(rows[idx + 1].item.id);
3689
+ }
3690
+ break;
3691
+ case "ArrowLeft":
3692
+ if (row.hasChildren && row.expanded) {
3693
+ e.preventDefault();
3694
+ toggleItem(row.item.id);
3695
+ } else {
3696
+ for (let i = idx - 1; i >= 0; i--) {
3697
+ if (rows[i].depth < row.depth) {
3698
+ e.preventDefault();
3699
+ focusRow(rows[i].item.id);
3700
+ break;
3701
+ }
3702
+ }
3703
+ }
3704
+ break;
3705
+ case "Home":
3706
+ e.preventDefault();
3707
+ if (rows.length) focusRow(rows[0].item.id);
3708
+ break;
3709
+ case "End":
3710
+ e.preventDefault();
3711
+ if (rows.length) focusRow(rows[rows.length - 1].item.id);
3712
+ break;
3713
+ case "Enter":
3714
+ case " ":
3715
+ e.preventDefault();
3716
+ if (row.hasChildren) toggleItem(row.item.id);
3717
+ else onItemSelect?.(row.item, category);
3718
+ break;
3719
+ default:
3720
+ break;
3721
+ }
3722
+ };
3723
+ const searchRows = (0, import_react16.useMemo)(
3724
+ () => hits ? hits.map(({ hit }) => ({
3725
+ item: hit.item,
3726
+ depth: 0,
3727
+ hasChildren: false,
3728
+ expanded: false
3729
+ })) : [],
3730
+ [hits]
3731
+ );
3732
+ const renderRow = (row, category, rows, crumb) => {
3733
+ const { item, depth, hasChildren, expanded } = row;
3734
+ const mono = category.kind === "function";
3735
+ const draggable = !!item.insertValue;
3736
+ const isFocused = focusedId === item.id;
3737
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3738
+ "div",
3739
+ {
3740
+ "data-fx-row": item.id,
3741
+ className: cn(
3742
+ "ods-flow-fx-panel__row",
3743
+ hasChildren ? "ods-flow-fx-panel__row--branch" : "ods-flow-fx-panel__row--leaf",
3744
+ draggingId === item.id && "ods-flow-fx-panel__row--dragging",
3745
+ isFocused && "ods-flow-fx-panel__row--focused"
3746
+ ),
3747
+ role: "treeitem",
3748
+ "aria-expanded": hasChildren ? expanded : void 0,
3749
+ "aria-level": depth + 1,
3750
+ tabIndex: isFocused || focusedId === null && rows[0]?.item.id === item.id ? 0 : -1,
3751
+ draggable,
3752
+ onDragStart: draggable ? handleDragStart(item, category) : void 0,
3753
+ onDragEnd: () => setDraggingId(null),
3754
+ onFocus: () => setFocusedId(item.id),
3755
+ onKeyDown: (e) => onRowKeyDown(e, rows, row, category),
3756
+ onClick: () => {
3757
+ if (hasChildren) toggleItem(item.id);
3758
+ else onItemSelect?.(item, category);
3759
+ },
3760
+ title: item.insertValue,
3761
+ children: [
3762
+ Array.from({ length: depth }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-flow-fx-panel__indent", "aria-hidden": "true" }, i)),
3763
+ hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-flow-fx-panel__twisty", "aria-hidden": "true", children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.ChevronDownIcon, { size: "sm" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.ChevronRightIcon, { size: "sm" }) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-flow-fx-panel__grip", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.DraggableIcon, { size: "sm" }) }),
3764
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ods-flow-fx-panel__row-main", children: [
3765
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ods-flow-fx-panel__row-line", children: [
3766
+ mono ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { className: "ods-flow-fx-panel__row-label ods-flow-fx-panel__row-label--mono", children: item.label }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-flow-fx-panel__row-label", children: item.label }),
3767
+ item.valueType && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3768
+ "span",
3769
+ {
3770
+ className: cn(
3771
+ "ods-flow-fx-panel__type",
3772
+ `ods-flow-fx-panel__type--${item.valueType}`
3773
+ ),
3774
+ children: item.valueType
3775
+ }
3776
+ )
3777
+ ] }),
3778
+ (item.description || crumb) && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ods-flow-fx-panel__row-sub", children: [
3779
+ crumb && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-flow-fx-panel__crumb", children: crumb }),
3780
+ item.description
3781
+ ] })
3782
+ ] })
3783
+ ]
3784
+ },
3785
+ item.id
3786
+ );
3787
+ };
3788
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3789
+ "aside",
3790
+ {
3791
+ className: cn("ods-flow-fx-panel", className),
3792
+ style: { width, ...style },
3793
+ "aria-label": typeof title === "string" ? title : "Data References",
3794
+ children: [
3795
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("header", { className: "ods-flow-fx-panel__header", children: [
3796
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { className: "ods-flow-fx-panel__title", children: title }),
3797
+ onClose && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3798
+ "button",
3799
+ {
3800
+ type: "button",
3801
+ className: "ods-flow-fx-panel__close",
3802
+ onClick: onClose,
3803
+ "aria-label": "Close data references",
3804
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.CloseIcon, { size: "sm" })
3805
+ }
3806
+ )
3807
+ ] }),
3808
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-flow-fx-panel__search", children: [
3809
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-flow-fx-panel__search-icon", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.SearchIcon, { size: "sm" }) }),
3810
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3811
+ "input",
3812
+ {
3813
+ type: "text",
3814
+ className: "ods-flow-fx-panel__search-input",
3815
+ placeholder: searchPlaceholder,
3816
+ "aria-label": "Search fields, functions and variables",
3817
+ value: search,
3818
+ onChange: (e) => setSearch(e.target.value)
3819
+ }
3820
+ ),
3821
+ search && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3822
+ "button",
3823
+ {
3824
+ type: "button",
3825
+ className: "ods-flow-fx-panel__search-clear",
3826
+ onClick: () => setSearch(""),
3827
+ "aria-label": "Clear search",
3828
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.CloseIcon, { size: "sm" })
3829
+ }
3830
+ )
3831
+ ] }),
3832
+ hint && !query && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ods-flow-fx-panel__hint", children: hint }),
3833
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-flow-fx-panel__list", ref: listRef, role: "tree", children: [
3834
+ query && hits && (hits.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-flow-fx-panel__results", children: [
3835
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-flow-fx-panel__results-count", children: [
3836
+ hits.length,
3837
+ " ",
3838
+ hits.length === 1 ? "match" : "matches"
3839
+ ] }),
3840
+ hits.map(
3841
+ ({ category, hit }, i) => renderRow(
3842
+ searchRows[i],
3843
+ category,
3844
+ searchRows,
3845
+ hit.ancestors.length ? hit.ancestors.join(" / ") : category.label
3846
+ )
3847
+ )
3848
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ods-flow-fx-panel__empty", children: emptyLabel })),
3849
+ !query && visibleCategories.map((cat) => {
3850
+ const isOpen = openCategoryId === cat.id;
3851
+ const kind = cat.kind ?? "function";
3852
+ const KindIcon = KIND_ICON[kind];
3853
+ const branchIds = /* @__PURE__ */ new Set();
3854
+ collectBranchIds(cat.items, branchIds);
3855
+ const hasTree = branchIds.size > 0;
3856
+ const rows = [];
3857
+ if (isOpen) flattenTree(cat.items, 0, expandedItems, rows);
3858
+ const allExpanded = hasTree && [...branchIds].every((id) => expandedItems.has(id));
3859
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-flow-fx-panel__category", children: [
3860
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3861
+ "div",
3862
+ {
3863
+ className: cn(
3864
+ "ods-flow-fx-panel__cat-header",
3865
+ isOpen && "ods-flow-fx-panel__cat-header--open"
3866
+ ),
3867
+ children: [
3868
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3869
+ "button",
3870
+ {
3871
+ type: "button",
3872
+ className: "ods-flow-fx-panel__cat-toggle",
3873
+ "aria-expanded": isOpen,
3874
+ onClick: () => setOpenCategory(isOpen ? null : cat.id),
3875
+ children: [
3876
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3877
+ import_icons2.ChevronRightIcon,
3878
+ {
3879
+ size: "sm",
3880
+ className: cn(
3881
+ "ods-flow-fx-panel__chevron",
3882
+ isOpen && "ods-flow-fx-panel__chevron--open"
3883
+ )
3884
+ }
3885
+ ),
3886
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3887
+ "span",
3888
+ {
3889
+ className: cn(
3890
+ "ods-flow-fx-panel__badge",
3891
+ `ods-flow-fx-panel__badge--${kind}`
3892
+ ),
3893
+ children: [
3894
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3895
+ KindIcon,
3896
+ {
3897
+ size: "sm",
3898
+ className: "ods-flow-fx-panel__badge-icon"
3899
+ }
3900
+ ),
3901
+ cat.label
3902
+ ]
3903
+ }
3904
+ ),
3905
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-flow-fx-panel__count", children: countLeaves(cat.items) })
3906
+ ]
3907
+ }
3908
+ ),
3909
+ isOpen && hasTree && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3910
+ "button",
3911
+ {
3912
+ type: "button",
3913
+ className: "ods-flow-fx-panel__cat-action",
3914
+ "aria-label": allExpanded ? "Collapse all" : "Expand all",
3915
+ title: allExpanded ? "Collapse all" : "Expand all",
3916
+ onClick: () => setCategoryExpansion(cat, !allExpanded),
3917
+ children: allExpanded ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.CollapseAllIcon, { size: "sm" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.ExpandAllIcon, { size: "sm" })
3918
+ }
3919
+ )
3920
+ ]
3921
+ }
3922
+ ),
3923
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3924
+ "div",
3925
+ {
3926
+ className: "ods-flow-fx-panel__rows",
3927
+ role: "group",
3928
+ "aria-label": cat.label,
3929
+ children: rows.map((row) => renderRow(row, cat, rows))
3930
+ }
3931
+ )
3932
+ ] }, cat.id);
3933
+ })
3934
+ ] })
3935
+ ]
3936
+ }
3937
+ );
3938
+ }
3939
+
3940
+ // src/workflow/components/FxPanel/DataRefButton.tsx
3941
+ var import_icons3 = require("@octaviaflow/icons");
3942
+ var import_jsx_runtime10 = require("react/jsx-runtime");
3943
+ function DataRefButton({
3944
+ active = false,
3945
+ label = "Data ref",
3946
+ className,
3947
+ type = "button",
3948
+ ...rest
3949
+ }) {
3950
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3951
+ "button",
3952
+ {
3953
+ ...rest,
3954
+ type,
3955
+ className: cn(
3956
+ "ods-flow-dataref-button",
3957
+ active && "ods-flow-dataref-button--active",
3958
+ label == null && "ods-flow-dataref-button--icon-only",
3959
+ className
3960
+ ),
3961
+ "aria-pressed": active,
3962
+ title: active ? "Hide data references" : "Show data references",
3963
+ children: [
3964
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3965
+ import_icons3.DataReferenceIcon,
3966
+ {
3967
+ size: "sm",
3968
+ className: "ods-flow-dataref-button__icon"
3969
+ }
3970
+ ),
3971
+ label != null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ods-flow-dataref-button__label", children: label })
3972
+ ]
3973
+ }
3974
+ );
3975
+ }
3976
+
3977
+ // src/workflow/components/NodeToolbar/NodeToolbar.tsx
3978
+ var import_jsx_runtime11 = require("react/jsx-runtime");
3499
3979
  function NodeToolbar({
3500
3980
  isVisible,
3501
3981
  position = "top",
@@ -3510,7 +3990,7 @@ function NodeToolbar({
3510
3990
  const show = isVisible ?? node.selected;
3511
3991
  if (!show) return null;
3512
3992
  const inverseScale = 1 / viewport.zoom;
3513
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3993
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3514
3994
  "div",
3515
3995
  {
3516
3996
  className: cn("ods-node-toolbar", `ods-node-toolbar--${position}`, className),
@@ -3522,7 +4002,7 @@ function NodeToolbar({
3522
4002
  onPointerDown: (e) => e.stopPropagation(),
3523
4003
  onMouseDown: (e) => e.stopPropagation(),
3524
4004
  onClick: (e) => e.stopPropagation(),
3525
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4005
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3526
4006
  "div",
3527
4007
  {
3528
4008
  className: "ods-node-toolbar__inner",
@@ -3671,11 +4151,13 @@ function toggleGroupCollapse(groupId, nodes) {
3671
4151
  DEFAULT_NODE_HEIGHT,
3672
4152
  DEFAULT_NODE_KINDS,
3673
4153
  DEFAULT_NODE_WIDTH,
4154
+ DataRefButton,
3674
4155
  ErrorNode,
3675
4156
  FlowCanvas,
3676
4157
  FlowEdge,
3677
4158
  FlowNode,
3678
4159
  ForEachNode,
4160
+ FxPanel,
3679
4161
  GroupNode,
3680
4162
  Handle,
3681
4163
  HttpRequestNode,