@octaviaflow/core 3.0.18-beta.3 → 3.0.18-beta.30

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 (178) hide show
  1. package/dist/chunk-2NGC7AI3.js +2637 -0
  2. package/dist/chunk-2NGC7AI3.js.map +1 -0
  3. package/dist/chunk-2O6K5PLY.js +2637 -0
  4. package/dist/chunk-2O6K5PLY.js.map +1 -0
  5. package/dist/chunk-2ZIOFEIS.js +3001 -0
  6. package/dist/chunk-2ZIOFEIS.js.map +1 -0
  7. package/dist/chunk-3DWB2PUF.js +2991 -0
  8. package/dist/chunk-3DWB2PUF.js.map +1 -0
  9. package/dist/chunk-4ZALUTZS.js +2936 -0
  10. package/dist/chunk-4ZALUTZS.js.map +1 -0
  11. package/dist/chunk-5ARKSRED.js +2951 -0
  12. package/dist/chunk-5ARKSRED.js.map +1 -0
  13. package/dist/chunk-5OSGSJMM.js +2981 -0
  14. package/dist/chunk-5OSGSJMM.js.map +1 -0
  15. package/dist/chunk-5YQQMEF3.js +2981 -0
  16. package/dist/chunk-5YQQMEF3.js.map +1 -0
  17. package/dist/chunk-6QCJK7H7.js +2991 -0
  18. package/dist/chunk-6QCJK7H7.js.map +1 -0
  19. package/dist/chunk-6QDYERZD.js +2944 -0
  20. package/dist/chunk-6QDYERZD.js.map +1 -0
  21. package/dist/chunk-76TP67ED.js +2984 -0
  22. package/dist/chunk-76TP67ED.js.map +1 -0
  23. package/dist/chunk-A6KMO4JV.js +2949 -0
  24. package/dist/chunk-A6KMO4JV.js.map +1 -0
  25. package/dist/chunk-B7FTWSTM.js +2938 -0
  26. package/dist/chunk-B7FTWSTM.js.map +1 -0
  27. package/dist/chunk-BCO6M26F.js +2940 -0
  28. package/dist/chunk-BCO6M26F.js.map +1 -0
  29. package/dist/chunk-C3UD2AZ5.js +2637 -0
  30. package/dist/chunk-C3UD2AZ5.js.map +1 -0
  31. package/dist/chunk-CEUP4NK2.js +2850 -0
  32. package/dist/chunk-CEUP4NK2.js.map +1 -0
  33. package/dist/chunk-DBNSBJO7.js +2993 -0
  34. package/dist/chunk-DBNSBJO7.js.map +1 -0
  35. package/dist/chunk-ECIHUVU7.js +2986 -0
  36. package/dist/chunk-ECIHUVU7.js.map +1 -0
  37. package/dist/chunk-EERNYLFL.js +2860 -0
  38. package/dist/chunk-EERNYLFL.js.map +1 -0
  39. package/dist/chunk-EKFDJX4G.js +2872 -0
  40. package/dist/chunk-EKFDJX4G.js.map +1 -0
  41. package/dist/chunk-GJA3GJUZ.js +2844 -0
  42. package/dist/chunk-GJA3GJUZ.js.map +1 -0
  43. package/dist/chunk-HDOTOZNA.js +2936 -0
  44. package/dist/chunk-HDOTOZNA.js.map +1 -0
  45. package/dist/chunk-IOKUV7FD.js +2658 -0
  46. package/dist/chunk-IOKUV7FD.js.map +1 -0
  47. package/dist/chunk-IUIICQU5.js +2946 -0
  48. package/dist/chunk-IUIICQU5.js.map +1 -0
  49. package/dist/chunk-J2UYZI6D.js +2946 -0
  50. package/dist/chunk-J2UYZI6D.js.map +1 -0
  51. package/dist/chunk-J7YASALS.js +2859 -0
  52. package/dist/chunk-J7YASALS.js.map +1 -0
  53. package/dist/chunk-JIEUYBQT.js +2658 -0
  54. package/dist/chunk-JIEUYBQT.js.map +1 -0
  55. package/dist/chunk-K2H7JLQW.js +2952 -0
  56. package/dist/chunk-K2H7JLQW.js.map +1 -0
  57. package/dist/chunk-KUXYBP66.js +2953 -0
  58. package/dist/chunk-KUXYBP66.js.map +1 -0
  59. package/dist/chunk-KYMYNYFV.js +2656 -0
  60. package/dist/chunk-KYMYNYFV.js.map +1 -0
  61. package/dist/chunk-MMXL343D.js +2974 -0
  62. package/dist/chunk-MMXL343D.js.map +1 -0
  63. package/dist/chunk-MXJL3EPE.js +2986 -0
  64. package/dist/chunk-MXJL3EPE.js.map +1 -0
  65. package/dist/chunk-MYZ25B2R.js +2995 -0
  66. package/dist/chunk-MYZ25B2R.js.map +1 -0
  67. package/dist/chunk-NTMEYB7B.js +2949 -0
  68. package/dist/chunk-NTMEYB7B.js.map +1 -0
  69. package/dist/chunk-PVJXX6GP.js +2640 -0
  70. package/dist/chunk-PVJXX6GP.js.map +1 -0
  71. package/dist/chunk-Q6ERDPQR.js +2981 -0
  72. package/dist/chunk-Q6ERDPQR.js.map +1 -0
  73. package/dist/chunk-S2SSBMWJ.js +2658 -0
  74. package/dist/chunk-S2SSBMWJ.js.map +1 -0
  75. package/dist/chunk-SLVDAZSX.js +2946 -0
  76. package/dist/chunk-SLVDAZSX.js.map +1 -0
  77. package/dist/chunk-UQBPYONV.js +2991 -0
  78. package/dist/chunk-UQBPYONV.js.map +1 -0
  79. package/dist/chunk-UXMNBS22.js +2955 -0
  80. package/dist/chunk-UXMNBS22.js.map +1 -0
  81. package/dist/chunk-WEGED7TA.js +2991 -0
  82. package/dist/chunk-WEGED7TA.js.map +1 -0
  83. package/dist/chunk-WEPTBLWX.js +2847 -0
  84. package/dist/chunk-WEPTBLWX.js.map +1 -0
  85. package/dist/chunk-WG4ZQMPS.js +2844 -0
  86. package/dist/chunk-WG4ZQMPS.js.map +1 -0
  87. package/dist/chunk-XEPEBHAW.js +2808 -0
  88. package/dist/chunk-XEPEBHAW.js.map +1 -0
  89. package/dist/chunk-XG2OYFX6.js +2925 -0
  90. package/dist/chunk-XG2OYFX6.js.map +1 -0
  91. package/dist/chunk-ZAMJEU42.js +2992 -0
  92. package/dist/chunk-ZAMJEU42.js.map +1 -0
  93. package/dist/chunk-ZRAM6FXB.js +2949 -0
  94. package/dist/chunk-ZRAM6FXB.js.map +1 -0
  95. package/dist/components/CsvViewer/CsvViewer.d.ts +51 -0
  96. package/dist/components/CsvViewer/CsvViewer.d.ts.map +1 -0
  97. package/dist/components/CsvViewer/index.d.ts +2 -0
  98. package/dist/components/CsvViewer/index.d.ts.map +1 -0
  99. package/dist/components/DataTable/DataTable.d.ts +19 -1
  100. package/dist/components/DataTable/DataTable.d.ts.map +1 -1
  101. package/dist/components/DropdownMenu/DropdownMenu.d.ts +12 -2
  102. package/dist/components/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  103. package/dist/components/ExecutionConsole/ExecutionConsole.d.ts +8 -2
  104. package/dist/components/ExecutionConsole/ExecutionConsole.d.ts.map +1 -1
  105. package/dist/components/FlowMinimap/FlowMinimap.d.ts +17 -1
  106. package/dist/components/FlowMinimap/FlowMinimap.d.ts.map +1 -1
  107. package/dist/components/FlowToolbar/FlowToolbar.d.ts +16 -10
  108. package/dist/components/FlowToolbar/FlowToolbar.d.ts.map +1 -1
  109. package/dist/components/JsonViewer/JsonViewer.d.ts +42 -7
  110. package/dist/components/JsonViewer/JsonViewer.d.ts.map +1 -1
  111. package/dist/components/JsonViewer/index.d.ts +1 -1
  112. package/dist/components/JsonViewer/index.d.ts.map +1 -1
  113. package/dist/components/Select/Select.d.ts.map +1 -1
  114. package/dist/components/WorkflowHeader/WorkflowHeader.d.ts +130 -0
  115. package/dist/components/WorkflowHeader/WorkflowHeader.d.ts.map +1 -0
  116. package/dist/components/WorkflowHeader/WorkflowHeaderExpanded.d.ts +69 -0
  117. package/dist/components/WorkflowHeader/WorkflowHeaderExpanded.d.ts.map +1 -0
  118. package/dist/components/WorkflowHeader/index.d.ts +3 -0
  119. package/dist/components/WorkflowHeader/index.d.ts.map +1 -0
  120. package/dist/components/WorkflowHeader/misc/WorkflowHeaderCentered.d.ts +40 -0
  121. package/dist/components/WorkflowHeader/misc/WorkflowHeaderCentered.d.ts.map +1 -0
  122. package/dist/components/WorkflowHeader/misc/WorkflowHeaderCommand.d.ts +39 -0
  123. package/dist/components/WorkflowHeader/misc/WorkflowHeaderCommand.d.ts.map +1 -0
  124. package/dist/components/WorkflowHeader/misc/WorkflowHeaderMinimal.d.ts +44 -0
  125. package/dist/components/WorkflowHeader/misc/WorkflowHeaderMinimal.d.ts.map +1 -0
  126. package/dist/components/WorkflowHeader/misc/WorkflowHeaderRail.d.ts +45 -0
  127. package/dist/components/WorkflowHeader/misc/WorkflowHeaderRail.d.ts.map +1 -0
  128. package/dist/components/WorkflowHeader/misc/WorkflowHeaderStudio.d.ts +48 -0
  129. package/dist/components/WorkflowHeader/misc/WorkflowHeaderStudio.d.ts.map +1 -0
  130. package/dist/components/WorkflowHeader/misc/WorkflowHeaderTiered.d.ts +52 -0
  131. package/dist/components/WorkflowHeader/misc/WorkflowHeaderTiered.d.ts.map +1 -0
  132. package/dist/components/XmlViewer/XmlViewer.d.ts +26 -1
  133. package/dist/components/XmlViewer/XmlViewer.d.ts.map +1 -1
  134. package/dist/components/XmlViewer/index.d.ts +1 -1
  135. package/dist/components/XmlViewer/index.d.ts.map +1 -1
  136. package/dist/components/YamlViewer/YamlViewer.d.ts +26 -1
  137. package/dist/components/YamlViewer/YamlViewer.d.ts.map +1 -1
  138. package/dist/components/YamlViewer/index.d.ts +1 -1
  139. package/dist/components/YamlViewer/index.d.ts.map +1 -1
  140. package/dist/hooks/useRelativeTime.d.ts +28 -0
  141. package/dist/hooks/useRelativeTime.d.ts.map +1 -0
  142. package/dist/hooks/useWorkflowRuntime.d.ts +20 -0
  143. package/dist/hooks/useWorkflowRuntime.d.ts.map +1 -0
  144. package/dist/index.cjs +4884 -3581
  145. package/dist/index.cjs.map +1 -1
  146. package/dist/index.d.ts +7 -3
  147. package/dist/index.d.ts.map +1 -1
  148. package/dist/index.js +4505 -3515
  149. package/dist/index.js.map +1 -1
  150. package/dist/styles.css +1 -1
  151. package/dist/workflow/components/ConfigPanel/ConfigPanel.d.ts +27 -9
  152. package/dist/workflow/components/ConfigPanel/ConfigPanel.d.ts.map +1 -1
  153. package/dist/workflow/components/FlowCanvas/FlowCanvas.d.ts +49 -1
  154. package/dist/workflow/components/FlowCanvas/FlowCanvas.d.ts.map +1 -1
  155. package/dist/workflow/components/FlowEdge/FlowEdge.d.ts.map +1 -1
  156. package/dist/workflow/components/FxPanel/DataRefButton.d.ts +9 -0
  157. package/dist/workflow/components/FxPanel/DataRefButton.d.ts.map +1 -0
  158. package/dist/workflow/components/FxPanel/FxPanel.d.ts +74 -0
  159. package/dist/workflow/components/FxPanel/FxPanel.d.ts.map +1 -0
  160. package/dist/workflow/components/FxPanel/FxToggleButton.d.ts +9 -0
  161. package/dist/workflow/components/FxPanel/FxToggleButton.d.ts.map +1 -0
  162. package/dist/workflow/components/Handle/Handle.d.ts +9 -1
  163. package/dist/workflow/components/Handle/Handle.d.ts.map +1 -1
  164. package/dist/workflow/components/Handle/handleRegistry.d.ts +19 -0
  165. package/dist/workflow/components/Handle/handleRegistry.d.ts.map +1 -1
  166. package/dist/workflow/components/kinds/index.d.ts +4 -0
  167. package/dist/workflow/components/kinds/index.d.ts.map +1 -1
  168. package/dist/workflow/index.d.ts +3 -1
  169. package/dist/workflow/index.d.ts.map +1 -1
  170. package/dist/workflow/store/selectors.d.ts +12 -0
  171. package/dist/workflow/store/selectors.d.ts.map +1 -1
  172. package/dist/workflow/utils/parenting.d.ts +5 -3
  173. package/dist/workflow/utils/parenting.d.ts.map +1 -1
  174. package/dist/workflow.cjs +1108 -471
  175. package/dist/workflow.cjs.map +1 -1
  176. package/dist/workflow.js +535 -250
  177. package/dist/workflow.js.map +1 -1
  178. package/package.json +8 -3
package/dist/workflow.cjs CHANGED
@@ -26,11 +26,13 @@ __export(workflow_exports, {
26
26
  DEFAULT_NODE_HEIGHT: () => DEFAULT_NODE_HEIGHT,
27
27
  DEFAULT_NODE_KINDS: () => DEFAULT_NODE_KINDS,
28
28
  DEFAULT_NODE_WIDTH: () => DEFAULT_NODE_WIDTH,
29
+ DataRefButton: () => DataRefButton,
29
30
  ErrorNode: () => ErrorNode,
30
31
  FlowCanvas: () => FlowCanvas,
31
32
  FlowEdge: () => FlowEdge,
32
33
  FlowNode: () => FlowNode,
33
34
  ForEachNode: () => ForEachNode,
35
+ FxPanel: () => FxPanel,
34
36
  GroupNode: () => GroupNode,
35
37
  Handle: () => Handle,
36
38
  HttpRequestNode: () => HttpRequestNode,
@@ -77,7 +79,8 @@ __export(workflow_exports, {
77
79
  useNodeData: () => useNodeData,
78
80
  useNodes: () => useNodes,
79
81
  useSelection: () => useSelection,
80
- useViewport: () => useViewport
82
+ useViewport: () => useViewport,
83
+ useViewportOrNull: () => useViewportOrNull
81
84
  });
82
85
  module.exports = __toCommonJS(workflow_exports);
83
86
 
@@ -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
  );
@@ -2001,56 +2192,35 @@ var ForEachNode = ({
2001
2192
  node
2002
2193
  }) => {
2003
2194
  const d = node.data ?? {};
2004
- const iteratorExpr = d.iterator ?? d.description ?? "items[]";
2005
- const collapsed = !!d.collapsed;
2006
- const hiddenCount = d.hiddenCount;
2007
- const bridge = useFlowNodeBridge();
2008
- const onChevronClick = (e) => {
2009
- e.stopPropagation();
2010
- bridge.toggleNodeCollapse(node.id);
2011
- };
2012
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2013
- "div",
2195
+ const branches = d.branches ?? [
2196
+ { id: "each", label: "Each" },
2197
+ { id: "done", label: "Done" }
2198
+ ];
2199
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2200
+ BaseNode,
2014
2201
  {
2015
- className: "ods-flow-foreach",
2016
- "data-collapsed": collapsed ? "true" : "false",
2017
- style: {
2018
- width: node.width ?? 420,
2019
- height: collapsed ? 40 : node.height ?? 260
2020
- },
2202
+ kind: d.kind ?? "FOR EACH",
2203
+ icon: d.icon,
2204
+ title: d.title ?? "For each",
2205
+ chip: d.chip ?? d.badge,
2206
+ description: d.description ?? d.subtitle ?? d.iterator,
2207
+ valueChip: d.valueChip,
2208
+ status: d.status,
2209
+ accent: "amber",
2021
2210
  children: [
2022
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-foreach__header", children: [
2023
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2024
- "button",
2025
- {
2026
- type: "button",
2027
- className: "ods-flow-foreach__chevron",
2028
- "data-flow-no-drag": "true",
2029
- "aria-label": collapsed ? "Expand iterator" : "Collapse iterator",
2030
- "aria-expanded": !collapsed,
2031
- onClick: onChevronClick,
2032
- onPointerDown: (e) => e.stopPropagation(),
2033
- children: collapsed ? "\u25B8" : "\u25BE"
2034
- }
2035
- ),
2036
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-foreach__icon", "aria-hidden": "true", children: "\u21BB" }),
2037
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-foreach__title", children: d.title ?? "For each" }),
2038
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { className: "ods-flow-foreach__iterator", children: iteratorExpr }),
2039
- collapsed && hiddenCount !== void 0 && hiddenCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ods-flow-foreach__count", "aria-label": `${hiddenCount} hidden steps`, children: [
2040
- hiddenCount,
2041
- " steps"
2042
- ] })
2043
- ] }),
2044
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top", id: "__group_in" }),
2045
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom", id: "__group_out" }),
2046
- !collapsed && // Expanded mode — full 3-handle iterator surface (in / item / done).
2047
- // `in` shares the top edge with `__group_in`; consumers wiring an
2048
- // explicit `targetHandle: "in"` get the labelled, visible variant.
2049
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2050
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top", id: "in", label: "in" }),
2051
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "right", id: "item", label: "item" }),
2052
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom", id: "done", label: "done" })
2053
- ] })
2211
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2212
+ branches.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2213
+ Handle,
2214
+ {
2215
+ type: "source",
2216
+ position: "bottom",
2217
+ id: b.id,
2218
+ index: i,
2219
+ total: branches.length,
2220
+ label: b.label
2221
+ },
2222
+ b.id
2223
+ ))
2054
2224
  ]
2055
2225
  }
2056
2226
  );
@@ -2059,7 +2229,7 @@ var OutputNode = ({
2059
2229
  node
2060
2230
  }) => {
2061
2231
  const d = node.data ?? {};
2062
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2232
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2063
2233
  BaseNode,
2064
2234
  {
2065
2235
  kind: d.kind ?? "OUTPUT",
@@ -2069,7 +2239,7 @@ var OutputNode = ({
2069
2239
  description: d.description ?? d.subtitle,
2070
2240
  status: d.status,
2071
2241
  accent: "green",
2072
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" })
2242
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" })
2073
2243
  }
2074
2244
  );
2075
2245
  };
@@ -2077,7 +2247,7 @@ var ErrorNode = ({
2077
2247
  node
2078
2248
  }) => {
2079
2249
  const d = node.data ?? {};
2080
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2250
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2081
2251
  BaseNode,
2082
2252
  {
2083
2253
  kind: d.kind ?? "ERROR",
@@ -2088,8 +2258,8 @@ var ErrorNode = ({
2088
2258
  status: d.status ?? "error",
2089
2259
  accent: "red",
2090
2260
  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" })
2261
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2262
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
2093
2263
  ]
2094
2264
  }
2095
2265
  );
@@ -2100,7 +2270,7 @@ var WaitNode = ({
2100
2270
  const d = node.data ?? {};
2101
2271
  const waitMs = d.waitMs;
2102
2272
  const durationChip = waitMs ? `${Math.round(waitMs / 100) / 10}s` : void 0;
2103
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2273
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2104
2274
  BaseNode,
2105
2275
  {
2106
2276
  kind: d.kind ?? "WAIT",
@@ -2111,8 +2281,8 @@ var WaitNode = ({
2111
2281
  status: d.status,
2112
2282
  accent: "violet",
2113
2283
  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" })
2284
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2285
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
2116
2286
  ]
2117
2287
  }
2118
2288
  );
@@ -2125,7 +2295,7 @@ var ParallelNode = ({
2125
2295
  { id: "a", label: "a" },
2126
2296
  { id: "b", label: "b" }
2127
2297
  ];
2128
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2298
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2129
2299
  BaseNode,
2130
2300
  {
2131
2301
  kind: d.kind ?? "PARALLEL",
@@ -2136,8 +2306,8 @@ var ParallelNode = ({
2136
2306
  status: d.status,
2137
2307
  accent: "blue",
2138
2308
  children: [
2139
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" }),
2140
- branches.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2309
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2310
+ branches.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2141
2311
  Handle,
2142
2312
  {
2143
2313
  type: "source",
@@ -2157,7 +2327,7 @@ var StickyNode = ({
2157
2327
  node
2158
2328
  }) => {
2159
2329
  const d = node.data ?? {};
2160
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2330
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2161
2331
  "div",
2162
2332
  {
2163
2333
  className: "ods-flow-sticky",
@@ -2166,8 +2336,8 @@ var StickyNode = ({
2166
2336
  minHeight: node.height ?? 120
2167
2337
  },
2168
2338
  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 })
2339
+ d.title && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ods-flow-sticky__title", children: d.title }),
2340
+ d.description && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ods-flow-sticky__body", children: d.description })
2171
2341
  ]
2172
2342
  }
2173
2343
  );
@@ -2176,7 +2346,7 @@ var WebhookNode = ({
2176
2346
  node
2177
2347
  }) => {
2178
2348
  const d = node.data ?? {};
2179
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2349
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2180
2350
  BaseNode,
2181
2351
  {
2182
2352
  kind: d.kind ?? "WEBHOOK",
@@ -2187,7 +2357,7 @@ var WebhookNode = ({
2187
2357
  valueChip: d.valueChip,
2188
2358
  status: d.status,
2189
2359
  accent: "blue",
2190
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom" })
2360
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
2191
2361
  }
2192
2362
  );
2193
2363
  };
@@ -2195,7 +2365,7 @@ var HttpRequestNode = ({
2195
2365
  node
2196
2366
  }) => {
2197
2367
  const d = node.data ?? {};
2198
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2368
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2199
2369
  BaseNode,
2200
2370
  {
2201
2371
  kind: d.kind ?? "HTTP",
@@ -2207,8 +2377,8 @@ var HttpRequestNode = ({
2207
2377
  status: d.status,
2208
2378
  accent: "blue",
2209
2379
  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" })
2380
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "target", position: "top" }),
2381
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Handle, { type: "source", position: "bottom" })
2212
2382
  ]
2213
2383
  }
2214
2384
  );
@@ -2229,52 +2399,60 @@ var DEFAULT_NODE_KINDS = {
2229
2399
  };
2230
2400
 
2231
2401
  // src/workflow/components/FlowCanvas/FlowCanvas.tsx
2232
- var import_jsx_runtime7 = require("react/jsx-runtime");
2402
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2233
2403
  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
- }) {
2404
+ function FlowCanvas(props) {
2405
+ const viewportPropProvided = props.viewport !== void 0 || props.defaultViewport !== void 0;
2406
+ const {
2407
+ nodes,
2408
+ edges,
2409
+ onNodesChange,
2410
+ onEdgesChange,
2411
+ viewport: controlledViewport,
2412
+ defaultViewport = DEFAULT_VIEWPORT2,
2413
+ onViewportChange,
2414
+ minZoom = 0.25,
2415
+ maxZoom = 2,
2416
+ fitViewOnInit,
2417
+ nodeKinds,
2418
+ onConnect,
2419
+ onConnectStart,
2420
+ onConnectEnd,
2421
+ isValidConnection,
2422
+ onSelectionChange,
2423
+ onPaneClick,
2424
+ onNodeClick,
2425
+ onEdgeClick,
2426
+ onEdgeLabelChange,
2427
+ onInit,
2428
+ onBeforeDelete,
2429
+ onNodeContextMenu,
2430
+ onEdgeContextMenu,
2431
+ onPaneContextMenu,
2432
+ nodesDraggable = true,
2433
+ nodesConnectable = true,
2434
+ panOnDrag: panOnDragProp,
2435
+ zoomOnScroll: zoomOnScrollProp,
2436
+ preset = "mouse",
2437
+ paneClickDistance = 4,
2438
+ paneClickClearsSelection = true,
2439
+ background = "dots",
2440
+ gridSize = 20,
2441
+ snapToGrid = false,
2442
+ nodeCollisionGap = -1,
2443
+ subflowCollisionGap,
2444
+ reparentOnDrag = false,
2445
+ autoResizeContainers = false,
2446
+ containerAutoResizePadding = 32,
2447
+ containerMinWidth = 320,
2448
+ containerMinHeight = 200,
2449
+ height = "100%",
2450
+ width = "100%",
2451
+ className,
2452
+ style,
2453
+ children,
2454
+ emptyState
2455
+ } = props;
2278
2456
  const presetDefaults = {
2279
2457
  mouse: { panOnDrag: true, zoomOnScroll: true },
2280
2458
  trackpad: { panOnDrag: true, zoomOnScroll: true },
@@ -2282,46 +2460,46 @@ function FlowCanvas({
2282
2460
  };
2283
2461
  const panOnDrag = panOnDragProp ?? presetDefaults[preset].panOnDrag;
2284
2462
  const zoomOnScroll = zoomOnScrollProp ?? presetDefaults[preset].zoomOnScroll;
2285
- const store = (0, import_react14.useState)(
2463
+ const store = (0, import_react15.useState)(
2286
2464
  () => createFlowStore({
2287
2465
  initialNodes: nodes,
2288
2466
  initialEdges: edges,
2289
2467
  initialViewport: controlledViewport ?? defaultViewport
2290
2468
  })
2291
2469
  )[0];
2292
- const handleRegistry = (0, import_react14.useState)(() => createHandleRegistry())[0];
2293
- const [handleVersion, setHandleVersion] = (0, import_react14.useState)(0);
2294
- (0, import_react14.useEffect)(() => {
2470
+ const handleRegistry = (0, import_react15.useState)(() => createHandleRegistry())[0];
2471
+ const [handleVersion, setHandleVersion] = (0, import_react15.useState)(0);
2472
+ (0, import_react15.useEffect)(() => {
2295
2473
  const unsub = handleRegistry.subscribe(() => {
2296
2474
  setHandleVersion((v) => v + 1);
2297
2475
  });
2298
2476
  return unsub;
2299
2477
  }, [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);
2478
+ const kinds = (0, import_react15.useMemo)(() => buildNodeKindRegistry(DEFAULT_NODE_KINDS, nodeKinds), [nodeKinds]);
2479
+ const containerRef = (0, import_react15.useRef)(null);
2480
+ (0, import_react15.useEffect)(() => store.setNodes(nodes), [store, nodes]);
2481
+ (0, import_react15.useEffect)(() => store.setEdges(edges), [store, edges]);
2482
+ const [uncontrolledVp, setUncontrolledVp] = (0, import_react15.useState)(controlledViewport ?? defaultViewport);
2305
2483
  const viewport = controlledViewport ?? uncontrolledVp;
2306
- (0, import_react14.useEffect)(() => store.setViewport(viewport), [store, viewport]);
2307
- const setViewport = (0, import_react14.useCallback)(
2484
+ (0, import_react15.useEffect)(() => store.setViewport(viewport), [store, viewport]);
2485
+ const setViewport = (0, import_react15.useCallback)(
2308
2486
  (next) => {
2309
2487
  if (controlledViewport === void 0) setUncontrolledVp(next);
2310
2488
  onViewportChange?.(next);
2311
2489
  },
2312
2490
  [controlledViewport, onViewportChange]
2313
2491
  );
2314
- const selectedNodeIds = (0, import_react14.useMemo)(() => {
2492
+ const selectedNodeIds = (0, import_react15.useMemo)(() => {
2315
2493
  const s = /* @__PURE__ */ new Set();
2316
2494
  for (const n of nodes) if (n.selected) s.add(n.id);
2317
2495
  return s;
2318
2496
  }, [nodes]);
2319
- const selectedEdgeIds = (0, import_react14.useMemo)(() => {
2497
+ const selectedEdgeIds = (0, import_react15.useMemo)(() => {
2320
2498
  const s = /* @__PURE__ */ new Set();
2321
2499
  for (const e of edges) if (e.selected) s.add(e.id);
2322
2500
  return s;
2323
2501
  }, [edges]);
2324
- (0, import_react14.useEffect)(() => {
2502
+ (0, import_react15.useEffect)(() => {
2325
2503
  store.setSelection(selectedNodeIds, selectedEdgeIds);
2326
2504
  if (onSelectionChange) {
2327
2505
  onSelectionChange({
@@ -2330,7 +2508,7 @@ function FlowCanvas({
2330
2508
  });
2331
2509
  }
2332
2510
  }, [store, selectedNodeIds, selectedEdgeIds, nodes, edges, onSelectionChange]);
2333
- const selectNode = (0, import_react14.useCallback)(
2511
+ const selectNode = (0, import_react15.useCallback)(
2334
2512
  (id, additive) => {
2335
2513
  const next = [];
2336
2514
  const nextEdges = [];
@@ -2353,14 +2531,14 @@ function FlowCanvas({
2353
2531
  },
2354
2532
  [nodes, edges, selectedNodeIds, onNodesChange, onEdgesChange]
2355
2533
  );
2356
- const notifyNodeClick = (0, import_react14.useCallback)(
2534
+ const notifyNodeClick = (0, import_react15.useCallback)(
2357
2535
  (id) => {
2358
2536
  const node = nodes.find((n) => n.id === id);
2359
2537
  if (node) onNodeClick?.(node);
2360
2538
  },
2361
2539
  [nodes, onNodeClick]
2362
2540
  );
2363
- const selectEdge = (0, import_react14.useCallback)(
2541
+ const selectEdge = (0, import_react15.useCallback)(
2364
2542
  (id, additive) => {
2365
2543
  const next = [];
2366
2544
  const nextNodes = [];
@@ -2385,7 +2563,7 @@ function FlowCanvas({
2385
2563
  },
2386
2564
  [nodes, edges, selectedEdgeIds, onEdgesChange, onNodesChange, onEdgeClick]
2387
2565
  );
2388
- const clearSelection = (0, import_react14.useCallback)(() => {
2566
+ const clearSelection = (0, import_react15.useCallback)(() => {
2389
2567
  const ns = [];
2390
2568
  const es = [];
2391
2569
  for (const n of nodes) if (n.selected) ns.push(change.node.select(n.id, false));
@@ -2393,9 +2571,9 @@ function FlowCanvas({
2393
2571
  if (ns.length) onNodesChange?.(ns);
2394
2572
  if (es.length) onEdgesChange?.(es);
2395
2573
  }, [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)(
2574
+ const dragRef = (0, import_react15.useRef)(null);
2575
+ const [draggingId, setDraggingId] = (0, import_react15.useState)(null);
2576
+ const beginNodeDrag = (0, import_react15.useCallback)(
2399
2577
  (nodeId, pointerId, clientX, clientY, altKey = false) => {
2400
2578
  if (!nodesDraggable) return;
2401
2579
  const node = nodes.find((n) => n.id === nodeId);
@@ -2404,6 +2582,9 @@ function FlowCanvas({
2404
2582
  id: d.id,
2405
2583
  startPosition: d.position
2406
2584
  }));
2585
+ const evaluateReparent = altKey || reparentOnDragRef.current;
2586
+ const hasParent = !!node.parentId;
2587
+ const skipClamp = altKey || reparentOnDragRef.current && !hasParent;
2407
2588
  dragRef.current = {
2408
2589
  pointerId,
2409
2590
  nodeId,
@@ -2411,7 +2592,8 @@ function FlowCanvas({
2411
2592
  startClientY: clientY,
2412
2593
  startPosition: node.position,
2413
2594
  descendants: kids,
2414
- altDetach: altKey && !!node.parentId,
2595
+ skipClamp,
2596
+ evaluateReparent,
2415
2597
  rafScheduled: false,
2416
2598
  nextDelta: null
2417
2599
  };
@@ -2420,12 +2602,12 @@ function FlowCanvas({
2420
2602
  },
2421
2603
  [nodes, nodesDraggable, selectNode]
2422
2604
  );
2423
- const [conn, setConn] = (0, import_react14.useState)(null);
2424
- const connRef = (0, import_react14.useRef)(null);
2425
- (0, import_react14.useEffect)(() => {
2605
+ const [conn, setConn] = (0, import_react15.useState)(null);
2606
+ const connRef = (0, import_react15.useRef)(null);
2607
+ (0, import_react15.useEffect)(() => {
2426
2608
  connRef.current = conn;
2427
2609
  }, [conn]);
2428
- const beginConnection = (0, import_react14.useCallback)(
2610
+ const beginConnection = (0, import_react15.useCallback)(
2429
2611
  (nodeId, handleId, handleType, pointerId, clientX, clientY) => {
2430
2612
  if (!nodesConnectable) return;
2431
2613
  const node = nodes.find((n) => n.id === nodeId);
@@ -2458,63 +2640,83 @@ function FlowCanvas({
2458
2640
  },
2459
2641
  [nodes, nodesConnectable, handleRegistry, viewport, store, onConnectStart]
2460
2642
  );
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)(() => {
2643
+ const viewportRef = (0, import_react15.useRef)(viewport);
2644
+ const nodesRef = (0, import_react15.useRef)(nodes);
2645
+ const edgesRef = (0, import_react15.useRef)(edges);
2646
+ const onNodesChangeRefForInstance = (0, import_react15.useRef)(onNodesChange);
2647
+ const onEdgesChangeRefForInstance = (0, import_react15.useRef)(onEdgesChange);
2648
+ const onBeforeDeleteRef = (0, import_react15.useRef)(onBeforeDelete);
2649
+ const snapToGridRef = (0, import_react15.useRef)(snapToGrid);
2650
+ const gridSizeRef = (0, import_react15.useRef)(gridSize);
2651
+ const nodeCollisionGapRef = (0, import_react15.useRef)(nodeCollisionGap);
2652
+ const subflowCollisionGapRef = (0, import_react15.useRef)(subflowCollisionGap ?? nodeCollisionGap);
2653
+ const reparentOnDragRef = (0, import_react15.useRef)(reparentOnDrag);
2654
+ const autoResizeContainersRef = (0, import_react15.useRef)(autoResizeContainers);
2655
+ const containerAutoResizePaddingRef = (0, import_react15.useRef)(containerAutoResizePadding);
2656
+ const containerMinWidthRef = (0, import_react15.useRef)(containerMinWidth);
2657
+ const containerMinHeightRef = (0, import_react15.useRef)(containerMinHeight);
2658
+ (0, import_react15.useEffect)(() => {
2472
2659
  edgesRef.current = edges;
2473
2660
  }, [edges]);
2474
- (0, import_react14.useEffect)(() => {
2661
+ (0, import_react15.useEffect)(() => {
2475
2662
  onNodesChangeRefForInstance.current = onNodesChange;
2476
2663
  }, [onNodesChange]);
2477
- (0, import_react14.useEffect)(() => {
2664
+ (0, import_react15.useEffect)(() => {
2478
2665
  onEdgesChangeRefForInstance.current = onEdgesChange;
2479
2666
  }, [onEdgesChange]);
2480
- (0, import_react14.useEffect)(() => {
2667
+ (0, import_react15.useEffect)(() => {
2481
2668
  onBeforeDeleteRef.current = onBeforeDelete;
2482
2669
  }, [onBeforeDelete]);
2483
- (0, import_react14.useEffect)(() => {
2670
+ (0, import_react15.useEffect)(() => {
2484
2671
  snapToGridRef.current = snapToGrid;
2485
2672
  }, [snapToGrid]);
2486
- (0, import_react14.useEffect)(() => {
2673
+ (0, import_react15.useEffect)(() => {
2487
2674
  gridSizeRef.current = gridSize;
2488
2675
  }, [gridSize]);
2489
- (0, import_react14.useEffect)(() => {
2676
+ (0, import_react15.useEffect)(() => {
2490
2677
  nodeCollisionGapRef.current = nodeCollisionGap;
2491
2678
  }, [nodeCollisionGap]);
2492
- (0, import_react14.useEffect)(() => {
2679
+ (0, import_react15.useEffect)(() => {
2493
2680
  subflowCollisionGapRef.current = subflowCollisionGap ?? nodeCollisionGap;
2494
2681
  }, [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)(() => {
2682
+ (0, import_react15.useEffect)(() => {
2683
+ reparentOnDragRef.current = reparentOnDrag;
2684
+ }, [reparentOnDrag]);
2685
+ (0, import_react15.useEffect)(() => {
2686
+ autoResizeContainersRef.current = autoResizeContainers;
2687
+ }, [autoResizeContainers]);
2688
+ (0, import_react15.useEffect)(() => {
2689
+ containerAutoResizePaddingRef.current = containerAutoResizePadding;
2690
+ }, [containerAutoResizePadding]);
2691
+ (0, import_react15.useEffect)(() => {
2692
+ containerMinWidthRef.current = containerMinWidth;
2693
+ }, [containerMinWidth]);
2694
+ (0, import_react15.useEffect)(() => {
2695
+ containerMinHeightRef.current = containerMinHeight;
2696
+ }, [containerMinHeight]);
2697
+ const onNodesChangeRef = (0, import_react15.useRef)(onNodesChange);
2698
+ const onConnectRef = (0, import_react15.useRef)(onConnect);
2699
+ const onConnectEndRef = (0, import_react15.useRef)(onConnectEnd);
2700
+ const isValidConnectionRef = (0, import_react15.useRef)(isValidConnection);
2701
+ (0, import_react15.useEffect)(() => {
2500
2702
  viewportRef.current = viewport;
2501
2703
  }, [viewport]);
2502
- (0, import_react14.useEffect)(() => {
2704
+ (0, import_react15.useEffect)(() => {
2503
2705
  nodesRef.current = nodes;
2504
2706
  }, [nodes]);
2505
- (0, import_react14.useEffect)(() => {
2707
+ (0, import_react15.useEffect)(() => {
2506
2708
  onNodesChangeRef.current = onNodesChange;
2507
2709
  }, [onNodesChange]);
2508
- (0, import_react14.useEffect)(() => {
2710
+ (0, import_react15.useEffect)(() => {
2509
2711
  onConnectRef.current = onConnect;
2510
2712
  }, [onConnect]);
2511
- (0, import_react14.useEffect)(() => {
2713
+ (0, import_react15.useEffect)(() => {
2512
2714
  onConnectEndRef.current = onConnectEnd;
2513
2715
  }, [onConnectEnd]);
2514
- (0, import_react14.useEffect)(() => {
2716
+ (0, import_react15.useEffect)(() => {
2515
2717
  isValidConnectionRef.current = isValidConnection;
2516
2718
  }, [isValidConnection]);
2517
- (0, import_react14.useEffect)(() => {
2719
+ (0, import_react15.useEffect)(() => {
2518
2720
  const onPointerMove = (e) => {
2519
2721
  const vp = viewportRef.current;
2520
2722
  const drag = dragRef.current;
@@ -2536,7 +2738,7 @@ function FlowCanvas({
2536
2738
  x: d.startPosition.x + delta.dx,
2537
2739
  y: d.startPosition.y + delta.dy
2538
2740
  };
2539
- const clamped = d.altDetach ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2741
+ const clamped = d.skipClamp ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2540
2742
  const isContainer = dragNode.type === "group" || dragNode.type === "forEach";
2541
2743
  const gap = isContainer ? subflowCollisionGapRef.current : nodeCollisionGapRef.current;
2542
2744
  const excludeIds = /* @__PURE__ */ new Set([d.nodeId, ...d.descendants.map((kid) => kid.id)]);
@@ -2588,7 +2790,7 @@ function FlowCanvas({
2588
2790
  y: Math.round(proposed.y / g) * g
2589
2791
  };
2590
2792
  }
2591
- const clamped = drag.altDetach ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2793
+ const clamped = drag.skipClamp ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2592
2794
  const isContainer = dragNode.type === "group" || dragNode.type === "forEach";
2593
2795
  const gap = isContainer ? subflowCollisionGapRef.current : nodeCollisionGapRef.current;
2594
2796
  const excludeIds = /* @__PURE__ */ new Set([
@@ -2611,7 +2813,7 @@ function FlowCanvas({
2611
2813
  )
2612
2814
  );
2613
2815
  }
2614
- if (drag.altDetach && dragNode.parentId) {
2816
+ if (drag.evaluateReparent) {
2615
2817
  const targetGroup = findContainingGroup(
2616
2818
  {
2617
2819
  x: finalPos.x + (dragNode.width ?? 0) / 2,
@@ -2621,14 +2823,30 @@ function FlowCanvas({
2621
2823
  [drag.nodeId, ...drag.descendants.map((d) => d.id)]
2622
2824
  );
2623
2825
  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));
2826
+ if (nextParentId !== dragNode.parentId) {
2827
+ const updated = {
2828
+ ...dragNode,
2829
+ position: finalPos,
2830
+ parentId: nextParentId,
2831
+ // Adopting into a container always pins the child with
2832
+ // `extent: 'parent'` so it's clamped from the next drag
2833
+ // on. Detaching clears it.
2834
+ extent: nextParentId ? "parent" : void 0
2835
+ };
2836
+ changes.push(change.node.replace(drag.nodeId, updated));
2837
+ }
2838
+ }
2839
+ if (autoResizeContainersRef.current) {
2840
+ const containerChanges = computeContainerAutoResize(
2841
+ nodesRef.current,
2842
+ // Projected children: apply the position + reparent
2843
+ // changes we just built before measuring bboxes.
2844
+ changes,
2845
+ containerAutoResizePaddingRef.current,
2846
+ containerMinWidthRef.current,
2847
+ containerMinHeightRef.current
2848
+ );
2849
+ for (const c2 of containerChanges) changes.push(c2);
2632
2850
  }
2633
2851
  onNodesChangeRef.current?.(changes);
2634
2852
  }
@@ -2637,38 +2855,54 @@ function FlowCanvas({
2637
2855
  }
2638
2856
  const c = connRef.current;
2639
2857
  if (c && c.pointerId === e.pointerId) {
2640
- const target = e.target;
2641
- const handleEl = target?.closest("[data-handle-id]");
2858
+ const rect = containerRef.current?.getBoundingClientRect();
2859
+ const flowPos = rect ? screenToFlow({ x: e.clientX - rect.left, y: e.clientY - rect.top }, vp) : { x: 0, y: 0 };
2860
+ const snapRadius = 26 / Math.max(0.1, vp.zoom);
2861
+ let bestDesc = null;
2862
+ let bestDist = snapRadius;
2863
+ for (const desc of handleRegistry.all()) {
2864
+ if (desc.nodeId === c.from.nodeId) continue;
2865
+ if (desc.type === c.from.handleType) continue;
2866
+ if (!desc.canEnd) continue;
2867
+ const targetNode = nodesRef.current.find((n) => n.id === desc.nodeId);
2868
+ if (!targetNode || targetNode.hidden) continue;
2869
+ const centre = handleCentre(targetNode, desc.side, desc.index, desc.total);
2870
+ const dist = Math.hypot(centre.x - flowPos.x, centre.y - flowPos.y);
2871
+ if (dist < bestDist) {
2872
+ bestDist = dist;
2873
+ bestDesc = desc;
2874
+ }
2875
+ }
2642
2876
  let connection = null;
2643
2877
  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
- }
2878
+ if (bestDesc) {
2879
+ const source = c.from.handleType === "source" ? c.from : {
2880
+ nodeId: bestDesc.nodeId,
2881
+ handleId: bestDesc.handleId,
2882
+ handleType: "source"
2883
+ };
2884
+ const target2 = c.from.handleType === "target" ? c.from : {
2885
+ nodeId: bestDesc.nodeId,
2886
+ handleId: bestDesc.handleId,
2887
+ handleType: "target"
2888
+ };
2889
+ connection = {
2890
+ source: source.nodeId,
2891
+ sourceHandle: source.handleId,
2892
+ target: target2.nodeId,
2893
+ targetHandle: target2.handleId
2894
+ };
2895
+ connectedTo = {
2896
+ nodeId: bestDesc.nodeId,
2897
+ handleId: bestDesc.handleId,
2898
+ handleType: bestDesc.type
2899
+ };
2900
+ const validator = isValidConnectionRef.current;
2901
+ if (validator && !validator(connection)) {
2902
+ connection = null;
2903
+ connectedTo = void 0;
2668
2904
  }
2669
2905
  }
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
2906
  const endState = {
2673
2907
  cancelled: !connection,
2674
2908
  position: { x: e.clientX, y: e.clientY },
@@ -2701,7 +2935,26 @@ function FlowCanvas({
2701
2935
  window.removeEventListener("pointercancel", onPointerCancel);
2702
2936
  };
2703
2937
  }, [store]);
2704
- const panRef = (0, import_react14.useRef)(null);
2938
+ (0, import_react15.useEffect)(() => {
2939
+ if (!autoResizeContainers || draggingId) return;
2940
+ const resizeChanges = computeContainerAutoResize(
2941
+ nodes,
2942
+ [],
2943
+ containerAutoResizePadding,
2944
+ containerMinWidth,
2945
+ containerMinHeight
2946
+ );
2947
+ if (resizeChanges.length > 0) onNodesChange?.(resizeChanges);
2948
+ }, [
2949
+ nodes,
2950
+ draggingId,
2951
+ autoResizeContainers,
2952
+ containerAutoResizePadding,
2953
+ containerMinWidth,
2954
+ containerMinHeight,
2955
+ onNodesChange
2956
+ ]);
2957
+ const panRef = (0, import_react15.useRef)(null);
2705
2958
  const onCanvasPointerDown = (e) => {
2706
2959
  if (e.button !== 0) return;
2707
2960
  const t = e.target;
@@ -2726,7 +2979,7 @@ function FlowCanvas({
2726
2979
  moved: false
2727
2980
  };
2728
2981
  };
2729
- (0, import_react14.useEffect)(() => {
2982
+ (0, import_react15.useEffect)(() => {
2730
2983
  const onMove = (e) => {
2731
2984
  const pan = panRef.current;
2732
2985
  if (!pan || pan.pointerId !== e.pointerId) return;
@@ -2782,7 +3035,7 @@ function FlowCanvas({
2782
3035
  y: py - (py - viewport.y) * k
2783
3036
  });
2784
3037
  };
2785
- const dispatch = (0, import_react14.useCallback)(
3038
+ const dispatch = (0, import_react15.useCallback)(
2786
3039
  (a) => {
2787
3040
  if (a.type === "connection/start") {
2788
3041
  beginConnection(a.nodeId, a.handleId, a.handleType, a.pointerId, a.clientX, a.clientY);
@@ -2790,7 +3043,7 @@ function FlowCanvas({
2790
3043
  },
2791
3044
  [beginConnection]
2792
3045
  );
2793
- const reportDimensions = (0, import_react14.useCallback)(
3046
+ const reportDimensions = (0, import_react15.useCallback)(
2794
3047
  (nodeId, width2, height2) => {
2795
3048
  const node = nodes.find((n) => n.id === nodeId);
2796
3049
  if (!node) return;
@@ -2799,7 +3052,7 @@ function FlowCanvas({
2799
3052
  },
2800
3053
  [nodes, onNodesChange]
2801
3054
  );
2802
- const toggleNodeCollapseImpl = (0, import_react14.useCallback)(
3055
+ const toggleNodeCollapseImpl = (0, import_react15.useCallback)(
2803
3056
  (nodeId) => {
2804
3057
  const node = nodes.find((n) => n.id === nodeId);
2805
3058
  if (!node) return;
@@ -2812,7 +3065,7 @@ function FlowCanvas({
2812
3065
  },
2813
3066
  [nodes, onNodesChange]
2814
3067
  );
2815
- const deleteNodeImpl = (0, import_react14.useCallback)(
3068
+ const deleteNodeImpl = (0, import_react15.useCallback)(
2816
3069
  (nodeId) => {
2817
3070
  const incidentEdgeIds = edgesRef.current.filter((e) => e.source === nodeId || e.target === nodeId).map((e) => e.id);
2818
3071
  if (incidentEdgeIds.length > 0) {
@@ -2822,7 +3075,7 @@ function FlowCanvas({
2822
3075
  },
2823
3076
  [onNodesChange, onEdgesChange]
2824
3077
  );
2825
- const instance = (0, import_react14.useMemo)(
3078
+ const instance = (0, import_react15.useMemo)(
2826
3079
  () => ({
2827
3080
  // viewport
2828
3081
  getViewport: () => viewportRef.current,
@@ -2963,13 +3216,29 @@ function FlowCanvas({
2963
3216
  }),
2964
3217
  [setViewport, minZoom, maxZoom]
2965
3218
  );
2966
- const initFiredRef = (0, import_react14.useRef)(false);
2967
- (0, import_react14.useEffect)(() => {
3219
+ const initFiredRef = (0, import_react15.useRef)(false);
3220
+ (0, import_react15.useEffect)(() => {
2968
3221
  if (initFiredRef.current) return;
2969
3222
  initFiredRef.current = true;
2970
3223
  onInit?.(instance);
2971
3224
  }, [instance, onInit]);
2972
- (0, import_react14.useEffect)(() => {
3225
+ const fitOnInitFiredRef = (0, import_react15.useRef)(false);
3226
+ (0, import_react15.useEffect)(() => {
3227
+ if (fitOnInitFiredRef.current) return;
3228
+ const opt = fitViewOnInit;
3229
+ const shouldFit = opt === false ? false : opt !== void 0 ? true : !viewportPropProvided;
3230
+ if (!shouldFit) return;
3231
+ if (nodes.length === 0) return;
3232
+ const rect = containerRef.current?.getBoundingClientRect();
3233
+ if (!rect || rect.width === 0 || rect.height === 0) return;
3234
+ fitOnInitFiredRef.current = true;
3235
+ const fitOpts = typeof opt === "object" && opt !== null ? opt : void 0;
3236
+ const raf = requestAnimationFrame(() => {
3237
+ void instance.fitView(fitOpts);
3238
+ });
3239
+ return () => cancelAnimationFrame(raf);
3240
+ }, [fitViewOnInit, viewportPropProvided, nodes.length, instance]);
3241
+ (0, import_react15.useEffect)(() => {
2973
3242
  const onKey = (e) => {
2974
3243
  if (e.key !== "Backspace" && e.key !== "Delete") return;
2975
3244
  const target = e.target;
@@ -2990,7 +3259,7 @@ function FlowCanvas({
2990
3259
  window.addEventListener("keydown", onKey);
2991
3260
  return () => window.removeEventListener("keydown", onKey);
2992
3261
  }, [instance, store]);
2993
- const bridge = (0, import_react14.useMemo)(
3262
+ const bridge = (0, import_react15.useMemo)(
2994
3263
  () => ({
2995
3264
  beginNodeDrag,
2996
3265
  selectNode,
@@ -3010,16 +3279,16 @@ function FlowCanvas({
3010
3279
  toggleNodeCollapseImpl
3011
3280
  ]
3012
3281
  );
3013
- const [panGesture, setPanGesture] = (0, import_react14.useState)(false);
3282
+ const [panGesture, setPanGesture] = (0, import_react15.useState)(false);
3014
3283
  const isEmpty = nodes.length === 0 && edges.length === 0;
3015
3284
  const isConnecting = conn !== null;
3016
- const visibleNodes = (0, import_react14.useMemo)(() => nodes.filter((n) => !n.hidden), [nodes]);
3017
- const visibleEdges = (0, import_react14.useMemo)(() => {
3285
+ const visibleNodes = (0, import_react15.useMemo)(() => nodes.filter((n) => !n.hidden), [nodes]);
3286
+ const visibleEdges = (0, import_react15.useMemo)(() => {
3018
3287
  if (visibleNodes.length === nodes.length) return edges;
3019
3288
  const visibleIds = new Set(visibleNodes.map((n) => n.id));
3020
3289
  return edges.filter((e) => visibleIds.has(e.source) && visibleIds.has(e.target));
3021
3290
  }, [edges, nodes, visibleNodes]);
3022
- const orderedNodes = (0, import_react14.useMemo)(() => {
3291
+ const orderedNodes = (0, import_react15.useMemo)(() => {
3023
3292
  const isContainer = (n) => n.type === "group" || n.type === "forEach";
3024
3293
  const depth = (n) => {
3025
3294
  let d = 0;
@@ -3038,7 +3307,7 @@ function FlowCanvas({
3038
3307
  containers.sort((a, b) => depth(a) - depth(b));
3039
3308
  return [...containers, ...others];
3040
3309
  }, [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)(
3310
+ 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
3311
  "div",
3043
3312
  {
3044
3313
  ref: containerRef,
@@ -3083,7 +3352,7 @@ function FlowCanvas({
3083
3352
  },
3084
3353
  "data-empty": isEmpty ? "true" : void 0,
3085
3354
  children: [
3086
- background !== "none" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3355
+ background !== "none" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3087
3356
  "div",
3088
3357
  {
3089
3358
  className: cn(
@@ -3097,7 +3366,7 @@ function FlowCanvas({
3097
3366
  }
3098
3367
  }
3099
3368
  ),
3100
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3369
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3101
3370
  "div",
3102
3371
  {
3103
3372
  className: "ods-flow-canvas-v2__viewport",
@@ -3108,7 +3377,7 @@ function FlowCanvas({
3108
3377
  transformOrigin: "0 0"
3109
3378
  },
3110
3379
  children: [
3111
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3380
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3112
3381
  EdgesLayer,
3113
3382
  {
3114
3383
  edges: visibleEdges,
@@ -3123,7 +3392,7 @@ function FlowCanvas({
3123
3392
  orderedNodes.map((node) => {
3124
3393
  const Kind = kinds[node.type] ?? kinds.action;
3125
3394
  if (!Kind) return null;
3126
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3395
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3127
3396
  FlowNode,
3128
3397
  {
3129
3398
  node,
@@ -3138,7 +3407,7 @@ function FlowCanvas({
3138
3407
  ]
3139
3408
  }
3140
3409
  ),
3141
- isEmpty && emptyState && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ods-flow-canvas-v2__empty", children: emptyState }),
3410
+ isEmpty && emptyState && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "ods-flow-canvas-v2__empty", children: emptyState }),
3142
3411
  children
3143
3412
  ]
3144
3413
  }
@@ -3147,7 +3416,69 @@ function FlowCanvas({
3147
3416
  function onEdgesChangeRef(id, cb) {
3148
3417
  cb?.([change.edge.remove(id)]);
3149
3418
  }
3150
- var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3419
+ var CONTAINER_DROP_HEADROOM_W = 220;
3420
+ var CONTAINER_DROP_HEADROOM_H = 2 * 128;
3421
+ function computeContainerAutoResize(nodes, pending, padding, minWidth, minHeight) {
3422
+ const projected = /* @__PURE__ */ new Map();
3423
+ for (const n of nodes) projected.set(n.id, n);
3424
+ for (const c of pending) {
3425
+ if (c.type === "position" && c.position) {
3426
+ const cur = projected.get(c.id);
3427
+ if (cur) projected.set(c.id, { ...cur, position: c.position });
3428
+ } else if (c.type === "replace" && c.item) {
3429
+ projected.set(c.id, { ...projected.get(c.id), ...c.item });
3430
+ } else if (c.type === "add" && c.item) {
3431
+ projected.set(c.item.id, c.item);
3432
+ }
3433
+ }
3434
+ const HEADER = 56;
3435
+ const out = [];
3436
+ for (const parent of projected.values()) {
3437
+ if (parent.type !== "group" && parent.type !== "forEach") continue;
3438
+ const children = [];
3439
+ for (const n of projected.values()) {
3440
+ if (n.parentId === parent.id) children.push(n);
3441
+ }
3442
+ if (children.length === 0) {
3443
+ const curW2 = parent.width ?? 480;
3444
+ const curH2 = parent.height ?? 240;
3445
+ if (curW2 !== minWidth || curH2 !== minHeight) {
3446
+ out.push(change.node.dimensions(parent.id, { width: minWidth, height: minHeight }));
3447
+ }
3448
+ continue;
3449
+ }
3450
+ let minX = Number.POSITIVE_INFINITY;
3451
+ let minY = Number.POSITIVE_INFINITY;
3452
+ let maxX = Number.NEGATIVE_INFINITY;
3453
+ let maxY = Number.NEGATIVE_INFINITY;
3454
+ for (const c of children) {
3455
+ const cw = c.width ?? 240;
3456
+ const ch = c.height ?? 96;
3457
+ if (c.position.x < minX) minX = c.position.x;
3458
+ if (c.position.y < minY) minY = c.position.y;
3459
+ if (c.position.x + cw > maxX) maxX = c.position.x + cw;
3460
+ if (c.position.y + ch > maxY) maxY = c.position.y + ch;
3461
+ }
3462
+ const targetX = minX - padding;
3463
+ const targetY = minY - padding - HEADER;
3464
+ const curW = parent.width ?? 480;
3465
+ const curH = parent.height ?? 240;
3466
+ const fitW = maxX - minX + padding * 2 + CONTAINER_DROP_HEADROOM_W;
3467
+ const fitH = maxY - minY + padding * 2 + HEADER + CONTAINER_DROP_HEADROOM_H;
3468
+ const nextW = Math.max(minWidth, fitW);
3469
+ const nextH = Math.max(minHeight, fitH);
3470
+ const positionDirty = parent.position.x !== targetX || parent.position.y !== targetY;
3471
+ const sizeDirty = curW !== nextW || curH !== nextH;
3472
+ if (positionDirty) {
3473
+ out.push(change.node.position(parent.id, { x: targetX, y: targetY }, false));
3474
+ }
3475
+ if (sizeDirty) {
3476
+ out.push(change.node.dimensions(parent.id, { width: nextW, height: nextH }));
3477
+ }
3478
+ }
3479
+ return out;
3480
+ }
3481
+ var EdgesLayer = (0, import_react15.memo)(function EdgesLayer2({
3151
3482
  edges,
3152
3483
  nodes,
3153
3484
  onSelect,
@@ -3156,7 +3487,7 @@ var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3156
3487
  ghost,
3157
3488
  handleVersion: _handleVersion
3158
3489
  }) {
3159
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3490
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3160
3491
  "svg",
3161
3492
  {
3162
3493
  className: "ods-flow-canvas-v2__edges",
@@ -3164,7 +3495,7 @@ var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3164
3495
  width: "100%",
3165
3496
  height: "100%",
3166
3497
  children: [
3167
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("g", { style: { pointerEvents: "auto" }, children: edges.map((edge) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3498
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("g", { style: { pointerEvents: "auto" }, children: edges.map((edge) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3168
3499
  FlowEdge,
3169
3500
  {
3170
3501
  edge,
@@ -3176,7 +3507,7 @@ var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3176
3507
  },
3177
3508
  edge.id
3178
3509
  )) }),
3179
- ghost && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3510
+ ghost && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3180
3511
  "path",
3181
3512
  {
3182
3513
  d: `M ${ghost.start.x} ${ghost.start.y} L ${ghost.end.x} ${ghost.end.y}`,
@@ -3191,153 +3522,456 @@ var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3191
3522
  );
3192
3523
  });
3193
3524
 
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
3525
+ // src/workflow/components/FxPanel/FxPanel.tsx
3526
+ var import_react16 = require("react");
3527
+ var import_icons2 = require("@octaviaflow/icons");
3528
+ var import_jsx_runtime9 = require("react/jsx-runtime");
3529
+ var KIND_ICON = {
3530
+ function: import_icons2.FunctionMathIcon,
3531
+ variable: import_icons2.ValueVariableIcon
3532
+ };
3533
+ function countLeaves(items) {
3534
+ let n = 0;
3535
+ for (const it of items) {
3536
+ if (it.children && it.children.length > 0) n += countLeaves(it.children);
3537
+ else n += 1;
3538
+ }
3539
+ return n;
3540
+ }
3541
+ function collectBranchIds(items, out) {
3542
+ for (const it of items) {
3543
+ if (it.children && it.children.length > 0) {
3544
+ out.add(it.id);
3545
+ collectBranchIds(it.children, out);
3546
+ }
3547
+ }
3548
+ }
3549
+ function flattenTree(items, depth, expandedItems, out) {
3550
+ for (const it of items) {
3551
+ const hasChildren = !!it.children && it.children.length > 0;
3552
+ const expanded = hasChildren && expandedItems.has(it.id);
3553
+ out.push({ item: it, depth, hasChildren, expanded });
3554
+ if (expanded) flattenTree(it.children, depth + 1, expandedItems, out);
3555
+ }
3556
+ }
3557
+ function collectHits(items, ancestors, q, out) {
3558
+ for (const it of items) {
3559
+ if (it.children && it.children.length > 0) {
3560
+ collectHits(it.children, [...ancestors, it.label], q, out);
3561
+ } else {
3562
+ const path = [...ancestors, it.label].join(" / ");
3563
+ const hay = `${it.label} ${it.description ?? ""} ${path} ${it.insertValue ?? ""}`.toLowerCase();
3564
+ if (hay.includes(q)) out.push({ item: it, ancestors });
3565
+ }
3566
+ }
3567
+ }
3568
+ function FxPanel({
3569
+ categories,
3570
+ title = "Data References",
3571
+ hint = "Drag onto a field, or click an item to insert it.",
3572
+ search: controlledSearch,
3573
+ defaultSearch = "",
3574
+ onSearchChange,
3575
+ expandedCategory: controlledExpanded,
3576
+ defaultExpandedCategory,
3577
+ onExpandedCategoryChange,
3578
+ onClose,
3579
+ onItemDragStart,
3580
+ onItemSelect,
3581
+ width = 300,
3582
+ emptyLabel = "No matches",
3583
+ searchPlaceholder = "Search fields, functions & variables\u2026",
3584
+ className,
3585
+ style
3207
3586
  }) {
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
- };
3587
+ const [internalSearch, setInternalSearch] = (0, import_react16.useState)(defaultSearch);
3588
+ const search = controlledSearch ?? internalSearch;
3589
+ const query = search.trim().toLowerCase();
3590
+ const setSearch = (next) => {
3591
+ if (controlledSearch === void 0) setInternalSearch(next);
3592
+ onSearchChange?.(next);
3231
3593
  };
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);
3594
+ const visibleCategories = (0, import_react16.useMemo)(
3595
+ () => categories.filter((c) => c.items.length > 0),
3596
+ [categories]
3597
+ );
3598
+ const [internalCat, setInternalCat] = (0, import_react16.useState)(
3599
+ defaultExpandedCategory !== void 0 ? defaultExpandedCategory : visibleCategories[0]?.id ?? null
3600
+ );
3601
+ const openCategoryId = controlledExpanded ?? internalCat;
3602
+ const setOpenCategory = (id) => {
3603
+ if (controlledExpanded === void 0) setInternalCat(id);
3604
+ onExpandedCategoryChange?.(id);
3605
+ };
3606
+ const [expandedItems, setExpandedItems] = (0, import_react16.useState)(() => {
3607
+ const ids = /* @__PURE__ */ new Set();
3608
+ for (const cat of categories) {
3609
+ for (const item of cat.items) {
3610
+ if (item.children && item.children.length > 0) ids.add(item.id);
3267
3611
  }
3268
3612
  }
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 }
3613
+ return ids;
3614
+ });
3615
+ const toggleItem = (id) => {
3616
+ setExpandedItems((prev) => {
3617
+ const next = new Set(prev);
3618
+ if (next.has(id)) next.delete(id);
3619
+ else next.add(id);
3620
+ return next;
3275
3621
  });
3276
- onResize?.({ width: nextW, height: nextH });
3277
3622
  };
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
- });
3623
+ const setCategoryExpansion = (cat, expand) => {
3624
+ const branchIds = /* @__PURE__ */ new Set();
3625
+ collectBranchIds(cat.items, branchIds);
3626
+ setExpandedItems((prev) => {
3627
+ const next = new Set(prev);
3628
+ for (const id of branchIds) {
3629
+ if (expand) next.add(id);
3630
+ else next.delete(id);
3286
3631
  }
3287
- dragRef.current = null;
3632
+ return next;
3633
+ });
3634
+ };
3635
+ const [draggingId, setDraggingId] = (0, import_react16.useState)(null);
3636
+ const [focusedId, setFocusedId] = (0, import_react16.useState)(null);
3637
+ const hits = (0, import_react16.useMemo)(() => {
3638
+ if (!query) return null;
3639
+ const all = [];
3640
+ for (const cat of visibleCategories) {
3641
+ const out = [];
3642
+ collectHits(cat.items, [], query, out);
3643
+ for (const hit of out) all.push({ category: cat, hit });
3644
+ }
3645
+ return all;
3646
+ }, [query, visibleCategories]);
3647
+ const handleDragStart = (item, category) => (e) => {
3648
+ setDraggingId(item.id);
3649
+ if (onItemDragStart) {
3650
+ onItemDragStart(item, category, e);
3651
+ } else if (item.insertValue) {
3652
+ e.dataTransfer.setData("text/plain", item.insertValue);
3653
+ e.dataTransfer.setData("application/x-fx-insert", item.insertValue);
3654
+ e.dataTransfer.setData(
3655
+ "application/x-fx-type",
3656
+ category.kind ?? "function"
3657
+ );
3658
+ e.dataTransfer.effectAllowed = "copy";
3288
3659
  }
3289
3660
  };
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%" };
3661
+ const listRef = (0, import_react16.useRef)(null);
3662
+ const focusRow = (id) => {
3663
+ setFocusedId(id);
3664
+ requestAnimationFrame(() => {
3665
+ listRef.current?.querySelector(`[data-fx-row="${CSS.escape(id)}"]`)?.focus();
3666
+ });
3667
+ };
3668
+ const onRowKeyDown = (e, rows, row, category) => {
3669
+ const idx = rows.findIndex((r) => r.item.id === row.item.id);
3670
+ if (idx < 0) return;
3671
+ switch (e.key) {
3672
+ case "ArrowDown":
3673
+ e.preventDefault();
3674
+ if (idx < rows.length - 1) focusRow(rows[idx + 1].item.id);
3675
+ break;
3676
+ case "ArrowUp":
3677
+ e.preventDefault();
3678
+ if (idx > 0) focusRow(rows[idx - 1].item.id);
3679
+ break;
3680
+ case "ArrowRight":
3681
+ if (row.hasChildren) {
3682
+ e.preventDefault();
3683
+ if (!row.expanded) toggleItem(row.item.id);
3684
+ else if (idx < rows.length - 1) focusRow(rows[idx + 1].item.id);
3685
+ }
3686
+ break;
3687
+ case "ArrowLeft":
3688
+ if (row.hasChildren && row.expanded) {
3689
+ e.preventDefault();
3690
+ toggleItem(row.item.id);
3691
+ } else {
3692
+ for (let i = idx - 1; i >= 0; i--) {
3693
+ if (rows[i].depth < row.depth) {
3694
+ e.preventDefault();
3695
+ focusRow(rows[i].item.id);
3696
+ break;
3697
+ }
3698
+ }
3699
+ }
3700
+ break;
3701
+ case "Home":
3702
+ e.preventDefault();
3703
+ if (rows.length) focusRow(rows[0].item.id);
3704
+ break;
3705
+ case "End":
3706
+ e.preventDefault();
3707
+ if (rows.length) focusRow(rows[rows.length - 1].item.id);
3708
+ break;
3709
+ case "Enter":
3710
+ case " ":
3711
+ e.preventDefault();
3712
+ if (row.hasChildren) toggleItem(row.item.id);
3713
+ else onItemSelect?.(row.item, category);
3714
+ break;
3715
+ default:
3716
+ break;
3313
3717
  }
3314
3718
  };
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",
3719
+ const searchRows = (0, import_react16.useMemo)(
3720
+ () => hits ? hits.map(({ hit }) => ({
3721
+ item: hit.item,
3722
+ depth: 0,
3723
+ hasChildren: false,
3724
+ expanded: false
3725
+ })) : [],
3726
+ [hits]
3727
+ );
3728
+ const renderRow = (row, category, rows, crumb) => {
3729
+ const { item, depth, hasChildren, expanded } = row;
3730
+ const mono = category.kind === "function";
3731
+ const draggable = !!item.insertValue;
3732
+ const isFocused = focusedId === item.id;
3733
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3734
+ "div",
3735
+ {
3736
+ "data-fx-row": item.id,
3737
+ className: cn(
3738
+ "ods-flow-fx-panel__row",
3739
+ hasChildren ? "ods-flow-fx-panel__row--branch" : "ods-flow-fx-panel__row--leaf",
3740
+ draggingId === item.id && "ods-flow-fx-panel__row--dragging",
3741
+ isFocused && "ods-flow-fx-panel__row--focused"
3742
+ ),
3743
+ role: "treeitem",
3744
+ "aria-expanded": hasChildren ? expanded : void 0,
3745
+ "aria-level": depth + 1,
3746
+ tabIndex: isFocused || focusedId === null && rows[0]?.item.id === item.id ? 0 : -1,
3747
+ draggable,
3748
+ onDragStart: draggable ? handleDragStart(item, category) : void 0,
3749
+ onDragEnd: () => setDraggingId(null),
3750
+ onFocus: () => setFocusedId(item.id),
3751
+ onKeyDown: (e) => onRowKeyDown(e, rows, row, category),
3752
+ onClick: () => {
3753
+ if (hasChildren) toggleItem(item.id);
3754
+ else onItemSelect?.(item, category);
3755
+ },
3756
+ title: item.insertValue,
3757
+ children: [
3758
+ Array.from({ length: depth }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-flow-fx-panel__indent", "aria-hidden": "true" }, i)),
3759
+ hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-flow-fx-panel__twisty", "aria-hidden": "true", children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.ChevronDownIcon, { size: "sm" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.ChevronRightIcon, { size: "sm" }) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-flow-fx-panel__grip", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.DraggableIcon, { size: "sm" }) }),
3760
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ods-flow-fx-panel__row-main", children: [
3761
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ods-flow-fx-panel__row-line", children: [
3762
+ mono ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { className: "ods-flow-fx-panel__row-label ods-flow-fx-panel__row-label--mono", children: item.label }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-flow-fx-panel__row-label", children: item.label }),
3763
+ item.valueType && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3764
+ "span",
3765
+ {
3766
+ className: cn(
3767
+ "ods-flow-fx-panel__type",
3768
+ `ods-flow-fx-panel__type--${item.valueType}`
3769
+ ),
3770
+ children: item.valueType
3771
+ }
3772
+ )
3773
+ ] }),
3774
+ (item.description || crumb) && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "ods-flow-fx-panel__row-sub", children: [
3775
+ crumb && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-flow-fx-panel__crumb", children: crumb }),
3776
+ item.description
3777
+ ] })
3778
+ ] })
3779
+ ]
3780
+ },
3781
+ item.id
3782
+ );
3783
+ };
3784
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3785
+ "aside",
3317
3786
  {
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
- )) });
3787
+ className: cn("ods-flow-fx-panel", className),
3788
+ style: { width, ...style },
3789
+ "aria-label": typeof title === "string" ? title : "Data References",
3790
+ children: [
3791
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("header", { className: "ods-flow-fx-panel__header", children: [
3792
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { className: "ods-flow-fx-panel__title", children: title }),
3793
+ onClose && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3794
+ "button",
3795
+ {
3796
+ type: "button",
3797
+ className: "ods-flow-fx-panel__close",
3798
+ onClick: onClose,
3799
+ "aria-label": "Close data references",
3800
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.CloseIcon, { size: "sm" })
3801
+ }
3802
+ )
3803
+ ] }),
3804
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-flow-fx-panel__search", children: [
3805
+ /* @__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" }) }),
3806
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3807
+ "input",
3808
+ {
3809
+ type: "text",
3810
+ className: "ods-flow-fx-panel__search-input",
3811
+ placeholder: searchPlaceholder,
3812
+ "aria-label": "Search fields, functions and variables",
3813
+ value: search,
3814
+ onChange: (e) => setSearch(e.target.value)
3815
+ }
3816
+ ),
3817
+ search && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3818
+ "button",
3819
+ {
3820
+ type: "button",
3821
+ className: "ods-flow-fx-panel__search-clear",
3822
+ onClick: () => setSearch(""),
3823
+ "aria-label": "Clear search",
3824
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.CloseIcon, { size: "sm" })
3825
+ }
3826
+ )
3827
+ ] }),
3828
+ hint && !query && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ods-flow-fx-panel__hint", children: hint }),
3829
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-flow-fx-panel__list", ref: listRef, role: "tree", children: [
3830
+ query && hits && (hits.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-flow-fx-panel__results", children: [
3831
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-flow-fx-panel__results-count", children: [
3832
+ hits.length,
3833
+ " ",
3834
+ hits.length === 1 ? "match" : "matches"
3835
+ ] }),
3836
+ hits.map(
3837
+ ({ category, hit }, i) => renderRow(
3838
+ searchRows[i],
3839
+ category,
3840
+ searchRows,
3841
+ hit.ancestors.length ? hit.ancestors.join(" / ") : category.label
3842
+ )
3843
+ )
3844
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ods-flow-fx-panel__empty", children: emptyLabel })),
3845
+ !query && visibleCategories.map((cat) => {
3846
+ const isOpen = openCategoryId === cat.id;
3847
+ const kind = cat.kind ?? "function";
3848
+ const KindIcon = KIND_ICON[kind];
3849
+ const branchIds = /* @__PURE__ */ new Set();
3850
+ collectBranchIds(cat.items, branchIds);
3851
+ const hasTree = branchIds.size > 0;
3852
+ const rows = [];
3853
+ if (isOpen) flattenTree(cat.items, 0, expandedItems, rows);
3854
+ const allExpanded = hasTree && [...branchIds].every((id) => expandedItems.has(id));
3855
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-flow-fx-panel__category", children: [
3856
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3857
+ "div",
3858
+ {
3859
+ className: cn(
3860
+ "ods-flow-fx-panel__cat-header",
3861
+ isOpen && "ods-flow-fx-panel__cat-header--open"
3862
+ ),
3863
+ children: [
3864
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3865
+ "button",
3866
+ {
3867
+ type: "button",
3868
+ className: "ods-flow-fx-panel__cat-toggle",
3869
+ "aria-expanded": isOpen,
3870
+ onClick: () => setOpenCategory(isOpen ? null : cat.id),
3871
+ children: [
3872
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3873
+ import_icons2.ChevronRightIcon,
3874
+ {
3875
+ size: "sm",
3876
+ className: cn(
3877
+ "ods-flow-fx-panel__chevron",
3878
+ isOpen && "ods-flow-fx-panel__chevron--open"
3879
+ )
3880
+ }
3881
+ ),
3882
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3883
+ "span",
3884
+ {
3885
+ className: cn(
3886
+ "ods-flow-fx-panel__badge",
3887
+ `ods-flow-fx-panel__badge--${kind}`
3888
+ ),
3889
+ children: [
3890
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3891
+ KindIcon,
3892
+ {
3893
+ size: "sm",
3894
+ className: "ods-flow-fx-panel__badge-icon"
3895
+ }
3896
+ ),
3897
+ cat.label
3898
+ ]
3899
+ }
3900
+ ),
3901
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-flow-fx-panel__count", children: countLeaves(cat.items) })
3902
+ ]
3903
+ }
3904
+ ),
3905
+ isOpen && hasTree && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3906
+ "button",
3907
+ {
3908
+ type: "button",
3909
+ className: "ods-flow-fx-panel__cat-action",
3910
+ "aria-label": allExpanded ? "Collapse all" : "Expand all",
3911
+ title: allExpanded ? "Collapse all" : "Expand all",
3912
+ onClick: () => setCategoryExpansion(cat, !allExpanded),
3913
+ children: allExpanded ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.CollapseAllIcon, { size: "sm" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.ExpandAllIcon, { size: "sm" })
3914
+ }
3915
+ )
3916
+ ]
3917
+ }
3918
+ ),
3919
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3920
+ "div",
3921
+ {
3922
+ className: "ods-flow-fx-panel__rows",
3923
+ role: "group",
3924
+ "aria-label": cat.label,
3925
+ children: rows.map((row) => renderRow(row, cat, rows))
3926
+ }
3927
+ )
3928
+ ] }, cat.id);
3929
+ })
3930
+ ] })
3931
+ ]
3932
+ }
3933
+ );
3327
3934
  }
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
- }
3935
+
3936
+ // src/workflow/components/FxPanel/DataRefButton.tsx
3937
+ var import_icons3 = require("@octaviaflow/icons");
3938
+ var import_jsx_runtime10 = require("react/jsx-runtime");
3939
+ function DataRefButton({
3940
+ active = false,
3941
+ label = "Data ref",
3942
+ className,
3943
+ type = "button",
3944
+ ...rest
3945
+ }) {
3946
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3947
+ "button",
3948
+ {
3949
+ ...rest,
3950
+ type,
3951
+ className: cn(
3952
+ "ods-flow-dataref-button",
3953
+ active && "ods-flow-dataref-button--active",
3954
+ label == null && "ods-flow-dataref-button--icon-only",
3955
+ className
3956
+ ),
3957
+ "aria-pressed": active,
3958
+ title: active ? "Hide data references" : "Show data references",
3959
+ children: [
3960
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3961
+ import_icons3.DataReferenceIcon,
3962
+ {
3963
+ size: "sm",
3964
+ className: "ods-flow-dataref-button__icon"
3965
+ }
3966
+ ),
3967
+ label != null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ods-flow-dataref-button__label", children: label })
3968
+ ]
3969
+ }
3970
+ );
3337
3971
  }
3338
3972
 
3339
3973
  // src/workflow/components/NodeToolbar/NodeToolbar.tsx
3340
- var import_jsx_runtime9 = require("react/jsx-runtime");
3974
+ var import_jsx_runtime11 = require("react/jsx-runtime");
3341
3975
  function NodeToolbar({
3342
3976
  isVisible,
3343
3977
  position = "top",
@@ -3352,7 +3986,7 @@ function NodeToolbar({
3352
3986
  const show = isVisible ?? node.selected;
3353
3987
  if (!show) return null;
3354
3988
  const inverseScale = 1 / viewport.zoom;
3355
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3989
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3356
3990
  "div",
3357
3991
  {
3358
3992
  className: cn("ods-node-toolbar", `ods-node-toolbar--${position}`, className),
@@ -3364,7 +3998,7 @@ function NodeToolbar({
3364
3998
  onPointerDown: (e) => e.stopPropagation(),
3365
3999
  onMouseDown: (e) => e.stopPropagation(),
3366
4000
  onClick: (e) => e.stopPropagation(),
3367
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4001
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3368
4002
  "div",
3369
4003
  {
3370
4004
  className: "ods-node-toolbar__inner",
@@ -3513,11 +4147,13 @@ function toggleGroupCollapse(groupId, nodes) {
3513
4147
  DEFAULT_NODE_HEIGHT,
3514
4148
  DEFAULT_NODE_KINDS,
3515
4149
  DEFAULT_NODE_WIDTH,
4150
+ DataRefButton,
3516
4151
  ErrorNode,
3517
4152
  FlowCanvas,
3518
4153
  FlowEdge,
3519
4154
  FlowNode,
3520
4155
  ForEachNode,
4156
+ FxPanel,
3521
4157
  GroupNode,
3522
4158
  Handle,
3523
4159
  HttpRequestNode,
@@ -3564,6 +4200,7 @@ function toggleGroupCollapse(groupId, nodes) {
3564
4200
  useNodeData,
3565
4201
  useNodes,
3566
4202
  useSelection,
3567
- useViewport
4203
+ useViewport,
4204
+ useViewportOrNull
3568
4205
  });
3569
4206
  //# sourceMappingURL=workflow.cjs.map