@octaviaflow/core 3.0.18-beta.2 → 3.0.18-beta.21

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 (174) 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-76TP67ED.js +2984 -0
  20. package/dist/chunk-76TP67ED.js.map +1 -0
  21. package/dist/chunk-A6KMO4JV.js +2949 -0
  22. package/dist/chunk-A6KMO4JV.js.map +1 -0
  23. package/dist/chunk-B7FTWSTM.js +2938 -0
  24. package/dist/chunk-B7FTWSTM.js.map +1 -0
  25. package/dist/chunk-BCO6M26F.js +2940 -0
  26. package/dist/chunk-BCO6M26F.js.map +1 -0
  27. package/dist/chunk-C3UD2AZ5.js +2637 -0
  28. package/dist/chunk-C3UD2AZ5.js.map +1 -0
  29. package/dist/chunk-CEUP4NK2.js +2850 -0
  30. package/dist/chunk-CEUP4NK2.js.map +1 -0
  31. package/dist/chunk-DBNSBJO7.js +2993 -0
  32. package/dist/chunk-DBNSBJO7.js.map +1 -0
  33. package/dist/chunk-ECIHUVU7.js +2986 -0
  34. package/dist/chunk-ECIHUVU7.js.map +1 -0
  35. package/dist/chunk-EERNYLFL.js +2860 -0
  36. package/dist/chunk-EERNYLFL.js.map +1 -0
  37. package/dist/chunk-EKFDJX4G.js +2872 -0
  38. package/dist/chunk-EKFDJX4G.js.map +1 -0
  39. package/dist/chunk-GJA3GJUZ.js +2844 -0
  40. package/dist/chunk-GJA3GJUZ.js.map +1 -0
  41. package/dist/chunk-HDOTOZNA.js +2936 -0
  42. package/dist/chunk-HDOTOZNA.js.map +1 -0
  43. package/dist/chunk-IOKUV7FD.js +2658 -0
  44. package/dist/chunk-IOKUV7FD.js.map +1 -0
  45. package/dist/chunk-IUIICQU5.js +2946 -0
  46. package/dist/chunk-IUIICQU5.js.map +1 -0
  47. package/dist/chunk-J2UYZI6D.js +2946 -0
  48. package/dist/chunk-J2UYZI6D.js.map +1 -0
  49. package/dist/chunk-J7YASALS.js +2859 -0
  50. package/dist/chunk-J7YASALS.js.map +1 -0
  51. package/dist/chunk-JIEUYBQT.js +2658 -0
  52. package/dist/chunk-JIEUYBQT.js.map +1 -0
  53. package/dist/chunk-K2H7JLQW.js +2952 -0
  54. package/dist/chunk-K2H7JLQW.js.map +1 -0
  55. package/dist/chunk-KUXYBP66.js +2953 -0
  56. package/dist/chunk-KUXYBP66.js.map +1 -0
  57. package/dist/chunk-KYMYNYFV.js +2656 -0
  58. package/dist/chunk-KYMYNYFV.js.map +1 -0
  59. package/dist/chunk-MMXL343D.js +2974 -0
  60. package/dist/chunk-MMXL343D.js.map +1 -0
  61. package/dist/chunk-MXJL3EPE.js +2986 -0
  62. package/dist/chunk-MXJL3EPE.js.map +1 -0
  63. package/dist/chunk-MYZ25B2R.js +2995 -0
  64. package/dist/chunk-MYZ25B2R.js.map +1 -0
  65. package/dist/chunk-NTMEYB7B.js +2949 -0
  66. package/dist/chunk-NTMEYB7B.js.map +1 -0
  67. package/dist/chunk-PVJXX6GP.js +2640 -0
  68. package/dist/chunk-PVJXX6GP.js.map +1 -0
  69. package/dist/chunk-Q6ERDPQR.js +2981 -0
  70. package/dist/chunk-Q6ERDPQR.js.map +1 -0
  71. package/dist/chunk-S2SSBMWJ.js +2658 -0
  72. package/dist/chunk-S2SSBMWJ.js.map +1 -0
  73. package/dist/chunk-SLVDAZSX.js +2946 -0
  74. package/dist/chunk-SLVDAZSX.js.map +1 -0
  75. package/dist/chunk-UQBPYONV.js +2991 -0
  76. package/dist/chunk-UQBPYONV.js.map +1 -0
  77. package/dist/chunk-UXMNBS22.js +2955 -0
  78. package/dist/chunk-UXMNBS22.js.map +1 -0
  79. package/dist/chunk-WEGED7TA.js +2991 -0
  80. package/dist/chunk-WEGED7TA.js.map +1 -0
  81. package/dist/chunk-WEPTBLWX.js +2847 -0
  82. package/dist/chunk-WEPTBLWX.js.map +1 -0
  83. package/dist/chunk-WG4ZQMPS.js +2844 -0
  84. package/dist/chunk-WG4ZQMPS.js.map +1 -0
  85. package/dist/chunk-XEPEBHAW.js +2808 -0
  86. package/dist/chunk-XEPEBHAW.js.map +1 -0
  87. package/dist/chunk-XG2OYFX6.js +2925 -0
  88. package/dist/chunk-XG2OYFX6.js.map +1 -0
  89. package/dist/chunk-ZAMJEU42.js +2992 -0
  90. package/dist/chunk-ZAMJEU42.js.map +1 -0
  91. package/dist/chunk-ZRAM6FXB.js +2949 -0
  92. package/dist/chunk-ZRAM6FXB.js.map +1 -0
  93. package/dist/components/CsvViewer/CsvViewer.d.ts +51 -0
  94. package/dist/components/CsvViewer/CsvViewer.d.ts.map +1 -0
  95. package/dist/components/CsvViewer/index.d.ts +2 -0
  96. package/dist/components/CsvViewer/index.d.ts.map +1 -0
  97. package/dist/components/DataTable/DataTable.d.ts +19 -1
  98. package/dist/components/DataTable/DataTable.d.ts.map +1 -1
  99. package/dist/components/DropdownMenu/DropdownMenu.d.ts +12 -2
  100. package/dist/components/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  101. package/dist/components/ExecutionConsole/ExecutionConsole.d.ts +8 -2
  102. package/dist/components/ExecutionConsole/ExecutionConsole.d.ts.map +1 -1
  103. package/dist/components/FlowMinimap/FlowMinimap.d.ts +17 -1
  104. package/dist/components/FlowMinimap/FlowMinimap.d.ts.map +1 -1
  105. package/dist/components/FlowToolbar/FlowToolbar.d.ts +16 -10
  106. package/dist/components/FlowToolbar/FlowToolbar.d.ts.map +1 -1
  107. package/dist/components/JsonViewer/JsonViewer.d.ts +42 -7
  108. package/dist/components/JsonViewer/JsonViewer.d.ts.map +1 -1
  109. package/dist/components/JsonViewer/index.d.ts +1 -1
  110. package/dist/components/JsonViewer/index.d.ts.map +1 -1
  111. package/dist/components/Select/Select.d.ts.map +1 -1
  112. package/dist/components/WorkflowHeader/WorkflowHeader.d.ts +130 -0
  113. package/dist/components/WorkflowHeader/WorkflowHeader.d.ts.map +1 -0
  114. package/dist/components/WorkflowHeader/WorkflowHeaderExpanded.d.ts +69 -0
  115. package/dist/components/WorkflowHeader/WorkflowHeaderExpanded.d.ts.map +1 -0
  116. package/dist/components/WorkflowHeader/index.d.ts +3 -0
  117. package/dist/components/WorkflowHeader/index.d.ts.map +1 -0
  118. package/dist/components/WorkflowHeader/misc/WorkflowHeaderCentered.d.ts +40 -0
  119. package/dist/components/WorkflowHeader/misc/WorkflowHeaderCentered.d.ts.map +1 -0
  120. package/dist/components/WorkflowHeader/misc/WorkflowHeaderCommand.d.ts +39 -0
  121. package/dist/components/WorkflowHeader/misc/WorkflowHeaderCommand.d.ts.map +1 -0
  122. package/dist/components/WorkflowHeader/misc/WorkflowHeaderMinimal.d.ts +44 -0
  123. package/dist/components/WorkflowHeader/misc/WorkflowHeaderMinimal.d.ts.map +1 -0
  124. package/dist/components/WorkflowHeader/misc/WorkflowHeaderRail.d.ts +45 -0
  125. package/dist/components/WorkflowHeader/misc/WorkflowHeaderRail.d.ts.map +1 -0
  126. package/dist/components/WorkflowHeader/misc/WorkflowHeaderStudio.d.ts +48 -0
  127. package/dist/components/WorkflowHeader/misc/WorkflowHeaderStudio.d.ts.map +1 -0
  128. package/dist/components/WorkflowHeader/misc/WorkflowHeaderTiered.d.ts +52 -0
  129. package/dist/components/WorkflowHeader/misc/WorkflowHeaderTiered.d.ts.map +1 -0
  130. package/dist/components/XmlViewer/XmlViewer.d.ts +26 -1
  131. package/dist/components/XmlViewer/XmlViewer.d.ts.map +1 -1
  132. package/dist/components/XmlViewer/index.d.ts +1 -1
  133. package/dist/components/XmlViewer/index.d.ts.map +1 -1
  134. package/dist/components/YamlViewer/YamlViewer.d.ts +26 -1
  135. package/dist/components/YamlViewer/YamlViewer.d.ts.map +1 -1
  136. package/dist/components/YamlViewer/index.d.ts +1 -1
  137. package/dist/components/YamlViewer/index.d.ts.map +1 -1
  138. package/dist/hooks/useRelativeTime.d.ts +28 -0
  139. package/dist/hooks/useRelativeTime.d.ts.map +1 -0
  140. package/dist/hooks/useWorkflowRuntime.d.ts +20 -0
  141. package/dist/hooks/useWorkflowRuntime.d.ts.map +1 -0
  142. package/dist/index.cjs +4918 -3536
  143. package/dist/index.cjs.map +1 -1
  144. package/dist/index.d.ts +7 -3
  145. package/dist/index.d.ts.map +1 -1
  146. package/dist/index.js +4317 -3305
  147. package/dist/index.js.map +1 -1
  148. package/dist/styles.css +1 -1
  149. package/dist/workflow/components/ConfigPanel/ConfigPanel.d.ts +27 -9
  150. package/dist/workflow/components/ConfigPanel/ConfigPanel.d.ts.map +1 -1
  151. package/dist/workflow/components/FlowCanvas/FlowCanvas.d.ts +49 -1
  152. package/dist/workflow/components/FlowCanvas/FlowCanvas.d.ts.map +1 -1
  153. package/dist/workflow/components/FlowEdge/FlowEdge.d.ts.map +1 -1
  154. package/dist/workflow/components/FxPanel/FxPanel.d.ts +61 -0
  155. package/dist/workflow/components/FxPanel/FxPanel.d.ts.map +1 -0
  156. package/dist/workflow/components/FxPanel/FxToggleButton.d.ts +9 -0
  157. package/dist/workflow/components/FxPanel/FxToggleButton.d.ts.map +1 -0
  158. package/dist/workflow/components/Handle/Handle.d.ts +9 -1
  159. package/dist/workflow/components/Handle/Handle.d.ts.map +1 -1
  160. package/dist/workflow/components/Handle/handleRegistry.d.ts +19 -0
  161. package/dist/workflow/components/Handle/handleRegistry.d.ts.map +1 -1
  162. package/dist/workflow/components/kinds/index.d.ts +4 -0
  163. package/dist/workflow/components/kinds/index.d.ts.map +1 -1
  164. package/dist/workflow/index.d.ts +3 -1
  165. package/dist/workflow/index.d.ts.map +1 -1
  166. package/dist/workflow/store/selectors.d.ts +12 -0
  167. package/dist/workflow/store/selectors.d.ts.map +1 -1
  168. package/dist/workflow/utils/parenting.d.ts +5 -3
  169. package/dist/workflow/utils/parenting.d.ts.map +1 -1
  170. package/dist/workflow.cjs +915 -446
  171. package/dist/workflow.cjs.map +1 -1
  172. package/dist/workflow.js +309 -256
  173. package/dist/workflow.js.map +1 -1
  174. package/package.json +7 -3
package/dist/workflow.cjs CHANGED
@@ -31,6 +31,8 @@ __export(workflow_exports, {
31
31
  FlowEdge: () => FlowEdge,
32
32
  FlowNode: () => FlowNode,
33
33
  ForEachNode: () => ForEachNode,
34
+ FxPanel: () => FxPanel,
35
+ FxToggleButton: () => FxToggleButton,
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
 
@@ -646,6 +649,23 @@ function useEdges() {
646
649
  function useViewport() {
647
650
  return useFlowSelector((s) => s.viewport);
648
651
  }
652
+ var VIEWPORT_OR_NULL_NO_STORE_SUBSCRIBE = (_cb) => () => {
653
+ };
654
+ var VIEWPORT_OR_NULL_NO_STORE_SNAPSHOT = () => null;
655
+ function useViewportOrNull() {
656
+ const store = (0, import_react5.useContext)(FlowStoreContext);
657
+ const { sub, snap } = (0, import_react5.useMemo)(
658
+ () => store ? {
659
+ sub: store.subscribe,
660
+ snap: () => store.getSnapshot().viewport
661
+ } : {
662
+ sub: VIEWPORT_OR_NULL_NO_STORE_SUBSCRIBE,
663
+ snap: VIEWPORT_OR_NULL_NO_STORE_SNAPSHOT
664
+ },
665
+ [store]
666
+ );
667
+ return (0, import_react5.useSyncExternalStore)(sub, snap, snap);
668
+ }
649
669
  function useNodeById(id) {
650
670
  return useFlowSelector((s) => s.nodes.find((n) => n.id === id));
651
671
  }
@@ -708,26 +728,27 @@ var SAVE_STATE_DOT = {
708
728
  function ConfigPanel({
709
729
  open = true,
710
730
  title,
711
- kindLabel,
712
731
  icon,
732
+ headerActions,
713
733
  banner,
714
734
  tabs,
715
735
  activeTab: controlledActive,
716
736
  defaultActiveTab,
717
737
  onTabChange,
718
- saveState = "clean",
738
+ saveState,
719
739
  onTitleChange,
720
740
  onClose,
721
741
  onSave,
722
742
  onCancel,
743
+ padding = 12,
723
744
  resizable = true,
724
- defaultWidth = 360,
725
- minWidth = 280,
745
+ defaultWidth = 400,
746
+ minWidth = 400,
726
747
  maxWidth = 720,
727
748
  maxWidthContainerInset = 80,
728
749
  variant = "pinned",
729
750
  children,
730
- footer,
751
+ footerActions,
731
752
  className,
732
753
  style
733
754
  }) {
@@ -824,55 +845,53 @@ function ConfigPanel({
824
845
  "aria-hidden": "true"
825
846
  }
826
847
  ),
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
- ] }),
848
+ /* @__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: [
849
+ icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ods-flow-config-panel__icon", children: icon }),
850
+ editingTitle ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
851
+ "input",
852
+ {
853
+ className: "ods-flow-config-panel__title-input",
854
+ value: draftTitle,
855
+ autoFocus: true,
856
+ onChange: (e) => setDraftTitle(e.target.value),
857
+ onBlur: () => commitTitle(draftTitle),
858
+ onKeyDown: titleKeyDown,
859
+ "aria-label": "Edit title"
860
+ }
861
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
862
+ "button",
863
+ {
864
+ type: "button",
865
+ className: "ods-flow-config-panel__title",
866
+ onDoubleClick: () => {
867
+ if (!onTitleChange) return;
868
+ setDraftTitle(title ?? "");
869
+ setEditingTitle(true);
870
+ },
871
+ title: onTitleChange ? "Double-click to rename" : void 0,
872
+ children: title ?? "Untitled"
873
+ }
874
+ ),
875
+ headerActions && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ods-flow-config-panel__header-actions", children: headerActions }),
876
+ onClose && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
877
+ "button",
878
+ {
879
+ type: "button",
880
+ className: "ods-flow-config-panel__close",
881
+ onClick: onClose,
882
+ "aria-label": "Close panel",
883
+ 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)(
884
+ "path",
885
+ {
886
+ d: "M3 3l8 8M11 3l-8 8",
887
+ stroke: "currentColor",
888
+ strokeWidth: "1.5",
889
+ strokeLinecap: "round"
890
+ }
891
+ ) })
892
+ }
893
+ )
894
+ ] }) }),
876
895
  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
896
  "button",
878
897
  {
@@ -893,9 +912,17 @@ function ConfigPanel({
893
912
  t.id
894
913
  )) }),
895
914
  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 }),
915
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
916
+ "div",
917
+ {
918
+ className: "ods-flow-config-panel__body",
919
+ role: "tabpanel",
920
+ style: { padding },
921
+ children: tabs ? activeTab?.content : children
922
+ }
923
+ ),
897
924
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", { className: "ods-flow-config-panel__footer", children: [
898
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
925
+ saveState && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
899
926
  "span",
900
927
  {
901
928
  className: cn(
@@ -915,7 +942,7 @@ function ConfigPanel({
915
942
  ]
916
943
  }
917
944
  ),
918
- footer ?? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ods-flow-config-panel__footer-actions", children: [
945
+ /* @__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
946
  onCancel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
920
947
  "button",
921
948
  {
@@ -931,11 +958,11 @@ function ConfigPanel({
931
958
  type: "button",
932
959
  className: "ods-flow-config-panel__btn ods-flow-config-panel__btn--primary",
933
960
  onClick: onSave,
934
- disabled: saveState === "saving" || saveState === "clean",
961
+ disabled: saveState === "saving",
935
962
  children: saveState === "saving" ? "Saving\u2026" : "Save"
936
963
  }
937
964
  )
938
- ] })
965
+ ] }) })
939
966
  ] })
940
967
  ]
941
968
  }
@@ -943,7 +970,7 @@ function ConfigPanel({
943
970
  }
944
971
 
945
972
  // src/workflow/components/FlowCanvas/FlowCanvas.tsx
946
- var import_react14 = require("react");
973
+ var import_react15 = require("react");
947
974
 
948
975
  // src/workflow/store/createFlowStore.ts
949
976
  var DEFAULT_VIEWPORT = { x: 0, y: 0, zoom: 1 };
@@ -1126,7 +1153,7 @@ function findContainingGroup(point, nodes, exclude = []) {
1126
1153
  for (let i = nodes.length - 1; i >= 0; i--) {
1127
1154
  const n = nodes[i];
1128
1155
  if (exclude.includes(n.id)) continue;
1129
- if (n.type !== "group") continue;
1156
+ if (n.type !== "group" && n.type !== "forEach") continue;
1130
1157
  if (n.data && typeof n.data === "object" && n.data.collapsed) {
1131
1158
  continue;
1132
1159
  }
@@ -1336,6 +1363,9 @@ function createHandleRegistry() {
1336
1363
  resolve(nodeId, type, handleId) {
1337
1364
  return map.get(key(nodeId, type, handleId));
1338
1365
  },
1366
+ all() {
1367
+ return Array.from(map.values());
1368
+ },
1339
1369
  subscribe(listener) {
1340
1370
  listeners.add(listener);
1341
1371
  return () => {
@@ -1376,6 +1406,8 @@ function FlowEdgeImpl({
1376
1406
  const targetDesc = registry.resolve(targetNode.id, "target", targetHandleId);
1377
1407
  const sourceSide = sourceDesc?.side ?? sourceNode.sourcePosition ?? "bottom";
1378
1408
  const targetSide = targetDesc?.side ?? targetNode.targetPosition ?? "top";
1409
+ const sourceRoute = sourceDesc?.routeSide ?? sourceSide;
1410
+ const targetRoute = targetDesc?.routeSide ?? targetSide;
1379
1411
  const sourceIndex = sourceDesc?.index ?? 0;
1380
1412
  const sourceTotal = sourceDesc?.total ?? 1;
1381
1413
  const targetIndex = targetDesc?.index ?? 0;
@@ -1383,14 +1415,14 @@ function FlowEdgeImpl({
1383
1415
  const rawStart = handleCentre(sourceNode, sourceSide, sourceIndex, sourceTotal);
1384
1416
  const rawEnd = handleCentre(targetNode, targetSide, targetIndex, targetTotal);
1385
1417
  const HANDLE_GAP = 8;
1386
- const start = offsetAlongSide2(rawStart, sourceSide, HANDLE_GAP);
1387
- const end = offsetAlongSide2(rawEnd, targetSide, HANDLE_GAP);
1418
+ const start = offsetAlongSide2(rawStart, sourceRoute, HANDLE_GAP);
1419
+ const end = offsetAlongSide2(rawEnd, targetRoute, HANDLE_GAP);
1388
1420
  const routing = edge.routing ?? "bezier";
1389
- const { d, midX, midY } = buildEdgePath(routing, start, sourceSide, end, targetSide, {
1421
+ const { d, midX, midY } = buildEdgePath(routing, start, sourceRoute, end, targetRoute, {
1390
1422
  curvature,
1391
1423
  borderRadius
1392
1424
  });
1393
- const { d: hitD } = buildEdgePath(routing, rawStart, sourceSide, rawEnd, targetSide, {
1425
+ const { d: hitD } = buildEdgePath(routing, rawStart, sourceRoute, rawEnd, targetRoute, {
1394
1426
  curvature,
1395
1427
  borderRadius
1396
1428
  });
@@ -1437,6 +1469,12 @@ function FlowEdgeImpl({
1437
1469
  e.stopPropagation();
1438
1470
  onSelect?.(edge.id);
1439
1471
  },
1472
+ onDoubleClick: (e) => {
1473
+ if (!onLabelChange) return;
1474
+ e.stopPropagation();
1475
+ setDraftLabel(edge.label ?? "");
1476
+ setEditing(true);
1477
+ },
1440
1478
  children: [
1441
1479
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1442
1480
  "marker",
@@ -1708,6 +1746,7 @@ var DEFAULT_HANDLE_ID = "default";
1708
1746
  function Handle({
1709
1747
  type,
1710
1748
  position,
1749
+ routeSide,
1711
1750
  id = DEFAULT_HANDLE_ID,
1712
1751
  isConnectable = true,
1713
1752
  isConnectableStart,
@@ -1730,11 +1769,14 @@ function Handle({
1730
1769
  handleId: id,
1731
1770
  type,
1732
1771
  side: position,
1772
+ routeSide: routeSide ?? position,
1733
1773
  index,
1734
- total
1774
+ total,
1775
+ canStart,
1776
+ canEnd
1735
1777
  });
1736
1778
  return dispose;
1737
- }, [registry, node.id, id, type, position, index, total]);
1779
+ }, [registry, node.id, id, type, position, routeSide, index, total, canStart, canEnd]);
1738
1780
  const handlePointerDown = (e) => {
1739
1781
  if (!canStart) return;
1740
1782
  e.stopPropagation();
@@ -1791,10 +1833,155 @@ function handleSideStyle(side, index, total) {
1791
1833
  }
1792
1834
  }
1793
1835
 
1794
- // src/workflow/components/kinds/BaseNode.tsx
1795
- var import_icons = require("@octaviaflow/icons");
1836
+ // src/workflow/components/NodeResizer/NodeResizer.tsx
1796
1837
  var import_react13 = require("react");
1797
1838
  var import_jsx_runtime5 = require("react/jsx-runtime");
1839
+ function NodeResizer({
1840
+ isVisible,
1841
+ minWidth = 80,
1842
+ minHeight = 60,
1843
+ maxWidth,
1844
+ maxHeight,
1845
+ keepAspectRatio = false,
1846
+ onResize,
1847
+ onResizeEnd,
1848
+ color
1849
+ }) {
1850
+ const { node, selected } = useFlowNodeContext();
1851
+ const viewport = useViewport();
1852
+ const flow = useFlow();
1853
+ const dragRef = (0, import_react13.useRef)(null);
1854
+ const show = isVisible ?? selected;
1855
+ if (!show) return null;
1856
+ const beginResize = (e, corner) => {
1857
+ e.preventDefault();
1858
+ e.stopPropagation();
1859
+ e.target.setPointerCapture(e.pointerId);
1860
+ const w = node.width ?? DEFAULT_NODE_WIDTH;
1861
+ const h = node.height ?? DEFAULT_NODE_HEIGHT;
1862
+ dragRef.current = {
1863
+ pointerId: e.pointerId,
1864
+ corner,
1865
+ startClientX: e.clientX,
1866
+ startClientY: e.clientY,
1867
+ startWidth: w,
1868
+ startHeight: h,
1869
+ startX: node.position.x,
1870
+ startY: node.position.y,
1871
+ aspect: w / Math.max(1, h)
1872
+ };
1873
+ };
1874
+ const onMove = (e) => {
1875
+ const drag = dragRef.current;
1876
+ if (!drag || drag.pointerId !== e.pointerId) return;
1877
+ const dx = (e.clientX - drag.startClientX) / viewport.zoom;
1878
+ const dy = (e.clientY - drag.startClientY) / viewport.zoom;
1879
+ let nextW = drag.startWidth;
1880
+ let nextH = drag.startHeight;
1881
+ let nextX = drag.startX;
1882
+ let nextY = drag.startY;
1883
+ switch (drag.corner) {
1884
+ case "se":
1885
+ nextW = drag.startWidth + dx;
1886
+ nextH = drag.startHeight + dy;
1887
+ break;
1888
+ case "sw":
1889
+ nextW = drag.startWidth - dx;
1890
+ nextH = drag.startHeight + dy;
1891
+ nextX = drag.startX + dx;
1892
+ break;
1893
+ case "ne":
1894
+ nextW = drag.startWidth + dx;
1895
+ nextH = drag.startHeight - dy;
1896
+ nextY = drag.startY + dy;
1897
+ break;
1898
+ case "nw":
1899
+ nextW = drag.startWidth - dx;
1900
+ nextH = drag.startHeight - dy;
1901
+ nextX = drag.startX + dx;
1902
+ nextY = drag.startY + dy;
1903
+ break;
1904
+ }
1905
+ if (keepAspectRatio) {
1906
+ nextH = nextW / drag.aspect;
1907
+ if (drag.corner === "nw" || drag.corner === "ne") {
1908
+ nextY = drag.startY + (drag.startHeight - nextH);
1909
+ }
1910
+ }
1911
+ nextW = Math.max(minWidth, maxWidth ? Math.min(maxWidth, nextW) : nextW);
1912
+ nextH = Math.max(minHeight, maxHeight ? Math.min(maxHeight, nextH) : nextH);
1913
+ flow.updateNode(node.id, {
1914
+ width: nextW,
1915
+ height: nextH,
1916
+ position: { x: nextX, y: nextY }
1917
+ });
1918
+ onResize?.({ width: nextW, height: nextH });
1919
+ };
1920
+ const onUp = (e) => {
1921
+ if (dragRef.current?.pointerId === e.pointerId) {
1922
+ const cur = flow.getNode(node.id);
1923
+ if (cur) {
1924
+ onResizeEnd?.({
1925
+ width: cur.width ?? DEFAULT_NODE_WIDTH,
1926
+ height: cur.height ?? DEFAULT_NODE_HEIGHT
1927
+ });
1928
+ }
1929
+ dragRef.current = null;
1930
+ }
1931
+ };
1932
+ const handleColor = color ?? "var(--ods-accent)";
1933
+ const handleStyle = (corner) => {
1934
+ const base = {
1935
+ position: "absolute",
1936
+ width: 12,
1937
+ height: 12,
1938
+ background: "var(--ods-surface-canvas)",
1939
+ border: `2px solid ${handleColor}`,
1940
+ borderRadius: 2,
1941
+ cursor: cursorFor(corner),
1942
+ touchAction: "none",
1943
+ // Place each handle so its CENTRE sits on the corresponding corner.
1944
+ transform: "translate(-50%, -50%)"
1945
+ };
1946
+ switch (corner) {
1947
+ case "nw":
1948
+ return { ...base, top: 0, left: 0 };
1949
+ case "ne":
1950
+ return { ...base, top: 0, left: "100%" };
1951
+ case "sw":
1952
+ return { ...base, top: "100%", left: 0 };
1953
+ case "se":
1954
+ return { ...base, top: "100%", left: "100%" };
1955
+ }
1956
+ };
1957
+ 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)(
1958
+ "div",
1959
+ {
1960
+ style: handleStyle(corner),
1961
+ onPointerDown: (e) => beginResize(e, corner),
1962
+ onPointerMove: onMove,
1963
+ onPointerUp: onUp,
1964
+ onPointerCancel: onUp,
1965
+ "aria-label": `Resize ${corner}`
1966
+ },
1967
+ corner
1968
+ )) });
1969
+ }
1970
+ function cursorFor(corner) {
1971
+ switch (corner) {
1972
+ case "nw":
1973
+ case "se":
1974
+ return "nwse-resize";
1975
+ case "ne":
1976
+ case "sw":
1977
+ return "nesw-resize";
1978
+ }
1979
+ }
1980
+
1981
+ // src/workflow/components/kinds/BaseNode.tsx
1982
+ var import_icons = require("@octaviaflow/icons");
1983
+ var import_react14 = require("react");
1984
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1798
1985
  function BaseNode({
1799
1986
  kind,
1800
1987
  kindIcon,
@@ -1810,10 +1997,10 @@ function BaseNode({
1810
1997
  className,
1811
1998
  children
1812
1999
  }) {
1813
- const ctx = (0, import_react13.useContext)(FlowNodeContext);
1814
- const bridge = (0, import_react13.useContext)(FlowNodeBridgeContext);
2000
+ const ctx = (0, import_react14.useContext)(FlowNodeContext);
2001
+ const bridge = (0, import_react14.useContext)(FlowNodeBridgeContext);
1815
2002
  const deleteHandler = onDelete === false ? void 0 : onDelete ?? (ctx && bridge ? () => bridge.deleteNode(ctx.id) : void 0);
1816
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2003
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1817
2004
  "div",
1818
2005
  {
1819
2006
  className: cn(
@@ -1823,31 +2010,31 @@ function BaseNode({
1823
2010
  className
1824
2011
  ),
1825
2012
  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 })
2013
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-base-node__pill", children: [
2014
+ kindIcon && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-base-node__pill-icon", "aria-hidden": "true", children: kindIcon }),
2015
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-base-node__pill-label", children: kind })
1829
2016
  ] }),
1830
- status && status !== "idle" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2017
+ status && status !== "idle" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1831
2018
  "span",
1832
2019
  {
1833
2020
  className: cn("ods-flow-base-node__status", `ods-flow-base-node__status--${status}`),
1834
2021
  "aria-hidden": "true"
1835
2022
  }
1836
2023
  ),
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 })
2024
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-base-node__body", children: [
2025
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-base-node__content", children: [
2026
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ods-flow-base-node__bubble", "aria-hidden": "true", children: icon }),
2027
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-base-node__content-text", children: [
2028
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ods-flow-base-node__content-title", children: title }),
2029
+ (chip !== void 0 || description !== void 0 || valueChip !== void 0) && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-base-node__content-info", children: [
2030
+ chip !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-base-node__chip", children: chip }),
2031
+ description !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-base-node__description", children: description }),
2032
+ valueChip !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-base-node__value-chip", children: valueChip })
1846
2033
  ] })
1847
2034
  ] })
1848
2035
  ] }),
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)(
2036
+ footer && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ods-flow-base-node__footer", children: footer }),
2037
+ deleteHandler && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1851
2038
  "button",
1852
2039
  {
1853
2040
  type: "button",
@@ -1859,7 +2046,7 @@ function BaseNode({
1859
2046
  "aria-label": "Delete node",
1860
2047
  "data-flow-no-drag": "true",
1861
2048
  title: "Delete node",
1862
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_icons.TrashCanIcon, { size: 16, "aria-hidden": true })
2049
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons.TrashCanIcon, { size: 16, "aria-hidden": true })
1863
2050
  }
1864
2051
  )
1865
2052
  ] }),
@@ -1870,12 +2057,12 @@ function BaseNode({
1870
2057
  }
1871
2058
 
1872
2059
  // src/workflow/components/kinds/index.tsx
1873
- var import_jsx_runtime6 = require("react/jsx-runtime");
2060
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1874
2061
  var ActionNode = ({
1875
2062
  node
1876
2063
  }) => {
1877
2064
  const d = node.data ?? {};
1878
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2065
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1879
2066
  BaseNode,
1880
2067
  {
1881
2068
  kind: d.kind ?? "ACTION",
@@ -1887,8 +2074,8 @@ var ActionNode = ({
1887
2074
  status: d.status,
1888
2075
  accent: "green",
1889
2076
  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" })
2077
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2078
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
1892
2079
  ]
1893
2080
  }
1894
2081
  );
@@ -1897,7 +2084,7 @@ var TriggerNode = ({
1897
2084
  node
1898
2085
  }) => {
1899
2086
  const d = node.data ?? {};
1900
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2087
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1901
2088
  BaseNode,
1902
2089
  {
1903
2090
  kind: d.kind ?? "TRIGGER",
@@ -1908,7 +2095,7 @@ var TriggerNode = ({
1908
2095
  valueChip: d.valueChip,
1909
2096
  status: d.status,
1910
2097
  accent: "green",
1911
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom" })
2098
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
1912
2099
  }
1913
2100
  );
1914
2101
  };
@@ -1920,7 +2107,7 @@ var ConditionNode = ({
1920
2107
  { id: "true", label: "true" },
1921
2108
  { id: "false", label: "false" }
1922
2109
  ];
1923
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2110
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1924
2111
  BaseNode,
1925
2112
  {
1926
2113
  kind: d.kind ?? "CONDITION",
@@ -1932,8 +2119,8 @@ var ConditionNode = ({
1932
2119
  status: d.status,
1933
2120
  accent: "amber",
1934
2121
  children: [
1935
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" }),
1936
- branches.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2122
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2123
+ branches.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1937
2124
  Handle,
1938
2125
  {
1939
2126
  type: "source",
@@ -1954,24 +2141,27 @@ var GroupNode = ({
1954
2141
  }) => {
1955
2142
  const d = node.data ?? {};
1956
2143
  const collapsed = !!d.collapsed;
2144
+ const disabled = !!d.disabled;
1957
2145
  const hiddenCount = d.hiddenCount;
1958
2146
  const bridge = useFlowNodeBridge();
1959
2147
  const onChevronClick = (e) => {
1960
2148
  e.stopPropagation();
1961
2149
  bridge.toggleNodeCollapse(node.id);
1962
2150
  };
1963
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2151
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1964
2152
  "div",
1965
2153
  {
1966
2154
  className: "ods-flow-group",
1967
2155
  "data-collapsed": collapsed ? "true" : "false",
2156
+ "data-disabled": disabled ? "true" : void 0,
1968
2157
  style: {
1969
2158
  width: node.width ?? 360,
1970
2159
  height: collapsed ? 36 : node.height ?? 200
1971
2160
  },
1972
2161
  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)(
2162
+ !collapsed && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(NodeResizer, { minWidth: 240, minHeight: 120 }),
2163
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ods-flow-group__header", "data-flow-no-drag": "false", children: [
2164
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1975
2165
  "button",
1976
2166
  {
1977
2167
  type: "button",
@@ -1984,15 +2174,16 @@ var GroupNode = ({
1984
2174
  children: collapsed ? "\u25B8" : "\u25BE"
1985
2175
  }
1986
2176
  ),
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: [
2177
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ods-flow-group__title", children: d.title ?? "Group" }),
2178
+ d.subtitle && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ods-flow-group__subtitle", children: d.subtitle }),
2179
+ disabled && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ods-flow-group__disabled-badge", "aria-label": "Disabled subflow", children: "Disabled" }),
2180
+ 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
2181
  hiddenCount,
1991
2182
  " steps"
1992
2183
  ] })
1993
2184
  ] }),
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" })
2185
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top", id: "__group_in" }),
2186
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom", id: "__group_out" })
1996
2187
  ]
1997
2188
  }
1998
2189
  );
@@ -2003,24 +2194,27 @@ var ForEachNode = ({
2003
2194
  const d = node.data ?? {};
2004
2195
  const iteratorExpr = d.iterator ?? d.description ?? "items[]";
2005
2196
  const collapsed = !!d.collapsed;
2197
+ const disabled = !!d.disabled;
2006
2198
  const hiddenCount = d.hiddenCount;
2007
2199
  const bridge = useFlowNodeBridge();
2008
2200
  const onChevronClick = (e) => {
2009
2201
  e.stopPropagation();
2010
2202
  bridge.toggleNodeCollapse(node.id);
2011
2203
  };
2012
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2204
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2013
2205
  "div",
2014
2206
  {
2015
2207
  className: "ods-flow-foreach",
2016
2208
  "data-collapsed": collapsed ? "true" : "false",
2209
+ "data-disabled": disabled ? "true" : void 0,
2017
2210
  style: {
2018
2211
  width: node.width ?? 420,
2019
2212
  height: collapsed ? 40 : node.height ?? 260
2020
2213
  },
2021
2214
  children: [
2022
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-foreach__header", children: [
2023
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2215
+ !collapsed && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(NodeResizer, { minWidth: 240, minHeight: 120 }),
2216
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ods-flow-foreach__header", children: [
2217
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2024
2218
  "button",
2025
2219
  {
2026
2220
  type: "button",
@@ -2033,24 +2227,57 @@ var ForEachNode = ({
2033
2227
  children: collapsed ? "\u25B8" : "\u25BE"
2034
2228
  }
2035
2229
  ),
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: [
2230
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ods-flow-foreach__icon", "aria-hidden": "true", children: "\u21BB" }),
2231
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ods-flow-foreach__title", children: d.title ?? "For each" }),
2232
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("code", { className: "ods-flow-foreach__iterator", children: iteratorExpr }),
2233
+ disabled && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2234
+ "span",
2235
+ {
2236
+ className: "ods-flow-foreach__disabled-badge",
2237
+ "aria-label": "Disabled subflow",
2238
+ children: "Disabled"
2239
+ }
2240
+ ),
2241
+ collapsed && hiddenCount !== void 0 && hiddenCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "ods-flow-foreach__count", "aria-label": `${hiddenCount} hidden steps`, children: [
2040
2242
  hiddenCount,
2041
2243
  " steps"
2042
2244
  ] })
2043
2245
  ] }),
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
- ] })
2246
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top", id: "__group_in", label: !collapsed ? "in" : void 0 }),
2247
+ collapsed ? (
2248
+ // Collapsed: single bottom exit so the container behaves like a
2249
+ // regular action in the chain.
2250
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom", id: "__group_out" })
2251
+ ) : (
2252
+ // Expanded: two bottom outputs spread across the bottom edge.
2253
+ // Index/total tell the Handle to position them at 33% and 66%
2254
+ // along the edge (per `handleSideStyle`). `each` is left of
2255
+ // centre, `__group_out` (labelled "done") is right of centre.
2256
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2257
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2258
+ Handle,
2259
+ {
2260
+ type: "source",
2261
+ position: "bottom",
2262
+ id: "each",
2263
+ label: "each",
2264
+ index: 0,
2265
+ total: 2
2266
+ }
2267
+ ),
2268
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2269
+ Handle,
2270
+ {
2271
+ type: "source",
2272
+ position: "bottom",
2273
+ id: "__group_out",
2274
+ label: "done",
2275
+ index: 1,
2276
+ total: 2
2277
+ }
2278
+ )
2279
+ ] })
2280
+ )
2054
2281
  ]
2055
2282
  }
2056
2283
  );
@@ -2059,7 +2286,7 @@ var OutputNode = ({
2059
2286
  node
2060
2287
  }) => {
2061
2288
  const d = node.data ?? {};
2062
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2289
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2063
2290
  BaseNode,
2064
2291
  {
2065
2292
  kind: d.kind ?? "OUTPUT",
@@ -2069,7 +2296,7 @@ var OutputNode = ({
2069
2296
  description: d.description ?? d.subtitle,
2070
2297
  status: d.status,
2071
2298
  accent: "green",
2072
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" })
2299
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" })
2073
2300
  }
2074
2301
  );
2075
2302
  };
@@ -2077,7 +2304,7 @@ var ErrorNode = ({
2077
2304
  node
2078
2305
  }) => {
2079
2306
  const d = node.data ?? {};
2080
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2307
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2081
2308
  BaseNode,
2082
2309
  {
2083
2310
  kind: d.kind ?? "ERROR",
@@ -2088,8 +2315,8 @@ var ErrorNode = ({
2088
2315
  status: d.status ?? "error",
2089
2316
  accent: "red",
2090
2317
  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" })
2318
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2319
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
2093
2320
  ]
2094
2321
  }
2095
2322
  );
@@ -2100,7 +2327,7 @@ var WaitNode = ({
2100
2327
  const d = node.data ?? {};
2101
2328
  const waitMs = d.waitMs;
2102
2329
  const durationChip = waitMs ? `${Math.round(waitMs / 100) / 10}s` : void 0;
2103
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2330
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2104
2331
  BaseNode,
2105
2332
  {
2106
2333
  kind: d.kind ?? "WAIT",
@@ -2111,8 +2338,8 @@ var WaitNode = ({
2111
2338
  status: d.status,
2112
2339
  accent: "violet",
2113
2340
  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" })
2341
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2342
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
2116
2343
  ]
2117
2344
  }
2118
2345
  );
@@ -2125,7 +2352,7 @@ var ParallelNode = ({
2125
2352
  { id: "a", label: "a" },
2126
2353
  { id: "b", label: "b" }
2127
2354
  ];
2128
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2355
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2129
2356
  BaseNode,
2130
2357
  {
2131
2358
  kind: d.kind ?? "PARALLEL",
@@ -2136,8 +2363,8 @@ var ParallelNode = ({
2136
2363
  status: d.status,
2137
2364
  accent: "blue",
2138
2365
  children: [
2139
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" }),
2140
- branches.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2366
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2367
+ branches.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2141
2368
  Handle,
2142
2369
  {
2143
2370
  type: "source",
@@ -2157,7 +2384,7 @@ var StickyNode = ({
2157
2384
  node
2158
2385
  }) => {
2159
2386
  const d = node.data ?? {};
2160
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2387
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2161
2388
  "div",
2162
2389
  {
2163
2390
  className: "ods-flow-sticky",
@@ -2166,8 +2393,8 @@ var StickyNode = ({
2166
2393
  minHeight: node.height ?? 120
2167
2394
  },
2168
2395
  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 })
2396
+ d.title && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ods-flow-sticky__title", children: d.title }),
2397
+ d.description && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ods-flow-sticky__body", children: d.description })
2171
2398
  ]
2172
2399
  }
2173
2400
  );
@@ -2176,7 +2403,7 @@ var WebhookNode = ({
2176
2403
  node
2177
2404
  }) => {
2178
2405
  const d = node.data ?? {};
2179
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2406
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2180
2407
  BaseNode,
2181
2408
  {
2182
2409
  kind: d.kind ?? "WEBHOOK",
@@ -2187,7 +2414,7 @@ var WebhookNode = ({
2187
2414
  valueChip: d.valueChip,
2188
2415
  status: d.status,
2189
2416
  accent: "blue",
2190
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom" })
2417
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
2191
2418
  }
2192
2419
  );
2193
2420
  };
@@ -2195,7 +2422,7 @@ var HttpRequestNode = ({
2195
2422
  node
2196
2423
  }) => {
2197
2424
  const d = node.data ?? {};
2198
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2425
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2199
2426
  BaseNode,
2200
2427
  {
2201
2428
  kind: d.kind ?? "HTTP",
@@ -2207,8 +2434,8 @@ var HttpRequestNode = ({
2207
2434
  status: d.status,
2208
2435
  accent: "blue",
2209
2436
  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" })
2437
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2438
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
2212
2439
  ]
2213
2440
  }
2214
2441
  );
@@ -2229,52 +2456,60 @@ var DEFAULT_NODE_KINDS = {
2229
2456
  };
2230
2457
 
2231
2458
  // src/workflow/components/FlowCanvas/FlowCanvas.tsx
2232
- var import_jsx_runtime7 = require("react/jsx-runtime");
2459
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2233
2460
  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
- }) {
2461
+ function FlowCanvas(props) {
2462
+ const viewportPropProvided = props.viewport !== void 0 || props.defaultViewport !== void 0;
2463
+ const {
2464
+ nodes,
2465
+ edges,
2466
+ onNodesChange,
2467
+ onEdgesChange,
2468
+ viewport: controlledViewport,
2469
+ defaultViewport = DEFAULT_VIEWPORT2,
2470
+ onViewportChange,
2471
+ minZoom = 0.25,
2472
+ maxZoom = 2,
2473
+ fitViewOnInit,
2474
+ nodeKinds,
2475
+ onConnect,
2476
+ onConnectStart,
2477
+ onConnectEnd,
2478
+ isValidConnection,
2479
+ onSelectionChange,
2480
+ onPaneClick,
2481
+ onNodeClick,
2482
+ onEdgeClick,
2483
+ onEdgeLabelChange,
2484
+ onInit,
2485
+ onBeforeDelete,
2486
+ onNodeContextMenu,
2487
+ onEdgeContextMenu,
2488
+ onPaneContextMenu,
2489
+ nodesDraggable = true,
2490
+ nodesConnectable = true,
2491
+ panOnDrag: panOnDragProp,
2492
+ zoomOnScroll: zoomOnScrollProp,
2493
+ preset = "mouse",
2494
+ paneClickDistance = 4,
2495
+ paneClickClearsSelection = true,
2496
+ background = "dots",
2497
+ gridSize = 20,
2498
+ snapToGrid = false,
2499
+ nodeCollisionGap = -1,
2500
+ subflowCollisionGap,
2501
+ reparentOnDrag = false,
2502
+ autoResizeContainers = false,
2503
+ containerAutoResizePadding = 32,
2504
+ containerMinWidth = 320,
2505
+ containerMinHeight = 200,
2506
+ height = "100%",
2507
+ width = "100%",
2508
+ className,
2509
+ style,
2510
+ children,
2511
+ emptyState
2512
+ } = props;
2278
2513
  const presetDefaults = {
2279
2514
  mouse: { panOnDrag: true, zoomOnScroll: true },
2280
2515
  trackpad: { panOnDrag: true, zoomOnScroll: true },
@@ -2282,46 +2517,46 @@ function FlowCanvas({
2282
2517
  };
2283
2518
  const panOnDrag = panOnDragProp ?? presetDefaults[preset].panOnDrag;
2284
2519
  const zoomOnScroll = zoomOnScrollProp ?? presetDefaults[preset].zoomOnScroll;
2285
- const store = (0, import_react14.useState)(
2520
+ const store = (0, import_react15.useState)(
2286
2521
  () => createFlowStore({
2287
2522
  initialNodes: nodes,
2288
2523
  initialEdges: edges,
2289
2524
  initialViewport: controlledViewport ?? defaultViewport
2290
2525
  })
2291
2526
  )[0];
2292
- const handleRegistry = (0, import_react14.useState)(() => createHandleRegistry())[0];
2293
- const [handleVersion, setHandleVersion] = (0, import_react14.useState)(0);
2294
- (0, import_react14.useEffect)(() => {
2527
+ const handleRegistry = (0, import_react15.useState)(() => createHandleRegistry())[0];
2528
+ const [handleVersion, setHandleVersion] = (0, import_react15.useState)(0);
2529
+ (0, import_react15.useEffect)(() => {
2295
2530
  const unsub = handleRegistry.subscribe(() => {
2296
2531
  setHandleVersion((v) => v + 1);
2297
2532
  });
2298
2533
  return unsub;
2299
2534
  }, [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);
2535
+ const kinds = (0, import_react15.useMemo)(() => buildNodeKindRegistry(DEFAULT_NODE_KINDS, nodeKinds), [nodeKinds]);
2536
+ const containerRef = (0, import_react15.useRef)(null);
2537
+ (0, import_react15.useEffect)(() => store.setNodes(nodes), [store, nodes]);
2538
+ (0, import_react15.useEffect)(() => store.setEdges(edges), [store, edges]);
2539
+ const [uncontrolledVp, setUncontrolledVp] = (0, import_react15.useState)(controlledViewport ?? defaultViewport);
2305
2540
  const viewport = controlledViewport ?? uncontrolledVp;
2306
- (0, import_react14.useEffect)(() => store.setViewport(viewport), [store, viewport]);
2307
- const setViewport = (0, import_react14.useCallback)(
2541
+ (0, import_react15.useEffect)(() => store.setViewport(viewport), [store, viewport]);
2542
+ const setViewport = (0, import_react15.useCallback)(
2308
2543
  (next) => {
2309
2544
  if (controlledViewport === void 0) setUncontrolledVp(next);
2310
2545
  onViewportChange?.(next);
2311
2546
  },
2312
2547
  [controlledViewport, onViewportChange]
2313
2548
  );
2314
- const selectedNodeIds = (0, import_react14.useMemo)(() => {
2549
+ const selectedNodeIds = (0, import_react15.useMemo)(() => {
2315
2550
  const s = /* @__PURE__ */ new Set();
2316
2551
  for (const n of nodes) if (n.selected) s.add(n.id);
2317
2552
  return s;
2318
2553
  }, [nodes]);
2319
- const selectedEdgeIds = (0, import_react14.useMemo)(() => {
2554
+ const selectedEdgeIds = (0, import_react15.useMemo)(() => {
2320
2555
  const s = /* @__PURE__ */ new Set();
2321
2556
  for (const e of edges) if (e.selected) s.add(e.id);
2322
2557
  return s;
2323
2558
  }, [edges]);
2324
- (0, import_react14.useEffect)(() => {
2559
+ (0, import_react15.useEffect)(() => {
2325
2560
  store.setSelection(selectedNodeIds, selectedEdgeIds);
2326
2561
  if (onSelectionChange) {
2327
2562
  onSelectionChange({
@@ -2330,7 +2565,7 @@ function FlowCanvas({
2330
2565
  });
2331
2566
  }
2332
2567
  }, [store, selectedNodeIds, selectedEdgeIds, nodes, edges, onSelectionChange]);
2333
- const selectNode = (0, import_react14.useCallback)(
2568
+ const selectNode = (0, import_react15.useCallback)(
2334
2569
  (id, additive) => {
2335
2570
  const next = [];
2336
2571
  const nextEdges = [];
@@ -2353,14 +2588,14 @@ function FlowCanvas({
2353
2588
  },
2354
2589
  [nodes, edges, selectedNodeIds, onNodesChange, onEdgesChange]
2355
2590
  );
2356
- const notifyNodeClick = (0, import_react14.useCallback)(
2591
+ const notifyNodeClick = (0, import_react15.useCallback)(
2357
2592
  (id) => {
2358
2593
  const node = nodes.find((n) => n.id === id);
2359
2594
  if (node) onNodeClick?.(node);
2360
2595
  },
2361
2596
  [nodes, onNodeClick]
2362
2597
  );
2363
- const selectEdge = (0, import_react14.useCallback)(
2598
+ const selectEdge = (0, import_react15.useCallback)(
2364
2599
  (id, additive) => {
2365
2600
  const next = [];
2366
2601
  const nextNodes = [];
@@ -2385,7 +2620,7 @@ function FlowCanvas({
2385
2620
  },
2386
2621
  [nodes, edges, selectedEdgeIds, onEdgesChange, onNodesChange, onEdgeClick]
2387
2622
  );
2388
- const clearSelection = (0, import_react14.useCallback)(() => {
2623
+ const clearSelection = (0, import_react15.useCallback)(() => {
2389
2624
  const ns = [];
2390
2625
  const es = [];
2391
2626
  for (const n of nodes) if (n.selected) ns.push(change.node.select(n.id, false));
@@ -2393,9 +2628,9 @@ function FlowCanvas({
2393
2628
  if (ns.length) onNodesChange?.(ns);
2394
2629
  if (es.length) onEdgesChange?.(es);
2395
2630
  }, [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)(
2631
+ const dragRef = (0, import_react15.useRef)(null);
2632
+ const [draggingId, setDraggingId] = (0, import_react15.useState)(null);
2633
+ const beginNodeDrag = (0, import_react15.useCallback)(
2399
2634
  (nodeId, pointerId, clientX, clientY, altKey = false) => {
2400
2635
  if (!nodesDraggable) return;
2401
2636
  const node = nodes.find((n) => n.id === nodeId);
@@ -2404,6 +2639,9 @@ function FlowCanvas({
2404
2639
  id: d.id,
2405
2640
  startPosition: d.position
2406
2641
  }));
2642
+ const evaluateReparent = altKey || reparentOnDragRef.current;
2643
+ const hasParent = !!node.parentId;
2644
+ const skipClamp = altKey || reparentOnDragRef.current && !hasParent;
2407
2645
  dragRef.current = {
2408
2646
  pointerId,
2409
2647
  nodeId,
@@ -2411,7 +2649,8 @@ function FlowCanvas({
2411
2649
  startClientY: clientY,
2412
2650
  startPosition: node.position,
2413
2651
  descendants: kids,
2414
- altDetach: altKey && !!node.parentId,
2652
+ skipClamp,
2653
+ evaluateReparent,
2415
2654
  rafScheduled: false,
2416
2655
  nextDelta: null
2417
2656
  };
@@ -2420,12 +2659,12 @@ function FlowCanvas({
2420
2659
  },
2421
2660
  [nodes, nodesDraggable, selectNode]
2422
2661
  );
2423
- const [conn, setConn] = (0, import_react14.useState)(null);
2424
- const connRef = (0, import_react14.useRef)(null);
2425
- (0, import_react14.useEffect)(() => {
2662
+ const [conn, setConn] = (0, import_react15.useState)(null);
2663
+ const connRef = (0, import_react15.useRef)(null);
2664
+ (0, import_react15.useEffect)(() => {
2426
2665
  connRef.current = conn;
2427
2666
  }, [conn]);
2428
- const beginConnection = (0, import_react14.useCallback)(
2667
+ const beginConnection = (0, import_react15.useCallback)(
2429
2668
  (nodeId, handleId, handleType, pointerId, clientX, clientY) => {
2430
2669
  if (!nodesConnectable) return;
2431
2670
  const node = nodes.find((n) => n.id === nodeId);
@@ -2458,63 +2697,83 @@ function FlowCanvas({
2458
2697
  },
2459
2698
  [nodes, nodesConnectable, handleRegistry, viewport, store, onConnectStart]
2460
2699
  );
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)(() => {
2700
+ const viewportRef = (0, import_react15.useRef)(viewport);
2701
+ const nodesRef = (0, import_react15.useRef)(nodes);
2702
+ const edgesRef = (0, import_react15.useRef)(edges);
2703
+ const onNodesChangeRefForInstance = (0, import_react15.useRef)(onNodesChange);
2704
+ const onEdgesChangeRefForInstance = (0, import_react15.useRef)(onEdgesChange);
2705
+ const onBeforeDeleteRef = (0, import_react15.useRef)(onBeforeDelete);
2706
+ const snapToGridRef = (0, import_react15.useRef)(snapToGrid);
2707
+ const gridSizeRef = (0, import_react15.useRef)(gridSize);
2708
+ const nodeCollisionGapRef = (0, import_react15.useRef)(nodeCollisionGap);
2709
+ const subflowCollisionGapRef = (0, import_react15.useRef)(subflowCollisionGap ?? nodeCollisionGap);
2710
+ const reparentOnDragRef = (0, import_react15.useRef)(reparentOnDrag);
2711
+ const autoResizeContainersRef = (0, import_react15.useRef)(autoResizeContainers);
2712
+ const containerAutoResizePaddingRef = (0, import_react15.useRef)(containerAutoResizePadding);
2713
+ const containerMinWidthRef = (0, import_react15.useRef)(containerMinWidth);
2714
+ const containerMinHeightRef = (0, import_react15.useRef)(containerMinHeight);
2715
+ (0, import_react15.useEffect)(() => {
2472
2716
  edgesRef.current = edges;
2473
2717
  }, [edges]);
2474
- (0, import_react14.useEffect)(() => {
2718
+ (0, import_react15.useEffect)(() => {
2475
2719
  onNodesChangeRefForInstance.current = onNodesChange;
2476
2720
  }, [onNodesChange]);
2477
- (0, import_react14.useEffect)(() => {
2721
+ (0, import_react15.useEffect)(() => {
2478
2722
  onEdgesChangeRefForInstance.current = onEdgesChange;
2479
2723
  }, [onEdgesChange]);
2480
- (0, import_react14.useEffect)(() => {
2724
+ (0, import_react15.useEffect)(() => {
2481
2725
  onBeforeDeleteRef.current = onBeforeDelete;
2482
2726
  }, [onBeforeDelete]);
2483
- (0, import_react14.useEffect)(() => {
2727
+ (0, import_react15.useEffect)(() => {
2484
2728
  snapToGridRef.current = snapToGrid;
2485
2729
  }, [snapToGrid]);
2486
- (0, import_react14.useEffect)(() => {
2730
+ (0, import_react15.useEffect)(() => {
2487
2731
  gridSizeRef.current = gridSize;
2488
2732
  }, [gridSize]);
2489
- (0, import_react14.useEffect)(() => {
2733
+ (0, import_react15.useEffect)(() => {
2490
2734
  nodeCollisionGapRef.current = nodeCollisionGap;
2491
2735
  }, [nodeCollisionGap]);
2492
- (0, import_react14.useEffect)(() => {
2736
+ (0, import_react15.useEffect)(() => {
2493
2737
  subflowCollisionGapRef.current = subflowCollisionGap ?? nodeCollisionGap;
2494
2738
  }, [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)(() => {
2739
+ (0, import_react15.useEffect)(() => {
2740
+ reparentOnDragRef.current = reparentOnDrag;
2741
+ }, [reparentOnDrag]);
2742
+ (0, import_react15.useEffect)(() => {
2743
+ autoResizeContainersRef.current = autoResizeContainers;
2744
+ }, [autoResizeContainers]);
2745
+ (0, import_react15.useEffect)(() => {
2746
+ containerAutoResizePaddingRef.current = containerAutoResizePadding;
2747
+ }, [containerAutoResizePadding]);
2748
+ (0, import_react15.useEffect)(() => {
2749
+ containerMinWidthRef.current = containerMinWidth;
2750
+ }, [containerMinWidth]);
2751
+ (0, import_react15.useEffect)(() => {
2752
+ containerMinHeightRef.current = containerMinHeight;
2753
+ }, [containerMinHeight]);
2754
+ const onNodesChangeRef = (0, import_react15.useRef)(onNodesChange);
2755
+ const onConnectRef = (0, import_react15.useRef)(onConnect);
2756
+ const onConnectEndRef = (0, import_react15.useRef)(onConnectEnd);
2757
+ const isValidConnectionRef = (0, import_react15.useRef)(isValidConnection);
2758
+ (0, import_react15.useEffect)(() => {
2500
2759
  viewportRef.current = viewport;
2501
2760
  }, [viewport]);
2502
- (0, import_react14.useEffect)(() => {
2761
+ (0, import_react15.useEffect)(() => {
2503
2762
  nodesRef.current = nodes;
2504
2763
  }, [nodes]);
2505
- (0, import_react14.useEffect)(() => {
2764
+ (0, import_react15.useEffect)(() => {
2506
2765
  onNodesChangeRef.current = onNodesChange;
2507
2766
  }, [onNodesChange]);
2508
- (0, import_react14.useEffect)(() => {
2767
+ (0, import_react15.useEffect)(() => {
2509
2768
  onConnectRef.current = onConnect;
2510
2769
  }, [onConnect]);
2511
- (0, import_react14.useEffect)(() => {
2770
+ (0, import_react15.useEffect)(() => {
2512
2771
  onConnectEndRef.current = onConnectEnd;
2513
2772
  }, [onConnectEnd]);
2514
- (0, import_react14.useEffect)(() => {
2773
+ (0, import_react15.useEffect)(() => {
2515
2774
  isValidConnectionRef.current = isValidConnection;
2516
2775
  }, [isValidConnection]);
2517
- (0, import_react14.useEffect)(() => {
2776
+ (0, import_react15.useEffect)(() => {
2518
2777
  const onPointerMove = (e) => {
2519
2778
  const vp = viewportRef.current;
2520
2779
  const drag = dragRef.current;
@@ -2536,7 +2795,7 @@ function FlowCanvas({
2536
2795
  x: d.startPosition.x + delta.dx,
2537
2796
  y: d.startPosition.y + delta.dy
2538
2797
  };
2539
- const clamped = d.altDetach ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2798
+ const clamped = d.skipClamp ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2540
2799
  const isContainer = dragNode.type === "group" || dragNode.type === "forEach";
2541
2800
  const gap = isContainer ? subflowCollisionGapRef.current : nodeCollisionGapRef.current;
2542
2801
  const excludeIds = /* @__PURE__ */ new Set([d.nodeId, ...d.descendants.map((kid) => kid.id)]);
@@ -2588,7 +2847,7 @@ function FlowCanvas({
2588
2847
  y: Math.round(proposed.y / g) * g
2589
2848
  };
2590
2849
  }
2591
- const clamped = drag.altDetach ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2850
+ const clamped = drag.skipClamp ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2592
2851
  const isContainer = dragNode.type === "group" || dragNode.type === "forEach";
2593
2852
  const gap = isContainer ? subflowCollisionGapRef.current : nodeCollisionGapRef.current;
2594
2853
  const excludeIds = /* @__PURE__ */ new Set([
@@ -2611,7 +2870,7 @@ function FlowCanvas({
2611
2870
  )
2612
2871
  );
2613
2872
  }
2614
- if (drag.altDetach && dragNode.parentId) {
2873
+ if (drag.evaluateReparent) {
2615
2874
  const targetGroup = findContainingGroup(
2616
2875
  {
2617
2876
  x: finalPos.x + (dragNode.width ?? 0) / 2,
@@ -2621,14 +2880,30 @@ function FlowCanvas({
2621
2880
  [drag.nodeId, ...drag.descendants.map((d) => d.id)]
2622
2881
  );
2623
2882
  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));
2883
+ if (nextParentId !== dragNode.parentId) {
2884
+ const updated = {
2885
+ ...dragNode,
2886
+ position: finalPos,
2887
+ parentId: nextParentId,
2888
+ // Adopting into a container always pins the child with
2889
+ // `extent: 'parent'` so it's clamped from the next drag
2890
+ // on. Detaching clears it.
2891
+ extent: nextParentId ? "parent" : void 0
2892
+ };
2893
+ changes.push(change.node.replace(drag.nodeId, updated));
2894
+ }
2895
+ }
2896
+ if (autoResizeContainersRef.current) {
2897
+ const containerChanges = computeContainerAutoResize(
2898
+ nodesRef.current,
2899
+ // Projected children: apply the position + reparent
2900
+ // changes we just built before measuring bboxes.
2901
+ changes,
2902
+ containerAutoResizePaddingRef.current,
2903
+ containerMinWidthRef.current,
2904
+ containerMinHeightRef.current
2905
+ );
2906
+ for (const c2 of containerChanges) changes.push(c2);
2632
2907
  }
2633
2908
  onNodesChangeRef.current?.(changes);
2634
2909
  }
@@ -2637,38 +2912,54 @@ function FlowCanvas({
2637
2912
  }
2638
2913
  const c = connRef.current;
2639
2914
  if (c && c.pointerId === e.pointerId) {
2640
- const target = e.target;
2641
- const handleEl = target?.closest("[data-handle-id]");
2915
+ const rect = containerRef.current?.getBoundingClientRect();
2916
+ const flowPos = rect ? screenToFlow({ x: e.clientX - rect.left, y: e.clientY - rect.top }, vp) : { x: 0, y: 0 };
2917
+ const snapRadius = 26 / Math.max(0.1, vp.zoom);
2918
+ let bestDesc = null;
2919
+ let bestDist = snapRadius;
2920
+ for (const desc of handleRegistry.all()) {
2921
+ if (desc.nodeId === c.from.nodeId) continue;
2922
+ if (desc.type === c.from.handleType) continue;
2923
+ if (!desc.canEnd) continue;
2924
+ const targetNode = nodesRef.current.find((n) => n.id === desc.nodeId);
2925
+ if (!targetNode || targetNode.hidden) continue;
2926
+ const centre = handleCentre(targetNode, desc.side, desc.index, desc.total);
2927
+ const dist = Math.hypot(centre.x - flowPos.x, centre.y - flowPos.y);
2928
+ if (dist < bestDist) {
2929
+ bestDist = dist;
2930
+ bestDesc = desc;
2931
+ }
2932
+ }
2642
2933
  let connection = null;
2643
2934
  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
- }
2935
+ if (bestDesc) {
2936
+ const source = c.from.handleType === "source" ? c.from : {
2937
+ nodeId: bestDesc.nodeId,
2938
+ handleId: bestDesc.handleId,
2939
+ handleType: "source"
2940
+ };
2941
+ const target2 = c.from.handleType === "target" ? c.from : {
2942
+ nodeId: bestDesc.nodeId,
2943
+ handleId: bestDesc.handleId,
2944
+ handleType: "target"
2945
+ };
2946
+ connection = {
2947
+ source: source.nodeId,
2948
+ sourceHandle: source.handleId,
2949
+ target: target2.nodeId,
2950
+ targetHandle: target2.handleId
2951
+ };
2952
+ connectedTo = {
2953
+ nodeId: bestDesc.nodeId,
2954
+ handleId: bestDesc.handleId,
2955
+ handleType: bestDesc.type
2956
+ };
2957
+ const validator = isValidConnectionRef.current;
2958
+ if (validator && !validator(connection)) {
2959
+ connection = null;
2960
+ connectedTo = void 0;
2668
2961
  }
2669
2962
  }
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
2963
  const endState = {
2673
2964
  cancelled: !connection,
2674
2965
  position: { x: e.clientX, y: e.clientY },
@@ -2701,7 +2992,26 @@ function FlowCanvas({
2701
2992
  window.removeEventListener("pointercancel", onPointerCancel);
2702
2993
  };
2703
2994
  }, [store]);
2704
- const panRef = (0, import_react14.useRef)(null);
2995
+ (0, import_react15.useEffect)(() => {
2996
+ if (!autoResizeContainers || draggingId) return;
2997
+ const resizeChanges = computeContainerAutoResize(
2998
+ nodes,
2999
+ [],
3000
+ containerAutoResizePadding,
3001
+ containerMinWidth,
3002
+ containerMinHeight
3003
+ );
3004
+ if (resizeChanges.length > 0) onNodesChange?.(resizeChanges);
3005
+ }, [
3006
+ nodes,
3007
+ draggingId,
3008
+ autoResizeContainers,
3009
+ containerAutoResizePadding,
3010
+ containerMinWidth,
3011
+ containerMinHeight,
3012
+ onNodesChange
3013
+ ]);
3014
+ const panRef = (0, import_react15.useRef)(null);
2705
3015
  const onCanvasPointerDown = (e) => {
2706
3016
  if (e.button !== 0) return;
2707
3017
  const t = e.target;
@@ -2726,7 +3036,7 @@ function FlowCanvas({
2726
3036
  moved: false
2727
3037
  };
2728
3038
  };
2729
- (0, import_react14.useEffect)(() => {
3039
+ (0, import_react15.useEffect)(() => {
2730
3040
  const onMove = (e) => {
2731
3041
  const pan = panRef.current;
2732
3042
  if (!pan || pan.pointerId !== e.pointerId) return;
@@ -2782,7 +3092,7 @@ function FlowCanvas({
2782
3092
  y: py - (py - viewport.y) * k
2783
3093
  });
2784
3094
  };
2785
- const dispatch = (0, import_react14.useCallback)(
3095
+ const dispatch = (0, import_react15.useCallback)(
2786
3096
  (a) => {
2787
3097
  if (a.type === "connection/start") {
2788
3098
  beginConnection(a.nodeId, a.handleId, a.handleType, a.pointerId, a.clientX, a.clientY);
@@ -2790,7 +3100,7 @@ function FlowCanvas({
2790
3100
  },
2791
3101
  [beginConnection]
2792
3102
  );
2793
- const reportDimensions = (0, import_react14.useCallback)(
3103
+ const reportDimensions = (0, import_react15.useCallback)(
2794
3104
  (nodeId, width2, height2) => {
2795
3105
  const node = nodes.find((n) => n.id === nodeId);
2796
3106
  if (!node) return;
@@ -2799,7 +3109,7 @@ function FlowCanvas({
2799
3109
  },
2800
3110
  [nodes, onNodesChange]
2801
3111
  );
2802
- const toggleNodeCollapseImpl = (0, import_react14.useCallback)(
3112
+ const toggleNodeCollapseImpl = (0, import_react15.useCallback)(
2803
3113
  (nodeId) => {
2804
3114
  const node = nodes.find((n) => n.id === nodeId);
2805
3115
  if (!node) return;
@@ -2812,7 +3122,7 @@ function FlowCanvas({
2812
3122
  },
2813
3123
  [nodes, onNodesChange]
2814
3124
  );
2815
- const deleteNodeImpl = (0, import_react14.useCallback)(
3125
+ const deleteNodeImpl = (0, import_react15.useCallback)(
2816
3126
  (nodeId) => {
2817
3127
  const incidentEdgeIds = edgesRef.current.filter((e) => e.source === nodeId || e.target === nodeId).map((e) => e.id);
2818
3128
  if (incidentEdgeIds.length > 0) {
@@ -2822,7 +3132,7 @@ function FlowCanvas({
2822
3132
  },
2823
3133
  [onNodesChange, onEdgesChange]
2824
3134
  );
2825
- const instance = (0, import_react14.useMemo)(
3135
+ const instance = (0, import_react15.useMemo)(
2826
3136
  () => ({
2827
3137
  // viewport
2828
3138
  getViewport: () => viewportRef.current,
@@ -2963,13 +3273,29 @@ function FlowCanvas({
2963
3273
  }),
2964
3274
  [setViewport, minZoom, maxZoom]
2965
3275
  );
2966
- const initFiredRef = (0, import_react14.useRef)(false);
2967
- (0, import_react14.useEffect)(() => {
3276
+ const initFiredRef = (0, import_react15.useRef)(false);
3277
+ (0, import_react15.useEffect)(() => {
2968
3278
  if (initFiredRef.current) return;
2969
3279
  initFiredRef.current = true;
2970
3280
  onInit?.(instance);
2971
3281
  }, [instance, onInit]);
2972
- (0, import_react14.useEffect)(() => {
3282
+ const fitOnInitFiredRef = (0, import_react15.useRef)(false);
3283
+ (0, import_react15.useEffect)(() => {
3284
+ if (fitOnInitFiredRef.current) return;
3285
+ const opt = fitViewOnInit;
3286
+ const shouldFit = opt === false ? false : opt !== void 0 ? true : !viewportPropProvided;
3287
+ if (!shouldFit) return;
3288
+ if (nodes.length === 0) return;
3289
+ const rect = containerRef.current?.getBoundingClientRect();
3290
+ if (!rect || rect.width === 0 || rect.height === 0) return;
3291
+ fitOnInitFiredRef.current = true;
3292
+ const fitOpts = typeof opt === "object" && opt !== null ? opt : void 0;
3293
+ const raf = requestAnimationFrame(() => {
3294
+ void instance.fitView(fitOpts);
3295
+ });
3296
+ return () => cancelAnimationFrame(raf);
3297
+ }, [fitViewOnInit, viewportPropProvided, nodes.length, instance]);
3298
+ (0, import_react15.useEffect)(() => {
2973
3299
  const onKey = (e) => {
2974
3300
  if (e.key !== "Backspace" && e.key !== "Delete") return;
2975
3301
  const target = e.target;
@@ -2990,7 +3316,7 @@ function FlowCanvas({
2990
3316
  window.addEventListener("keydown", onKey);
2991
3317
  return () => window.removeEventListener("keydown", onKey);
2992
3318
  }, [instance, store]);
2993
- const bridge = (0, import_react14.useMemo)(
3319
+ const bridge = (0, import_react15.useMemo)(
2994
3320
  () => ({
2995
3321
  beginNodeDrag,
2996
3322
  selectNode,
@@ -3010,16 +3336,16 @@ function FlowCanvas({
3010
3336
  toggleNodeCollapseImpl
3011
3337
  ]
3012
3338
  );
3013
- const [panGesture, setPanGesture] = (0, import_react14.useState)(false);
3339
+ const [panGesture, setPanGesture] = (0, import_react15.useState)(false);
3014
3340
  const isEmpty = nodes.length === 0 && edges.length === 0;
3015
3341
  const isConnecting = conn !== null;
3016
- const visibleNodes = (0, import_react14.useMemo)(() => nodes.filter((n) => !n.hidden), [nodes]);
3017
- const visibleEdges = (0, import_react14.useMemo)(() => {
3342
+ const visibleNodes = (0, import_react15.useMemo)(() => nodes.filter((n) => !n.hidden), [nodes]);
3343
+ const visibleEdges = (0, import_react15.useMemo)(() => {
3018
3344
  if (visibleNodes.length === nodes.length) return edges;
3019
3345
  const visibleIds = new Set(visibleNodes.map((n) => n.id));
3020
3346
  return edges.filter((e) => visibleIds.has(e.source) && visibleIds.has(e.target));
3021
3347
  }, [edges, nodes, visibleNodes]);
3022
- const orderedNodes = (0, import_react14.useMemo)(() => {
3348
+ const orderedNodes = (0, import_react15.useMemo)(() => {
3023
3349
  const isContainer = (n) => n.type === "group" || n.type === "forEach";
3024
3350
  const depth = (n) => {
3025
3351
  let d = 0;
@@ -3038,7 +3364,7 @@ function FlowCanvas({
3038
3364
  containers.sort((a, b) => depth(a) - depth(b));
3039
3365
  return [...containers, ...others];
3040
3366
  }, [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)(
3367
+ 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
3368
  "div",
3043
3369
  {
3044
3370
  ref: containerRef,
@@ -3083,7 +3409,7 @@ function FlowCanvas({
3083
3409
  },
3084
3410
  "data-empty": isEmpty ? "true" : void 0,
3085
3411
  children: [
3086
- background !== "none" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3412
+ background !== "none" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3087
3413
  "div",
3088
3414
  {
3089
3415
  className: cn(
@@ -3097,7 +3423,7 @@ function FlowCanvas({
3097
3423
  }
3098
3424
  }
3099
3425
  ),
3100
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3426
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3101
3427
  "div",
3102
3428
  {
3103
3429
  className: "ods-flow-canvas-v2__viewport",
@@ -3108,7 +3434,7 @@ function FlowCanvas({
3108
3434
  transformOrigin: "0 0"
3109
3435
  },
3110
3436
  children: [
3111
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3437
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3112
3438
  EdgesLayer,
3113
3439
  {
3114
3440
  edges: visibleEdges,
@@ -3123,7 +3449,7 @@ function FlowCanvas({
3123
3449
  orderedNodes.map((node) => {
3124
3450
  const Kind = kinds[node.type] ?? kinds.action;
3125
3451
  if (!Kind) return null;
3126
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3452
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3127
3453
  FlowNode,
3128
3454
  {
3129
3455
  node,
@@ -3138,7 +3464,7 @@ function FlowCanvas({
3138
3464
  ]
3139
3465
  }
3140
3466
  ),
3141
- isEmpty && emptyState && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ods-flow-canvas-v2__empty", children: emptyState }),
3467
+ isEmpty && emptyState && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "ods-flow-canvas-v2__empty", children: emptyState }),
3142
3468
  children
3143
3469
  ]
3144
3470
  }
@@ -3147,7 +3473,69 @@ function FlowCanvas({
3147
3473
  function onEdgesChangeRef(id, cb) {
3148
3474
  cb?.([change.edge.remove(id)]);
3149
3475
  }
3150
- var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3476
+ var CONTAINER_DROP_HEADROOM_W = 220;
3477
+ var CONTAINER_DROP_HEADROOM_H = 2 * 128;
3478
+ function computeContainerAutoResize(nodes, pending, padding, minWidth, minHeight) {
3479
+ const projected = /* @__PURE__ */ new Map();
3480
+ for (const n of nodes) projected.set(n.id, n);
3481
+ for (const c of pending) {
3482
+ if (c.type === "position" && c.position) {
3483
+ const cur = projected.get(c.id);
3484
+ if (cur) projected.set(c.id, { ...cur, position: c.position });
3485
+ } else if (c.type === "replace" && c.item) {
3486
+ projected.set(c.id, { ...projected.get(c.id), ...c.item });
3487
+ } else if (c.type === "add" && c.item) {
3488
+ projected.set(c.item.id, c.item);
3489
+ }
3490
+ }
3491
+ const HEADER = 56;
3492
+ const out = [];
3493
+ for (const parent of projected.values()) {
3494
+ if (parent.type !== "group" && parent.type !== "forEach") continue;
3495
+ const children = [];
3496
+ for (const n of projected.values()) {
3497
+ if (n.parentId === parent.id) children.push(n);
3498
+ }
3499
+ if (children.length === 0) {
3500
+ const curW2 = parent.width ?? 480;
3501
+ const curH2 = parent.height ?? 240;
3502
+ if (curW2 !== minWidth || curH2 !== minHeight) {
3503
+ out.push(change.node.dimensions(parent.id, { width: minWidth, height: minHeight }));
3504
+ }
3505
+ continue;
3506
+ }
3507
+ let minX = Number.POSITIVE_INFINITY;
3508
+ let minY = Number.POSITIVE_INFINITY;
3509
+ let maxX = Number.NEGATIVE_INFINITY;
3510
+ let maxY = Number.NEGATIVE_INFINITY;
3511
+ for (const c of children) {
3512
+ const cw = c.width ?? 240;
3513
+ const ch = c.height ?? 96;
3514
+ if (c.position.x < minX) minX = c.position.x;
3515
+ if (c.position.y < minY) minY = c.position.y;
3516
+ if (c.position.x + cw > maxX) maxX = c.position.x + cw;
3517
+ if (c.position.y + ch > maxY) maxY = c.position.y + ch;
3518
+ }
3519
+ const targetX = minX - padding;
3520
+ const targetY = minY - padding - HEADER;
3521
+ const curW = parent.width ?? 480;
3522
+ const curH = parent.height ?? 240;
3523
+ const fitW = maxX - minX + padding * 2 + CONTAINER_DROP_HEADROOM_W;
3524
+ const fitH = maxY - minY + padding * 2 + HEADER + CONTAINER_DROP_HEADROOM_H;
3525
+ const nextW = Math.max(minWidth, fitW);
3526
+ const nextH = Math.max(minHeight, fitH);
3527
+ const positionDirty = parent.position.x !== targetX || parent.position.y !== targetY;
3528
+ const sizeDirty = curW !== nextW || curH !== nextH;
3529
+ if (positionDirty) {
3530
+ out.push(change.node.position(parent.id, { x: targetX, y: targetY }, false));
3531
+ }
3532
+ if (sizeDirty) {
3533
+ out.push(change.node.dimensions(parent.id, { width: nextW, height: nextH }));
3534
+ }
3535
+ }
3536
+ return out;
3537
+ }
3538
+ var EdgesLayer = (0, import_react15.memo)(function EdgesLayer2({
3151
3539
  edges,
3152
3540
  nodes,
3153
3541
  onSelect,
@@ -3156,7 +3544,7 @@ var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3156
3544
  ghost,
3157
3545
  handleVersion: _handleVersion
3158
3546
  }) {
3159
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3547
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3160
3548
  "svg",
3161
3549
  {
3162
3550
  className: "ods-flow-canvas-v2__edges",
@@ -3164,7 +3552,7 @@ var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3164
3552
  width: "100%",
3165
3553
  height: "100%",
3166
3554
  children: [
3167
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("g", { style: { pointerEvents: "auto" }, children: edges.map((edge) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3555
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("g", { style: { pointerEvents: "auto" }, children: edges.map((edge) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3168
3556
  FlowEdge,
3169
3557
  {
3170
3558
  edge,
@@ -3176,7 +3564,7 @@ var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3176
3564
  },
3177
3565
  edge.id
3178
3566
  )) }),
3179
- ghost && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3567
+ ghost && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3180
3568
  "path",
3181
3569
  {
3182
3570
  d: `M ${ghost.start.x} ${ghost.start.y} L ${ghost.end.x} ${ghost.end.y}`,
@@ -3191,153 +3579,231 @@ var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3191
3579
  );
3192
3580
  });
3193
3581
 
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
3582
+ // src/workflow/components/FxPanel/FxPanel.tsx
3583
+ var import_react16 = require("react");
3584
+ var import_icons2 = require("@octaviaflow/icons");
3585
+ var import_jsx_runtime9 = require("react/jsx-runtime");
3586
+ var KIND_GLYPH = {
3587
+ function: "\u0192",
3588
+ variable: "\u2B21"
3589
+ };
3590
+ function FxPanel({
3591
+ categories,
3592
+ title = "FX / IO",
3593
+ hint = /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
3594
+ "Drag items into any input field with the ",
3595
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { children: "FX" }),
3596
+ " indicator"
3597
+ ] }),
3598
+ search: controlledSearch,
3599
+ defaultSearch = "",
3600
+ onSearchChange,
3601
+ expandedCategory: controlledExpanded,
3602
+ defaultExpandedCategory,
3603
+ onExpandedCategoryChange,
3604
+ onClose,
3605
+ onItemDragStart,
3606
+ onItemSelect,
3607
+ width = 292,
3608
+ emptyLabel = "No matches",
3609
+ searchPlaceholder = "Search functions & variables\u2026",
3610
+ className,
3611
+ style
3207
3612
  }) {
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
- };
3613
+ const [internalSearch, setInternalSearch] = (0, import_react16.useState)(defaultSearch);
3614
+ const search = controlledSearch ?? internalSearch;
3615
+ const setSearch = (next) => {
3616
+ if (controlledSearch === void 0) setInternalSearch(next);
3617
+ onSearchChange?.(next);
3231
3618
  };
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);
3267
- }
3268
- }
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 }
3275
- });
3276
- onResize?.({ width: nextW, height: nextH });
3277
- };
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
- });
3286
- }
3287
- dragRef.current = null;
3288
- }
3619
+ const [internalExpanded, setInternalExpanded] = (0, import_react16.useState)(
3620
+ defaultExpandedCategory !== void 0 ? defaultExpandedCategory : categories.find((c) => c.items.length > 0)?.id ?? null
3621
+ );
3622
+ const expanded = controlledExpanded ?? internalExpanded;
3623
+ const setExpanded = (id) => {
3624
+ if (controlledExpanded === void 0) setInternalExpanded(id);
3625
+ onExpandedCategoryChange?.(id);
3289
3626
  };
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%" };
3627
+ const [draggingId, setDraggingId] = (0, import_react16.useState)(null);
3628
+ const filtered = (0, import_react16.useMemo)(() => {
3629
+ const q = search.trim().toLowerCase();
3630
+ if (!q) return categories;
3631
+ return categories.map((cat) => {
3632
+ if (cat.label.toLowerCase().includes(q)) return cat;
3633
+ const items = cat.items.filter(
3634
+ (it) => it.label.toLowerCase().includes(q) || (it.description?.toLowerCase().includes(q) ?? false)
3635
+ );
3636
+ return { ...cat, items };
3637
+ }).filter((cat) => cat.items.length > 0);
3638
+ }, [categories, search]);
3639
+ const hasResults = filtered.length > 0;
3640
+ const handleDragStart = (item, category) => (e) => {
3641
+ setDraggingId(item.id);
3642
+ if (onItemDragStart) {
3643
+ onItemDragStart(item, category, e);
3644
+ } else {
3645
+ e.dataTransfer.setData("text/plain", item.insertValue);
3646
+ e.dataTransfer.setData("application/x-fx-insert", item.insertValue);
3647
+ e.dataTransfer.setData(
3648
+ "application/x-fx-type",
3649
+ category.kind ?? "function"
3650
+ );
3651
+ e.dataTransfer.effectAllowed = "copy";
3313
3652
  }
3314
3653
  };
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",
3654
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3655
+ "aside",
3317
3656
  {
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
- )) });
3657
+ className: cn("ods-flow-fx-panel", className),
3658
+ style: { width, ...style },
3659
+ "aria-label": typeof title === "string" ? title : "FX / IO",
3660
+ children: [
3661
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("header", { className: "ods-flow-fx-panel__header", children: [
3662
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { className: "ods-flow-fx-panel__title", children: title }),
3663
+ onClose && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3664
+ "button",
3665
+ {
3666
+ type: "button",
3667
+ className: "ods-flow-fx-panel__close",
3668
+ onClick: onClose,
3669
+ "aria-label": "Close FX panel",
3670
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.CloseIcon, { size: "sm" })
3671
+ }
3672
+ )
3673
+ ] }),
3674
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-flow-fx-panel__search", children: [
3675
+ /* @__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" }) }),
3676
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3677
+ "input",
3678
+ {
3679
+ type: "text",
3680
+ className: "ods-flow-fx-panel__search-input",
3681
+ placeholder: searchPlaceholder,
3682
+ "aria-label": "Search functions and variables",
3683
+ value: search,
3684
+ onChange: (e) => setSearch(e.target.value)
3685
+ }
3686
+ ),
3687
+ search && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3688
+ "button",
3689
+ {
3690
+ type: "button",
3691
+ className: "ods-flow-fx-panel__search-clear",
3692
+ onClick: () => setSearch(""),
3693
+ "aria-label": "Clear search",
3694
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.CloseIcon, { size: "sm" })
3695
+ }
3696
+ )
3697
+ ] }),
3698
+ hint && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ods-flow-fx-panel__hint", children: hint }),
3699
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-flow-fx-panel__list", children: [
3700
+ filtered.map((cat) => {
3701
+ const isOpen = expanded === cat.id;
3702
+ const kind = cat.kind ?? "function";
3703
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-flow-fx-panel__category", children: [
3704
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3705
+ "button",
3706
+ {
3707
+ type: "button",
3708
+ className: "ods-flow-fx-panel__category-header",
3709
+ "aria-expanded": isOpen,
3710
+ onClick: () => setExpanded(isOpen ? null : cat.id),
3711
+ children: [
3712
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3713
+ import_icons2.ChevronRightIcon,
3714
+ {
3715
+ size: "sm",
3716
+ className: cn(
3717
+ "ods-flow-fx-panel__chevron",
3718
+ isOpen && "ods-flow-fx-panel__chevron--open"
3719
+ )
3720
+ }
3721
+ ),
3722
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3723
+ "span",
3724
+ {
3725
+ className: cn(
3726
+ "ods-flow-fx-panel__badge",
3727
+ `ods-flow-fx-panel__badge--${kind}`
3728
+ ),
3729
+ children: [
3730
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3731
+ "span",
3732
+ {
3733
+ className: "ods-flow-fx-panel__badge-glyph",
3734
+ "aria-hidden": "true",
3735
+ children: KIND_GLYPH[kind]
3736
+ }
3737
+ ),
3738
+ cat.label
3739
+ ]
3740
+ }
3741
+ ),
3742
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-flow-fx-panel__count", children: cat.items.length })
3743
+ ]
3744
+ }
3745
+ ),
3746
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ods-flow-fx-panel__items", children: cat.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3747
+ "div",
3748
+ {
3749
+ className: cn(
3750
+ "ods-flow-fx-panel__item",
3751
+ draggingId === item.id && "ods-flow-fx-panel__item--dragging"
3752
+ ),
3753
+ draggable: true,
3754
+ title: item.insertValue,
3755
+ onDragStart: handleDragStart(item, cat),
3756
+ onDragEnd: () => setDraggingId(null),
3757
+ onClick: onItemSelect ? () => onItemSelect(item, cat) : void 0,
3758
+ children: [
3759
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { className: "ods-flow-fx-panel__item-label", children: item.label }),
3760
+ item.description && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-flow-fx-panel__item-desc", children: item.description })
3761
+ ]
3762
+ },
3763
+ item.id
3764
+ )) })
3765
+ ] }, cat.id);
3766
+ }),
3767
+ !hasResults && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ods-flow-fx-panel__empty", children: emptyLabel })
3768
+ ] })
3769
+ ]
3770
+ }
3771
+ );
3327
3772
  }
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
- }
3773
+
3774
+ // src/workflow/components/FxPanel/FxToggleButton.tsx
3775
+ var import_icons3 = require("@octaviaflow/icons");
3776
+ var import_jsx_runtime10 = require("react/jsx-runtime");
3777
+ function FxToggleButton({
3778
+ active = false,
3779
+ label = "FX",
3780
+ className,
3781
+ type = "button",
3782
+ ...rest
3783
+ }) {
3784
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3785
+ "button",
3786
+ {
3787
+ ...rest,
3788
+ type,
3789
+ className: cn(
3790
+ "ods-flow-fx-toggle",
3791
+ active && "ods-flow-fx-toggle--active",
3792
+ label == null && "ods-flow-fx-toggle--icon-only",
3793
+ className
3794
+ ),
3795
+ "aria-pressed": active,
3796
+ title: active ? "Hide FX / IO" : "Show FX / IO",
3797
+ children: [
3798
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_icons3.FunctionIcon, { size: "sm", className: "ods-flow-fx-toggle__icon" }),
3799
+ label != null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ods-flow-fx-toggle__label", children: label })
3800
+ ]
3801
+ }
3802
+ );
3337
3803
  }
3338
3804
 
3339
3805
  // src/workflow/components/NodeToolbar/NodeToolbar.tsx
3340
- var import_jsx_runtime9 = require("react/jsx-runtime");
3806
+ var import_jsx_runtime11 = require("react/jsx-runtime");
3341
3807
  function NodeToolbar({
3342
3808
  isVisible,
3343
3809
  position = "top",
@@ -3352,7 +3818,7 @@ function NodeToolbar({
3352
3818
  const show = isVisible ?? node.selected;
3353
3819
  if (!show) return null;
3354
3820
  const inverseScale = 1 / viewport.zoom;
3355
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3821
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3356
3822
  "div",
3357
3823
  {
3358
3824
  className: cn("ods-node-toolbar", `ods-node-toolbar--${position}`, className),
@@ -3364,7 +3830,7 @@ function NodeToolbar({
3364
3830
  onPointerDown: (e) => e.stopPropagation(),
3365
3831
  onMouseDown: (e) => e.stopPropagation(),
3366
3832
  onClick: (e) => e.stopPropagation(),
3367
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3833
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3368
3834
  "div",
3369
3835
  {
3370
3836
  className: "ods-node-toolbar__inner",
@@ -3518,6 +3984,8 @@ function toggleGroupCollapse(groupId, nodes) {
3518
3984
  FlowEdge,
3519
3985
  FlowNode,
3520
3986
  ForEachNode,
3987
+ FxPanel,
3988
+ FxToggleButton,
3521
3989
  GroupNode,
3522
3990
  Handle,
3523
3991
  HttpRequestNode,
@@ -3564,6 +4032,7 @@ function toggleGroupCollapse(groupId, nodes) {
3564
4032
  useNodeData,
3565
4033
  useNodes,
3566
4034
  useSelection,
3567
- useViewport
4035
+ useViewport,
4036
+ useViewportOrNull
3568
4037
  });
3569
4038
  //# sourceMappingURL=workflow.cjs.map