@octaviaflow/core 3.0.18 → 3.1.0-beta.53

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 (257) hide show
  1. package/dist/chunk-2NGC7AI3.js +2637 -0
  2. package/dist/chunk-2NGC7AI3.js.map +1 -0
  3. package/dist/chunk-2O6K5PLY.js +2637 -0
  4. package/dist/chunk-2O6K5PLY.js.map +1 -0
  5. package/dist/chunk-2ZIOFEIS.js +3001 -0
  6. package/dist/chunk-2ZIOFEIS.js.map +1 -0
  7. package/dist/chunk-3DWB2PUF.js +2991 -0
  8. package/dist/chunk-3DWB2PUF.js.map +1 -0
  9. package/dist/chunk-4ZALUTZS.js +2936 -0
  10. package/dist/chunk-4ZALUTZS.js.map +1 -0
  11. package/dist/chunk-5ARKSRED.js +2951 -0
  12. package/dist/chunk-5ARKSRED.js.map +1 -0
  13. package/dist/chunk-5OSGSJMM.js +2981 -0
  14. package/dist/chunk-5OSGSJMM.js.map +1 -0
  15. package/dist/chunk-5YQQMEF3.js +2981 -0
  16. package/dist/chunk-5YQQMEF3.js.map +1 -0
  17. package/dist/chunk-6QCJK7H7.js +2991 -0
  18. package/dist/chunk-6QCJK7H7.js.map +1 -0
  19. package/dist/chunk-6QDYERZD.js +2944 -0
  20. package/dist/chunk-6QDYERZD.js.map +1 -0
  21. package/dist/chunk-76TP67ED.js +2984 -0
  22. package/dist/chunk-76TP67ED.js.map +1 -0
  23. package/dist/chunk-A6KMO4JV.js +2949 -0
  24. package/dist/chunk-A6KMO4JV.js.map +1 -0
  25. package/dist/chunk-B7FTWSTM.js +2938 -0
  26. package/dist/chunk-B7FTWSTM.js.map +1 -0
  27. package/dist/chunk-BCO6M26F.js +2940 -0
  28. package/dist/chunk-BCO6M26F.js.map +1 -0
  29. package/dist/chunk-C3UD2AZ5.js +2637 -0
  30. package/dist/chunk-C3UD2AZ5.js.map +1 -0
  31. package/dist/chunk-CEUP4NK2.js +2850 -0
  32. package/dist/chunk-CEUP4NK2.js.map +1 -0
  33. package/dist/chunk-DBNSBJO7.js +2993 -0
  34. package/dist/chunk-DBNSBJO7.js.map +1 -0
  35. package/dist/chunk-ECIHUVU7.js +2986 -0
  36. package/dist/chunk-ECIHUVU7.js.map +1 -0
  37. package/dist/chunk-EERNYLFL.js +2860 -0
  38. package/dist/chunk-EERNYLFL.js.map +1 -0
  39. package/dist/chunk-EKFDJX4G.js +2872 -0
  40. package/dist/chunk-EKFDJX4G.js.map +1 -0
  41. package/dist/chunk-GJA3GJUZ.js +2844 -0
  42. package/dist/chunk-GJA3GJUZ.js.map +1 -0
  43. package/dist/chunk-HDOTOZNA.js +2936 -0
  44. package/dist/chunk-HDOTOZNA.js.map +1 -0
  45. package/dist/chunk-IOKUV7FD.js +2658 -0
  46. package/dist/chunk-IOKUV7FD.js.map +1 -0
  47. package/dist/chunk-IUIICQU5.js +2946 -0
  48. package/dist/chunk-IUIICQU5.js.map +1 -0
  49. package/dist/chunk-J2UYZI6D.js +2946 -0
  50. package/dist/chunk-J2UYZI6D.js.map +1 -0
  51. package/dist/chunk-J7YASALS.js +2859 -0
  52. package/dist/chunk-J7YASALS.js.map +1 -0
  53. package/dist/chunk-JIEUYBQT.js +2658 -0
  54. package/dist/chunk-JIEUYBQT.js.map +1 -0
  55. package/dist/chunk-K2H7JLQW.js +2952 -0
  56. package/dist/chunk-K2H7JLQW.js.map +1 -0
  57. package/dist/chunk-KOE4VS4I.js +2945 -0
  58. package/dist/chunk-KOE4VS4I.js.map +1 -0
  59. package/dist/chunk-KUXYBP66.js +2953 -0
  60. package/dist/chunk-KUXYBP66.js.map +1 -0
  61. package/dist/chunk-KYMYNYFV.js +2656 -0
  62. package/dist/chunk-KYMYNYFV.js.map +1 -0
  63. package/dist/chunk-M2QXKYL2.js +2945 -0
  64. package/dist/chunk-M2QXKYL2.js.map +1 -0
  65. package/dist/chunk-MMXL343D.js +2974 -0
  66. package/dist/chunk-MMXL343D.js.map +1 -0
  67. package/dist/chunk-MXJL3EPE.js +2986 -0
  68. package/dist/chunk-MXJL3EPE.js.map +1 -0
  69. package/dist/chunk-MYZ25B2R.js +2995 -0
  70. package/dist/chunk-MYZ25B2R.js.map +1 -0
  71. package/dist/chunk-NTMEYB7B.js +2949 -0
  72. package/dist/chunk-NTMEYB7B.js.map +1 -0
  73. package/dist/chunk-PVJXX6GP.js +2640 -0
  74. package/dist/chunk-PVJXX6GP.js.map +1 -0
  75. package/dist/chunk-Q6ERDPQR.js +2981 -0
  76. package/dist/chunk-Q6ERDPQR.js.map +1 -0
  77. package/dist/chunk-S2SSBMWJ.js +2658 -0
  78. package/dist/chunk-S2SSBMWJ.js.map +1 -0
  79. package/dist/chunk-SLVDAZSX.js +2946 -0
  80. package/dist/chunk-SLVDAZSX.js.map +1 -0
  81. package/dist/chunk-UQBPYONV.js +2991 -0
  82. package/dist/chunk-UQBPYONV.js.map +1 -0
  83. package/dist/chunk-UXMNBS22.js +2955 -0
  84. package/dist/chunk-UXMNBS22.js.map +1 -0
  85. package/dist/chunk-WEGED7TA.js +2991 -0
  86. package/dist/chunk-WEGED7TA.js.map +1 -0
  87. package/dist/chunk-WEPTBLWX.js +2847 -0
  88. package/dist/chunk-WEPTBLWX.js.map +1 -0
  89. package/dist/chunk-WG4ZQMPS.js +2844 -0
  90. package/dist/chunk-WG4ZQMPS.js.map +1 -0
  91. package/dist/chunk-XEPEBHAW.js +2808 -0
  92. package/dist/chunk-XEPEBHAW.js.map +1 -0
  93. package/dist/chunk-XG2OYFX6.js +2925 -0
  94. package/dist/chunk-XG2OYFX6.js.map +1 -0
  95. package/dist/chunk-XSRJM2TR.js +2944 -0
  96. package/dist/chunk-XSRJM2TR.js.map +1 -0
  97. package/dist/chunk-ZAMJEU42.js +2992 -0
  98. package/dist/chunk-ZAMJEU42.js.map +1 -0
  99. package/dist/chunk-ZRAM6FXB.js +2949 -0
  100. package/dist/chunk-ZRAM6FXB.js.map +1 -0
  101. package/dist/components/ChatAttachment/ChatAttachment.d.ts +20 -0
  102. package/dist/components/ChatAttachment/ChatAttachment.d.ts.map +1 -0
  103. package/dist/components/ChatAttachment/index.d.ts +2 -0
  104. package/dist/components/ChatAttachment/index.d.ts.map +1 -0
  105. package/dist/components/ChatBubble/ChatBubble.d.ts +11 -1
  106. package/dist/components/ChatBubble/ChatBubble.d.ts.map +1 -1
  107. package/dist/components/ChatCodeBlock/ChatCodeBlock.d.ts +19 -0
  108. package/dist/components/ChatCodeBlock/ChatCodeBlock.d.ts.map +1 -0
  109. package/dist/components/ChatCodeBlock/index.d.ts +2 -0
  110. package/dist/components/ChatCodeBlock/index.d.ts.map +1 -0
  111. package/dist/components/ChatComposer/ChatComposer.d.ts +51 -0
  112. package/dist/components/ChatComposer/ChatComposer.d.ts.map +1 -0
  113. package/dist/components/ChatComposer/index.d.ts +2 -0
  114. package/dist/components/ChatComposer/index.d.ts.map +1 -0
  115. package/dist/components/ChatDayDivider/ChatDayDivider.d.ts +11 -0
  116. package/dist/components/ChatDayDivider/ChatDayDivider.d.ts.map +1 -0
  117. package/dist/components/ChatDayDivider/index.d.ts +2 -0
  118. package/dist/components/ChatDayDivider/index.d.ts.map +1 -0
  119. package/dist/components/ChatFeedback/ChatFeedback.d.ts +22 -0
  120. package/dist/components/ChatFeedback/ChatFeedback.d.ts.map +1 -0
  121. package/dist/components/ChatFeedback/index.d.ts +2 -0
  122. package/dist/components/ChatFeedback/index.d.ts.map +1 -0
  123. package/dist/components/ChatPanel/ChatPanel.d.ts +34 -0
  124. package/dist/components/ChatPanel/ChatPanel.d.ts.map +1 -0
  125. package/dist/components/ChatPanel/index.d.ts +2 -0
  126. package/dist/components/ChatPanel/index.d.ts.map +1 -0
  127. package/dist/components/ChatScrollToBottom/ChatScrollToBottom.d.ts +12 -0
  128. package/dist/components/ChatScrollToBottom/ChatScrollToBottom.d.ts.map +1 -0
  129. package/dist/components/ChatScrollToBottom/index.d.ts +2 -0
  130. package/dist/components/ChatScrollToBottom/index.d.ts.map +1 -0
  131. package/dist/components/ChatSources/ChatSources.d.ts +34 -0
  132. package/dist/components/ChatSources/ChatSources.d.ts.map +1 -0
  133. package/dist/components/ChatSources/index.d.ts +2 -0
  134. package/dist/components/ChatSources/index.d.ts.map +1 -0
  135. package/dist/components/ChatSuggestions/ChatSuggestions.d.ts +30 -0
  136. package/dist/components/ChatSuggestions/ChatSuggestions.d.ts.map +1 -0
  137. package/dist/components/ChatSuggestions/index.d.ts +2 -0
  138. package/dist/components/ChatSuggestions/index.d.ts.map +1 -0
  139. package/dist/components/ChatThinking/ChatThinking.d.ts +29 -0
  140. package/dist/components/ChatThinking/ChatThinking.d.ts.map +1 -0
  141. package/dist/components/ChatThinking/index.d.ts +2 -0
  142. package/dist/components/ChatThinking/index.d.ts.map +1 -0
  143. package/dist/components/ChatThread/ChatThread.d.ts +25 -0
  144. package/dist/components/ChatThread/ChatThread.d.ts.map +1 -0
  145. package/dist/components/ChatThread/index.d.ts +2 -0
  146. package/dist/components/ChatThread/index.d.ts.map +1 -0
  147. package/dist/components/ChatToolTrace/ChatToolTrace.d.ts +27 -0
  148. package/dist/components/ChatToolTrace/ChatToolTrace.d.ts.map +1 -0
  149. package/dist/components/ChatToolTrace/index.d.ts +2 -0
  150. package/dist/components/ChatToolTrace/index.d.ts.map +1 -0
  151. package/dist/components/ConditionBuilder/ConditionBuilder.d.ts +6 -0
  152. package/dist/components/ConditionBuilder/ConditionBuilder.d.ts.map +1 -1
  153. package/dist/components/CsvViewer/CsvViewer.d.ts +51 -0
  154. package/dist/components/CsvViewer/CsvViewer.d.ts.map +1 -0
  155. package/dist/components/CsvViewer/index.d.ts +2 -0
  156. package/dist/components/CsvViewer/index.d.ts.map +1 -0
  157. package/dist/components/DataTable/DataTable.d.ts +19 -1
  158. package/dist/components/DataTable/DataTable.d.ts.map +1 -1
  159. package/dist/components/DatePicker/DatePicker.d.ts.map +1 -1
  160. package/dist/components/Dialog/Dialog.d.ts.map +1 -1
  161. package/dist/components/DropdownMenu/DropdownMenu.d.ts +12 -2
  162. package/dist/components/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  163. package/dist/components/ExecutionConsole/ExecutionConsole.d.ts +8 -2
  164. package/dist/components/ExecutionConsole/ExecutionConsole.d.ts.map +1 -1
  165. package/dist/components/FlowMinimap/FlowMinimap.d.ts +17 -1
  166. package/dist/components/FlowMinimap/FlowMinimap.d.ts.map +1 -1
  167. package/dist/components/FlowToolbar/FlowToolbar.d.ts +16 -10
  168. package/dist/components/FlowToolbar/FlowToolbar.d.ts.map +1 -1
  169. package/dist/components/Input/Input.d.ts +1 -1
  170. package/dist/components/Input/Input.d.ts.map +1 -1
  171. package/dist/components/JsonViewer/JsonViewer.d.ts +42 -7
  172. package/dist/components/JsonViewer/JsonViewer.d.ts.map +1 -1
  173. package/dist/components/JsonViewer/index.d.ts +1 -1
  174. package/dist/components/JsonViewer/index.d.ts.map +1 -1
  175. package/dist/components/MultiSelect/MultiSelect.d.ts +7 -0
  176. package/dist/components/MultiSelect/MultiSelect.d.ts.map +1 -1
  177. package/dist/components/NumberInput/NumberInput.d.ts +7 -0
  178. package/dist/components/NumberInput/NumberInput.d.ts.map +1 -1
  179. package/dist/components/PageHeader/PageHeader.d.ts +5 -1
  180. package/dist/components/PageHeader/PageHeader.d.ts.map +1 -1
  181. package/dist/components/Select/Select.d.ts.map +1 -1
  182. package/dist/components/SlideoutPanel/SlideoutPanel.d.ts.map +1 -1
  183. package/dist/components/TimePicker/TimePicker.d.ts.map +1 -1
  184. package/dist/components/VersionHistory/VersionHistory.d.ts +82 -0
  185. package/dist/components/VersionHistory/VersionHistory.d.ts.map +1 -0
  186. package/dist/components/VersionHistory/index.d.ts +2 -0
  187. package/dist/components/VersionHistory/index.d.ts.map +1 -0
  188. package/dist/components/WorkflowHeader/WorkflowHeader.d.ts +130 -0
  189. package/dist/components/WorkflowHeader/WorkflowHeader.d.ts.map +1 -0
  190. package/dist/components/WorkflowHeader/WorkflowHeaderExpanded.d.ts +69 -0
  191. package/dist/components/WorkflowHeader/WorkflowHeaderExpanded.d.ts.map +1 -0
  192. package/dist/components/WorkflowHeader/index.d.ts +3 -0
  193. package/dist/components/WorkflowHeader/index.d.ts.map +1 -0
  194. package/dist/components/WorkflowHeader/misc/WorkflowHeaderCentered.d.ts +40 -0
  195. package/dist/components/WorkflowHeader/misc/WorkflowHeaderCentered.d.ts.map +1 -0
  196. package/dist/components/WorkflowHeader/misc/WorkflowHeaderCommand.d.ts +39 -0
  197. package/dist/components/WorkflowHeader/misc/WorkflowHeaderCommand.d.ts.map +1 -0
  198. package/dist/components/WorkflowHeader/misc/WorkflowHeaderMinimal.d.ts +44 -0
  199. package/dist/components/WorkflowHeader/misc/WorkflowHeaderMinimal.d.ts.map +1 -0
  200. package/dist/components/WorkflowHeader/misc/WorkflowHeaderRail.d.ts +45 -0
  201. package/dist/components/WorkflowHeader/misc/WorkflowHeaderRail.d.ts.map +1 -0
  202. package/dist/components/WorkflowHeader/misc/WorkflowHeaderStudio.d.ts +48 -0
  203. package/dist/components/WorkflowHeader/misc/WorkflowHeaderStudio.d.ts.map +1 -0
  204. package/dist/components/WorkflowHeader/misc/WorkflowHeaderTiered.d.ts +52 -0
  205. package/dist/components/WorkflowHeader/misc/WorkflowHeaderTiered.d.ts.map +1 -0
  206. package/dist/components/XmlViewer/XmlViewer.d.ts +26 -1
  207. package/dist/components/XmlViewer/XmlViewer.d.ts.map +1 -1
  208. package/dist/components/XmlViewer/index.d.ts +1 -1
  209. package/dist/components/XmlViewer/index.d.ts.map +1 -1
  210. package/dist/components/YamlViewer/YamlViewer.d.ts +26 -1
  211. package/dist/components/YamlViewer/YamlViewer.d.ts.map +1 -1
  212. package/dist/components/YamlViewer/index.d.ts +1 -1
  213. package/dist/components/YamlViewer/index.d.ts.map +1 -1
  214. package/dist/hooks/useRelativeTime.d.ts +28 -0
  215. package/dist/hooks/useRelativeTime.d.ts.map +1 -0
  216. package/dist/hooks/useWorkflowRuntime.d.ts +20 -0
  217. package/dist/hooks/useWorkflowRuntime.d.ts.map +1 -0
  218. package/dist/index.cjs +7702 -4815
  219. package/dist/index.cjs.map +1 -1
  220. package/dist/index.d.ts +21 -3
  221. package/dist/index.d.ts.map +1 -1
  222. package/dist/index.js +7937 -5334
  223. package/dist/index.js.map +1 -1
  224. package/dist/styles.css +1 -1
  225. package/dist/utils/useAnchoredPopover.d.ts +63 -0
  226. package/dist/utils/useAnchoredPopover.d.ts.map +1 -0
  227. package/dist/workflow/components/ConfigPanel/ConfigPanel.d.ts +27 -9
  228. package/dist/workflow/components/ConfigPanel/ConfigPanel.d.ts.map +1 -1
  229. package/dist/workflow/components/FlowCanvas/FlowCanvas.d.ts +49 -1
  230. package/dist/workflow/components/FlowCanvas/FlowCanvas.d.ts.map +1 -1
  231. package/dist/workflow/components/FlowEdge/FlowEdge.d.ts.map +1 -1
  232. package/dist/workflow/components/FxPanel/DataRefButton.d.ts +9 -0
  233. package/dist/workflow/components/FxPanel/DataRefButton.d.ts.map +1 -0
  234. package/dist/workflow/components/FxPanel/FxPanel.d.ts +74 -0
  235. package/dist/workflow/components/FxPanel/FxPanel.d.ts.map +1 -0
  236. package/dist/workflow/components/FxPanel/FxToggleButton.d.ts +9 -0
  237. package/dist/workflow/components/FxPanel/FxToggleButton.d.ts.map +1 -0
  238. package/dist/workflow/components/Handle/Handle.d.ts +9 -1
  239. package/dist/workflow/components/Handle/Handle.d.ts.map +1 -1
  240. package/dist/workflow/components/Handle/handleRegistry.d.ts +19 -0
  241. package/dist/workflow/components/Handle/handleRegistry.d.ts.map +1 -1
  242. package/dist/workflow/components/kinds/index.d.ts +4 -0
  243. package/dist/workflow/components/kinds/index.d.ts.map +1 -1
  244. package/dist/workflow/index.d.ts +3 -1
  245. package/dist/workflow/index.d.ts.map +1 -1
  246. package/dist/workflow/layout/elk.d.ts.map +1 -1
  247. package/dist/workflow/store/selectors.d.ts +12 -0
  248. package/dist/workflow/store/selectors.d.ts.map +1 -1
  249. package/dist/workflow/types.d.ts +108 -0
  250. package/dist/workflow/types.d.ts.map +1 -1
  251. package/dist/workflow/utils/parenting.d.ts +5 -3
  252. package/dist/workflow/utils/parenting.d.ts.map +1 -1
  253. package/dist/workflow.cjs +1114 -473
  254. package/dist/workflow.cjs.map +1 -1
  255. package/dist/workflow.js +540 -251
  256. package/dist/workflow.js.map +1 -1
  257. package/package.json +7 -3
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,
@@ -77,7 +79,8 @@ __export(workflow_exports, {
77
79
  useNodeData: () => useNodeData,
78
80
  useNodes: () => useNodes,
79
81
  useSelection: () => useSelection,
80
- useViewport: () => useViewport
82
+ useViewport: () => useViewport,
83
+ useViewportOrNull: () => useViewportOrNull
81
84
  });
82
85
  module.exports = __toCommonJS(workflow_exports);
83
86
 
@@ -559,6 +562,9 @@ function createElkEngine(elk, defaults = {}) {
559
562
  if (list) list.push(n);
560
563
  else childrenByParent.set(key, [n]);
561
564
  }
565
+ const engineOptions = Object.fromEntries(
566
+ Object.entries(options?.engine ?? {}).map(([k, v]) => [k, String(v)])
567
+ );
562
568
  const buildChildren = (parentId) => {
563
569
  const list = childrenByParent.get(parentId) ?? [];
564
570
  return list.map((n) => ({
@@ -570,7 +576,8 @@ function createElkEngine(elk, defaults = {}) {
570
576
  "elk.algorithm": algorithm,
571
577
  "elk.direction": ELK_DIRECTION[direction],
572
578
  "elk.spacing.nodeNode": String(nodeSpacing),
573
- "elk.layered.spacing.nodeNodeBetweenLayers": String(rankSpacing)
579
+ "elk.layered.spacing.nodeNodeBetweenLayers": String(rankSpacing),
580
+ ...engineOptions
574
581
  } : void 0
575
582
  }));
576
583
  };
@@ -646,6 +653,23 @@ function useEdges() {
646
653
  function useViewport() {
647
654
  return useFlowSelector((s) => s.viewport);
648
655
  }
656
+ var VIEWPORT_OR_NULL_NO_STORE_SUBSCRIBE = (_cb) => () => {
657
+ };
658
+ var VIEWPORT_OR_NULL_NO_STORE_SNAPSHOT = () => null;
659
+ function useViewportOrNull() {
660
+ const store = (0, import_react5.useContext)(FlowStoreContext);
661
+ const { sub, snap } = (0, import_react5.useMemo)(
662
+ () => store ? {
663
+ sub: store.subscribe,
664
+ snap: () => store.getSnapshot().viewport
665
+ } : {
666
+ sub: VIEWPORT_OR_NULL_NO_STORE_SUBSCRIBE,
667
+ snap: VIEWPORT_OR_NULL_NO_STORE_SNAPSHOT
668
+ },
669
+ [store]
670
+ );
671
+ return (0, import_react5.useSyncExternalStore)(sub, snap, snap);
672
+ }
649
673
  function useNodeById(id) {
650
674
  return useFlowSelector((s) => s.nodes.find((n) => n.id === id));
651
675
  }
@@ -708,26 +732,27 @@ var SAVE_STATE_DOT = {
708
732
  function ConfigPanel({
709
733
  open = true,
710
734
  title,
711
- kindLabel,
712
735
  icon,
736
+ headerActions,
713
737
  banner,
714
738
  tabs,
715
739
  activeTab: controlledActive,
716
740
  defaultActiveTab,
717
741
  onTabChange,
718
- saveState = "clean",
742
+ saveState,
719
743
  onTitleChange,
720
744
  onClose,
721
745
  onSave,
722
746
  onCancel,
747
+ padding = 12,
723
748
  resizable = true,
724
- defaultWidth = 360,
725
- minWidth = 280,
749
+ defaultWidth = 400,
750
+ minWidth = 400,
726
751
  maxWidth = 720,
727
752
  maxWidthContainerInset = 80,
728
753
  variant = "pinned",
729
754
  children,
730
- footer,
755
+ footerActions,
731
756
  className,
732
757
  style
733
758
  }) {
@@ -824,55 +849,53 @@ function ConfigPanel({
824
849
  "aria-hidden": "true"
825
850
  }
826
851
  ),
827
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "ods-flow-config-panel__header", children: [
828
- kindLabel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ods-flow-config-panel__breadcrumb", children: kindLabel }),
829
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ods-flow-config-panel__title-row", children: [
830
- icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ods-flow-config-panel__icon", children: icon }),
831
- editingTitle ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
832
- "input",
833
- {
834
- className: "ods-flow-config-panel__title-input",
835
- value: draftTitle,
836
- autoFocus: true,
837
- onChange: (e) => setDraftTitle(e.target.value),
838
- onBlur: () => commitTitle(draftTitle),
839
- onKeyDown: titleKeyDown,
840
- "aria-label": "Edit title"
841
- }
842
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
843
- "button",
844
- {
845
- type: "button",
846
- className: "ods-flow-config-panel__title",
847
- onDoubleClick: () => {
848
- if (!onTitleChange) return;
849
- setDraftTitle(title ?? "");
850
- setEditingTitle(true);
851
- },
852
- title: onTitleChange ? "Double-click to rename" : void 0,
853
- children: title ?? "Untitled"
854
- }
855
- ),
856
- onClose && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
857
- "button",
858
- {
859
- type: "button",
860
- className: "ods-flow-config-panel__close",
861
- onClick: onClose,
862
- "aria-label": "Close panel",
863
- 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)(
864
- "path",
865
- {
866
- d: "M3 3l8 8M11 3l-8 8",
867
- stroke: "currentColor",
868
- strokeWidth: "1.5",
869
- strokeLinecap: "round"
870
- }
871
- ) })
872
- }
873
- )
874
- ] })
875
- ] }),
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
+ ] }) }),
876
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)(
877
900
  "button",
878
901
  {
@@ -893,9 +916,17 @@ function ConfigPanel({
893
916
  t.id
894
917
  )) }),
895
918
  banner && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ods-flow-config-panel__banner", children: banner }),
896
- /* @__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
+ ),
897
928
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", { className: "ods-flow-config-panel__footer", children: [
898
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
929
+ saveState && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
899
930
  "span",
900
931
  {
901
932
  className: cn(
@@ -915,7 +946,7 @@ function ConfigPanel({
915
946
  ]
916
947
  }
917
948
  ),
918
- 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: [
919
950
  onCancel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
920
951
  "button",
921
952
  {
@@ -931,11 +962,11 @@ function ConfigPanel({
931
962
  type: "button",
932
963
  className: "ods-flow-config-panel__btn ods-flow-config-panel__btn--primary",
933
964
  onClick: onSave,
934
- disabled: saveState === "saving" || saveState === "clean",
965
+ disabled: saveState === "saving",
935
966
  children: saveState === "saving" ? "Saving\u2026" : "Save"
936
967
  }
937
968
  )
938
- ] })
969
+ ] }) })
939
970
  ] })
940
971
  ]
941
972
  }
@@ -943,7 +974,7 @@ function ConfigPanel({
943
974
  }
944
975
 
945
976
  // src/workflow/components/FlowCanvas/FlowCanvas.tsx
946
- var import_react14 = require("react");
977
+ var import_react15 = require("react");
947
978
 
948
979
  // src/workflow/store/createFlowStore.ts
949
980
  var DEFAULT_VIEWPORT = { x: 0, y: 0, zoom: 1 };
@@ -1126,7 +1157,7 @@ function findContainingGroup(point, nodes, exclude = []) {
1126
1157
  for (let i = nodes.length - 1; i >= 0; i--) {
1127
1158
  const n = nodes[i];
1128
1159
  if (exclude.includes(n.id)) continue;
1129
- if (n.type !== "group") continue;
1160
+ if (n.type !== "group" && n.type !== "forEach") continue;
1130
1161
  if (n.data && typeof n.data === "object" && n.data.collapsed) {
1131
1162
  continue;
1132
1163
  }
@@ -1336,6 +1367,9 @@ function createHandleRegistry() {
1336
1367
  resolve(nodeId, type, handleId) {
1337
1368
  return map.get(key(nodeId, type, handleId));
1338
1369
  },
1370
+ all() {
1371
+ return Array.from(map.values());
1372
+ },
1339
1373
  subscribe(listener) {
1340
1374
  listeners.add(listener);
1341
1375
  return () => {
@@ -1376,21 +1410,23 @@ function FlowEdgeImpl({
1376
1410
  const targetDesc = registry.resolve(targetNode.id, "target", targetHandleId);
1377
1411
  const sourceSide = sourceDesc?.side ?? sourceNode.sourcePosition ?? "bottom";
1378
1412
  const targetSide = targetDesc?.side ?? targetNode.targetPosition ?? "top";
1413
+ const sourceRoute = sourceDesc?.routeSide ?? sourceSide;
1414
+ const targetRoute = targetDesc?.routeSide ?? targetSide;
1379
1415
  const sourceIndex = sourceDesc?.index ?? 0;
1380
1416
  const sourceTotal = sourceDesc?.total ?? 1;
1381
1417
  const targetIndex = targetDesc?.index ?? 0;
1382
1418
  const targetTotal = targetDesc?.total ?? 1;
1383
1419
  const rawStart = handleCentre(sourceNode, sourceSide, sourceIndex, sourceTotal);
1384
1420
  const rawEnd = handleCentre(targetNode, targetSide, targetIndex, targetTotal);
1385
- const HANDLE_GAP = 8;
1386
- const start = offsetAlongSide2(rawStart, sourceSide, HANDLE_GAP);
1387
- 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);
1388
1424
  const routing = edge.routing ?? "bezier";
1389
- const { d, midX, midY } = buildEdgePath(routing, start, sourceSide, end, targetSide, {
1425
+ const { d, midX, midY } = buildEdgePath(routing, start, sourceRoute, end, targetRoute, {
1390
1426
  curvature,
1391
1427
  borderRadius
1392
1428
  });
1393
- const { d: hitD } = buildEdgePath(routing, rawStart, sourceSide, rawEnd, targetSide, {
1429
+ const { d: hitD } = buildEdgePath(routing, rawStart, sourceRoute, rawEnd, targetRoute, {
1394
1430
  curvature,
1395
1431
  borderRadius
1396
1432
  });
@@ -1437,6 +1473,12 @@ function FlowEdgeImpl({
1437
1473
  e.stopPropagation();
1438
1474
  onSelect?.(edge.id);
1439
1475
  },
1476
+ onDoubleClick: (e) => {
1477
+ if (!onLabelChange) return;
1478
+ e.stopPropagation();
1479
+ setDraftLabel(edge.label ?? "");
1480
+ setEditing(true);
1481
+ },
1440
1482
  children: [
1441
1483
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1442
1484
  "marker",
@@ -1708,6 +1750,7 @@ var DEFAULT_HANDLE_ID = "default";
1708
1750
  function Handle({
1709
1751
  type,
1710
1752
  position,
1753
+ routeSide,
1711
1754
  id = DEFAULT_HANDLE_ID,
1712
1755
  isConnectable = true,
1713
1756
  isConnectableStart,
@@ -1730,11 +1773,14 @@ function Handle({
1730
1773
  handleId: id,
1731
1774
  type,
1732
1775
  side: position,
1776
+ routeSide: routeSide ?? position,
1733
1777
  index,
1734
- total
1778
+ total,
1779
+ canStart,
1780
+ canEnd
1735
1781
  });
1736
1782
  return dispose;
1737
- }, [registry, node.id, id, type, position, index, total]);
1783
+ }, [registry, node.id, id, type, position, routeSide, index, total, canStart, canEnd]);
1738
1784
  const handlePointerDown = (e) => {
1739
1785
  if (!canStart) return;
1740
1786
  e.stopPropagation();
@@ -1791,10 +1837,155 @@ function handleSideStyle(side, index, total) {
1791
1837
  }
1792
1838
  }
1793
1839
 
1794
- // src/workflow/components/kinds/BaseNode.tsx
1795
- var import_icons = require("@octaviaflow/icons");
1840
+ // src/workflow/components/NodeResizer/NodeResizer.tsx
1796
1841
  var import_react13 = require("react");
1797
1842
  var import_jsx_runtime5 = require("react/jsx-runtime");
1843
+ function NodeResizer({
1844
+ isVisible,
1845
+ minWidth = 80,
1846
+ minHeight = 60,
1847
+ maxWidth,
1848
+ maxHeight,
1849
+ keepAspectRatio = false,
1850
+ onResize,
1851
+ onResizeEnd,
1852
+ color
1853
+ }) {
1854
+ const { node, selected } = useFlowNodeContext();
1855
+ const viewport = useViewport();
1856
+ const flow = useFlow();
1857
+ const dragRef = (0, import_react13.useRef)(null);
1858
+ const show = isVisible ?? selected;
1859
+ if (!show) return null;
1860
+ const beginResize = (e, corner) => {
1861
+ e.preventDefault();
1862
+ e.stopPropagation();
1863
+ e.target.setPointerCapture(e.pointerId);
1864
+ const w = node.width ?? DEFAULT_NODE_WIDTH;
1865
+ const h = node.height ?? DEFAULT_NODE_HEIGHT;
1866
+ dragRef.current = {
1867
+ pointerId: e.pointerId,
1868
+ corner,
1869
+ startClientX: e.clientX,
1870
+ startClientY: e.clientY,
1871
+ startWidth: w,
1872
+ startHeight: h,
1873
+ startX: node.position.x,
1874
+ startY: node.position.y,
1875
+ aspect: w / Math.max(1, h)
1876
+ };
1877
+ };
1878
+ const onMove = (e) => {
1879
+ const drag = dragRef.current;
1880
+ if (!drag || drag.pointerId !== e.pointerId) return;
1881
+ const dx = (e.clientX - drag.startClientX) / viewport.zoom;
1882
+ const dy = (e.clientY - drag.startClientY) / viewport.zoom;
1883
+ let nextW = drag.startWidth;
1884
+ let nextH = drag.startHeight;
1885
+ let nextX = drag.startX;
1886
+ let nextY = drag.startY;
1887
+ switch (drag.corner) {
1888
+ case "se":
1889
+ nextW = drag.startWidth + dx;
1890
+ nextH = drag.startHeight + dy;
1891
+ break;
1892
+ case "sw":
1893
+ nextW = drag.startWidth - dx;
1894
+ nextH = drag.startHeight + dy;
1895
+ nextX = drag.startX + dx;
1896
+ break;
1897
+ case "ne":
1898
+ nextW = drag.startWidth + dx;
1899
+ nextH = drag.startHeight - dy;
1900
+ nextY = drag.startY + dy;
1901
+ break;
1902
+ case "nw":
1903
+ nextW = drag.startWidth - dx;
1904
+ nextH = drag.startHeight - dy;
1905
+ nextX = drag.startX + dx;
1906
+ nextY = drag.startY + dy;
1907
+ break;
1908
+ }
1909
+ if (keepAspectRatio) {
1910
+ nextH = nextW / drag.aspect;
1911
+ if (drag.corner === "nw" || drag.corner === "ne") {
1912
+ nextY = drag.startY + (drag.startHeight - nextH);
1913
+ }
1914
+ }
1915
+ nextW = Math.max(minWidth, maxWidth ? Math.min(maxWidth, nextW) : nextW);
1916
+ nextH = Math.max(minHeight, maxHeight ? Math.min(maxHeight, nextH) : nextH);
1917
+ flow.updateNode(node.id, {
1918
+ width: nextW,
1919
+ height: nextH,
1920
+ position: { x: nextX, y: nextY }
1921
+ });
1922
+ onResize?.({ width: nextW, height: nextH });
1923
+ };
1924
+ const onUp = (e) => {
1925
+ if (dragRef.current?.pointerId === e.pointerId) {
1926
+ const cur = flow.getNode(node.id);
1927
+ if (cur) {
1928
+ onResizeEnd?.({
1929
+ width: cur.width ?? DEFAULT_NODE_WIDTH,
1930
+ height: cur.height ?? DEFAULT_NODE_HEIGHT
1931
+ });
1932
+ }
1933
+ dragRef.current = null;
1934
+ }
1935
+ };
1936
+ const handleColor = color ?? "var(--ods-accent)";
1937
+ const handleStyle = (corner) => {
1938
+ const base = {
1939
+ position: "absolute",
1940
+ width: 12,
1941
+ height: 12,
1942
+ background: "var(--ods-surface-canvas)",
1943
+ border: `2px solid ${handleColor}`,
1944
+ borderRadius: 2,
1945
+ cursor: cursorFor(corner),
1946
+ touchAction: "none",
1947
+ // Place each handle so its CENTRE sits on the corresponding corner.
1948
+ transform: "translate(-50%, -50%)"
1949
+ };
1950
+ switch (corner) {
1951
+ case "nw":
1952
+ return { ...base, top: 0, left: 0 };
1953
+ case "ne":
1954
+ return { ...base, top: 0, left: "100%" };
1955
+ case "sw":
1956
+ return { ...base, top: "100%", left: 0 };
1957
+ case "se":
1958
+ return { ...base, top: "100%", left: "100%" };
1959
+ }
1960
+ };
1961
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: cn("ods-node-resizer"), "data-flow-no-drag": "true", children: ["nw", "ne", "sw", "se"].map((corner) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1962
+ "div",
1963
+ {
1964
+ style: handleStyle(corner),
1965
+ onPointerDown: (e) => beginResize(e, corner),
1966
+ onPointerMove: onMove,
1967
+ onPointerUp: onUp,
1968
+ onPointerCancel: onUp,
1969
+ "aria-label": `Resize ${corner}`
1970
+ },
1971
+ corner
1972
+ )) });
1973
+ }
1974
+ function cursorFor(corner) {
1975
+ switch (corner) {
1976
+ case "nw":
1977
+ case "se":
1978
+ return "nwse-resize";
1979
+ case "ne":
1980
+ case "sw":
1981
+ return "nesw-resize";
1982
+ }
1983
+ }
1984
+
1985
+ // src/workflow/components/kinds/BaseNode.tsx
1986
+ var import_icons = require("@octaviaflow/icons");
1987
+ var import_react14 = require("react");
1988
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1798
1989
  function BaseNode({
1799
1990
  kind,
1800
1991
  kindIcon,
@@ -1810,10 +2001,10 @@ function BaseNode({
1810
2001
  className,
1811
2002
  children
1812
2003
  }) {
1813
- const ctx = (0, import_react13.useContext)(FlowNodeContext);
1814
- const bridge = (0, import_react13.useContext)(FlowNodeBridgeContext);
2004
+ const ctx = (0, import_react14.useContext)(FlowNodeContext);
2005
+ const bridge = (0, import_react14.useContext)(FlowNodeBridgeContext);
1815
2006
  const deleteHandler = onDelete === false ? void 0 : onDelete ?? (ctx && bridge ? () => bridge.deleteNode(ctx.id) : void 0);
1816
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2007
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1817
2008
  "div",
1818
2009
  {
1819
2010
  className: cn(
@@ -1823,31 +2014,31 @@ function BaseNode({
1823
2014
  className
1824
2015
  ),
1825
2016
  children: [
1826
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ods-flow-base-node__pill", children: [
1827
- kindIcon && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ods-flow-base-node__pill-icon", "aria-hidden": "true", children: kindIcon }),
1828
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ods-flow-base-node__pill-label", children: kind })
2017
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-base-node__pill", children: [
2018
+ kindIcon && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-base-node__pill-icon", "aria-hidden": "true", children: kindIcon }),
2019
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-base-node__pill-label", children: kind })
1829
2020
  ] }),
1830
- status && status !== "idle" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2021
+ status && status !== "idle" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1831
2022
  "span",
1832
2023
  {
1833
2024
  className: cn("ods-flow-base-node__status", `ods-flow-base-node__status--${status}`),
1834
2025
  "aria-hidden": "true"
1835
2026
  }
1836
2027
  ),
1837
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ods-flow-base-node__body", children: [
1838
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ods-flow-base-node__content", children: [
1839
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ods-flow-base-node__bubble", "aria-hidden": "true", children: icon }),
1840
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ods-flow-base-node__content-text", children: [
1841
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ods-flow-base-node__content-title", children: title }),
1842
- (chip !== void 0 || description !== void 0 || valueChip !== void 0) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ods-flow-base-node__content-info", children: [
1843
- chip !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ods-flow-base-node__chip", children: chip }),
1844
- description !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ods-flow-base-node__description", children: description }),
1845
- valueChip !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ods-flow-base-node__value-chip", children: valueChip })
2028
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-base-node__body", children: [
2029
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-base-node__content", children: [
2030
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ods-flow-base-node__bubble", "aria-hidden": "true", children: icon }),
2031
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-base-node__content-text", children: [
2032
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ods-flow-base-node__content-title", children: title }),
2033
+ (chip !== void 0 || description !== void 0 || valueChip !== void 0) && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-base-node__content-info", children: [
2034
+ chip !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-base-node__chip", children: chip }),
2035
+ description !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-base-node__description", children: description }),
2036
+ valueChip !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-base-node__value-chip", children: valueChip })
1846
2037
  ] })
1847
2038
  ] })
1848
2039
  ] }),
1849
- footer && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ods-flow-base-node__footer", children: footer }),
1850
- deleteHandler && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2040
+ footer && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ods-flow-base-node__footer", children: footer }),
2041
+ deleteHandler && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1851
2042
  "button",
1852
2043
  {
1853
2044
  type: "button",
@@ -1859,7 +2050,7 @@ function BaseNode({
1859
2050
  "aria-label": "Delete node",
1860
2051
  "data-flow-no-drag": "true",
1861
2052
  title: "Delete node",
1862
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_icons.TrashCanIcon, { size: 16, "aria-hidden": true })
2053
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons.TrashCanIcon, { size: 16, "aria-hidden": true })
1863
2054
  }
1864
2055
  )
1865
2056
  ] }),
@@ -1870,12 +2061,12 @@ function BaseNode({
1870
2061
  }
1871
2062
 
1872
2063
  // src/workflow/components/kinds/index.tsx
1873
- var import_jsx_runtime6 = require("react/jsx-runtime");
2064
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1874
2065
  var ActionNode = ({
1875
2066
  node
1876
2067
  }) => {
1877
2068
  const d = node.data ?? {};
1878
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2069
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1879
2070
  BaseNode,
1880
2071
  {
1881
2072
  kind: d.kind ?? "ACTION",
@@ -1887,8 +2078,8 @@ var ActionNode = ({
1887
2078
  status: d.status,
1888
2079
  accent: "green",
1889
2080
  children: [
1890
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" }),
1891
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom" })
2081
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2082
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
1892
2083
  ]
1893
2084
  }
1894
2085
  );
@@ -1897,7 +2088,7 @@ var TriggerNode = ({
1897
2088
  node
1898
2089
  }) => {
1899
2090
  const d = node.data ?? {};
1900
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2091
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1901
2092
  BaseNode,
1902
2093
  {
1903
2094
  kind: d.kind ?? "TRIGGER",
@@ -1908,7 +2099,7 @@ var TriggerNode = ({
1908
2099
  valueChip: d.valueChip,
1909
2100
  status: d.status,
1910
2101
  accent: "green",
1911
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom" })
2102
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
1912
2103
  }
1913
2104
  );
1914
2105
  };
@@ -1920,7 +2111,7 @@ var ConditionNode = ({
1920
2111
  { id: "true", label: "true" },
1921
2112
  { id: "false", label: "false" }
1922
2113
  ];
1923
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2114
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1924
2115
  BaseNode,
1925
2116
  {
1926
2117
  kind: d.kind ?? "CONDITION",
@@ -1932,8 +2123,8 @@ var ConditionNode = ({
1932
2123
  status: d.status,
1933
2124
  accent: "amber",
1934
2125
  children: [
1935
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" }),
1936
- branches.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2126
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2127
+ branches.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1937
2128
  Handle,
1938
2129
  {
1939
2130
  type: "source",
@@ -1954,24 +2145,27 @@ var GroupNode = ({
1954
2145
  }) => {
1955
2146
  const d = node.data ?? {};
1956
2147
  const collapsed = !!d.collapsed;
2148
+ const disabled = !!d.disabled;
1957
2149
  const hiddenCount = d.hiddenCount;
1958
2150
  const bridge = useFlowNodeBridge();
1959
2151
  const onChevronClick = (e) => {
1960
2152
  e.stopPropagation();
1961
2153
  bridge.toggleNodeCollapse(node.id);
1962
2154
  };
1963
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2155
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1964
2156
  "div",
1965
2157
  {
1966
2158
  className: "ods-flow-group",
1967
2159
  "data-collapsed": collapsed ? "true" : "false",
2160
+ "data-disabled": disabled ? "true" : void 0,
1968
2161
  style: {
1969
2162
  width: node.width ?? 360,
1970
2163
  height: collapsed ? 36 : node.height ?? 200
1971
2164
  },
1972
2165
  children: [
1973
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-group__header", "data-flow-no-drag": "false", children: [
1974
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2166
+ !collapsed && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(NodeResizer, { minWidth: 240, minHeight: 120 }),
2167
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ods-flow-group__header", "data-flow-no-drag": "false", children: [
2168
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1975
2169
  "button",
1976
2170
  {
1977
2171
  type: "button",
@@ -1984,15 +2178,16 @@ var GroupNode = ({
1984
2178
  children: collapsed ? "\u25B8" : "\u25BE"
1985
2179
  }
1986
2180
  ),
1987
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-group__title", children: d.title ?? "Group" }),
1988
- d.subtitle && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-group__subtitle", children: d.subtitle }),
1989
- collapsed && hiddenCount !== void 0 && hiddenCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ods-flow-group__count", "aria-label": `${hiddenCount} hidden steps`, children: [
2181
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ods-flow-group__title", children: d.title ?? "Group" }),
2182
+ d.subtitle && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ods-flow-group__subtitle", children: d.subtitle }),
2183
+ disabled && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ods-flow-group__disabled-badge", "aria-label": "Disabled subflow", children: "Disabled" }),
2184
+ collapsed && hiddenCount !== void 0 && hiddenCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "ods-flow-group__count", "aria-label": `${hiddenCount} hidden steps`, children: [
1990
2185
  hiddenCount,
1991
2186
  " steps"
1992
2187
  ] })
1993
2188
  ] }),
1994
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top", id: "__group_in" }),
1995
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom", id: "__group_out" })
2189
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top", id: "__group_in" }),
2190
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom", id: "__group_out" })
1996
2191
  ]
1997
2192
  }
1998
2193
  );
@@ -2001,56 +2196,35 @@ var ForEachNode = ({
2001
2196
  node
2002
2197
  }) => {
2003
2198
  const d = node.data ?? {};
2004
- const iteratorExpr = d.iterator ?? d.description ?? "items[]";
2005
- const collapsed = !!d.collapsed;
2006
- const hiddenCount = d.hiddenCount;
2007
- const bridge = useFlowNodeBridge();
2008
- const onChevronClick = (e) => {
2009
- e.stopPropagation();
2010
- bridge.toggleNodeCollapse(node.id);
2011
- };
2012
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2013
- "div",
2199
+ const branches = d.branches ?? [
2200
+ { id: "each", label: "Each" },
2201
+ { id: "done", label: "Done" }
2202
+ ];
2203
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2204
+ BaseNode,
2014
2205
  {
2015
- className: "ods-flow-foreach",
2016
- "data-collapsed": collapsed ? "true" : "false",
2017
- style: {
2018
- width: node.width ?? 420,
2019
- height: collapsed ? 40 : node.height ?? 260
2020
- },
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",
2021
2214
  children: [
2022
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-foreach__header", children: [
2023
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2024
- "button",
2025
- {
2026
- type: "button",
2027
- className: "ods-flow-foreach__chevron",
2028
- "data-flow-no-drag": "true",
2029
- "aria-label": collapsed ? "Expand iterator" : "Collapse iterator",
2030
- "aria-expanded": !collapsed,
2031
- onClick: onChevronClick,
2032
- onPointerDown: (e) => e.stopPropagation(),
2033
- children: collapsed ? "\u25B8" : "\u25BE"
2034
- }
2035
- ),
2036
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-foreach__icon", "aria-hidden": "true", children: "\u21BB" }),
2037
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-foreach__title", children: d.title ?? "For each" }),
2038
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { className: "ods-flow-foreach__iterator", children: iteratorExpr }),
2039
- collapsed && hiddenCount !== void 0 && hiddenCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ods-flow-foreach__count", "aria-label": `${hiddenCount} hidden steps`, children: [
2040
- hiddenCount,
2041
- " steps"
2042
- ] })
2043
- ] }),
2044
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top", id: "__group_in" }),
2045
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom", id: "__group_out" }),
2046
- !collapsed && // Expanded mode — full 3-handle iterator surface (in / item / done).
2047
- // `in` shares the top edge with `__group_in`; consumers wiring an
2048
- // explicit `targetHandle: "in"` get the labelled, visible variant.
2049
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2050
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top", id: "in", label: "in" }),
2051
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "right", id: "item", label: "item" }),
2052
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom", id: "done", label: "done" })
2053
- ] })
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
+ ))
2054
2228
  ]
2055
2229
  }
2056
2230
  );
@@ -2059,7 +2233,7 @@ var OutputNode = ({
2059
2233
  node
2060
2234
  }) => {
2061
2235
  const d = node.data ?? {};
2062
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2236
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2063
2237
  BaseNode,
2064
2238
  {
2065
2239
  kind: d.kind ?? "OUTPUT",
@@ -2069,7 +2243,7 @@ var OutputNode = ({
2069
2243
  description: d.description ?? d.subtitle,
2070
2244
  status: d.status,
2071
2245
  accent: "green",
2072
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" })
2246
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" })
2073
2247
  }
2074
2248
  );
2075
2249
  };
@@ -2077,7 +2251,7 @@ var ErrorNode = ({
2077
2251
  node
2078
2252
  }) => {
2079
2253
  const d = node.data ?? {};
2080
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2254
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2081
2255
  BaseNode,
2082
2256
  {
2083
2257
  kind: d.kind ?? "ERROR",
@@ -2088,8 +2262,8 @@ var ErrorNode = ({
2088
2262
  status: d.status ?? "error",
2089
2263
  accent: "red",
2090
2264
  children: [
2091
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" }),
2092
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom" })
2265
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2266
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
2093
2267
  ]
2094
2268
  }
2095
2269
  );
@@ -2100,7 +2274,7 @@ var WaitNode = ({
2100
2274
  const d = node.data ?? {};
2101
2275
  const waitMs = d.waitMs;
2102
2276
  const durationChip = waitMs ? `${Math.round(waitMs / 100) / 10}s` : void 0;
2103
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2277
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2104
2278
  BaseNode,
2105
2279
  {
2106
2280
  kind: d.kind ?? "WAIT",
@@ -2111,8 +2285,8 @@ var WaitNode = ({
2111
2285
  status: d.status,
2112
2286
  accent: "violet",
2113
2287
  children: [
2114
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" }),
2115
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom" })
2288
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2289
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
2116
2290
  ]
2117
2291
  }
2118
2292
  );
@@ -2125,7 +2299,7 @@ var ParallelNode = ({
2125
2299
  { id: "a", label: "a" },
2126
2300
  { id: "b", label: "b" }
2127
2301
  ];
2128
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2302
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2129
2303
  BaseNode,
2130
2304
  {
2131
2305
  kind: d.kind ?? "PARALLEL",
@@ -2136,8 +2310,8 @@ var ParallelNode = ({
2136
2310
  status: d.status,
2137
2311
  accent: "blue",
2138
2312
  children: [
2139
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" }),
2140
- branches.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2313
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2314
+ branches.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2141
2315
  Handle,
2142
2316
  {
2143
2317
  type: "source",
@@ -2157,7 +2331,7 @@ var StickyNode = ({
2157
2331
  node
2158
2332
  }) => {
2159
2333
  const d = node.data ?? {};
2160
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2334
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2161
2335
  "div",
2162
2336
  {
2163
2337
  className: "ods-flow-sticky",
@@ -2166,8 +2340,8 @@ var StickyNode = ({
2166
2340
  minHeight: node.height ?? 120
2167
2341
  },
2168
2342
  children: [
2169
- d.title && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ods-flow-sticky__title", children: d.title }),
2170
- d.description && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ods-flow-sticky__body", children: d.description })
2343
+ d.title && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ods-flow-sticky__title", children: d.title }),
2344
+ d.description && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ods-flow-sticky__body", children: d.description })
2171
2345
  ]
2172
2346
  }
2173
2347
  );
@@ -2176,7 +2350,7 @@ var WebhookNode = ({
2176
2350
  node
2177
2351
  }) => {
2178
2352
  const d = node.data ?? {};
2179
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2353
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2180
2354
  BaseNode,
2181
2355
  {
2182
2356
  kind: d.kind ?? "WEBHOOK",
@@ -2187,7 +2361,7 @@ var WebhookNode = ({
2187
2361
  valueChip: d.valueChip,
2188
2362
  status: d.status,
2189
2363
  accent: "blue",
2190
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom" })
2364
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
2191
2365
  }
2192
2366
  );
2193
2367
  };
@@ -2195,7 +2369,7 @@ var HttpRequestNode = ({
2195
2369
  node
2196
2370
  }) => {
2197
2371
  const d = node.data ?? {};
2198
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2372
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2199
2373
  BaseNode,
2200
2374
  {
2201
2375
  kind: d.kind ?? "HTTP",
@@ -2207,8 +2381,8 @@ var HttpRequestNode = ({
2207
2381
  status: d.status,
2208
2382
  accent: "blue",
2209
2383
  children: [
2210
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" }),
2211
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom" })
2384
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2385
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
2212
2386
  ]
2213
2387
  }
2214
2388
  );
@@ -2229,52 +2403,60 @@ var DEFAULT_NODE_KINDS = {
2229
2403
  };
2230
2404
 
2231
2405
  // src/workflow/components/FlowCanvas/FlowCanvas.tsx
2232
- var import_jsx_runtime7 = require("react/jsx-runtime");
2406
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2233
2407
  var DEFAULT_VIEWPORT2 = { x: 0, y: 0, zoom: 1 };
2234
- function FlowCanvas({
2235
- nodes,
2236
- edges,
2237
- onNodesChange,
2238
- onEdgesChange,
2239
- viewport: controlledViewport,
2240
- defaultViewport = DEFAULT_VIEWPORT2,
2241
- onViewportChange,
2242
- minZoom = 0.25,
2243
- maxZoom = 2,
2244
- nodeKinds,
2245
- onConnect,
2246
- onConnectStart,
2247
- onConnectEnd,
2248
- isValidConnection,
2249
- onSelectionChange,
2250
- onPaneClick,
2251
- onNodeClick,
2252
- onEdgeClick,
2253
- onEdgeLabelChange,
2254
- onInit,
2255
- onBeforeDelete,
2256
- onNodeContextMenu,
2257
- onEdgeContextMenu,
2258
- onPaneContextMenu,
2259
- nodesDraggable = true,
2260
- nodesConnectable = true,
2261
- panOnDrag: panOnDragProp,
2262
- zoomOnScroll: zoomOnScrollProp,
2263
- preset = "mouse",
2264
- paneClickDistance = 4,
2265
- paneClickClearsSelection = true,
2266
- background = "dots",
2267
- gridSize = 20,
2268
- snapToGrid = false,
2269
- nodeCollisionGap = -1,
2270
- subflowCollisionGap,
2271
- height = "100%",
2272
- width = "100%",
2273
- className,
2274
- style,
2275
- children,
2276
- emptyState
2277
- }) {
2408
+ function FlowCanvas(props) {
2409
+ const viewportPropProvided = props.viewport !== void 0 || props.defaultViewport !== void 0;
2410
+ const {
2411
+ nodes,
2412
+ edges,
2413
+ onNodesChange,
2414
+ onEdgesChange,
2415
+ viewport: controlledViewport,
2416
+ defaultViewport = DEFAULT_VIEWPORT2,
2417
+ onViewportChange,
2418
+ minZoom = 0.25,
2419
+ maxZoom = 2,
2420
+ fitViewOnInit,
2421
+ nodeKinds,
2422
+ onConnect,
2423
+ onConnectStart,
2424
+ onConnectEnd,
2425
+ isValidConnection,
2426
+ onSelectionChange,
2427
+ onPaneClick,
2428
+ onNodeClick,
2429
+ onEdgeClick,
2430
+ onEdgeLabelChange,
2431
+ onInit,
2432
+ onBeforeDelete,
2433
+ onNodeContextMenu,
2434
+ onEdgeContextMenu,
2435
+ onPaneContextMenu,
2436
+ nodesDraggable = true,
2437
+ nodesConnectable = true,
2438
+ panOnDrag: panOnDragProp,
2439
+ zoomOnScroll: zoomOnScrollProp,
2440
+ preset = "mouse",
2441
+ paneClickDistance = 4,
2442
+ paneClickClearsSelection = true,
2443
+ background = "dots",
2444
+ gridSize = 20,
2445
+ snapToGrid = false,
2446
+ nodeCollisionGap = -1,
2447
+ subflowCollisionGap,
2448
+ reparentOnDrag = false,
2449
+ autoResizeContainers = false,
2450
+ containerAutoResizePadding = 32,
2451
+ containerMinWidth = 320,
2452
+ containerMinHeight = 200,
2453
+ height = "100%",
2454
+ width = "100%",
2455
+ className,
2456
+ style,
2457
+ children,
2458
+ emptyState
2459
+ } = props;
2278
2460
  const presetDefaults = {
2279
2461
  mouse: { panOnDrag: true, zoomOnScroll: true },
2280
2462
  trackpad: { panOnDrag: true, zoomOnScroll: true },
@@ -2282,46 +2464,46 @@ function FlowCanvas({
2282
2464
  };
2283
2465
  const panOnDrag = panOnDragProp ?? presetDefaults[preset].panOnDrag;
2284
2466
  const zoomOnScroll = zoomOnScrollProp ?? presetDefaults[preset].zoomOnScroll;
2285
- const store = (0, import_react14.useState)(
2467
+ const store = (0, import_react15.useState)(
2286
2468
  () => createFlowStore({
2287
2469
  initialNodes: nodes,
2288
2470
  initialEdges: edges,
2289
2471
  initialViewport: controlledViewport ?? defaultViewport
2290
2472
  })
2291
2473
  )[0];
2292
- const handleRegistry = (0, import_react14.useState)(() => createHandleRegistry())[0];
2293
- const [handleVersion, setHandleVersion] = (0, import_react14.useState)(0);
2294
- (0, import_react14.useEffect)(() => {
2474
+ const handleRegistry = (0, import_react15.useState)(() => createHandleRegistry())[0];
2475
+ const [handleVersion, setHandleVersion] = (0, import_react15.useState)(0);
2476
+ (0, import_react15.useEffect)(() => {
2295
2477
  const unsub = handleRegistry.subscribe(() => {
2296
2478
  setHandleVersion((v) => v + 1);
2297
2479
  });
2298
2480
  return unsub;
2299
2481
  }, [handleRegistry]);
2300
- const kinds = (0, import_react14.useMemo)(() => buildNodeKindRegistry(DEFAULT_NODE_KINDS, nodeKinds), [nodeKinds]);
2301
- const containerRef = (0, import_react14.useRef)(null);
2302
- (0, import_react14.useEffect)(() => store.setNodes(nodes), [store, nodes]);
2303
- (0, import_react14.useEffect)(() => store.setEdges(edges), [store, edges]);
2304
- const [uncontrolledVp, setUncontrolledVp] = (0, import_react14.useState)(controlledViewport ?? defaultViewport);
2482
+ const kinds = (0, import_react15.useMemo)(() => buildNodeKindRegistry(DEFAULT_NODE_KINDS, nodeKinds), [nodeKinds]);
2483
+ const containerRef = (0, import_react15.useRef)(null);
2484
+ (0, import_react15.useEffect)(() => store.setNodes(nodes), [store, nodes]);
2485
+ (0, import_react15.useEffect)(() => store.setEdges(edges), [store, edges]);
2486
+ const [uncontrolledVp, setUncontrolledVp] = (0, import_react15.useState)(controlledViewport ?? defaultViewport);
2305
2487
  const viewport = controlledViewport ?? uncontrolledVp;
2306
- (0, import_react14.useEffect)(() => store.setViewport(viewport), [store, viewport]);
2307
- const setViewport = (0, import_react14.useCallback)(
2488
+ (0, import_react15.useEffect)(() => store.setViewport(viewport), [store, viewport]);
2489
+ const setViewport = (0, import_react15.useCallback)(
2308
2490
  (next) => {
2309
2491
  if (controlledViewport === void 0) setUncontrolledVp(next);
2310
2492
  onViewportChange?.(next);
2311
2493
  },
2312
2494
  [controlledViewport, onViewportChange]
2313
2495
  );
2314
- const selectedNodeIds = (0, import_react14.useMemo)(() => {
2496
+ const selectedNodeIds = (0, import_react15.useMemo)(() => {
2315
2497
  const s = /* @__PURE__ */ new Set();
2316
2498
  for (const n of nodes) if (n.selected) s.add(n.id);
2317
2499
  return s;
2318
2500
  }, [nodes]);
2319
- const selectedEdgeIds = (0, import_react14.useMemo)(() => {
2501
+ const selectedEdgeIds = (0, import_react15.useMemo)(() => {
2320
2502
  const s = /* @__PURE__ */ new Set();
2321
2503
  for (const e of edges) if (e.selected) s.add(e.id);
2322
2504
  return s;
2323
2505
  }, [edges]);
2324
- (0, import_react14.useEffect)(() => {
2506
+ (0, import_react15.useEffect)(() => {
2325
2507
  store.setSelection(selectedNodeIds, selectedEdgeIds);
2326
2508
  if (onSelectionChange) {
2327
2509
  onSelectionChange({
@@ -2330,7 +2512,7 @@ function FlowCanvas({
2330
2512
  });
2331
2513
  }
2332
2514
  }, [store, selectedNodeIds, selectedEdgeIds, nodes, edges, onSelectionChange]);
2333
- const selectNode = (0, import_react14.useCallback)(
2515
+ const selectNode = (0, import_react15.useCallback)(
2334
2516
  (id, additive) => {
2335
2517
  const next = [];
2336
2518
  const nextEdges = [];
@@ -2353,14 +2535,14 @@ function FlowCanvas({
2353
2535
  },
2354
2536
  [nodes, edges, selectedNodeIds, onNodesChange, onEdgesChange]
2355
2537
  );
2356
- const notifyNodeClick = (0, import_react14.useCallback)(
2538
+ const notifyNodeClick = (0, import_react15.useCallback)(
2357
2539
  (id) => {
2358
2540
  const node = nodes.find((n) => n.id === id);
2359
2541
  if (node) onNodeClick?.(node);
2360
2542
  },
2361
2543
  [nodes, onNodeClick]
2362
2544
  );
2363
- const selectEdge = (0, import_react14.useCallback)(
2545
+ const selectEdge = (0, import_react15.useCallback)(
2364
2546
  (id, additive) => {
2365
2547
  const next = [];
2366
2548
  const nextNodes = [];
@@ -2385,7 +2567,7 @@ function FlowCanvas({
2385
2567
  },
2386
2568
  [nodes, edges, selectedEdgeIds, onEdgesChange, onNodesChange, onEdgeClick]
2387
2569
  );
2388
- const clearSelection = (0, import_react14.useCallback)(() => {
2570
+ const clearSelection = (0, import_react15.useCallback)(() => {
2389
2571
  const ns = [];
2390
2572
  const es = [];
2391
2573
  for (const n of nodes) if (n.selected) ns.push(change.node.select(n.id, false));
@@ -2393,9 +2575,9 @@ function FlowCanvas({
2393
2575
  if (ns.length) onNodesChange?.(ns);
2394
2576
  if (es.length) onEdgesChange?.(es);
2395
2577
  }, [nodes, edges, onNodesChange, onEdgesChange]);
2396
- const dragRef = (0, import_react14.useRef)(null);
2397
- const [draggingId, setDraggingId] = (0, import_react14.useState)(null);
2398
- const beginNodeDrag = (0, import_react14.useCallback)(
2578
+ const dragRef = (0, import_react15.useRef)(null);
2579
+ const [draggingId, setDraggingId] = (0, import_react15.useState)(null);
2580
+ const beginNodeDrag = (0, import_react15.useCallback)(
2399
2581
  (nodeId, pointerId, clientX, clientY, altKey = false) => {
2400
2582
  if (!nodesDraggable) return;
2401
2583
  const node = nodes.find((n) => n.id === nodeId);
@@ -2404,6 +2586,9 @@ function FlowCanvas({
2404
2586
  id: d.id,
2405
2587
  startPosition: d.position
2406
2588
  }));
2589
+ const evaluateReparent = altKey || reparentOnDragRef.current;
2590
+ const hasParent = !!node.parentId;
2591
+ const skipClamp = altKey || reparentOnDragRef.current && !hasParent;
2407
2592
  dragRef.current = {
2408
2593
  pointerId,
2409
2594
  nodeId,
@@ -2411,7 +2596,8 @@ function FlowCanvas({
2411
2596
  startClientY: clientY,
2412
2597
  startPosition: node.position,
2413
2598
  descendants: kids,
2414
- altDetach: altKey && !!node.parentId,
2599
+ skipClamp,
2600
+ evaluateReparent,
2415
2601
  rafScheduled: false,
2416
2602
  nextDelta: null
2417
2603
  };
@@ -2420,12 +2606,12 @@ function FlowCanvas({
2420
2606
  },
2421
2607
  [nodes, nodesDraggable, selectNode]
2422
2608
  );
2423
- const [conn, setConn] = (0, import_react14.useState)(null);
2424
- const connRef = (0, import_react14.useRef)(null);
2425
- (0, import_react14.useEffect)(() => {
2609
+ const [conn, setConn] = (0, import_react15.useState)(null);
2610
+ const connRef = (0, import_react15.useRef)(null);
2611
+ (0, import_react15.useEffect)(() => {
2426
2612
  connRef.current = conn;
2427
2613
  }, [conn]);
2428
- const beginConnection = (0, import_react14.useCallback)(
2614
+ const beginConnection = (0, import_react15.useCallback)(
2429
2615
  (nodeId, handleId, handleType, pointerId, clientX, clientY) => {
2430
2616
  if (!nodesConnectable) return;
2431
2617
  const node = nodes.find((n) => n.id === nodeId);
@@ -2458,63 +2644,83 @@ function FlowCanvas({
2458
2644
  },
2459
2645
  [nodes, nodesConnectable, handleRegistry, viewport, store, onConnectStart]
2460
2646
  );
2461
- const viewportRef = (0, import_react14.useRef)(viewport);
2462
- const nodesRef = (0, import_react14.useRef)(nodes);
2463
- const edgesRef = (0, import_react14.useRef)(edges);
2464
- const onNodesChangeRefForInstance = (0, import_react14.useRef)(onNodesChange);
2465
- const onEdgesChangeRefForInstance = (0, import_react14.useRef)(onEdgesChange);
2466
- const onBeforeDeleteRef = (0, import_react14.useRef)(onBeforeDelete);
2467
- const snapToGridRef = (0, import_react14.useRef)(snapToGrid);
2468
- const gridSizeRef = (0, import_react14.useRef)(gridSize);
2469
- const nodeCollisionGapRef = (0, import_react14.useRef)(nodeCollisionGap);
2470
- const subflowCollisionGapRef = (0, import_react14.useRef)(subflowCollisionGap ?? nodeCollisionGap);
2471
- (0, import_react14.useEffect)(() => {
2647
+ const viewportRef = (0, import_react15.useRef)(viewport);
2648
+ const nodesRef = (0, import_react15.useRef)(nodes);
2649
+ const edgesRef = (0, import_react15.useRef)(edges);
2650
+ const onNodesChangeRefForInstance = (0, import_react15.useRef)(onNodesChange);
2651
+ const onEdgesChangeRefForInstance = (0, import_react15.useRef)(onEdgesChange);
2652
+ const onBeforeDeleteRef = (0, import_react15.useRef)(onBeforeDelete);
2653
+ const snapToGridRef = (0, import_react15.useRef)(snapToGrid);
2654
+ const gridSizeRef = (0, import_react15.useRef)(gridSize);
2655
+ const nodeCollisionGapRef = (0, import_react15.useRef)(nodeCollisionGap);
2656
+ const subflowCollisionGapRef = (0, import_react15.useRef)(subflowCollisionGap ?? nodeCollisionGap);
2657
+ const reparentOnDragRef = (0, import_react15.useRef)(reparentOnDrag);
2658
+ const autoResizeContainersRef = (0, import_react15.useRef)(autoResizeContainers);
2659
+ const containerAutoResizePaddingRef = (0, import_react15.useRef)(containerAutoResizePadding);
2660
+ const containerMinWidthRef = (0, import_react15.useRef)(containerMinWidth);
2661
+ const containerMinHeightRef = (0, import_react15.useRef)(containerMinHeight);
2662
+ (0, import_react15.useEffect)(() => {
2472
2663
  edgesRef.current = edges;
2473
2664
  }, [edges]);
2474
- (0, import_react14.useEffect)(() => {
2665
+ (0, import_react15.useEffect)(() => {
2475
2666
  onNodesChangeRefForInstance.current = onNodesChange;
2476
2667
  }, [onNodesChange]);
2477
- (0, import_react14.useEffect)(() => {
2668
+ (0, import_react15.useEffect)(() => {
2478
2669
  onEdgesChangeRefForInstance.current = onEdgesChange;
2479
2670
  }, [onEdgesChange]);
2480
- (0, import_react14.useEffect)(() => {
2671
+ (0, import_react15.useEffect)(() => {
2481
2672
  onBeforeDeleteRef.current = onBeforeDelete;
2482
2673
  }, [onBeforeDelete]);
2483
- (0, import_react14.useEffect)(() => {
2674
+ (0, import_react15.useEffect)(() => {
2484
2675
  snapToGridRef.current = snapToGrid;
2485
2676
  }, [snapToGrid]);
2486
- (0, import_react14.useEffect)(() => {
2677
+ (0, import_react15.useEffect)(() => {
2487
2678
  gridSizeRef.current = gridSize;
2488
2679
  }, [gridSize]);
2489
- (0, import_react14.useEffect)(() => {
2680
+ (0, import_react15.useEffect)(() => {
2490
2681
  nodeCollisionGapRef.current = nodeCollisionGap;
2491
2682
  }, [nodeCollisionGap]);
2492
- (0, import_react14.useEffect)(() => {
2683
+ (0, import_react15.useEffect)(() => {
2493
2684
  subflowCollisionGapRef.current = subflowCollisionGap ?? nodeCollisionGap;
2494
2685
  }, [subflowCollisionGap, nodeCollisionGap]);
2495
- const onNodesChangeRef = (0, import_react14.useRef)(onNodesChange);
2496
- const onConnectRef = (0, import_react14.useRef)(onConnect);
2497
- const onConnectEndRef = (0, import_react14.useRef)(onConnectEnd);
2498
- const isValidConnectionRef = (0, import_react14.useRef)(isValidConnection);
2499
- (0, import_react14.useEffect)(() => {
2686
+ (0, import_react15.useEffect)(() => {
2687
+ reparentOnDragRef.current = reparentOnDrag;
2688
+ }, [reparentOnDrag]);
2689
+ (0, import_react15.useEffect)(() => {
2690
+ autoResizeContainersRef.current = autoResizeContainers;
2691
+ }, [autoResizeContainers]);
2692
+ (0, import_react15.useEffect)(() => {
2693
+ containerAutoResizePaddingRef.current = containerAutoResizePadding;
2694
+ }, [containerAutoResizePadding]);
2695
+ (0, import_react15.useEffect)(() => {
2696
+ containerMinWidthRef.current = containerMinWidth;
2697
+ }, [containerMinWidth]);
2698
+ (0, import_react15.useEffect)(() => {
2699
+ containerMinHeightRef.current = containerMinHeight;
2700
+ }, [containerMinHeight]);
2701
+ const onNodesChangeRef = (0, import_react15.useRef)(onNodesChange);
2702
+ const onConnectRef = (0, import_react15.useRef)(onConnect);
2703
+ const onConnectEndRef = (0, import_react15.useRef)(onConnectEnd);
2704
+ const isValidConnectionRef = (0, import_react15.useRef)(isValidConnection);
2705
+ (0, import_react15.useEffect)(() => {
2500
2706
  viewportRef.current = viewport;
2501
2707
  }, [viewport]);
2502
- (0, import_react14.useEffect)(() => {
2708
+ (0, import_react15.useEffect)(() => {
2503
2709
  nodesRef.current = nodes;
2504
2710
  }, [nodes]);
2505
- (0, import_react14.useEffect)(() => {
2711
+ (0, import_react15.useEffect)(() => {
2506
2712
  onNodesChangeRef.current = onNodesChange;
2507
2713
  }, [onNodesChange]);
2508
- (0, import_react14.useEffect)(() => {
2714
+ (0, import_react15.useEffect)(() => {
2509
2715
  onConnectRef.current = onConnect;
2510
2716
  }, [onConnect]);
2511
- (0, import_react14.useEffect)(() => {
2717
+ (0, import_react15.useEffect)(() => {
2512
2718
  onConnectEndRef.current = onConnectEnd;
2513
2719
  }, [onConnectEnd]);
2514
- (0, import_react14.useEffect)(() => {
2720
+ (0, import_react15.useEffect)(() => {
2515
2721
  isValidConnectionRef.current = isValidConnection;
2516
2722
  }, [isValidConnection]);
2517
- (0, import_react14.useEffect)(() => {
2723
+ (0, import_react15.useEffect)(() => {
2518
2724
  const onPointerMove = (e) => {
2519
2725
  const vp = viewportRef.current;
2520
2726
  const drag = dragRef.current;
@@ -2536,7 +2742,7 @@ function FlowCanvas({
2536
2742
  x: d.startPosition.x + delta.dx,
2537
2743
  y: d.startPosition.y + delta.dy
2538
2744
  };
2539
- const clamped = d.altDetach ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2745
+ const clamped = d.skipClamp ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2540
2746
  const isContainer = dragNode.type === "group" || dragNode.type === "forEach";
2541
2747
  const gap = isContainer ? subflowCollisionGapRef.current : nodeCollisionGapRef.current;
2542
2748
  const excludeIds = /* @__PURE__ */ new Set([d.nodeId, ...d.descendants.map((kid) => kid.id)]);
@@ -2588,7 +2794,7 @@ function FlowCanvas({
2588
2794
  y: Math.round(proposed.y / g) * g
2589
2795
  };
2590
2796
  }
2591
- const clamped = drag.altDetach ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2797
+ const clamped = drag.skipClamp ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2592
2798
  const isContainer = dragNode.type === "group" || dragNode.type === "forEach";
2593
2799
  const gap = isContainer ? subflowCollisionGapRef.current : nodeCollisionGapRef.current;
2594
2800
  const excludeIds = /* @__PURE__ */ new Set([
@@ -2611,7 +2817,7 @@ function FlowCanvas({
2611
2817
  )
2612
2818
  );
2613
2819
  }
2614
- if (drag.altDetach && dragNode.parentId) {
2820
+ if (drag.evaluateReparent) {
2615
2821
  const targetGroup = findContainingGroup(
2616
2822
  {
2617
2823
  x: finalPos.x + (dragNode.width ?? 0) / 2,
@@ -2621,14 +2827,30 @@ function FlowCanvas({
2621
2827
  [drag.nodeId, ...drag.descendants.map((d) => d.id)]
2622
2828
  );
2623
2829
  const nextParentId = targetGroup?.id;
2624
- const updated = {
2625
- ...dragNode,
2626
- position: finalPos,
2627
- parentId: nextParentId,
2628
- // Preserve extent only when staying in a group.
2629
- extent: nextParentId ? dragNode.extent : void 0
2630
- };
2631
- changes.push(change.node.replace(drag.nodeId, updated));
2830
+ if (nextParentId !== dragNode.parentId) {
2831
+ const updated = {
2832
+ ...dragNode,
2833
+ position: finalPos,
2834
+ parentId: nextParentId,
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
2839
+ };
2840
+ changes.push(change.node.replace(drag.nodeId, updated));
2841
+ }
2842
+ }
2843
+ if (autoResizeContainersRef.current) {
2844
+ const containerChanges = computeContainerAutoResize(
2845
+ nodesRef.current,
2846
+ // Projected children: apply the position + reparent
2847
+ // changes we just built before measuring bboxes.
2848
+ changes,
2849
+ containerAutoResizePaddingRef.current,
2850
+ containerMinWidthRef.current,
2851
+ containerMinHeightRef.current
2852
+ );
2853
+ for (const c2 of containerChanges) changes.push(c2);
2632
2854
  }
2633
2855
  onNodesChangeRef.current?.(changes);
2634
2856
  }
@@ -2637,38 +2859,54 @@ function FlowCanvas({
2637
2859
  }
2638
2860
  const c = connRef.current;
2639
2861
  if (c && c.pointerId === e.pointerId) {
2640
- const target = e.target;
2641
- 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
+ }
2642
2880
  let connection = null;
2643
2881
  let connectedTo;
2644
- if (handleEl) {
2645
- const targetNodeId = handleEl.dataset.handleNodeId;
2646
- const targetHandleId = handleEl.dataset.handleId;
2647
- const targetType = handleEl.dataset.handleType;
2648
- const connectableEnd = handleEl.dataset.handleConnectableEnd === "true";
2649
- if (connectableEnd && (targetNodeId !== c.from.nodeId || targetHandleId !== c.from.handleId) && targetType !== c.from.handleType) {
2650
- const source = c.from.handleType === "source" ? c.from : { nodeId: targetNodeId, handleId: targetHandleId, handleType: "source" };
2651
- const target2 = c.from.handleType === "target" ? c.from : { nodeId: targetNodeId, handleId: targetHandleId, handleType: "target" };
2652
- connection = {
2653
- source: source.nodeId,
2654
- sourceHandle: source.handleId,
2655
- target: target2.nodeId,
2656
- targetHandle: target2.handleId
2657
- };
2658
- connectedTo = {
2659
- nodeId: targetNodeId,
2660
- handleId: targetHandleId,
2661
- handleType: targetType
2662
- };
2663
- const validator = isValidConnectionRef.current;
2664
- if (validator && !validator(connection)) {
2665
- connection = null;
2666
- connectedTo = void 0;
2667
- }
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;
2668
2908
  }
2669
2909
  }
2670
- const rect = containerRef.current?.getBoundingClientRect();
2671
- const flowPos = rect ? screenToFlow({ x: e.clientX - rect.left, y: e.clientY - rect.top }, vp) : { x: 0, y: 0 };
2672
2910
  const endState = {
2673
2911
  cancelled: !connection,
2674
2912
  position: { x: e.clientX, y: e.clientY },
@@ -2701,7 +2939,26 @@ function FlowCanvas({
2701
2939
  window.removeEventListener("pointercancel", onPointerCancel);
2702
2940
  };
2703
2941
  }, [store]);
2704
- const panRef = (0, import_react14.useRef)(null);
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
+ ]);
2961
+ const panRef = (0, import_react15.useRef)(null);
2705
2962
  const onCanvasPointerDown = (e) => {
2706
2963
  if (e.button !== 0) return;
2707
2964
  const t = e.target;
@@ -2726,7 +2983,7 @@ function FlowCanvas({
2726
2983
  moved: false
2727
2984
  };
2728
2985
  };
2729
- (0, import_react14.useEffect)(() => {
2986
+ (0, import_react15.useEffect)(() => {
2730
2987
  const onMove = (e) => {
2731
2988
  const pan = panRef.current;
2732
2989
  if (!pan || pan.pointerId !== e.pointerId) return;
@@ -2782,7 +3039,7 @@ function FlowCanvas({
2782
3039
  y: py - (py - viewport.y) * k
2783
3040
  });
2784
3041
  };
2785
- const dispatch = (0, import_react14.useCallback)(
3042
+ const dispatch = (0, import_react15.useCallback)(
2786
3043
  (a) => {
2787
3044
  if (a.type === "connection/start") {
2788
3045
  beginConnection(a.nodeId, a.handleId, a.handleType, a.pointerId, a.clientX, a.clientY);
@@ -2790,7 +3047,7 @@ function FlowCanvas({
2790
3047
  },
2791
3048
  [beginConnection]
2792
3049
  );
2793
- const reportDimensions = (0, import_react14.useCallback)(
3050
+ const reportDimensions = (0, import_react15.useCallback)(
2794
3051
  (nodeId, width2, height2) => {
2795
3052
  const node = nodes.find((n) => n.id === nodeId);
2796
3053
  if (!node) return;
@@ -2799,7 +3056,7 @@ function FlowCanvas({
2799
3056
  },
2800
3057
  [nodes, onNodesChange]
2801
3058
  );
2802
- const toggleNodeCollapseImpl = (0, import_react14.useCallback)(
3059
+ const toggleNodeCollapseImpl = (0, import_react15.useCallback)(
2803
3060
  (nodeId) => {
2804
3061
  const node = nodes.find((n) => n.id === nodeId);
2805
3062
  if (!node) return;
@@ -2812,7 +3069,7 @@ function FlowCanvas({
2812
3069
  },
2813
3070
  [nodes, onNodesChange]
2814
3071
  );
2815
- const deleteNodeImpl = (0, import_react14.useCallback)(
3072
+ const deleteNodeImpl = (0, import_react15.useCallback)(
2816
3073
  (nodeId) => {
2817
3074
  const incidentEdgeIds = edgesRef.current.filter((e) => e.source === nodeId || e.target === nodeId).map((e) => e.id);
2818
3075
  if (incidentEdgeIds.length > 0) {
@@ -2822,7 +3079,7 @@ function FlowCanvas({
2822
3079
  },
2823
3080
  [onNodesChange, onEdgesChange]
2824
3081
  );
2825
- const instance = (0, import_react14.useMemo)(
3082
+ const instance = (0, import_react15.useMemo)(
2826
3083
  () => ({
2827
3084
  // viewport
2828
3085
  getViewport: () => viewportRef.current,
@@ -2963,13 +3220,29 @@ function FlowCanvas({
2963
3220
  }),
2964
3221
  [setViewport, minZoom, maxZoom]
2965
3222
  );
2966
- const initFiredRef = (0, import_react14.useRef)(false);
2967
- (0, import_react14.useEffect)(() => {
3223
+ const initFiredRef = (0, import_react15.useRef)(false);
3224
+ (0, import_react15.useEffect)(() => {
2968
3225
  if (initFiredRef.current) return;
2969
3226
  initFiredRef.current = true;
2970
3227
  onInit?.(instance);
2971
3228
  }, [instance, onInit]);
2972
- (0, import_react14.useEffect)(() => {
3229
+ const fitOnInitFiredRef = (0, import_react15.useRef)(false);
3230
+ (0, import_react15.useEffect)(() => {
3231
+ if (fitOnInitFiredRef.current) return;
3232
+ const opt = fitViewOnInit;
3233
+ const shouldFit = opt === false ? false : opt !== void 0 ? true : !viewportPropProvided;
3234
+ if (!shouldFit) return;
3235
+ if (nodes.length === 0) return;
3236
+ const rect = containerRef.current?.getBoundingClientRect();
3237
+ if (!rect || rect.width === 0 || rect.height === 0) return;
3238
+ fitOnInitFiredRef.current = true;
3239
+ const fitOpts = typeof opt === "object" && opt !== null ? opt : void 0;
3240
+ const raf = requestAnimationFrame(() => {
3241
+ void instance.fitView(fitOpts);
3242
+ });
3243
+ return () => cancelAnimationFrame(raf);
3244
+ }, [fitViewOnInit, viewportPropProvided, nodes.length, instance]);
3245
+ (0, import_react15.useEffect)(() => {
2973
3246
  const onKey = (e) => {
2974
3247
  if (e.key !== "Backspace" && e.key !== "Delete") return;
2975
3248
  const target = e.target;
@@ -2990,7 +3263,7 @@ function FlowCanvas({
2990
3263
  window.addEventListener("keydown", onKey);
2991
3264
  return () => window.removeEventListener("keydown", onKey);
2992
3265
  }, [instance, store]);
2993
- const bridge = (0, import_react14.useMemo)(
3266
+ const bridge = (0, import_react15.useMemo)(
2994
3267
  () => ({
2995
3268
  beginNodeDrag,
2996
3269
  selectNode,
@@ -3010,16 +3283,16 @@ function FlowCanvas({
3010
3283
  toggleNodeCollapseImpl
3011
3284
  ]
3012
3285
  );
3013
- const [panGesture, setPanGesture] = (0, import_react14.useState)(false);
3286
+ const [panGesture, setPanGesture] = (0, import_react15.useState)(false);
3014
3287
  const isEmpty = nodes.length === 0 && edges.length === 0;
3015
3288
  const isConnecting = conn !== null;
3016
- const visibleNodes = (0, import_react14.useMemo)(() => nodes.filter((n) => !n.hidden), [nodes]);
3017
- const visibleEdges = (0, import_react14.useMemo)(() => {
3289
+ const visibleNodes = (0, import_react15.useMemo)(() => nodes.filter((n) => !n.hidden), [nodes]);
3290
+ const visibleEdges = (0, import_react15.useMemo)(() => {
3018
3291
  if (visibleNodes.length === nodes.length) return edges;
3019
3292
  const visibleIds = new Set(visibleNodes.map((n) => n.id));
3020
3293
  return edges.filter((e) => visibleIds.has(e.source) && visibleIds.has(e.target));
3021
3294
  }, [edges, nodes, visibleNodes]);
3022
- const orderedNodes = (0, import_react14.useMemo)(() => {
3295
+ const orderedNodes = (0, import_react15.useMemo)(() => {
3023
3296
  const isContainer = (n) => n.type === "group" || n.type === "forEach";
3024
3297
  const depth = (n) => {
3025
3298
  let d = 0;
@@ -3038,7 +3311,7 @@ function FlowCanvas({
3038
3311
  containers.sort((a, b) => depth(a) - depth(b));
3039
3312
  return [...containers, ...others];
3040
3313
  }, [visibleNodes]);
3041
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(FlowStoreContext.Provider, { value: store, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(FlowInstanceContext.Provider, { value: instance, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(HandleRegistryContext.Provider, { value: handleRegistry, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(FlowDispatchContext.Provider, { value: dispatch, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(FlowNodeBridgeContext.Provider, { value: bridge, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3314
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(FlowStoreContext.Provider, { value: store, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(FlowInstanceContext.Provider, { value: instance, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(HandleRegistryContext.Provider, { value: handleRegistry, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(FlowDispatchContext.Provider, { value: dispatch, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(FlowNodeBridgeContext.Provider, { value: bridge, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3042
3315
  "div",
3043
3316
  {
3044
3317
  ref: containerRef,
@@ -3083,7 +3356,7 @@ function FlowCanvas({
3083
3356
  },
3084
3357
  "data-empty": isEmpty ? "true" : void 0,
3085
3358
  children: [
3086
- background !== "none" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3359
+ background !== "none" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3087
3360
  "div",
3088
3361
  {
3089
3362
  className: cn(
@@ -3097,7 +3370,7 @@ function FlowCanvas({
3097
3370
  }
3098
3371
  }
3099
3372
  ),
3100
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3373
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3101
3374
  "div",
3102
3375
  {
3103
3376
  className: "ods-flow-canvas-v2__viewport",
@@ -3108,7 +3381,7 @@ function FlowCanvas({
3108
3381
  transformOrigin: "0 0"
3109
3382
  },
3110
3383
  children: [
3111
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3384
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3112
3385
  EdgesLayer,
3113
3386
  {
3114
3387
  edges: visibleEdges,
@@ -3123,7 +3396,7 @@ function FlowCanvas({
3123
3396
  orderedNodes.map((node) => {
3124
3397
  const Kind = kinds[node.type] ?? kinds.action;
3125
3398
  if (!Kind) return null;
3126
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3399
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3127
3400
  FlowNode,
3128
3401
  {
3129
3402
  node,
@@ -3138,7 +3411,7 @@ function FlowCanvas({
3138
3411
  ]
3139
3412
  }
3140
3413
  ),
3141
- isEmpty && emptyState && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ods-flow-canvas-v2__empty", children: emptyState }),
3414
+ isEmpty && emptyState && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "ods-flow-canvas-v2__empty", children: emptyState }),
3142
3415
  children
3143
3416
  ]
3144
3417
  }
@@ -3147,7 +3420,69 @@ function FlowCanvas({
3147
3420
  function onEdgesChangeRef(id, cb) {
3148
3421
  cb?.([change.edge.remove(id)]);
3149
3422
  }
3150
- var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3423
+ var CONTAINER_DROP_HEADROOM_W = 220;
3424
+ var CONTAINER_DROP_HEADROOM_H = 2 * 128;
3425
+ function computeContainerAutoResize(nodes, pending, padding, minWidth, minHeight) {
3426
+ const projected = /* @__PURE__ */ new Map();
3427
+ for (const n of nodes) projected.set(n.id, n);
3428
+ for (const c of pending) {
3429
+ if (c.type === "position" && c.position) {
3430
+ const cur = projected.get(c.id);
3431
+ if (cur) projected.set(c.id, { ...cur, position: c.position });
3432
+ } else if (c.type === "replace" && c.item) {
3433
+ projected.set(c.id, { ...projected.get(c.id), ...c.item });
3434
+ } else if (c.type === "add" && c.item) {
3435
+ projected.set(c.item.id, c.item);
3436
+ }
3437
+ }
3438
+ const HEADER = 56;
3439
+ const out = [];
3440
+ for (const parent of projected.values()) {
3441
+ if (parent.type !== "group" && parent.type !== "forEach") continue;
3442
+ const children = [];
3443
+ for (const n of projected.values()) {
3444
+ if (n.parentId === parent.id) children.push(n);
3445
+ }
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
+ }
3454
+ let minX = Number.POSITIVE_INFINITY;
3455
+ let minY = Number.POSITIVE_INFINITY;
3456
+ let maxX = Number.NEGATIVE_INFINITY;
3457
+ let maxY = Number.NEGATIVE_INFINITY;
3458
+ for (const c of children) {
3459
+ const cw = c.width ?? 240;
3460
+ const ch = c.height ?? 96;
3461
+ if (c.position.x < minX) minX = c.position.x;
3462
+ if (c.position.y < minY) minY = c.position.y;
3463
+ if (c.position.x + cw > maxX) maxX = c.position.x + cw;
3464
+ if (c.position.y + ch > maxY) maxY = c.position.y + ch;
3465
+ }
3466
+ const targetX = minX - padding;
3467
+ const targetY = minY - padding - HEADER;
3468
+ const curW = parent.width ?? 480;
3469
+ const curH = parent.height ?? 240;
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);
3474
+ const positionDirty = parent.position.x !== targetX || parent.position.y !== targetY;
3475
+ const sizeDirty = curW !== nextW || curH !== nextH;
3476
+ if (positionDirty) {
3477
+ out.push(change.node.position(parent.id, { x: targetX, y: targetY }, false));
3478
+ }
3479
+ if (sizeDirty) {
3480
+ out.push(change.node.dimensions(parent.id, { width: nextW, height: nextH }));
3481
+ }
3482
+ }
3483
+ return out;
3484
+ }
3485
+ var EdgesLayer = (0, import_react15.memo)(function EdgesLayer2({
3151
3486
  edges,
3152
3487
  nodes,
3153
3488
  onSelect,
@@ -3156,7 +3491,7 @@ var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3156
3491
  ghost,
3157
3492
  handleVersion: _handleVersion
3158
3493
  }) {
3159
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3494
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3160
3495
  "svg",
3161
3496
  {
3162
3497
  className: "ods-flow-canvas-v2__edges",
@@ -3164,7 +3499,7 @@ var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3164
3499
  width: "100%",
3165
3500
  height: "100%",
3166
3501
  children: [
3167
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("g", { style: { pointerEvents: "auto" }, children: edges.map((edge) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3502
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("g", { style: { pointerEvents: "auto" }, children: edges.map((edge) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3168
3503
  FlowEdge,
3169
3504
  {
3170
3505
  edge,
@@ -3176,7 +3511,7 @@ var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3176
3511
  },
3177
3512
  edge.id
3178
3513
  )) }),
3179
- ghost && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3514
+ ghost && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3180
3515
  "path",
3181
3516
  {
3182
3517
  d: `M ${ghost.start.x} ${ghost.start.y} L ${ghost.end.x} ${ghost.end.y}`,
@@ -3191,153 +3526,456 @@ var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3191
3526
  );
3192
3527
  });
3193
3528
 
3194
- // src/workflow/components/NodeResizer/NodeResizer.tsx
3195
- var import_react15 = require("react");
3196
- var import_jsx_runtime8 = require("react/jsx-runtime");
3197
- function NodeResizer({
3198
- isVisible,
3199
- minWidth = 80,
3200
- minHeight = 60,
3201
- maxWidth,
3202
- maxHeight,
3203
- keepAspectRatio = false,
3204
- onResize,
3205
- onResizeEnd,
3206
- color
3529
+ // src/workflow/components/FxPanel/FxPanel.tsx
3530
+ var import_react16 = require("react");
3531
+ var import_icons2 = require("@octaviaflow/icons");
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
3207
3590
  }) {
3208
- const { node, selected } = useFlowNodeContext();
3209
- const viewport = useViewport();
3210
- const flow = useFlow();
3211
- const dragRef = (0, import_react15.useRef)(null);
3212
- const show = isVisible ?? selected;
3213
- if (!show) return null;
3214
- const beginResize = (e, corner) => {
3215
- e.preventDefault();
3216
- e.stopPropagation();
3217
- e.target.setPointerCapture(e.pointerId);
3218
- const w = node.width ?? DEFAULT_NODE_WIDTH;
3219
- const h = node.height ?? DEFAULT_NODE_HEIGHT;
3220
- dragRef.current = {
3221
- pointerId: e.pointerId,
3222
- corner,
3223
- startClientX: e.clientX,
3224
- startClientY: e.clientY,
3225
- startWidth: w,
3226
- startHeight: h,
3227
- startX: node.position.x,
3228
- startY: node.position.y,
3229
- aspect: w / Math.max(1, h)
3230
- };
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);
3231
3597
  };
3232
- const onMove = (e) => {
3233
- const drag = dragRef.current;
3234
- if (!drag || drag.pointerId !== e.pointerId) return;
3235
- const dx = (e.clientX - drag.startClientX) / viewport.zoom;
3236
- const dy = (e.clientY - drag.startClientY) / viewport.zoom;
3237
- let nextW = drag.startWidth;
3238
- let nextH = drag.startHeight;
3239
- let nextX = drag.startX;
3240
- let nextY = drag.startY;
3241
- switch (drag.corner) {
3242
- case "se":
3243
- nextW = drag.startWidth + dx;
3244
- nextH = drag.startHeight + dy;
3245
- break;
3246
- case "sw":
3247
- nextW = drag.startWidth - dx;
3248
- nextH = drag.startHeight + dy;
3249
- nextX = drag.startX + dx;
3250
- break;
3251
- case "ne":
3252
- nextW = drag.startWidth + dx;
3253
- nextH = drag.startHeight - dy;
3254
- nextY = drag.startY + dy;
3255
- break;
3256
- case "nw":
3257
- nextW = drag.startWidth - dx;
3258
- nextH = drag.startHeight - dy;
3259
- nextX = drag.startX + dx;
3260
- nextY = drag.startY + dy;
3261
- break;
3262
- }
3263
- if (keepAspectRatio) {
3264
- nextH = nextW / drag.aspect;
3265
- if (drag.corner === "nw" || drag.corner === "ne") {
3266
- nextY = drag.startY + (drag.startHeight - nextH);
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);
3267
3615
  }
3268
3616
  }
3269
- nextW = Math.max(minWidth, maxWidth ? Math.min(maxWidth, nextW) : nextW);
3270
- nextH = Math.max(minHeight, maxHeight ? Math.min(maxHeight, nextH) : nextH);
3271
- flow.updateNode(node.id, {
3272
- width: nextW,
3273
- height: nextH,
3274
- position: { x: nextX, y: nextY }
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;
3275
3625
  });
3276
- onResize?.({ width: nextW, height: nextH });
3277
3626
  };
3278
- const onUp = (e) => {
3279
- if (dragRef.current?.pointerId === e.pointerId) {
3280
- const cur = flow.getNode(node.id);
3281
- if (cur) {
3282
- onResizeEnd?.({
3283
- width: cur.width ?? DEFAULT_NODE_WIDTH,
3284
- height: cur.height ?? DEFAULT_NODE_HEIGHT
3285
- });
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);
3286
3635
  }
3287
- dragRef.current = null;
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";
3288
3663
  }
3289
3664
  };
3290
- const handleColor = color ?? "var(--ods-accent)";
3291
- const handleStyle = (corner) => {
3292
- const base = {
3293
- position: "absolute",
3294
- width: 12,
3295
- height: 12,
3296
- background: "var(--ods-surface-canvas)",
3297
- border: `2px solid ${handleColor}`,
3298
- borderRadius: 2,
3299
- cursor: cursorFor(corner),
3300
- touchAction: "none",
3301
- // Place each handle so its CENTRE sits on the corresponding corner.
3302
- transform: "translate(-50%, -50%)"
3303
- };
3304
- switch (corner) {
3305
- case "nw":
3306
- return { ...base, top: 0, left: 0 };
3307
- case "ne":
3308
- return { ...base, top: 0, left: "100%" };
3309
- case "sw":
3310
- return { ...base, top: "100%", left: 0 };
3311
- case "se":
3312
- return { ...base, top: "100%", left: "100%" };
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;
3313
3721
  }
3314
3722
  };
3315
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: cn("ods-node-resizer"), "data-flow-no-drag": "true", children: ["nw", "ne", "sw", "se"].map((corner) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3316
- "div",
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",
3317
3790
  {
3318
- style: handleStyle(corner),
3319
- onPointerDown: (e) => beginResize(e, corner),
3320
- onPointerMove: onMove,
3321
- onPointerUp: onUp,
3322
- onPointerCancel: onUp,
3323
- "aria-label": `Resize ${corner}`
3324
- },
3325
- corner
3326
- )) });
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
+ );
3327
3938
  }
3328
- function cursorFor(corner) {
3329
- switch (corner) {
3330
- case "nw":
3331
- case "se":
3332
- return "nwse-resize";
3333
- case "ne":
3334
- case "sw":
3335
- return "nesw-resize";
3336
- }
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
+ );
3337
3975
  }
3338
3976
 
3339
3977
  // src/workflow/components/NodeToolbar/NodeToolbar.tsx
3340
- var import_jsx_runtime9 = require("react/jsx-runtime");
3978
+ var import_jsx_runtime11 = require("react/jsx-runtime");
3341
3979
  function NodeToolbar({
3342
3980
  isVisible,
3343
3981
  position = "top",
@@ -3352,7 +3990,7 @@ function NodeToolbar({
3352
3990
  const show = isVisible ?? node.selected;
3353
3991
  if (!show) return null;
3354
3992
  const inverseScale = 1 / viewport.zoom;
3355
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3993
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3356
3994
  "div",
3357
3995
  {
3358
3996
  className: cn("ods-node-toolbar", `ods-node-toolbar--${position}`, className),
@@ -3364,7 +4002,7 @@ function NodeToolbar({
3364
4002
  onPointerDown: (e) => e.stopPropagation(),
3365
4003
  onMouseDown: (e) => e.stopPropagation(),
3366
4004
  onClick: (e) => e.stopPropagation(),
3367
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4005
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3368
4006
  "div",
3369
4007
  {
3370
4008
  className: "ods-node-toolbar__inner",
@@ -3513,11 +4151,13 @@ function toggleGroupCollapse(groupId, nodes) {
3513
4151
  DEFAULT_NODE_HEIGHT,
3514
4152
  DEFAULT_NODE_KINDS,
3515
4153
  DEFAULT_NODE_WIDTH,
4154
+ DataRefButton,
3516
4155
  ErrorNode,
3517
4156
  FlowCanvas,
3518
4157
  FlowEdge,
3519
4158
  FlowNode,
3520
4159
  ForEachNode,
4160
+ FxPanel,
3521
4161
  GroupNode,
3522
4162
  Handle,
3523
4163
  HttpRequestNode,
@@ -3564,6 +4204,7 @@ function toggleGroupCollapse(groupId, nodes) {
3564
4204
  useNodeData,
3565
4205
  useNodes,
3566
4206
  useSelection,
3567
- useViewport
4207
+ useViewport,
4208
+ useViewportOrNull
3568
4209
  });
3569
4210
  //# sourceMappingURL=workflow.cjs.map