@octaviaflow/core 3.0.13 → 3.0.17-beta.0

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 (231) hide show
  1. package/dist/chunk-2AWVQFSC.js +2456 -0
  2. package/dist/chunk-2AWVQFSC.js.map +1 -0
  3. package/dist/chunk-2K7IQR2F.js +2432 -0
  4. package/dist/chunk-2K7IQR2F.js.map +1 -0
  5. package/dist/chunk-2YUWZPIT.js +2483 -0
  6. package/dist/chunk-2YUWZPIT.js.map +1 -0
  7. package/dist/chunk-4VCRGPYQ.js +2452 -0
  8. package/dist/chunk-4VCRGPYQ.js.map +1 -0
  9. package/dist/chunk-4WU5YAL2.js +2470 -0
  10. package/dist/chunk-4WU5YAL2.js.map +1 -0
  11. package/dist/chunk-5L4TGL74.js +2448 -0
  12. package/dist/chunk-5L4TGL74.js.map +1 -0
  13. package/dist/chunk-7M5HFY2J.js +2483 -0
  14. package/dist/chunk-7M5HFY2J.js.map +1 -0
  15. package/dist/chunk-AJ4556EH.js +2490 -0
  16. package/dist/chunk-AJ4556EH.js.map +1 -0
  17. package/dist/chunk-AOL365B3.js +2448 -0
  18. package/dist/chunk-AOL365B3.js.map +1 -0
  19. package/dist/chunk-D4X7RMGF.js +2481 -0
  20. package/dist/chunk-D4X7RMGF.js.map +1 -0
  21. package/dist/chunk-DCGXCPD4.js +2389 -0
  22. package/dist/chunk-DCGXCPD4.js.map +1 -0
  23. package/dist/chunk-DG35MIV3.js +2480 -0
  24. package/dist/chunk-DG35MIV3.js.map +1 -0
  25. package/dist/chunk-DRZ73Q5X.js +2503 -0
  26. package/dist/chunk-DRZ73Q5X.js.map +1 -0
  27. package/dist/chunk-DRZMET2G.js +2458 -0
  28. package/dist/chunk-DRZMET2G.js.map +1 -0
  29. package/dist/chunk-DYCW4TYB.js +2546 -0
  30. package/dist/chunk-DYCW4TYB.js.map +1 -0
  31. package/dist/chunk-E7WR7DMP.js +2451 -0
  32. package/dist/chunk-E7WR7DMP.js.map +1 -0
  33. package/dist/chunk-ELNQQAHD.js +2454 -0
  34. package/dist/chunk-ELNQQAHD.js.map +1 -0
  35. package/dist/chunk-FN3MFN6C.js +2389 -0
  36. package/dist/chunk-FN3MFN6C.js.map +1 -0
  37. package/dist/chunk-IBTEGHFT.js +10 -0
  38. package/dist/chunk-IBTEGHFT.js.map +1 -0
  39. package/dist/chunk-JCPM7YF2.js +2474 -0
  40. package/dist/chunk-JCPM7YF2.js.map +1 -0
  41. package/dist/chunk-JLPCCZKG.js +2459 -0
  42. package/dist/chunk-JLPCCZKG.js.map +1 -0
  43. package/dist/chunk-KY4JHPIE.js +2546 -0
  44. package/dist/chunk-KY4JHPIE.js.map +1 -0
  45. package/dist/chunk-LCQMQAWJ.js +2461 -0
  46. package/dist/chunk-LCQMQAWJ.js.map +1 -0
  47. package/dist/chunk-LSDAQCUJ.js +2546 -0
  48. package/dist/chunk-LSDAQCUJ.js.map +1 -0
  49. package/dist/chunk-LW4ZHQKL.js +2432 -0
  50. package/dist/chunk-LW4ZHQKL.js.map +1 -0
  51. package/dist/chunk-MMZW3Q26.js +2442 -0
  52. package/dist/chunk-MMZW3Q26.js.map +1 -0
  53. package/dist/chunk-MXJR2WHG.js +2470 -0
  54. package/dist/chunk-MXJR2WHG.js.map +1 -0
  55. package/dist/chunk-OK2CUIKQ.js +2447 -0
  56. package/dist/chunk-OK2CUIKQ.js.map +1 -0
  57. package/dist/chunk-OSOP2WPT.js +2456 -0
  58. package/dist/chunk-OSOP2WPT.js.map +1 -0
  59. package/dist/chunk-PNBZLLWT.js +2529 -0
  60. package/dist/chunk-PNBZLLWT.js.map +1 -0
  61. package/dist/chunk-PULX2GK5.js +2447 -0
  62. package/dist/chunk-PULX2GK5.js.map +1 -0
  63. package/dist/chunk-QDT45KF4.js +2480 -0
  64. package/dist/chunk-QDT45KF4.js.map +1 -0
  65. package/dist/chunk-QI3ITHAZ.js +2450 -0
  66. package/dist/chunk-QI3ITHAZ.js.map +1 -0
  67. package/dist/chunk-QSGJ3DNA.js +2475 -0
  68. package/dist/chunk-QSGJ3DNA.js.map +1 -0
  69. package/dist/chunk-RDGJWRHM.js +2490 -0
  70. package/dist/chunk-RDGJWRHM.js.map +1 -0
  71. package/dist/chunk-SDE3ARKY.js +2451 -0
  72. package/dist/chunk-SDE3ARKY.js.map +1 -0
  73. package/dist/chunk-SX7WRJGD.js +2441 -0
  74. package/dist/chunk-SX7WRJGD.js.map +1 -0
  75. package/dist/chunk-TZ56MSYM.js +2452 -0
  76. package/dist/chunk-TZ56MSYM.js.map +1 -0
  77. package/dist/chunk-UEQLKS2B.js +2445 -0
  78. package/dist/chunk-UEQLKS2B.js.map +1 -0
  79. package/dist/chunk-UNU6YNTL.js +2452 -0
  80. package/dist/chunk-UNU6YNTL.js.map +1 -0
  81. package/dist/chunk-UUG6XYUC.js +2482 -0
  82. package/dist/chunk-UUG6XYUC.js.map +1 -0
  83. package/dist/chunk-VEY3BP5E.js +2447 -0
  84. package/dist/chunk-VEY3BP5E.js.map +1 -0
  85. package/dist/chunk-WBTXV3Q2.js +2482 -0
  86. package/dist/chunk-WBTXV3Q2.js.map +1 -0
  87. package/dist/chunk-WILVB7XA.js +2512 -0
  88. package/dist/chunk-WILVB7XA.js.map +1 -0
  89. package/dist/chunk-WQCHABP3.js +2441 -0
  90. package/dist/chunk-WQCHABP3.js.map +1 -0
  91. package/dist/chunk-WUFAW3TB.js +2512 -0
  92. package/dist/chunk-WUFAW3TB.js.map +1 -0
  93. package/dist/chunk-WYBJKALZ.js +2458 -0
  94. package/dist/chunk-WYBJKALZ.js.map +1 -0
  95. package/dist/chunk-XZ7FELMT.js +2448 -0
  96. package/dist/chunk-XZ7FELMT.js.map +1 -0
  97. package/dist/chunk-YATCRJVF.js +2458 -0
  98. package/dist/chunk-YATCRJVF.js.map +1 -0
  99. package/dist/chunk-YSSD2T4M.js +2451 -0
  100. package/dist/chunk-YSSD2T4M.js.map +1 -0
  101. package/dist/chunk-YW6ZOYCG.js +2451 -0
  102. package/dist/chunk-YW6ZOYCG.js.map +1 -0
  103. package/dist/components/ActionsDrawer/ActionsDrawer.d.ts +89 -0
  104. package/dist/components/ActionsDrawer/ActionsDrawer.d.ts.map +1 -0
  105. package/dist/components/ActionsDrawer/index.d.ts +2 -0
  106. package/dist/components/ActionsDrawer/index.d.ts.map +1 -0
  107. package/dist/components/AgentCard/AgentCard.d.ts +1 -1
  108. package/dist/components/AgentCard/AgentCard.d.ts.map +1 -1
  109. package/dist/components/AuthCard/AuthCard.d.ts.map +1 -1
  110. package/dist/components/Button/Button.d.ts.map +1 -1
  111. package/dist/components/CodeEditor/CodeEditor.d.ts.map +1 -1
  112. package/dist/components/CodeEditor/index.d.ts +1 -1
  113. package/dist/components/CodeEditor/index.d.ts.map +1 -1
  114. package/dist/components/DataMapper/DataMapper.d.ts.map +1 -1
  115. package/dist/components/DataTable/DataTable.d.ts.map +1 -1
  116. package/dist/components/DataTable/index.d.ts +1 -1
  117. package/dist/components/DataTable/index.d.ts.map +1 -1
  118. package/dist/components/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  119. package/dist/components/ExecutionConsole/ExecutionConsole.d.ts +29 -3
  120. package/dist/components/ExecutionConsole/ExecutionConsole.d.ts.map +1 -1
  121. package/dist/components/FileDropzone/FileDropzone.d.ts.map +1 -1
  122. package/dist/components/FlowEdge/FlowEdge.d.ts +15 -14
  123. package/dist/components/FlowEdge/FlowEdge.d.ts.map +1 -1
  124. package/dist/components/Input/Input.d.ts.map +1 -1
  125. package/dist/components/MonacoEditor/MonacoDiffEditor.d.ts.map +1 -1
  126. package/dist/components/MonacoEditor/MonacoEditor.d.ts.map +1 -1
  127. package/dist/components/MonacoEditor/index.d.ts +1 -1
  128. package/dist/components/MonacoEditor/index.d.ts.map +1 -1
  129. package/dist/components/PromptInput/PromptInput.d.ts.map +1 -1
  130. package/dist/components/Radio/Radio.d.ts.map +1 -1
  131. package/dist/components/Resizable/Resizable.d.ts.map +1 -1
  132. package/dist/components/Resizable/index.d.ts +1 -1
  133. package/dist/components/Resizable/index.d.ts.map +1 -1
  134. package/dist/components/Select/Select.d.ts +16 -1
  135. package/dist/components/Select/Select.d.ts.map +1 -1
  136. package/dist/components/Sortable/Sortable.d.ts.map +1 -1
  137. package/dist/components/Sortable/index.d.ts +1 -1
  138. package/dist/components/Sortable/index.d.ts.map +1 -1
  139. package/dist/components/Toast/Toast.d.ts.map +1 -1
  140. package/dist/components/ToolCard/ToolCard.d.ts +1 -1
  141. package/dist/components/ToolCard/ToolCard.d.ts.map +1 -1
  142. package/dist/components/TopBar/TopBar.d.ts +22 -1
  143. package/dist/components/TopBar/TopBar.d.ts.map +1 -1
  144. package/dist/components/TraceStep/TraceStep.d.ts.map +1 -1
  145. package/dist/components/TraceStep/index.d.ts +1 -1
  146. package/dist/components/TraceStep/index.d.ts.map +1 -1
  147. package/dist/components/WorkflowEditor/WorkflowEditor.d.ts +10 -2
  148. package/dist/components/WorkflowEditor/WorkflowEditor.d.ts.map +1 -1
  149. package/dist/components/XmlViewer/XmlViewer.d.ts.map +1 -1
  150. package/dist/components/YamlViewer/YamlViewer.d.ts.map +1 -1
  151. package/dist/index.cjs +6875 -3288
  152. package/dist/index.cjs.map +1 -1
  153. package/dist/index.d.ts +3 -3
  154. package/dist/index.d.ts.map +1 -1
  155. package/dist/index.js +3296 -2147
  156. package/dist/index.js.map +1 -1
  157. package/dist/monaco.cjs +344 -201
  158. package/dist/monaco.cjs.map +1 -1
  159. package/dist/monaco.js +348 -205
  160. package/dist/monaco.js.map +1 -1
  161. package/dist/stories/state-matrix.d.ts.map +1 -1
  162. package/dist/styles.css +1 -1
  163. package/dist/utils/a11y.d.ts.map +1 -1
  164. package/dist/utils/sanitizeUrl.d.ts.map +1 -1
  165. package/dist/workflow/components/ConfigPanel/ConfigPanel.d.ts +64 -0
  166. package/dist/workflow/components/ConfigPanel/ConfigPanel.d.ts.map +1 -0
  167. package/dist/workflow/components/FlowCanvas/FlowCanvas.d.ts +111 -0
  168. package/dist/workflow/components/FlowCanvas/FlowCanvas.d.ts.map +1 -0
  169. package/dist/workflow/components/FlowCanvas/FlowCanvasContext.d.ts +47 -0
  170. package/dist/workflow/components/FlowCanvas/FlowCanvasContext.d.ts.map +1 -0
  171. package/dist/workflow/components/FlowEdge/FlowEdge.d.ts +26 -0
  172. package/dist/workflow/components/FlowEdge/FlowEdge.d.ts.map +1 -0
  173. package/dist/workflow/components/FlowNode/FlowNode.d.ts +13 -0
  174. package/dist/workflow/components/FlowNode/FlowNode.d.ts.map +1 -0
  175. package/dist/workflow/components/FlowNode/FlowNodeContext.d.ts +9 -0
  176. package/dist/workflow/components/FlowNode/FlowNodeContext.d.ts.map +1 -0
  177. package/dist/workflow/components/FlowNode/nodeKinds.d.ts +25 -0
  178. package/dist/workflow/components/FlowNode/nodeKinds.d.ts.map +1 -0
  179. package/dist/workflow/components/Handle/Handle.d.ts +26 -0
  180. package/dist/workflow/components/Handle/Handle.d.ts.map +1 -0
  181. package/dist/workflow/components/Handle/handleRegistry.d.ts +32 -0
  182. package/dist/workflow/components/Handle/handleRegistry.d.ts.map +1 -0
  183. package/dist/workflow/components/NodeResizer/NodeResizer.d.ts +27 -0
  184. package/dist/workflow/components/NodeResizer/NodeResizer.d.ts.map +1 -0
  185. package/dist/workflow/components/NodeToolbar/NodeToolbar.d.ts +19 -0
  186. package/dist/workflow/components/NodeToolbar/NodeToolbar.d.ts.map +1 -0
  187. package/dist/workflow/components/kinds/BaseNode.d.ts +40 -0
  188. package/dist/workflow/components/kinds/BaseNode.d.ts.map +1 -0
  189. package/dist/workflow/components/kinds/index.d.ts +55 -0
  190. package/dist/workflow/components/kinds/index.d.ts.map +1 -0
  191. package/dist/workflow/editor.d.ts +181 -0
  192. package/dist/workflow/editor.d.ts.map +1 -0
  193. package/dist/workflow/hooks/useAutoLayout.d.ts +35 -0
  194. package/dist/workflow/hooks/useAutoLayout.d.ts.map +1 -0
  195. package/dist/workflow/hooks/useFlow.d.ts +61 -0
  196. package/dist/workflow/hooks/useFlow.d.ts.map +1 -0
  197. package/dist/workflow/hooks/useFlowState.d.ts +33 -0
  198. package/dist/workflow/hooks/useFlowState.d.ts.map +1 -0
  199. package/dist/workflow/index.d.ts +25 -0
  200. package/dist/workflow/index.d.ts.map +1 -0
  201. package/dist/workflow/layout/dagre.d.ts +24 -0
  202. package/dist/workflow/layout/dagre.d.ts.map +1 -0
  203. package/dist/workflow/layout/elk.d.ts +22 -0
  204. package/dist/workflow/layout/elk.d.ts.map +1 -0
  205. package/dist/workflow/layout/types.d.ts +55 -0
  206. package/dist/workflow/layout/types.d.ts.map +1 -0
  207. package/dist/workflow/store/changes.d.ts +34 -0
  208. package/dist/workflow/store/changes.d.ts.map +1 -0
  209. package/dist/workflow/store/context.d.ts +4 -0
  210. package/dist/workflow/store/context.d.ts.map +1 -0
  211. package/dist/workflow/store/createFlowStore.d.ts +27 -0
  212. package/dist/workflow/store/createFlowStore.d.ts.map +1 -0
  213. package/dist/workflow/store/selectors.d.ts +33 -0
  214. package/dist/workflow/store/selectors.d.ts.map +1 -0
  215. package/dist/workflow/types/index.d.ts +181 -0
  216. package/dist/workflow/types/index.d.ts.map +1 -0
  217. package/dist/workflow/utils/collapse.d.ts +26 -0
  218. package/dist/workflow/utils/collapse.d.ts.map +1 -0
  219. package/dist/workflow/utils/collision.d.ts +37 -0
  220. package/dist/workflow/utils/collision.d.ts.map +1 -0
  221. package/dist/workflow/utils/geometry.d.ts +46 -0
  222. package/dist/workflow/utils/geometry.d.ts.map +1 -0
  223. package/dist/workflow/utils/parenting.d.ts +28 -0
  224. package/dist/workflow/utils/parenting.d.ts.map +1 -0
  225. package/dist/workflow/utils/paths.d.ts +19 -0
  226. package/dist/workflow/utils/paths.d.ts.map +1 -0
  227. package/dist/workflow.cjs +3562 -0
  228. package/dist/workflow.cjs.map +1 -0
  229. package/dist/workflow.js +1029 -0
  230. package/dist/workflow.js.map +1 -0
  231. package/package.json +6 -1
@@ -0,0 +1,3562 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/workflow/index.ts
21
+ var workflow_exports = {};
22
+ __export(workflow_exports, {
23
+ ActionNode: () => ActionNode,
24
+ ConditionNode: () => ConditionNode,
25
+ ConfigPanel: () => ConfigPanel,
26
+ DEFAULT_NODE_HEIGHT: () => DEFAULT_NODE_HEIGHT,
27
+ DEFAULT_NODE_KINDS: () => DEFAULT_NODE_KINDS,
28
+ DEFAULT_NODE_WIDTH: () => DEFAULT_NODE_WIDTH,
29
+ ErrorNode: () => ErrorNode,
30
+ FlowCanvas: () => FlowCanvas,
31
+ FlowEdge: () => FlowEdge,
32
+ FlowNode: () => FlowNode,
33
+ ForEachNode: () => ForEachNode,
34
+ GroupNode: () => GroupNode,
35
+ Handle: () => Handle,
36
+ HttpRequestNode: () => HttpRequestNode,
37
+ NodeResizer: () => NodeResizer,
38
+ NodeToolbar: () => NodeToolbar,
39
+ OutputNode: () => OutputNode,
40
+ ParallelNode: () => ParallelNode,
41
+ StickyNode: () => StickyNode,
42
+ TriggerNode: () => TriggerNode,
43
+ WaitNode: () => WaitNode,
44
+ WebhookNode: () => WebhookNode,
45
+ applyEdgeChanges: () => applyEdgeChanges,
46
+ applyNodeChanges: () => applyNodeChanges,
47
+ bezierPath: () => bezierPath,
48
+ bezierPathFn: () => bezierPath2,
49
+ buildEdgePath: () => buildEdgePath,
50
+ buildNodeKindRegistry: () => buildNodeKindRegistry,
51
+ change: () => change,
52
+ clampToParentExtent: () => clampToParentExtent,
53
+ collapseFor: () => collapseFor,
54
+ createDagreEngine: () => createDagreEngine,
55
+ createElkEngine: () => createElkEngine,
56
+ descendantsOf: () => descendantsOf,
57
+ findAncestor: () => findAncestor,
58
+ findContainingGroup: () => findContainingGroup,
59
+ flowToScreen: () => flowToScreen,
60
+ handleCentre: () => handleCentre,
61
+ screenToFlow: () => screenToFlow,
62
+ smoothStepPath: () => smoothStepPath,
63
+ stepPath: () => stepPath,
64
+ straightPath: () => straightPath,
65
+ toggleGroupCollapse: () => toggleGroupCollapse,
66
+ useAutoLayout: () => useAutoLayout,
67
+ useConnection: () => useConnection,
68
+ useEdgeById: () => useEdgeById,
69
+ useEdges: () => useEdges,
70
+ useFlow: () => useFlow,
71
+ useFlowNodeContext: () => useFlowNodeContext,
72
+ useFlowSelector: () => useFlowSelector,
73
+ useFlowState: () => useFlowState,
74
+ useIsEdgeSelected: () => useIsEdgeSelected,
75
+ useIsNodeSelected: () => useIsNodeSelected,
76
+ useNodeById: () => useNodeById,
77
+ useNodeData: () => useNodeData,
78
+ useNodes: () => useNodes,
79
+ useSelection: () => useSelection,
80
+ useViewport: () => useViewport
81
+ });
82
+ module.exports = __toCommonJS(workflow_exports);
83
+
84
+ // src/workflow/hooks/useAutoLayout.ts
85
+ var import_react = require("react");
86
+
87
+ // src/workflow/store/changes.ts
88
+ function applyNodeChanges(nodes, changes) {
89
+ if (changes.length === 0) return nodes;
90
+ const byId = /* @__PURE__ */ new Map();
91
+ const adds = [];
92
+ const removes = /* @__PURE__ */ new Set();
93
+ let touched = false;
94
+ for (const change2 of changes) {
95
+ switch (change2.type) {
96
+ case "add":
97
+ adds.push(change2.item);
98
+ touched = true;
99
+ break;
100
+ case "remove":
101
+ removes.add(change2.id);
102
+ touched = true;
103
+ break;
104
+ default: {
105
+ const id = change2.id;
106
+ const arr = byId.get(id);
107
+ if (arr) arr.push(change2);
108
+ else byId.set(id, [change2]);
109
+ touched = true;
110
+ break;
111
+ }
112
+ }
113
+ }
114
+ if (!touched) return nodes;
115
+ const next = [];
116
+ for (const node of nodes) {
117
+ if (removes.has(node.id)) continue;
118
+ const patches = byId.get(node.id);
119
+ if (!patches) {
120
+ next.push(node);
121
+ continue;
122
+ }
123
+ let n = node;
124
+ for (const patch of patches) {
125
+ switch (patch.type) {
126
+ case "position":
127
+ n = {
128
+ ...n,
129
+ position: patch.position,
130
+ dragging: patch.dragging
131
+ };
132
+ break;
133
+ case "dimensions":
134
+ n = {
135
+ ...n,
136
+ width: patch.dimensions.width,
137
+ height: patch.dimensions.height
138
+ };
139
+ break;
140
+ case "select":
141
+ if (n.selected === patch.selected) break;
142
+ n = { ...n, selected: patch.selected };
143
+ break;
144
+ case "replace":
145
+ n = patch.item;
146
+ break;
147
+ }
148
+ }
149
+ next.push(n);
150
+ }
151
+ for (const add of adds) next.push(add);
152
+ return next;
153
+ }
154
+ function applyEdgeChanges(edges, changes) {
155
+ if (changes.length === 0) return edges;
156
+ const byId = /* @__PURE__ */ new Map();
157
+ const adds = [];
158
+ const removes = /* @__PURE__ */ new Set();
159
+ let touched = false;
160
+ for (const change2 of changes) {
161
+ switch (change2.type) {
162
+ case "add":
163
+ adds.push(change2.item);
164
+ touched = true;
165
+ break;
166
+ case "remove":
167
+ removes.add(change2.id);
168
+ touched = true;
169
+ break;
170
+ default: {
171
+ const arr = byId.get(change2.id);
172
+ if (arr) arr.push(change2);
173
+ else byId.set(change2.id, [change2]);
174
+ touched = true;
175
+ break;
176
+ }
177
+ }
178
+ }
179
+ if (!touched) return edges;
180
+ const next = [];
181
+ for (const edge of edges) {
182
+ if (removes.has(edge.id)) continue;
183
+ const patches = byId.get(edge.id);
184
+ if (!patches) {
185
+ next.push(edge);
186
+ continue;
187
+ }
188
+ let e = edge;
189
+ for (const patch of patches) {
190
+ switch (patch.type) {
191
+ case "select":
192
+ if (e.selected === patch.selected) break;
193
+ e = { ...e, selected: patch.selected };
194
+ break;
195
+ case "replace":
196
+ e = patch.item;
197
+ break;
198
+ }
199
+ }
200
+ next.push(e);
201
+ }
202
+ for (const add of adds) next.push(add);
203
+ return next;
204
+ }
205
+ var change = {
206
+ node: {
207
+ add(item) {
208
+ return { type: "add", item };
209
+ },
210
+ remove(id) {
211
+ return { type: "remove", id };
212
+ },
213
+ position(id, position, dragging) {
214
+ return { type: "position", id, position, dragging };
215
+ },
216
+ dimensions(id, dimensions) {
217
+ return { type: "dimensions", id, dimensions };
218
+ },
219
+ select(id, selected) {
220
+ return { type: "select", id, selected };
221
+ },
222
+ replace(id, item) {
223
+ return { type: "replace", id, item };
224
+ }
225
+ },
226
+ edge: {
227
+ add(item) {
228
+ return { type: "add", item };
229
+ },
230
+ remove(id) {
231
+ return { type: "remove", id };
232
+ },
233
+ select(id, selected) {
234
+ return { type: "select", id, selected };
235
+ },
236
+ replace(id, item) {
237
+ return { type: "replace", id, item };
238
+ }
239
+ }
240
+ };
241
+
242
+ // src/workflow/hooks/useAutoLayout.ts
243
+ function signature(nodes, edges) {
244
+ const nodeKeys = nodes.map((n) => `${n.id}:${n.parentId ?? ""}`).sort().join("|");
245
+ const edgeKeys = edges.map((e) => `${e.id}:${e.source}>${e.target}`).sort().join("|");
246
+ return `${nodeKeys}::${edgeKeys}`;
247
+ }
248
+ function useAutoLayout({
249
+ engine,
250
+ nodes,
251
+ edges,
252
+ onNodesChange,
253
+ options,
254
+ auto = false,
255
+ debounceMs = 250,
256
+ onLayout,
257
+ onError
258
+ }) {
259
+ const [isLaying, setIsLaying] = (0, import_react.useState)(false);
260
+ const runningRef = (0, import_react.useRef)(false);
261
+ const optionsRef = (0, import_react.useRef)(options);
262
+ const onLayoutRef = (0, import_react.useRef)(onLayout);
263
+ const onErrorRef = (0, import_react.useRef)(onError);
264
+ const onNodesChangeRef = (0, import_react.useRef)(onNodesChange);
265
+ (0, import_react.useEffect)(() => {
266
+ optionsRef.current = options;
267
+ }, [options]);
268
+ (0, import_react.useEffect)(() => {
269
+ onLayoutRef.current = onLayout;
270
+ }, [onLayout]);
271
+ (0, import_react.useEffect)(() => {
272
+ onErrorRef.current = onError;
273
+ }, [onError]);
274
+ (0, import_react.useEffect)(() => {
275
+ onNodesChangeRef.current = onNodesChange;
276
+ }, [onNodesChange]);
277
+ const nodesRef = (0, import_react.useRef)(nodes);
278
+ const edgesRef = (0, import_react.useRef)(edges);
279
+ (0, import_react.useEffect)(() => {
280
+ nodesRef.current = nodes;
281
+ }, [nodes]);
282
+ (0, import_react.useEffect)(() => {
283
+ edgesRef.current = edges;
284
+ }, [edges]);
285
+ const runLayout = (0, import_react.useCallback)(async () => {
286
+ if (!engine) return;
287
+ if (runningRef.current) return;
288
+ runningRef.current = true;
289
+ setIsLaying(true);
290
+ try {
291
+ const positions = await engine.layout(nodesRef.current, edgesRef.current, optionsRef.current);
292
+ const positionedIds = new Set(positions.map((p) => p.id));
293
+ const deltas = /* @__PURE__ */ new Map();
294
+ const cs = [];
295
+ for (const p of positions) {
296
+ const cur = nodesRef.current.find((n) => n.id === p.id);
297
+ if (!cur) continue;
298
+ if (cur.position.x === p.position.x && cur.position.y === p.position.y) continue;
299
+ cs.push(change.node.position(p.id, p.position, false));
300
+ deltas.set(p.id, {
301
+ dx: p.position.x - cur.position.x,
302
+ dy: p.position.y - cur.position.y
303
+ });
304
+ }
305
+ const findParent = (id) => nodesRef.current.find((x) => x.id === id);
306
+ for (const n of nodesRef.current) {
307
+ if (positionedIds.has(n.id)) continue;
308
+ if (!n.parentId) continue;
309
+ let cursor = n.parentId;
310
+ const seen = /* @__PURE__ */ new Set();
311
+ let delta;
312
+ while (cursor) {
313
+ if (seen.has(cursor)) break;
314
+ seen.add(cursor);
315
+ const found = deltas.get(cursor);
316
+ if (found) {
317
+ delta = found;
318
+ break;
319
+ }
320
+ cursor = findParent(cursor)?.parentId;
321
+ }
322
+ if (!delta) continue;
323
+ cs.push(
324
+ change.node.position(
325
+ n.id,
326
+ { x: n.position.x + delta.dx, y: n.position.y + delta.dy },
327
+ false
328
+ )
329
+ );
330
+ }
331
+ if (cs.length) onNodesChangeRef.current?.(cs);
332
+ onLayoutRef.current?.(positions);
333
+ } catch (err) {
334
+ onErrorRef.current?.(err);
335
+ } finally {
336
+ runningRef.current = false;
337
+ setIsLaying(false);
338
+ }
339
+ }, [engine]);
340
+ const sig = signature(nodes, edges);
341
+ const sigRef = (0, import_react.useRef)(sig);
342
+ (0, import_react.useEffect)(() => {
343
+ if (!auto) return;
344
+ if (sigRef.current === sig) return;
345
+ sigRef.current = sig;
346
+ const timer = setTimeout(() => void runLayout(), debounceMs);
347
+ return () => clearTimeout(timer);
348
+ }, [sig, auto, debounceMs, runLayout]);
349
+ return { runLayout, isLaying };
350
+ }
351
+
352
+ // src/workflow/hooks/useFlow.ts
353
+ var import_react2 = require("react");
354
+ var FlowInstanceContext = (0, import_react2.createContext)(null);
355
+ function useFlow() {
356
+ const instance = (0, import_react2.useContext)(FlowInstanceContext);
357
+ if (!instance) {
358
+ throw new Error("[@octaviaflow/core/workflow] useFlow() must be called inside <FlowCanvas>.");
359
+ }
360
+ return instance;
361
+ }
362
+
363
+ // src/workflow/hooks/useFlowState.ts
364
+ var import_react3 = require("react");
365
+ function useFlowState(options = {}) {
366
+ const [nodes, setNodes] = (0, import_react3.useState)(options.initialNodes ?? []);
367
+ const [edges, setEdges] = (0, import_react3.useState)(options.initialEdges ?? []);
368
+ const nodesRef = (0, import_react3.useRef)(nodes);
369
+ nodesRef.current = nodes;
370
+ const onNodesChange = (0, import_react3.useCallback)((changes) => {
371
+ setNodes((prev) => applyNodeChanges(prev, changes));
372
+ }, []);
373
+ const onEdgesChange = (0, import_react3.useCallback)((changes) => {
374
+ setEdges((prev) => applyEdgeChanges(prev, changes));
375
+ }, []);
376
+ const updateNodeData = (0, import_react3.useCallback)((id, patch) => {
377
+ setNodes((prev) => {
378
+ let touched = false;
379
+ const next = prev.map((n) => {
380
+ if (n.id !== id) return n;
381
+ const nextData = typeof patch === "function" ? patch(n.data) : { ...n.data ?? {}, ...patch };
382
+ if (Object.is(nextData, n.data)) return n;
383
+ touched = true;
384
+ return { ...n, data: nextData };
385
+ });
386
+ return touched ? next : prev;
387
+ });
388
+ }, []);
389
+ const getNode = (0, import_react3.useCallback)((id) => nodesRef.current.find((n) => n.id === id), []);
390
+ return (0, import_react3.useMemo)(
391
+ () => ({
392
+ nodes,
393
+ edges,
394
+ onNodesChange,
395
+ onEdgesChange,
396
+ setNodes,
397
+ setEdges,
398
+ updateNodeData,
399
+ getNode
400
+ }),
401
+ [nodes, edges, onNodesChange, onEdgesChange, updateNodeData, getNode]
402
+ );
403
+ }
404
+
405
+ // src/workflow/utils/geometry.ts
406
+ var DEFAULT_NODE_WIDTH = 368;
407
+ var DEFAULT_NODE_HEIGHT = 96;
408
+ var COLLAPSED_GROUP_HEIGHT = 36;
409
+ var COLLAPSED_FOREACH_HEIGHT = 40;
410
+ function effectiveHeight(node) {
411
+ const data = node.data;
412
+ if (data?.collapsed) {
413
+ if (node.type === "group") return COLLAPSED_GROUP_HEIGHT;
414
+ if (node.type === "forEach") return COLLAPSED_FOREACH_HEIGHT;
415
+ }
416
+ return node.height ?? DEFAULT_NODE_HEIGHT;
417
+ }
418
+ function handleCentre(node, side, index, total) {
419
+ const w = node.width ?? DEFAULT_NODE_WIDTH;
420
+ const h = effectiveHeight(node);
421
+ const x0 = node.position.x;
422
+ const y0 = node.position.y;
423
+ const denom = total + 1;
424
+ const ratio = (index + 1) / denom;
425
+ switch (side) {
426
+ case "top":
427
+ return { x: x0 + w * ratio, y: y0 };
428
+ case "bottom":
429
+ return { x: x0 + w * ratio, y: y0 + h };
430
+ case "left":
431
+ return { x: x0, y: y0 + h * ratio };
432
+ case "right":
433
+ return { x: x0 + w, y: y0 + h * ratio };
434
+ }
435
+ }
436
+ function bezierPath(start, startSide, end, endSide) {
437
+ const distance2 = Math.hypot(end.x - start.x, end.y - start.y);
438
+ const magnitude = Math.max(40, Math.min(160, distance2 * 0.45));
439
+ const c1 = offsetBySide(start, startSide, magnitude);
440
+ const c2 = offsetBySide(end, endSide, magnitude);
441
+ const d = `M ${start.x},${start.y} C ${c1.x},${c1.y} ${c2.x},${c2.y} ${end.x},${end.y}`;
442
+ const midX = 0.125 * start.x + 0.375 * c1.x + 0.375 * c2.x + 0.125 * end.x;
443
+ const midY = 0.125 * start.y + 0.375 * c1.y + 0.375 * c2.y + 0.125 * end.y;
444
+ return { d, midX, midY };
445
+ }
446
+ function offsetBySide(p, side, m) {
447
+ switch (side) {
448
+ case "top":
449
+ return { x: p.x, y: p.y - m };
450
+ case "bottom":
451
+ return { x: p.x, y: p.y + m };
452
+ case "left":
453
+ return { x: p.x - m, y: p.y };
454
+ case "right":
455
+ return { x: p.x + m, y: p.y };
456
+ }
457
+ }
458
+ function screenToFlow(p, vp) {
459
+ return {
460
+ x: (p.x - vp.x) / vp.zoom,
461
+ y: (p.y - vp.y) / vp.zoom
462
+ };
463
+ }
464
+ function flowToScreen(p, vp) {
465
+ return {
466
+ x: p.x * vp.zoom + vp.x,
467
+ y: p.y * vp.zoom + vp.y
468
+ };
469
+ }
470
+
471
+ // src/workflow/layout/dagre.ts
472
+ function createDagreEngine(dagre, defaults = {}) {
473
+ const lib = resolveDagre(dagre);
474
+ const defaultDir = defaults.direction ?? "TB";
475
+ const defaultNodeSpacing = defaults.nodeSpacing ?? 60;
476
+ const defaultRankSpacing = defaults.rankSpacing ?? 80;
477
+ return {
478
+ name: "dagre",
479
+ async layout(nodes, edges, options) {
480
+ const direction = options?.direction ?? defaultDir;
481
+ const nodesep = options?.nodeSpacing ?? defaultNodeSpacing;
482
+ const ranksep = options?.rankSpacing ?? defaultRankSpacing;
483
+ const g = new lib.graphlib.Graph({ multigraph: true, compound: true });
484
+ g.setGraph({
485
+ rankdir: direction,
486
+ nodesep,
487
+ ranksep,
488
+ ...options?.engine ?? {}
489
+ });
490
+ g.setDefaultEdgeLabel(() => ({}));
491
+ for (const n of nodes) {
492
+ if (n.hidden) continue;
493
+ g.setNode(n.id, {
494
+ width: n.width ?? DEFAULT_NODE_WIDTH,
495
+ height: n.height ?? DEFAULT_NODE_HEIGHT,
496
+ ...n.parentId ? { parent: n.parentId } : {}
497
+ });
498
+ if (n.parentId) {
499
+ g.setParent(
500
+ n.id,
501
+ n.parentId
502
+ );
503
+ }
504
+ }
505
+ for (const e of edges) {
506
+ if (e.hidden) continue;
507
+ g.setEdge(e.source, e.target, { id: e.id });
508
+ }
509
+ lib.layout(g);
510
+ const result = [];
511
+ for (const id of g.nodes()) {
512
+ const out = g.node(id);
513
+ if (!out) continue;
514
+ result.push({
515
+ id,
516
+ position: { x: out.x - out.width / 2, y: out.y - out.height / 2 }
517
+ });
518
+ }
519
+ return result;
520
+ }
521
+ };
522
+ }
523
+ function resolveDagre(input) {
524
+ const asAny = input;
525
+ if (asAny && typeof asAny.layout === "function" && asAny.graphlib?.Graph) {
526
+ return asAny;
527
+ }
528
+ if (asAny?.default && typeof asAny.default.layout === "function") {
529
+ return asAny.default;
530
+ }
531
+ throw new Error(
532
+ "[@octaviaflow/core/workflow] createDagreEngine: input does not look like the dagre module. Pass `dagre` (the default export) or `import * as dagre from 'dagre'`."
533
+ );
534
+ }
535
+
536
+ // src/workflow/layout/elk.ts
537
+ var ELK_DIRECTION = {
538
+ TB: "DOWN",
539
+ BT: "UP",
540
+ LR: "RIGHT",
541
+ RL: "LEFT"
542
+ };
543
+ function createElkEngine(elk, defaults = {}) {
544
+ const algorithm = defaults.algorithm ?? "layered";
545
+ const defaultDir = defaults.direction ?? "TB";
546
+ const defaultNodeSpacing = defaults.nodeSpacing ?? 60;
547
+ const defaultRankSpacing = defaults.rankSpacing ?? 80;
548
+ return {
549
+ name: "elk",
550
+ async layout(nodes, edges, options) {
551
+ const direction = options?.direction ?? defaultDir;
552
+ const nodeSpacing = options?.nodeSpacing ?? defaultNodeSpacing;
553
+ const rankSpacing = options?.rankSpacing ?? defaultRankSpacing;
554
+ const childrenByParent = /* @__PURE__ */ new Map();
555
+ for (const n of nodes) {
556
+ if (n.hidden) continue;
557
+ const key = n.parentId ?? void 0;
558
+ const list = childrenByParent.get(key);
559
+ if (list) list.push(n);
560
+ else childrenByParent.set(key, [n]);
561
+ }
562
+ const buildChildren = (parentId) => {
563
+ const list = childrenByParent.get(parentId) ?? [];
564
+ return list.map((n) => ({
565
+ id: n.id,
566
+ width: n.width ?? DEFAULT_NODE_WIDTH,
567
+ height: n.height ?? DEFAULT_NODE_HEIGHT,
568
+ children: childrenByParent.has(n.id) ? buildChildren(n.id) : void 0,
569
+ layoutOptions: childrenByParent.has(n.id) ? {
570
+ "elk.algorithm": algorithm,
571
+ "elk.direction": ELK_DIRECTION[direction],
572
+ "elk.spacing.nodeNode": String(nodeSpacing),
573
+ "elk.layered.spacing.nodeNodeBetweenLayers": String(rankSpacing)
574
+ } : void 0
575
+ }));
576
+ };
577
+ const elkEdges = edges.filter((e) => !e.hidden).map((e) => ({
578
+ id: e.id,
579
+ sources: [e.source],
580
+ targets: [e.target]
581
+ }));
582
+ const graph = {
583
+ id: "root",
584
+ layoutOptions: {
585
+ "elk.algorithm": algorithm,
586
+ "elk.direction": ELK_DIRECTION[direction],
587
+ "elk.spacing.nodeNode": String(nodeSpacing),
588
+ "elk.layered.spacing.nodeNodeBetweenLayers": String(rankSpacing),
589
+ ...Object.fromEntries(
590
+ Object.entries(options?.engine ?? {}).map(([k, v]) => [k, String(v)])
591
+ )
592
+ },
593
+ children: buildChildren(void 0),
594
+ edges: elkEdges
595
+ };
596
+ const laidOut = await elk.layout(graph);
597
+ const result = [];
598
+ const walk = (g, ox, oy) => {
599
+ const ax = ox + (g.x ?? 0);
600
+ const ay = oy + (g.y ?? 0);
601
+ if (g.id !== "root") {
602
+ result.push({ id: g.id, position: { x: ax, y: ay } });
603
+ }
604
+ if (g.children) {
605
+ for (const c of g.children) walk(c, ax, ay);
606
+ }
607
+ };
608
+ walk(laidOut, 0, 0);
609
+ return result;
610
+ }
611
+ };
612
+ }
613
+
614
+ // src/workflow/store/selectors.ts
615
+ var import_react5 = require("react");
616
+
617
+ // src/workflow/store/context.ts
618
+ var import_react4 = require("react");
619
+ var FlowStoreContext = (0, import_react4.createContext)(null);
620
+ function useFlowStore() {
621
+ const store = (0, import_react4.useContext)(FlowStoreContext);
622
+ if (!store) {
623
+ throw new Error(
624
+ "[@octaviaflow/core/workflow] useFlowStore must be called inside a <FlowCanvas>."
625
+ );
626
+ }
627
+ return store;
628
+ }
629
+
630
+ // src/workflow/store/selectors.ts
631
+ function useFlowSelector(selector, isEqual = Object.is) {
632
+ void isEqual;
633
+ const store = useFlowStore();
634
+ return (0, import_react5.useSyncExternalStore)(
635
+ store.subscribe,
636
+ () => selector(store.getSnapshot()),
637
+ () => selector(store.getSnapshot())
638
+ );
639
+ }
640
+ function useNodes() {
641
+ return useFlowSelector((s) => s.nodes);
642
+ }
643
+ function useEdges() {
644
+ return useFlowSelector((s) => s.edges);
645
+ }
646
+ function useViewport() {
647
+ return useFlowSelector((s) => s.viewport);
648
+ }
649
+ function useNodeById(id) {
650
+ return useFlowSelector((s) => s.nodes.find((n) => n.id === id));
651
+ }
652
+ function useNodeData(id) {
653
+ return useFlowSelector(
654
+ (s) => s.nodes.find((n) => n.id === id)?.data ?? void 0
655
+ );
656
+ }
657
+ function useEdgeById(id) {
658
+ return useFlowSelector((s) => s.edges.find((e) => e.id === id));
659
+ }
660
+ function useIsNodeSelected(id) {
661
+ return useFlowSelector((s) => s.selectedNodeIds.has(id));
662
+ }
663
+ function useIsEdgeSelected(id) {
664
+ return useFlowSelector((s) => s.selectedEdgeIds.has(id));
665
+ }
666
+ function useConnection() {
667
+ return useFlowSelector((s) => s.connection);
668
+ }
669
+ function useSelection() {
670
+ const nodes = useNodes();
671
+ const edges = useEdges();
672
+ const selectedNodeIds = useFlowSelector((s) => s.selectedNodeIds);
673
+ const selectedEdgeIds = useFlowSelector((s) => s.selectedEdgeIds);
674
+ return (0, import_react5.useMemo)(
675
+ () => ({
676
+ nodes: nodes.filter((n) => selectedNodeIds.has(n.id)),
677
+ edges: edges.filter((e) => selectedEdgeIds.has(e.id))
678
+ }),
679
+ [nodes, edges, selectedNodeIds, selectedEdgeIds]
680
+ );
681
+ }
682
+
683
+ // src/workflow/components/ConfigPanel/ConfigPanel.tsx
684
+ var import_react6 = require("react");
685
+
686
+ // src/utils/cn.ts
687
+ var import_clsx = require("clsx");
688
+ function cn(...inputs) {
689
+ return (0, import_clsx.clsx)(inputs);
690
+ }
691
+
692
+ // src/workflow/components/ConfigPanel/ConfigPanel.tsx
693
+ var import_jsx_runtime = require("react/jsx-runtime");
694
+ var SAVE_STATE_LABEL = {
695
+ clean: "Saved",
696
+ dirty: "Unsaved changes",
697
+ saving: "Saving\u2026",
698
+ saved: "Saved",
699
+ error: "Save failed"
700
+ };
701
+ var SAVE_STATE_DOT = {
702
+ clean: "var(--ods-text-tertiary)",
703
+ dirty: "var(--ods-status-warning, #d97706)",
704
+ saving: "var(--ods-status-running, #2563eb)",
705
+ saved: "var(--ods-status-success, #16a34a)",
706
+ error: "var(--ods-status-failed, #dc2626)"
707
+ };
708
+ function ConfigPanel({
709
+ open = true,
710
+ title,
711
+ kindLabel,
712
+ icon,
713
+ banner,
714
+ tabs,
715
+ activeTab: controlledActive,
716
+ defaultActiveTab,
717
+ onTabChange,
718
+ saveState = "clean",
719
+ onTitleChange,
720
+ onClose,
721
+ onSave,
722
+ onCancel,
723
+ resizable = true,
724
+ defaultWidth = 360,
725
+ minWidth = 280,
726
+ maxWidth = 720,
727
+ maxWidthContainerInset = 80,
728
+ variant = "pinned",
729
+ children,
730
+ footer,
731
+ className,
732
+ style
733
+ }) {
734
+ const [width, setWidth] = (0, import_react6.useState)(defaultWidth);
735
+ const [effectiveMax, setEffectiveMax] = (0, import_react6.useState)(maxWidth);
736
+ const panelRef = (0, import_react6.useRef)(null);
737
+ (0, import_react6.useEffect)(() => {
738
+ const el = panelRef.current;
739
+ if (!el) return;
740
+ const parent = el.parentElement;
741
+ if (!parent || typeof ResizeObserver === "undefined") return;
742
+ const update = () => {
743
+ const room = parent.clientWidth - maxWidthContainerInset;
744
+ const next = Math.max(minWidth, Math.min(maxWidth, room));
745
+ setEffectiveMax(next);
746
+ setWidth((w) => w > next ? next : w);
747
+ };
748
+ update();
749
+ const ro = new ResizeObserver(update);
750
+ ro.observe(parent);
751
+ return () => ro.disconnect();
752
+ }, [maxWidth, minWidth, maxWidthContainerInset]);
753
+ const [internalActive, setInternalActive] = (0, import_react6.useState)(
754
+ defaultActiveTab ?? tabs?.[0]?.id
755
+ );
756
+ const activeTabId = controlledActive ?? internalActive;
757
+ const selectTab = (id) => {
758
+ if (controlledActive === void 0) setInternalActive(id);
759
+ onTabChange?.(id);
760
+ };
761
+ const [editingTitle, setEditingTitle] = (0, import_react6.useState)(false);
762
+ const [draftTitle, setDraftTitle] = (0, import_react6.useState)(title ?? "");
763
+ (0, import_react6.useEffect)(() => {
764
+ if (!editingTitle) setDraftTitle(title ?? "");
765
+ }, [title, editingTitle]);
766
+ const commitTitle = (next) => {
767
+ setEditingTitle(false);
768
+ if (next !== title) onTitleChange?.(next);
769
+ };
770
+ const titleKeyDown = (e) => {
771
+ if (e.key === "Enter") {
772
+ e.preventDefault();
773
+ commitTitle(draftTitle);
774
+ } else if (e.key === "Escape") {
775
+ e.preventDefault();
776
+ setEditingTitle(false);
777
+ setDraftTitle(title ?? "");
778
+ }
779
+ };
780
+ const resizeRef = (0, import_react6.useRef)(null);
781
+ const onResizeDown = (0, import_react6.useCallback)(
782
+ (e) => {
783
+ if (!resizable) return;
784
+ e.preventDefault();
785
+ e.stopPropagation();
786
+ resizeRef.current = { pointerId: e.pointerId, startX: e.clientX, startWidth: width };
787
+ e.target.setPointerCapture(e.pointerId);
788
+ },
789
+ [resizable, width]
790
+ );
791
+ const onResizeMove = (e) => {
792
+ const r = resizeRef.current;
793
+ if (!r || r.pointerId !== e.pointerId) return;
794
+ const delta = r.startX - e.clientX;
795
+ const next = Math.max(minWidth, Math.min(effectiveMax, r.startWidth + delta));
796
+ setWidth(next);
797
+ };
798
+ const onResizeUp = (e) => {
799
+ if (resizeRef.current?.pointerId === e.pointerId) resizeRef.current = null;
800
+ };
801
+ if (!open) return null;
802
+ const activeTab = tabs?.find((t) => t.id === activeTabId);
803
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
804
+ "aside",
805
+ {
806
+ ref: panelRef,
807
+ className: cn(
808
+ "ods-flow-config-panel",
809
+ `ods-flow-config-panel--${variant}`,
810
+ editingTitle && "ods-flow-config-panel--editing-title",
811
+ className
812
+ ),
813
+ style: { width, ...style },
814
+ "aria-label": title ?? "Configuration",
815
+ children: [
816
+ resizable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
817
+ "div",
818
+ {
819
+ className: "ods-flow-config-panel__resize",
820
+ onPointerDown: onResizeDown,
821
+ onPointerMove: onResizeMove,
822
+ onPointerUp: onResizeUp,
823
+ onPointerCancel: onResizeUp,
824
+ "aria-hidden": "true"
825
+ }
826
+ ),
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
+ ] }),
876
+ 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
+ "button",
878
+ {
879
+ type: "button",
880
+ role: "tab",
881
+ "aria-selected": activeTabId === t.id,
882
+ disabled: t.disabled,
883
+ className: cn(
884
+ "ods-flow-config-panel__tab",
885
+ activeTabId === t.id && "ods-flow-config-panel__tab--active"
886
+ ),
887
+ onClick: () => selectTab(t.id),
888
+ children: [
889
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t.label }),
890
+ t.badge !== void 0 && t.badge !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ods-flow-config-panel__tab-badge", children: t.badge })
891
+ ]
892
+ },
893
+ t.id
894
+ )) }),
895
+ 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 }),
897
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", { className: "ods-flow-config-panel__footer", children: [
898
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
899
+ "span",
900
+ {
901
+ className: cn(
902
+ "ods-flow-config-panel__save-state",
903
+ `ods-flow-config-panel__save-state--${saveState}`
904
+ ),
905
+ children: [
906
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
907
+ "span",
908
+ {
909
+ className: "ods-flow-config-panel__save-dot",
910
+ style: { background: SAVE_STATE_DOT[saveState] },
911
+ "aria-hidden": "true"
912
+ }
913
+ ),
914
+ SAVE_STATE_LABEL[saveState]
915
+ ]
916
+ }
917
+ ),
918
+ footer ?? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ods-flow-config-panel__footer-actions", children: [
919
+ onCancel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
920
+ "button",
921
+ {
922
+ type: "button",
923
+ className: "ods-flow-config-panel__btn ods-flow-config-panel__btn--ghost",
924
+ onClick: onCancel,
925
+ children: "Cancel"
926
+ }
927
+ ),
928
+ onSave && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
929
+ "button",
930
+ {
931
+ type: "button",
932
+ className: "ods-flow-config-panel__btn ods-flow-config-panel__btn--primary",
933
+ onClick: onSave,
934
+ disabled: saveState === "saving" || saveState === "clean",
935
+ children: saveState === "saving" ? "Saving\u2026" : "Save"
936
+ }
937
+ )
938
+ ] })
939
+ ] })
940
+ ]
941
+ }
942
+ );
943
+ }
944
+
945
+ // src/workflow/components/FlowCanvas/FlowCanvas.tsx
946
+ var import_react14 = require("react");
947
+
948
+ // src/workflow/store/createFlowStore.ts
949
+ var DEFAULT_VIEWPORT = { x: 0, y: 0, zoom: 1 };
950
+ function createFlowStore({
951
+ initialNodes = [],
952
+ initialEdges = [],
953
+ initialViewport = DEFAULT_VIEWPORT
954
+ } = {}) {
955
+ let snapshot = {
956
+ nodes: initialNodes,
957
+ edges: initialEdges,
958
+ viewport: initialViewport,
959
+ selectedNodeIds: EMPTY_SET,
960
+ selectedEdgeIds: EMPTY_SET,
961
+ connection: null
962
+ };
963
+ const listeners = /* @__PURE__ */ new Set();
964
+ const emit = () => {
965
+ for (const l of listeners) l();
966
+ };
967
+ const subscribe = (listener) => {
968
+ listeners.add(listener);
969
+ return () => {
970
+ listeners.delete(listener);
971
+ };
972
+ };
973
+ const replace = (patch) => {
974
+ snapshot = { ...snapshot, ...patch };
975
+ emit();
976
+ };
977
+ return {
978
+ subscribe,
979
+ getSnapshot: () => snapshot,
980
+ setNodes(nodes) {
981
+ if (nodes === snapshot.nodes) return;
982
+ replace({ nodes });
983
+ },
984
+ setEdges(edges) {
985
+ if (edges === snapshot.edges) return;
986
+ replace({ edges });
987
+ },
988
+ setViewport(viewport) {
989
+ if (viewport.x === snapshot.viewport.x && viewport.y === snapshot.viewport.y && viewport.zoom === snapshot.viewport.zoom) {
990
+ return;
991
+ }
992
+ replace({ viewport });
993
+ },
994
+ setSelection(selectedNodeIds, selectedEdgeIds) {
995
+ replace({ selectedNodeIds, selectedEdgeIds });
996
+ },
997
+ setConnection(connection) {
998
+ if (connection === snapshot.connection) return;
999
+ replace({ connection });
1000
+ }
1001
+ };
1002
+ }
1003
+ var EMPTY_SET = /* @__PURE__ */ new Set();
1004
+
1005
+ // src/workflow/utils/collision.ts
1006
+ function resolveNodeCollisions(node, proposed, others, opts) {
1007
+ const { gap, maxIterations = 8, exclude, scopeToSiblings = true } = opts;
1008
+ if (gap < 0 || others.length === 0) return proposed;
1009
+ const w = node.width ?? DEFAULT_NODE_WIDTH;
1010
+ const h = effectiveHeight(node);
1011
+ const parentId = node.parentId;
1012
+ const candidates = others.filter((o) => {
1013
+ if (o.id === node.id) return false;
1014
+ if (o.hidden) return false;
1015
+ if (exclude?.has(o.id)) return false;
1016
+ if (scopeToSiblings && (o.parentId ?? void 0) !== (parentId ?? void 0)) {
1017
+ return false;
1018
+ }
1019
+ return true;
1020
+ });
1021
+ if (candidates.length === 0) return proposed;
1022
+ let cur = proposed;
1023
+ for (let iter = 0; iter < maxIterations; iter++) {
1024
+ let bestOverlap = Number.POSITIVE_INFINITY;
1025
+ let bestPushX = 0;
1026
+ let bestPushY = 0;
1027
+ let hit = false;
1028
+ const aLeft = cur.x - gap;
1029
+ const aRight = cur.x + w + gap;
1030
+ const aTop = cur.y - gap;
1031
+ const aBottom = cur.y + h + gap;
1032
+ for (const o of candidates) {
1033
+ const ow = o.width ?? DEFAULT_NODE_WIDTH;
1034
+ const oh = effectiveHeight(o);
1035
+ const bLeft = o.position.x;
1036
+ const bRight = o.position.x + ow;
1037
+ const bTop = o.position.y;
1038
+ const bBottom = o.position.y + oh;
1039
+ const overlapX = Math.min(aRight, bRight) - Math.max(aLeft, bLeft);
1040
+ const overlapY = Math.min(aBottom, bBottom) - Math.max(aTop, bTop);
1041
+ if (overlapX <= 0 || overlapY <= 0) continue;
1042
+ let pushX = 0;
1043
+ let pushY = 0;
1044
+ let overlap;
1045
+ if (overlapX < overlapY) {
1046
+ const aCx = cur.x + w / 2;
1047
+ const bCx = o.position.x + ow / 2;
1048
+ pushX = aCx < bCx ? -overlapX : overlapX;
1049
+ overlap = overlapX;
1050
+ } else {
1051
+ const aCy = cur.y + h / 2;
1052
+ const bCy = o.position.y + oh / 2;
1053
+ pushY = aCy < bCy ? -overlapY : overlapY;
1054
+ overlap = overlapY;
1055
+ }
1056
+ if (overlap < bestOverlap) {
1057
+ bestOverlap = overlap;
1058
+ bestPushX = pushX;
1059
+ bestPushY = pushY;
1060
+ hit = true;
1061
+ }
1062
+ }
1063
+ if (!hit) break;
1064
+ cur = { x: cur.x + bestPushX, y: cur.y + bestPushY };
1065
+ }
1066
+ return cur;
1067
+ }
1068
+
1069
+ // src/workflow/utils/parenting.ts
1070
+ function descendantsOf(nodeId, nodes) {
1071
+ const childrenByParent = /* @__PURE__ */ new Map();
1072
+ for (const n of nodes) {
1073
+ if (!n.parentId) continue;
1074
+ const arr = childrenByParent.get(n.parentId);
1075
+ if (arr) arr.push(n);
1076
+ else childrenByParent.set(n.parentId, [n]);
1077
+ }
1078
+ const out = [];
1079
+ const stack = [nodeId];
1080
+ const visited = /* @__PURE__ */ new Set([nodeId]);
1081
+ while (stack.length) {
1082
+ const cur = stack.pop();
1083
+ if (cur === void 0) break;
1084
+ const list = childrenByParent.get(cur);
1085
+ if (!list) continue;
1086
+ for (const c of list) {
1087
+ if (visited.has(c.id)) continue;
1088
+ visited.add(c.id);
1089
+ out.push(c);
1090
+ stack.push(c.id);
1091
+ }
1092
+ }
1093
+ return out;
1094
+ }
1095
+ function findAncestor(node, nodes, predicate) {
1096
+ let cursor = node;
1097
+ const seen = /* @__PURE__ */ new Set();
1098
+ while (cursor?.parentId) {
1099
+ if (seen.has(cursor.parentId)) return void 0;
1100
+ seen.add(cursor.parentId);
1101
+ const parent = nodes.find((n) => n.id === cursor.parentId);
1102
+ if (!parent) return void 0;
1103
+ if (predicate(parent)) return parent;
1104
+ cursor = parent;
1105
+ }
1106
+ return void 0;
1107
+ }
1108
+ function clampToParentExtent(node, proposed, nodes) {
1109
+ if (node.extent !== "parent" || !node.parentId) return proposed;
1110
+ const parent = nodes.find((n) => n.id === node.parentId);
1111
+ if (!parent) return proposed;
1112
+ const pw = parent.width ?? DEFAULT_NODE_WIDTH;
1113
+ const ph = effectiveHeight(parent);
1114
+ const w = node.width ?? DEFAULT_NODE_WIDTH;
1115
+ const h = effectiveHeight(node);
1116
+ const minX = parent.position.x;
1117
+ const minY = parent.position.y;
1118
+ const maxX = parent.position.x + pw - w;
1119
+ const maxY = parent.position.y + ph - h;
1120
+ return {
1121
+ x: Math.max(minX, Math.min(maxX, proposed.x)),
1122
+ y: Math.max(minY, Math.min(maxY, proposed.y))
1123
+ };
1124
+ }
1125
+ function findContainingGroup(point, nodes, exclude = []) {
1126
+ for (let i = nodes.length - 1; i >= 0; i--) {
1127
+ const n = nodes[i];
1128
+ if (exclude.includes(n.id)) continue;
1129
+ if (n.type !== "group") continue;
1130
+ if (n.data && typeof n.data === "object" && n.data.collapsed) {
1131
+ continue;
1132
+ }
1133
+ const w = n.width ?? DEFAULT_NODE_WIDTH;
1134
+ const h = effectiveHeight(n);
1135
+ if (point.x >= n.position.x && point.y >= n.position.y && point.x <= n.position.x + w && point.y <= n.position.y + h) {
1136
+ return n;
1137
+ }
1138
+ }
1139
+ return void 0;
1140
+ }
1141
+
1142
+ // src/workflow/components/FlowEdge/FlowEdge.tsx
1143
+ var import_react8 = require("react");
1144
+
1145
+ // src/workflow/utils/paths.ts
1146
+ function buildEdgePath(routing, start, startSide, end, endSide, options = {}) {
1147
+ switch (routing) {
1148
+ case "step":
1149
+ return stepPath(start, startSide, end, endSide);
1150
+ case "smoothstep":
1151
+ return smoothStepPath(start, startSide, end, endSide, options.borderRadius ?? 8);
1152
+ case "straight":
1153
+ return straightPath(start, end);
1154
+ default:
1155
+ return bezierPath2(start, startSide, end, endSide, options.curvature ?? 0.25);
1156
+ }
1157
+ }
1158
+ function bezierPath2(start, startSide, end, endSide, curvature = 0.25) {
1159
+ const sourceOffset = calcControlOffset(start, startSide, end, curvature);
1160
+ const targetOffset = calcControlOffset(end, endSide, start, curvature);
1161
+ const c1 = offsetAlongSide(start, startSide, sourceOffset);
1162
+ const c2 = offsetAlongSide(end, endSide, targetOffset);
1163
+ const d = `M ${start.x},${start.y} C ${c1.x},${c1.y} ${c2.x},${c2.y} ${end.x},${end.y}`;
1164
+ const midX = 0.125 * start.x + 0.375 * c1.x + 0.375 * c2.x + 0.125 * end.x;
1165
+ const midY = 0.125 * start.y + 0.375 * c1.y + 0.375 * c2.y + 0.125 * end.y;
1166
+ const tx = 3 * ((1 - 0.5) ** 2 * (c1.x - start.x) + 2 * (1 - 0.5) * 0.5 * (c2.x - c1.x) + 0.5 ** 2 * (end.x - c2.x));
1167
+ const ty = 3 * ((1 - 0.5) ** 2 * (c1.y - start.y) + 2 * (1 - 0.5) * 0.5 * (c2.y - c1.y) + 0.5 ** 2 * (end.y - c2.y));
1168
+ const midAngle = Math.atan2(ty, tx);
1169
+ return { d, midX, midY, midAngle };
1170
+ }
1171
+ function axisDistance(from, side, to) {
1172
+ switch (side) {
1173
+ case "top":
1174
+ return from.y - to.y;
1175
+ case "bottom":
1176
+ return to.y - from.y;
1177
+ case "left":
1178
+ return from.x - to.x;
1179
+ case "right":
1180
+ return to.x - from.x;
1181
+ }
1182
+ }
1183
+ function calcControlOffset(from, side, to, curvature) {
1184
+ const distance2 = axisDistance(from, side, to);
1185
+ if (distance2 >= 0) {
1186
+ return distance2 * 0.5 * (1 + curvature);
1187
+ }
1188
+ return curvature * 25 * Math.sqrt(-distance2);
1189
+ }
1190
+ function offsetAlongSide(p, side, magnitude) {
1191
+ switch (side) {
1192
+ case "top":
1193
+ return { x: p.x, y: p.y - magnitude };
1194
+ case "bottom":
1195
+ return { x: p.x, y: p.y + magnitude };
1196
+ case "left":
1197
+ return { x: p.x - magnitude, y: p.y };
1198
+ case "right":
1199
+ return { x: p.x + magnitude, y: p.y };
1200
+ }
1201
+ }
1202
+ function stepPath(start, startSide, end, endSide) {
1203
+ const corners = computeStepCorners(start, startSide, end, endSide);
1204
+ const pts = [start, ...corners, end];
1205
+ const d = pts.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x},${p.y}`).join(" ");
1206
+ const { midX, midY, midAngle } = midpointOnPolyline(pts);
1207
+ return { d, midX, midY, midAngle };
1208
+ }
1209
+ function smoothStepPath(start, startSide, end, endSide, borderRadius = 8) {
1210
+ const corners = computeStepCorners(start, startSide, end, endSide);
1211
+ const pts = [start, ...corners, end];
1212
+ if (corners.length === 0) {
1213
+ return straightPath(start, end);
1214
+ }
1215
+ const segments = [`M ${pts[0].x},${pts[0].y}`];
1216
+ for (let i = 1; i < pts.length - 1; i++) {
1217
+ const prev = pts[i - 1];
1218
+ const here = pts[i];
1219
+ const next = pts[i + 1];
1220
+ const r = Math.min(borderRadius, distance(prev, here) / 2, distance(here, next) / 2);
1221
+ const enter = pointTowards(here, prev, r);
1222
+ const exit = pointTowards(here, next, r);
1223
+ segments.push(`L ${enter.x},${enter.y}`);
1224
+ segments.push(`Q ${here.x},${here.y} ${exit.x},${exit.y}`);
1225
+ }
1226
+ const last = pts[pts.length - 1];
1227
+ segments.push(`L ${last.x},${last.y}`);
1228
+ const d = segments.join(" ");
1229
+ const { midX, midY, midAngle } = midpointOnPolyline(pts);
1230
+ return { d, midX, midY, midAngle };
1231
+ }
1232
+ function straightPath(start, end) {
1233
+ const d = `M ${start.x},${start.y} L ${end.x},${end.y}`;
1234
+ return {
1235
+ d,
1236
+ midX: (start.x + end.x) / 2,
1237
+ midY: (start.y + end.y) / 2,
1238
+ midAngle: Math.atan2(end.y - start.y, end.x - start.x)
1239
+ };
1240
+ }
1241
+ function computeStepCorners(start, startSide, end, endSide) {
1242
+ const sourceVertical = startSide === "top" || startSide === "bottom";
1243
+ const targetVertical = endSide === "top" || endSide === "bottom";
1244
+ if (sourceVertical && targetVertical) {
1245
+ const midY = (start.y + end.y) / 2;
1246
+ return [
1247
+ { x: start.x, y: midY },
1248
+ { x: end.x, y: midY }
1249
+ ];
1250
+ }
1251
+ if (!sourceVertical && !targetVertical) {
1252
+ const midX = (start.x + end.x) / 2;
1253
+ return [
1254
+ { x: midX, y: start.y },
1255
+ { x: midX, y: end.y }
1256
+ ];
1257
+ }
1258
+ if (sourceVertical) {
1259
+ return [{ x: end.x, y: start.y }];
1260
+ }
1261
+ return [{ x: start.x, y: end.y }];
1262
+ }
1263
+ function midpointOnPolyline(pts) {
1264
+ if (pts.length === 2) {
1265
+ return {
1266
+ midX: (pts[0].x + pts[1].x) / 2,
1267
+ midY: (pts[0].y + pts[1].y) / 2,
1268
+ midAngle: Math.atan2(pts[1].y - pts[0].y, pts[1].x - pts[0].x)
1269
+ };
1270
+ }
1271
+ let totalLen = 0;
1272
+ const lens = [];
1273
+ for (let i = 0; i < pts.length - 1; i++) {
1274
+ const l = distance(pts[i], pts[i + 1]);
1275
+ lens.push(l);
1276
+ totalLen += l;
1277
+ }
1278
+ const half = totalLen / 2;
1279
+ let acc = 0;
1280
+ for (let i = 0; i < lens.length; i++) {
1281
+ if (acc + lens[i] >= half) {
1282
+ const seg = lens[i];
1283
+ const remaining = half - acc;
1284
+ const t = seg === 0 ? 0 : remaining / seg;
1285
+ const a = pts[i];
1286
+ const b = pts[i + 1];
1287
+ return {
1288
+ midX: a.x + (b.x - a.x) * t,
1289
+ midY: a.y + (b.y - a.y) * t,
1290
+ midAngle: Math.atan2(b.y - a.y, b.x - a.x)
1291
+ };
1292
+ }
1293
+ acc += lens[i];
1294
+ }
1295
+ return { midX: pts[0].x, midY: pts[0].y, midAngle: 0 };
1296
+ }
1297
+ function distance(a, b) {
1298
+ return Math.hypot(b.x - a.x, b.y - a.y);
1299
+ }
1300
+ function pointTowards(from, to, dist) {
1301
+ const d = distance(from, to);
1302
+ if (d === 0) return from;
1303
+ const t = dist / d;
1304
+ return { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t };
1305
+ }
1306
+
1307
+ // src/workflow/components/Handle/handleRegistry.ts
1308
+ var import_react7 = require("react");
1309
+ var HandleRegistryContext = (0, import_react7.createContext)(null);
1310
+ function useHandleRegistry() {
1311
+ const r = (0, import_react7.useContext)(HandleRegistryContext);
1312
+ if (!r) {
1313
+ throw new Error("[@octaviaflow/core/workflow] Handle must be used inside <FlowCanvas>.");
1314
+ }
1315
+ return r;
1316
+ }
1317
+ function createHandleRegistry() {
1318
+ const map = /* @__PURE__ */ new Map();
1319
+ const key = (n, t, h) => `${n}::${t}::${h}`;
1320
+ const listeners = /* @__PURE__ */ new Set();
1321
+ const notify = () => {
1322
+ for (const l of listeners) l();
1323
+ };
1324
+ return {
1325
+ register(d) {
1326
+ map.set(key(d.nodeId, d.type, d.handleId), d);
1327
+ notify();
1328
+ return () => {
1329
+ const k = key(d.nodeId, d.type, d.handleId);
1330
+ if (map.get(k) === d) {
1331
+ map.delete(k);
1332
+ notify();
1333
+ }
1334
+ };
1335
+ },
1336
+ resolve(nodeId, type, handleId) {
1337
+ return map.get(key(nodeId, type, handleId));
1338
+ },
1339
+ subscribe(listener) {
1340
+ listeners.add(listener);
1341
+ return () => {
1342
+ listeners.delete(listener);
1343
+ };
1344
+ }
1345
+ };
1346
+ }
1347
+
1348
+ // src/workflow/components/FlowEdge/FlowEdge.tsx
1349
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1350
+ function FlowEdgeImpl({
1351
+ edge,
1352
+ nodes,
1353
+ onSelect,
1354
+ onDelete,
1355
+ onLabelChange,
1356
+ showDelete = true,
1357
+ curvature = 0.25,
1358
+ borderRadius = 8
1359
+ }) {
1360
+ const uid = (0, import_react8.useId)();
1361
+ const markerId = `ods-flow-edge-arrow-${uid.replace(/:/g, "")}`;
1362
+ const registry = useHandleRegistry();
1363
+ const [hovered, setHovered] = (0, import_react8.useState)(false);
1364
+ const [editing, setEditing] = (0, import_react8.useState)(false);
1365
+ const [draftLabel, setDraftLabel] = (0, import_react8.useState)(edge.label ?? "");
1366
+ const inputRef = (0, import_react8.useRef)(null);
1367
+ (0, import_react8.useEffect)(() => {
1368
+ if (!editing) setDraftLabel(edge.label ?? "");
1369
+ }, [edge.label, editing]);
1370
+ const sourceNode = nodes.find((n) => n.id === edge.source);
1371
+ const targetNode = nodes.find((n) => n.id === edge.target);
1372
+ if (!sourceNode || !targetNode) return null;
1373
+ const sourceHandleId = edge.sourceHandle ?? "default";
1374
+ const targetHandleId = edge.targetHandle ?? "default";
1375
+ const sourceDesc = registry.resolve(sourceNode.id, "source", sourceHandleId);
1376
+ const targetDesc = registry.resolve(targetNode.id, "target", targetHandleId);
1377
+ const sourceSide = sourceDesc?.side ?? sourceNode.sourcePosition ?? "bottom";
1378
+ const targetSide = targetDesc?.side ?? targetNode.targetPosition ?? "top";
1379
+ const sourceIndex = sourceDesc?.index ?? 0;
1380
+ const sourceTotal = sourceDesc?.total ?? 1;
1381
+ const targetIndex = targetDesc?.index ?? 0;
1382
+ const targetTotal = targetDesc?.total ?? 1;
1383
+ const rawStart = handleCentre(sourceNode, sourceSide, sourceIndex, sourceTotal);
1384
+ const rawEnd = handleCentre(targetNode, targetSide, targetIndex, targetTotal);
1385
+ const HANDLE_GAP = 8;
1386
+ const start = offsetAlongSide2(rawStart, sourceSide, HANDLE_GAP);
1387
+ const end = offsetAlongSide2(rawEnd, targetSide, HANDLE_GAP);
1388
+ const routing = edge.routing ?? "bezier";
1389
+ const { d, midX, midY } = buildEdgePath(routing, start, sourceSide, end, targetSide, {
1390
+ curvature,
1391
+ borderRadius
1392
+ });
1393
+ const { d: hitD } = buildEdgePath(routing, rawStart, sourceSide, rawEnd, targetSide, {
1394
+ curvature,
1395
+ borderRadius
1396
+ });
1397
+ const type = edge.type ?? "default";
1398
+ const isHot = hovered || edge.selected;
1399
+ const stroke = type === "error" ? "var(--ods-status-failed, #dc2626)" : isHot ? "var(--ods-accent, #4f46e5)" : "var(--ods-border-strong, #6b7280)";
1400
+ const typeDash = type === "conditional" ? "6 4" : type === "loop" ? "4 4" : void 0;
1401
+ const dash = edge.style?.strokeDasharray ?? typeDash;
1402
+ const strokeWidth = edge.style?.strokeWidth ?? (isHot ? 2 : 1.4);
1403
+ const animateDash = edge.animated === true;
1404
+ const label = edge.label;
1405
+ const hasDelete = showDelete && (isHot || edge.selected) && !!onDelete;
1406
+ const showChrome = !!label || editing || hasDelete;
1407
+ const commitLabel = (next) => {
1408
+ setEditing(false);
1409
+ if (next !== edge.label) onLabelChange?.(edge.id, next);
1410
+ };
1411
+ const onLabelKey = (e) => {
1412
+ if (e.key === "Enter") {
1413
+ e.preventDefault();
1414
+ commitLabel(draftLabel);
1415
+ } else if (e.key === "Escape") {
1416
+ e.preventDefault();
1417
+ setEditing(false);
1418
+ setDraftLabel(edge.label ?? "");
1419
+ }
1420
+ };
1421
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1422
+ "g",
1423
+ {
1424
+ className: cn(
1425
+ "ods-flow-edge-v2",
1426
+ `ods-flow-edge-v2--${type}`,
1427
+ `ods-flow-edge-v2--routing-${routing}`,
1428
+ edge.selected && "ods-flow-edge-v2--selected",
1429
+ hovered && "ods-flow-edge-v2--hovered",
1430
+ edge.className
1431
+ ),
1432
+ "data-edge-id": edge.id,
1433
+ "data-edge-routing": routing,
1434
+ onMouseEnter: () => setHovered(true),
1435
+ onMouseLeave: () => setHovered(false),
1436
+ onClick: (e) => {
1437
+ e.stopPropagation();
1438
+ onSelect?.(edge.id);
1439
+ },
1440
+ children: [
1441
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1442
+ "marker",
1443
+ {
1444
+ id: markerId,
1445
+ viewBox: "0 0 10 10",
1446
+ refX: "9",
1447
+ refY: "5",
1448
+ markerWidth: "7",
1449
+ markerHeight: "7",
1450
+ orient: "auto-start-reverse",
1451
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M0 0L10 5L0 10z", fill: stroke })
1452
+ }
1453
+ ) }),
1454
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: hitD, stroke: "transparent", strokeWidth: 20, fill: "none" }),
1455
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1456
+ "path",
1457
+ {
1458
+ d,
1459
+ stroke,
1460
+ strokeWidth,
1461
+ strokeDasharray: dash,
1462
+ fill: "none",
1463
+ markerEnd: `url(#${markerId})`,
1464
+ style: animateDash ? {
1465
+ strokeDasharray: dash ?? "6 4",
1466
+ animation: "ods-flow-edge-flow 0.6s linear infinite"
1467
+ } : void 0
1468
+ }
1469
+ ),
1470
+ showChrome && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1471
+ "foreignObject",
1472
+ {
1473
+ x: midX - 140,
1474
+ y: midY - 16,
1475
+ width: 280,
1476
+ height: 32,
1477
+ style: { overflow: "visible", pointerEvents: "none" },
1478
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ods-flow-edge-v2__chrome", children: [
1479
+ (label || editing) && (editing ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1480
+ "input",
1481
+ {
1482
+ ref: inputRef,
1483
+ className: "ods-flow-edge-v2__label-input",
1484
+ value: draftLabel,
1485
+ autoFocus: true,
1486
+ onChange: (e) => setDraftLabel(e.target.value),
1487
+ onBlur: () => commitLabel(draftLabel),
1488
+ onKeyDown: onLabelKey,
1489
+ onClick: (e) => e.stopPropagation(),
1490
+ onMouseDown: (e) => e.stopPropagation(),
1491
+ "aria-label": "Edit edge label"
1492
+ }
1493
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1494
+ "button",
1495
+ {
1496
+ type: "button",
1497
+ className: "ods-flow-edge-v2__label-chip",
1498
+ onDoubleClick: (e) => {
1499
+ if (!onLabelChange) return;
1500
+ e.stopPropagation();
1501
+ setDraftLabel(edge.label ?? "");
1502
+ setEditing(true);
1503
+ },
1504
+ onClick: (e) => {
1505
+ e.stopPropagation();
1506
+ onSelect?.(edge.id);
1507
+ },
1508
+ title: onLabelChange ? "Double-click to edit" : void 0,
1509
+ tabIndex: onLabelChange ? 0 : -1,
1510
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: label })
1511
+ }
1512
+ )),
1513
+ hasDelete && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1514
+ "button",
1515
+ {
1516
+ type: "button",
1517
+ className: "ods-flow-edge-v2__delete-btn",
1518
+ onClick: (e) => {
1519
+ e.stopPropagation();
1520
+ onDelete?.(edge.id);
1521
+ },
1522
+ "aria-label": "Delete edge",
1523
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "10", height: "10", viewBox: "0 0 10 10", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1524
+ "path",
1525
+ {
1526
+ d: "M2 2l6 6M8 2l-6 6",
1527
+ stroke: "currentColor",
1528
+ strokeWidth: 1.5,
1529
+ strokeLinecap: "round"
1530
+ }
1531
+ ) })
1532
+ }
1533
+ )
1534
+ ] })
1535
+ }
1536
+ )
1537
+ ]
1538
+ }
1539
+ );
1540
+ }
1541
+ function offsetAlongSide2(p, side, gap) {
1542
+ switch (side) {
1543
+ case "top":
1544
+ return { x: p.x, y: p.y - gap };
1545
+ case "bottom":
1546
+ return { x: p.x, y: p.y + gap };
1547
+ case "left":
1548
+ return { x: p.x - gap, y: p.y };
1549
+ case "right":
1550
+ return { x: p.x + gap, y: p.y };
1551
+ }
1552
+ }
1553
+ var FlowEdge = (0, import_react8.memo)(FlowEdgeImpl, (prev, next) => {
1554
+ if (prev.edge !== next.edge) return false;
1555
+ if (prev.onSelect !== next.onSelect || prev.onDelete !== next.onDelete) return false;
1556
+ if (prev.onLabelChange !== next.onLabelChange) return false;
1557
+ if (prev.showDelete !== next.showDelete) return false;
1558
+ if (prev.curvature !== next.curvature || prev.borderRadius !== next.borderRadius) return false;
1559
+ if (prev.handleVersion !== next.handleVersion) return false;
1560
+ const ps = prev.nodes.find((n) => n.id === prev.edge.source);
1561
+ const pt = prev.nodes.find((n) => n.id === prev.edge.target);
1562
+ const ns = next.nodes.find((n) => n.id === next.edge.source);
1563
+ const nt = next.nodes.find((n) => n.id === next.edge.target);
1564
+ if (ps !== ns || pt !== nt) return false;
1565
+ return true;
1566
+ });
1567
+
1568
+ // src/workflow/components/FlowNode/FlowNode.tsx
1569
+ var import_react11 = require("react");
1570
+
1571
+ // src/workflow/components/FlowCanvas/FlowCanvasContext.tsx
1572
+ var import_react9 = require("react");
1573
+ var FlowDispatchContext = (0, import_react9.createContext)(null);
1574
+ function useFlowDispatch() {
1575
+ const dispatch = (0, import_react9.useContext)(FlowDispatchContext);
1576
+ if (!dispatch) {
1577
+ throw new Error(
1578
+ "[@octaviaflow/core/workflow] useFlowDispatch must be called inside <FlowCanvas>."
1579
+ );
1580
+ }
1581
+ return dispatch;
1582
+ }
1583
+ var FlowNodeBridgeContext = (0, import_react9.createContext)(null);
1584
+ function useFlowNodeBridge() {
1585
+ const b = (0, import_react9.useContext)(FlowNodeBridgeContext);
1586
+ if (!b) {
1587
+ throw new Error("[@octaviaflow/core/workflow] FlowNode must be a child of <FlowCanvas>.");
1588
+ }
1589
+ return b;
1590
+ }
1591
+
1592
+ // src/workflow/components/FlowNode/FlowNodeContext.tsx
1593
+ var import_react10 = require("react");
1594
+ var FlowNodeContext = (0, import_react10.createContext)(null);
1595
+ function useFlowNodeContext() {
1596
+ const v = (0, import_react10.useContext)(FlowNodeContext);
1597
+ if (!v) {
1598
+ throw new Error(
1599
+ "[@octaviaflow/core/workflow] Handle / NodeToolbar must be inside a node renderer."
1600
+ );
1601
+ }
1602
+ return v;
1603
+ }
1604
+
1605
+ // src/workflow/components/FlowNode/FlowNode.tsx
1606
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1607
+ function FlowNode({
1608
+ node,
1609
+ selected,
1610
+ dragging,
1611
+ isConnecting,
1612
+ Kind,
1613
+ // Match the BaseNode body's fixed 368 px width so the wrapper bbox and
1614
+ // the rendered card line up on the first paint — before the
1615
+ // ResizeObserver has had a chance to write back the measured size.
1616
+ // Container kinds (group / forEach) opt out by setting `node.width`.
1617
+ defaultWidth = 368
1618
+ }) {
1619
+ const bridge = useFlowNodeBridge();
1620
+ const wrapperRef = (0, import_react11.useRef)(null);
1621
+ (0, import_react11.useEffect)(() => {
1622
+ const el = wrapperRef.current;
1623
+ if (!el || typeof ResizeObserver === "undefined") return;
1624
+ if (node.type === "group" || node.type === "forEach") return;
1625
+ const ro = new ResizeObserver(() => {
1626
+ bridge.reportDimensions(node.id, el.offsetWidth, el.offsetHeight);
1627
+ });
1628
+ ro.observe(el);
1629
+ return () => ro.disconnect();
1630
+ }, [bridge, node.id, node.type]);
1631
+ const ctx = (0, import_react11.useMemo)(
1632
+ () => ({ id: node.id, node, selected }),
1633
+ [node, selected]
1634
+ );
1635
+ const downPosRef = (0, import_react11.useRef)(null);
1636
+ const draggedRef = (0, import_react11.useRef)(false);
1637
+ const CLICK_VS_DRAG_PX = 5;
1638
+ const handlePointerDown = (e) => {
1639
+ if (e.target.closest("[data-handle-id]")) return;
1640
+ if (e.target.closest("[data-flow-no-drag='true']")) return;
1641
+ e.stopPropagation();
1642
+ downPosRef.current = { x: e.clientX, y: e.clientY };
1643
+ draggedRef.current = false;
1644
+ bridge.beginNodeDrag(node.id, e.pointerId, e.clientX, e.clientY, e.altKey);
1645
+ };
1646
+ const handlePointerMove = (e) => {
1647
+ const start = downPosRef.current;
1648
+ if (!start || draggedRef.current) return;
1649
+ if (Math.abs(e.clientX - start.x) > CLICK_VS_DRAG_PX || Math.abs(e.clientY - start.y) > CLICK_VS_DRAG_PX) {
1650
+ draggedRef.current = true;
1651
+ }
1652
+ };
1653
+ const handleClick = (e) => {
1654
+ if (e.target.closest("[data-handle-id]")) return;
1655
+ e.stopPropagation();
1656
+ if (draggedRef.current) {
1657
+ draggedRef.current = false;
1658
+ downPosRef.current = null;
1659
+ return;
1660
+ }
1661
+ downPosRef.current = null;
1662
+ bridge.selectNode(node.id, e.metaKey || e.ctrlKey || e.shiftKey);
1663
+ bridge.notifyNodeClick(node.id);
1664
+ };
1665
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1666
+ "div",
1667
+ {
1668
+ ref: wrapperRef,
1669
+ "data-node-id": node.id,
1670
+ "data-node-type": node.type,
1671
+ "data-node-selected": selected ? "true" : "false",
1672
+ className: cn(
1673
+ "ods-flow-node-v2",
1674
+ `ods-flow-node-v2--kind-${node.type}`,
1675
+ selected && "ods-flow-node-v2--selected",
1676
+ dragging && "ods-flow-node-v2--dragging",
1677
+ isConnecting && "ods-flow-node-v2--connecting",
1678
+ node.className
1679
+ ),
1680
+ style: {
1681
+ position: "absolute",
1682
+ left: node.position.x,
1683
+ top: node.position.y,
1684
+ width: node.width ?? defaultWidth,
1685
+ // Z-stack (per architecture doc): groups (20) sit below normal
1686
+ // nodes (30) so children render visually on top of their parent
1687
+ // frame. Selection raises by 10. Consumer override wins.
1688
+ zIndex: node.zIndex ?? (selected ? node.type === "group" ? 35 : 40 : node.type === "group" ? 20 : 30),
1689
+ ...node.style
1690
+ },
1691
+ onPointerDown: handlePointerDown,
1692
+ onPointerMove: handlePointerMove,
1693
+ onClick: handleClick,
1694
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FlowNodeContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Kind, { node, selected, dragging, isConnecting }) })
1695
+ }
1696
+ );
1697
+ }
1698
+
1699
+ // src/workflow/components/FlowNode/nodeKinds.ts
1700
+ function buildNodeKindRegistry(defaults, overrides) {
1701
+ return Object.freeze({ ...defaults, ...overrides ?? {} });
1702
+ }
1703
+
1704
+ // src/workflow/components/Handle/Handle.tsx
1705
+ var import_react12 = require("react");
1706
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1707
+ var DEFAULT_HANDLE_ID = "default";
1708
+ function Handle({
1709
+ type,
1710
+ position,
1711
+ id = DEFAULT_HANDLE_ID,
1712
+ isConnectable = true,
1713
+ isConnectableStart,
1714
+ isConnectableEnd,
1715
+ index = 0,
1716
+ total = 1,
1717
+ label,
1718
+ className,
1719
+ style
1720
+ }) {
1721
+ const registry = useHandleRegistry();
1722
+ const node = useFlowNodeContext();
1723
+ const dispatch = useFlowDispatch();
1724
+ const ref = (0, import_react12.useRef)(null);
1725
+ const canStart = isConnectableStart ?? isConnectable;
1726
+ const canEnd = isConnectableEnd ?? isConnectable;
1727
+ (0, import_react12.useEffect)(() => {
1728
+ const dispose = registry.register({
1729
+ nodeId: node.id,
1730
+ handleId: id,
1731
+ type,
1732
+ side: position,
1733
+ index,
1734
+ total
1735
+ });
1736
+ return dispose;
1737
+ }, [registry, node.id, id, type, position, index, total]);
1738
+ const handlePointerDown = (e) => {
1739
+ if (!canStart) return;
1740
+ e.stopPropagation();
1741
+ e.preventDefault();
1742
+ dispatch({
1743
+ type: "connection/start",
1744
+ nodeId: node.id,
1745
+ handleId: id,
1746
+ handleType: type,
1747
+ pointerId: e.pointerId,
1748
+ clientX: e.clientX,
1749
+ clientY: e.clientY
1750
+ });
1751
+ };
1752
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1753
+ "div",
1754
+ {
1755
+ ref,
1756
+ "data-handle-id": id,
1757
+ "data-handle-node-id": node.id,
1758
+ "data-handle-type": type,
1759
+ "data-handle-side": position,
1760
+ "data-handle-connectable-end": canEnd ? "true" : "false",
1761
+ className: cn(
1762
+ "ods-flow-handle",
1763
+ `ods-flow-handle--${type}`,
1764
+ `ods-flow-handle--side-${position}`,
1765
+ !isConnectable && "ods-flow-handle--disabled",
1766
+ className
1767
+ ),
1768
+ style: {
1769
+ ...handleSideStyle(position, index, total),
1770
+ ...style
1771
+ },
1772
+ onPointerDown: handlePointerDown,
1773
+ children: [
1774
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "ods-flow-handle__dot" }),
1775
+ label && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ods-flow-handle__label", children: label })
1776
+ ]
1777
+ }
1778
+ );
1779
+ }
1780
+ function handleSideStyle(side, index, total) {
1781
+ const ratio = (index + 1) / (total + 1) * 100;
1782
+ switch (side) {
1783
+ case "top":
1784
+ return { position: "absolute", top: -6, left: `${ratio}%`, transform: "translateX(-50%)" };
1785
+ case "bottom":
1786
+ return { position: "absolute", bottom: -6, left: `${ratio}%`, transform: "translateX(-50%)" };
1787
+ case "left":
1788
+ return { position: "absolute", left: -6, top: `${ratio}%`, transform: "translateY(-50%)" };
1789
+ case "right":
1790
+ return { position: "absolute", right: -6, top: `${ratio}%`, transform: "translateY(-50%)" };
1791
+ }
1792
+ }
1793
+
1794
+ // src/workflow/components/kinds/BaseNode.tsx
1795
+ var import_icons = require("@octaviaflow/icons");
1796
+ var import_react13 = require("react");
1797
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1798
+ function BaseNode({
1799
+ kind,
1800
+ kindIcon,
1801
+ icon,
1802
+ title,
1803
+ chip,
1804
+ description,
1805
+ accent = "green",
1806
+ status,
1807
+ onDelete,
1808
+ footer,
1809
+ className,
1810
+ children
1811
+ }) {
1812
+ const ctx = (0, import_react13.useContext)(FlowNodeContext);
1813
+ const bridge = (0, import_react13.useContext)(FlowNodeBridgeContext);
1814
+ const deleteHandler = onDelete === false ? void 0 : onDelete ?? (ctx && bridge ? () => bridge.deleteNode(ctx.id) : void 0);
1815
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1816
+ "div",
1817
+ {
1818
+ className: cn(
1819
+ "ods-flow-base-node",
1820
+ `ods-flow-base-node--accent-${accent}`,
1821
+ status && `ods-flow-base-node--status-${status}`,
1822
+ className
1823
+ ),
1824
+ children: [
1825
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ods-flow-base-node__pill", children: [
1826
+ kindIcon && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ods-flow-base-node__pill-icon", "aria-hidden": "true", children: kindIcon }),
1827
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ods-flow-base-node__pill-label", children: kind })
1828
+ ] }),
1829
+ status && status !== "idle" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1830
+ "span",
1831
+ {
1832
+ className: cn("ods-flow-base-node__status", `ods-flow-base-node__status--${status}`),
1833
+ "aria-hidden": "true"
1834
+ }
1835
+ ),
1836
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ods-flow-base-node__body", children: [
1837
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ods-flow-base-node__content", children: [
1838
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ods-flow-base-node__bubble", "aria-hidden": "true", children: icon }),
1839
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ods-flow-base-node__content-text", children: [
1840
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ods-flow-base-node__content-title", children: title }),
1841
+ (chip !== void 0 || description !== void 0) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ods-flow-base-node__content-info", children: [
1842
+ chip !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ods-flow-base-node__chip", children: chip }),
1843
+ description !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ods-flow-base-node__description", children: description })
1844
+ ] })
1845
+ ] })
1846
+ ] }),
1847
+ footer && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ods-flow-base-node__footer", children: footer }),
1848
+ deleteHandler && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1849
+ "button",
1850
+ {
1851
+ type: "button",
1852
+ className: "ods-flow-base-node__delete",
1853
+ onClick: (e) => {
1854
+ e.stopPropagation();
1855
+ deleteHandler();
1856
+ },
1857
+ "aria-label": "Delete node",
1858
+ "data-flow-no-drag": "true",
1859
+ title: "Delete node",
1860
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_icons.TrashCanIcon, { size: 16, "aria-hidden": true })
1861
+ }
1862
+ )
1863
+ ] }),
1864
+ children
1865
+ ]
1866
+ }
1867
+ );
1868
+ }
1869
+
1870
+ // src/workflow/components/kinds/index.tsx
1871
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1872
+ var ActionNode = ({
1873
+ node
1874
+ }) => {
1875
+ const d = node.data ?? {};
1876
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1877
+ BaseNode,
1878
+ {
1879
+ kind: d.kind ?? "ACTION",
1880
+ icon: d.icon,
1881
+ title: d.title ?? "Action",
1882
+ chip: d.chip ?? d.badge,
1883
+ description: d.description ?? d.subtitle,
1884
+ status: d.status,
1885
+ accent: "green",
1886
+ children: [
1887
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" }),
1888
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom" })
1889
+ ]
1890
+ }
1891
+ );
1892
+ };
1893
+ var TriggerNode = ({
1894
+ node
1895
+ }) => {
1896
+ const d = node.data ?? {};
1897
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1898
+ BaseNode,
1899
+ {
1900
+ kind: d.kind ?? "TRIGGER",
1901
+ icon: d.icon,
1902
+ title: d.title ?? "Trigger",
1903
+ chip: d.chip,
1904
+ description: d.description ?? "Manually triggered",
1905
+ status: d.status,
1906
+ accent: "green",
1907
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom" })
1908
+ }
1909
+ );
1910
+ };
1911
+ var ConditionNode = ({
1912
+ node
1913
+ }) => {
1914
+ const d = node.data ?? {};
1915
+ const branches = d.branches ?? [
1916
+ { id: "true", label: "true" },
1917
+ { id: "false", label: "false" }
1918
+ ];
1919
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1920
+ BaseNode,
1921
+ {
1922
+ kind: d.kind ?? "CONDITION",
1923
+ icon: d.icon,
1924
+ title: d.title ?? "Condition",
1925
+ chip: d.chip ?? d.badge,
1926
+ description: d.description ?? d.subtitle,
1927
+ status: d.status,
1928
+ accent: "amber",
1929
+ children: [
1930
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" }),
1931
+ branches.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1932
+ Handle,
1933
+ {
1934
+ type: "source",
1935
+ position: "bottom",
1936
+ id: b.id,
1937
+ index: i,
1938
+ total: branches.length,
1939
+ label: b.label
1940
+ },
1941
+ b.id
1942
+ ))
1943
+ ]
1944
+ }
1945
+ );
1946
+ };
1947
+ var GroupNode = ({
1948
+ node
1949
+ }) => {
1950
+ const d = node.data ?? {};
1951
+ const collapsed = !!d.collapsed;
1952
+ const hiddenCount = d.hiddenCount;
1953
+ const bridge = useFlowNodeBridge();
1954
+ const onChevronClick = (e) => {
1955
+ e.stopPropagation();
1956
+ bridge.toggleNodeCollapse(node.id);
1957
+ };
1958
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1959
+ "div",
1960
+ {
1961
+ className: "ods-flow-group",
1962
+ "data-collapsed": collapsed ? "true" : "false",
1963
+ style: {
1964
+ width: node.width ?? 360,
1965
+ height: collapsed ? 36 : node.height ?? 200
1966
+ },
1967
+ children: [
1968
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-group__header", "data-flow-no-drag": "false", children: [
1969
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1970
+ "button",
1971
+ {
1972
+ type: "button",
1973
+ className: "ods-flow-group__chevron",
1974
+ "data-flow-no-drag": "true",
1975
+ "aria-label": collapsed ? "Expand group" : "Collapse group",
1976
+ "aria-expanded": !collapsed,
1977
+ onClick: onChevronClick,
1978
+ onPointerDown: (e) => e.stopPropagation(),
1979
+ children: collapsed ? "\u25B8" : "\u25BE"
1980
+ }
1981
+ ),
1982
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-group__title", children: d.title ?? "Group" }),
1983
+ d.subtitle && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-group__subtitle", children: d.subtitle }),
1984
+ collapsed && hiddenCount !== void 0 && hiddenCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ods-flow-group__count", "aria-label": `${hiddenCount} hidden steps`, children: [
1985
+ hiddenCount,
1986
+ " steps"
1987
+ ] })
1988
+ ] }),
1989
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top", id: "__group_in" }),
1990
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom", id: "__group_out" })
1991
+ ]
1992
+ }
1993
+ );
1994
+ };
1995
+ var ForEachNode = ({
1996
+ node
1997
+ }) => {
1998
+ const d = node.data ?? {};
1999
+ const iteratorExpr = d.iterator ?? d.description ?? "items[]";
2000
+ const collapsed = !!d.collapsed;
2001
+ const hiddenCount = d.hiddenCount;
2002
+ const bridge = useFlowNodeBridge();
2003
+ const onChevronClick = (e) => {
2004
+ e.stopPropagation();
2005
+ bridge.toggleNodeCollapse(node.id);
2006
+ };
2007
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2008
+ "div",
2009
+ {
2010
+ className: "ods-flow-foreach",
2011
+ "data-collapsed": collapsed ? "true" : "false",
2012
+ style: {
2013
+ width: node.width ?? 420,
2014
+ height: collapsed ? 40 : node.height ?? 260
2015
+ },
2016
+ children: [
2017
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ods-flow-foreach__header", children: [
2018
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2019
+ "button",
2020
+ {
2021
+ type: "button",
2022
+ className: "ods-flow-foreach__chevron",
2023
+ "data-flow-no-drag": "true",
2024
+ "aria-label": collapsed ? "Expand iterator" : "Collapse iterator",
2025
+ "aria-expanded": !collapsed,
2026
+ onClick: onChevronClick,
2027
+ onPointerDown: (e) => e.stopPropagation(),
2028
+ children: collapsed ? "\u25B8" : "\u25BE"
2029
+ }
2030
+ ),
2031
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-foreach__icon", "aria-hidden": "true", children: "\u21BB" }),
2032
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ods-flow-foreach__title", children: d.title ?? "For each" }),
2033
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { className: "ods-flow-foreach__iterator", children: iteratorExpr }),
2034
+ collapsed && hiddenCount !== void 0 && hiddenCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ods-flow-foreach__count", "aria-label": `${hiddenCount} hidden steps`, children: [
2035
+ hiddenCount,
2036
+ " steps"
2037
+ ] })
2038
+ ] }),
2039
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top", id: "__group_in" }),
2040
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom", id: "__group_out" }),
2041
+ !collapsed && // Expanded mode — full 3-handle iterator surface (in / item / done).
2042
+ // `in` shares the top edge with `__group_in`; consumers wiring an
2043
+ // explicit `targetHandle: "in"` get the labelled, visible variant.
2044
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2045
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top", id: "in", label: "in" }),
2046
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "right", id: "item", label: "item" }),
2047
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom", id: "done", label: "done" })
2048
+ ] })
2049
+ ]
2050
+ }
2051
+ );
2052
+ };
2053
+ var OutputNode = ({
2054
+ node
2055
+ }) => {
2056
+ const d = node.data ?? {};
2057
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2058
+ BaseNode,
2059
+ {
2060
+ kind: d.kind ?? "OUTPUT",
2061
+ icon: d.icon,
2062
+ title: d.title ?? "Output",
2063
+ chip: d.chip ?? d.badge,
2064
+ description: d.description ?? d.subtitle,
2065
+ status: d.status,
2066
+ accent: "green",
2067
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" })
2068
+ }
2069
+ );
2070
+ };
2071
+ var ErrorNode = ({
2072
+ node
2073
+ }) => {
2074
+ const d = node.data ?? {};
2075
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2076
+ BaseNode,
2077
+ {
2078
+ kind: d.kind ?? "ERROR",
2079
+ icon: d.icon,
2080
+ title: d.title ?? "On error",
2081
+ chip: d.chip ?? d.badge,
2082
+ description: d.description ?? d.subtitle,
2083
+ status: d.status ?? "error",
2084
+ accent: "red",
2085
+ children: [
2086
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" }),
2087
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom" })
2088
+ ]
2089
+ }
2090
+ );
2091
+ };
2092
+ var WaitNode = ({
2093
+ node
2094
+ }) => {
2095
+ const d = node.data ?? {};
2096
+ const waitMs = d.waitMs;
2097
+ const durationChip = waitMs ? `${Math.round(waitMs / 100) / 10}s` : void 0;
2098
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2099
+ BaseNode,
2100
+ {
2101
+ kind: d.kind ?? "WAIT",
2102
+ icon: d.icon,
2103
+ title: d.title ?? "Wait",
2104
+ chip: d.chip ?? durationChip,
2105
+ description: d.description ?? (durationChip ? "Pause execution" : void 0),
2106
+ status: d.status,
2107
+ accent: "violet",
2108
+ children: [
2109
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" }),
2110
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom" })
2111
+ ]
2112
+ }
2113
+ );
2114
+ };
2115
+ var ParallelNode = ({
2116
+ node
2117
+ }) => {
2118
+ const d = node.data ?? {};
2119
+ const branches = d.branches ?? [
2120
+ { id: "a", label: "a" },
2121
+ { id: "b", label: "b" }
2122
+ ];
2123
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2124
+ BaseNode,
2125
+ {
2126
+ kind: d.kind ?? "PARALLEL",
2127
+ icon: d.icon,
2128
+ title: d.title ?? "Parallel",
2129
+ chip: d.chip ?? `${branches.length}\xD7`,
2130
+ description: d.description ?? "Fan-out branches",
2131
+ status: d.status,
2132
+ accent: "blue",
2133
+ children: [
2134
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" }),
2135
+ branches.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2136
+ Handle,
2137
+ {
2138
+ type: "source",
2139
+ position: "bottom",
2140
+ id: b.id,
2141
+ index: i,
2142
+ total: branches.length,
2143
+ label: b.label
2144
+ },
2145
+ b.id
2146
+ ))
2147
+ ]
2148
+ }
2149
+ );
2150
+ };
2151
+ var StickyNode = ({
2152
+ node
2153
+ }) => {
2154
+ const d = node.data ?? {};
2155
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2156
+ "div",
2157
+ {
2158
+ className: "ods-flow-sticky",
2159
+ style: {
2160
+ width: node.width ?? 240,
2161
+ minHeight: node.height ?? 120
2162
+ },
2163
+ children: [
2164
+ d.title && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ods-flow-sticky__title", children: d.title }),
2165
+ d.description && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ods-flow-sticky__body", children: d.description })
2166
+ ]
2167
+ }
2168
+ );
2169
+ };
2170
+ var WebhookNode = ({
2171
+ node
2172
+ }) => {
2173
+ const d = node.data ?? {};
2174
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2175
+ BaseNode,
2176
+ {
2177
+ kind: d.kind ?? "WEBHOOK",
2178
+ icon: d.icon,
2179
+ title: d.title ?? "Webhook",
2180
+ chip: d.chip ?? d.method ?? "POST",
2181
+ description: d.description ?? d.path ?? "/hooks/incoming",
2182
+ status: d.status,
2183
+ accent: "blue",
2184
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom" })
2185
+ }
2186
+ );
2187
+ };
2188
+ var HttpRequestNode = ({
2189
+ node
2190
+ }) => {
2191
+ const d = node.data ?? {};
2192
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2193
+ BaseNode,
2194
+ {
2195
+ kind: d.kind ?? "HTTP",
2196
+ icon: d.icon,
2197
+ title: d.title ?? "HTTP Request",
2198
+ chip: d.chip ?? d.method ?? "GET",
2199
+ description: d.description ?? d.url ?? "Call an API",
2200
+ status: d.status,
2201
+ accent: "blue",
2202
+ children: [
2203
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "target", position: "top" }),
2204
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Handle, { type: "source", position: "bottom" })
2205
+ ]
2206
+ }
2207
+ );
2208
+ };
2209
+ var DEFAULT_NODE_KINDS = {
2210
+ action: ActionNode,
2211
+ trigger: TriggerNode,
2212
+ condition: ConditionNode,
2213
+ group: GroupNode,
2214
+ forEach: ForEachNode,
2215
+ output: OutputNode,
2216
+ error: ErrorNode,
2217
+ wait: WaitNode,
2218
+ parallel: ParallelNode,
2219
+ sticky: StickyNode,
2220
+ webhook: WebhookNode,
2221
+ httpRequest: HttpRequestNode
2222
+ };
2223
+
2224
+ // src/workflow/components/FlowCanvas/FlowCanvas.tsx
2225
+ var import_jsx_runtime7 = require("react/jsx-runtime");
2226
+ var DEFAULT_VIEWPORT2 = { x: 0, y: 0, zoom: 1 };
2227
+ function FlowCanvas({
2228
+ nodes,
2229
+ edges,
2230
+ onNodesChange,
2231
+ onEdgesChange,
2232
+ viewport: controlledViewport,
2233
+ defaultViewport = DEFAULT_VIEWPORT2,
2234
+ onViewportChange,
2235
+ minZoom = 0.25,
2236
+ maxZoom = 2,
2237
+ nodeKinds,
2238
+ onConnect,
2239
+ onConnectStart,
2240
+ onConnectEnd,
2241
+ isValidConnection,
2242
+ onSelectionChange,
2243
+ onPaneClick,
2244
+ onNodeClick,
2245
+ onEdgeClick,
2246
+ onEdgeLabelChange,
2247
+ onInit,
2248
+ onBeforeDelete,
2249
+ onNodeContextMenu,
2250
+ onEdgeContextMenu,
2251
+ onPaneContextMenu,
2252
+ nodesDraggable = true,
2253
+ nodesConnectable = true,
2254
+ panOnDrag: panOnDragProp,
2255
+ zoomOnScroll: zoomOnScrollProp,
2256
+ preset = "mouse",
2257
+ paneClickDistance = 4,
2258
+ paneClickClearsSelection = true,
2259
+ background = "dots",
2260
+ gridSize = 20,
2261
+ snapToGrid = false,
2262
+ nodeCollisionGap = -1,
2263
+ subflowCollisionGap,
2264
+ height = "100%",
2265
+ width = "100%",
2266
+ className,
2267
+ style,
2268
+ children,
2269
+ emptyState
2270
+ }) {
2271
+ const presetDefaults = {
2272
+ mouse: { panOnDrag: true, zoomOnScroll: true },
2273
+ trackpad: { panOnDrag: true, zoomOnScroll: true },
2274
+ touch: { panOnDrag: false, zoomOnScroll: true }
2275
+ };
2276
+ const panOnDrag = panOnDragProp ?? presetDefaults[preset].panOnDrag;
2277
+ const zoomOnScroll = zoomOnScrollProp ?? presetDefaults[preset].zoomOnScroll;
2278
+ const store = (0, import_react14.useState)(
2279
+ () => createFlowStore({
2280
+ initialNodes: nodes,
2281
+ initialEdges: edges,
2282
+ initialViewport: controlledViewport ?? defaultViewport
2283
+ })
2284
+ )[0];
2285
+ const handleRegistry = (0, import_react14.useState)(() => createHandleRegistry())[0];
2286
+ const [handleVersion, setHandleVersion] = (0, import_react14.useState)(0);
2287
+ (0, import_react14.useEffect)(() => {
2288
+ const unsub = handleRegistry.subscribe(() => {
2289
+ setHandleVersion((v) => v + 1);
2290
+ });
2291
+ return unsub;
2292
+ }, [handleRegistry]);
2293
+ const kinds = (0, import_react14.useMemo)(() => buildNodeKindRegistry(DEFAULT_NODE_KINDS, nodeKinds), [nodeKinds]);
2294
+ const containerRef = (0, import_react14.useRef)(null);
2295
+ (0, import_react14.useEffect)(() => store.setNodes(nodes), [store, nodes]);
2296
+ (0, import_react14.useEffect)(() => store.setEdges(edges), [store, edges]);
2297
+ const [uncontrolledVp, setUncontrolledVp] = (0, import_react14.useState)(controlledViewport ?? defaultViewport);
2298
+ const viewport = controlledViewport ?? uncontrolledVp;
2299
+ (0, import_react14.useEffect)(() => store.setViewport(viewport), [store, viewport]);
2300
+ const setViewport = (0, import_react14.useCallback)(
2301
+ (next) => {
2302
+ if (controlledViewport === void 0) setUncontrolledVp(next);
2303
+ onViewportChange?.(next);
2304
+ },
2305
+ [controlledViewport, onViewportChange]
2306
+ );
2307
+ const selectedNodeIds = (0, import_react14.useMemo)(() => {
2308
+ const s = /* @__PURE__ */ new Set();
2309
+ for (const n of nodes) if (n.selected) s.add(n.id);
2310
+ return s;
2311
+ }, [nodes]);
2312
+ const selectedEdgeIds = (0, import_react14.useMemo)(() => {
2313
+ const s = /* @__PURE__ */ new Set();
2314
+ for (const e of edges) if (e.selected) s.add(e.id);
2315
+ return s;
2316
+ }, [edges]);
2317
+ (0, import_react14.useEffect)(() => {
2318
+ store.setSelection(selectedNodeIds, selectedEdgeIds);
2319
+ if (onSelectionChange) {
2320
+ onSelectionChange({
2321
+ nodes: nodes.filter((n) => selectedNodeIds.has(n.id)),
2322
+ edges: edges.filter((e) => selectedEdgeIds.has(e.id))
2323
+ });
2324
+ }
2325
+ }, [store, selectedNodeIds, selectedEdgeIds, nodes, edges, onSelectionChange]);
2326
+ const selectNode = (0, import_react14.useCallback)(
2327
+ (id, additive) => {
2328
+ const next = [];
2329
+ const nextEdges = [];
2330
+ if (!additive) {
2331
+ for (const n of nodes) {
2332
+ if (n.selected && n.id !== id) next.push(change.node.select(n.id, false));
2333
+ }
2334
+ for (const e of edges) {
2335
+ if (e.selected) nextEdges.push(change.edge.select(e.id, false));
2336
+ }
2337
+ }
2338
+ const isSelected = selectedNodeIds.has(id);
2339
+ if (additive && isSelected) {
2340
+ next.push(change.node.select(id, false));
2341
+ } else if (!isSelected) {
2342
+ next.push(change.node.select(id, true));
2343
+ }
2344
+ if (next.length) onNodesChange?.(next);
2345
+ if (nextEdges.length) onEdgesChange?.(nextEdges);
2346
+ },
2347
+ [nodes, edges, selectedNodeIds, onNodesChange, onEdgesChange]
2348
+ );
2349
+ const notifyNodeClick = (0, import_react14.useCallback)(
2350
+ (id) => {
2351
+ const node = nodes.find((n) => n.id === id);
2352
+ if (node) onNodeClick?.(node);
2353
+ },
2354
+ [nodes, onNodeClick]
2355
+ );
2356
+ const selectEdge = (0, import_react14.useCallback)(
2357
+ (id, additive) => {
2358
+ const next = [];
2359
+ const nextNodes = [];
2360
+ if (!additive) {
2361
+ for (const e of edges) {
2362
+ if (e.selected && e.id !== id) next.push(change.edge.select(e.id, false));
2363
+ }
2364
+ for (const n of nodes) {
2365
+ if (n.selected) nextNodes.push(change.node.select(n.id, false));
2366
+ }
2367
+ }
2368
+ const isSelected = selectedEdgeIds.has(id);
2369
+ if (additive && isSelected) {
2370
+ next.push(change.edge.select(id, false));
2371
+ } else if (!isSelected) {
2372
+ next.push(change.edge.select(id, true));
2373
+ }
2374
+ if (next.length) onEdgesChange?.(next);
2375
+ if (nextNodes.length) onNodesChange?.(nextNodes);
2376
+ const edge = edges.find((e) => e.id === id);
2377
+ if (edge) onEdgeClick?.(edge);
2378
+ },
2379
+ [nodes, edges, selectedEdgeIds, onEdgesChange, onNodesChange, onEdgeClick]
2380
+ );
2381
+ const clearSelection = (0, import_react14.useCallback)(() => {
2382
+ const ns = [];
2383
+ const es = [];
2384
+ for (const n of nodes) if (n.selected) ns.push(change.node.select(n.id, false));
2385
+ for (const e of edges) if (e.selected) es.push(change.edge.select(e.id, false));
2386
+ if (ns.length) onNodesChange?.(ns);
2387
+ if (es.length) onEdgesChange?.(es);
2388
+ }, [nodes, edges, onNodesChange, onEdgesChange]);
2389
+ const dragRef = (0, import_react14.useRef)(null);
2390
+ const [draggingId, setDraggingId] = (0, import_react14.useState)(null);
2391
+ const beginNodeDrag = (0, import_react14.useCallback)(
2392
+ (nodeId, pointerId, clientX, clientY, altKey = false) => {
2393
+ if (!nodesDraggable) return;
2394
+ const node = nodes.find((n) => n.id === nodeId);
2395
+ if (!node) return;
2396
+ const kids = descendantsOf(nodeId, nodes).map((d) => ({
2397
+ id: d.id,
2398
+ startPosition: d.position
2399
+ }));
2400
+ dragRef.current = {
2401
+ pointerId,
2402
+ nodeId,
2403
+ startClientX: clientX,
2404
+ startClientY: clientY,
2405
+ startPosition: node.position,
2406
+ descendants: kids,
2407
+ altDetach: altKey && !!node.parentId,
2408
+ rafScheduled: false,
2409
+ nextDelta: null
2410
+ };
2411
+ setDraggingId(nodeId);
2412
+ selectNode(nodeId, false);
2413
+ },
2414
+ [nodes, nodesDraggable, selectNode]
2415
+ );
2416
+ const [conn, setConn] = (0, import_react14.useState)(null);
2417
+ const connRef = (0, import_react14.useRef)(null);
2418
+ (0, import_react14.useEffect)(() => {
2419
+ connRef.current = conn;
2420
+ }, [conn]);
2421
+ const beginConnection = (0, import_react14.useCallback)(
2422
+ (nodeId, handleId, handleType, pointerId, clientX, clientY) => {
2423
+ if (!nodesConnectable) return;
2424
+ const node = nodes.find((n) => n.id === nodeId);
2425
+ if (!node) return;
2426
+ const desc = handleRegistry.resolve(nodeId, handleType, handleId);
2427
+ const side = desc?.side ?? (handleType === "source" ? "bottom" : "top");
2428
+ const index = desc?.index ?? 0;
2429
+ const total = desc?.total ?? 1;
2430
+ const ratio = (index + 1) / (total + 1);
2431
+ const w = node.width ?? 368;
2432
+ const h = effectiveHeight(node);
2433
+ const start = (() => {
2434
+ switch (side) {
2435
+ case "top":
2436
+ return { x: node.position.x + w * ratio, y: node.position.y };
2437
+ case "bottom":
2438
+ return { x: node.position.x + w * ratio, y: node.position.y + h };
2439
+ case "left":
2440
+ return { x: node.position.x, y: node.position.y + h * ratio };
2441
+ case "right":
2442
+ return { x: node.position.x + w, y: node.position.y + h * ratio };
2443
+ }
2444
+ })();
2445
+ const from = { nodeId, handleId, handleType };
2446
+ const rect = containerRef.current?.getBoundingClientRect();
2447
+ const end = rect ? screenToFlow({ x: clientX - rect.left, y: clientY - rect.top }, viewport) : start;
2448
+ setConn({ pointerId, from, start, end });
2449
+ store.setConnection(from);
2450
+ onConnectStart?.({ clientX, clientY, pointerId }, from);
2451
+ },
2452
+ [nodes, nodesConnectable, handleRegistry, viewport, store, onConnectStart]
2453
+ );
2454
+ const viewportRef = (0, import_react14.useRef)(viewport);
2455
+ const nodesRef = (0, import_react14.useRef)(nodes);
2456
+ const edgesRef = (0, import_react14.useRef)(edges);
2457
+ const onNodesChangeRefForInstance = (0, import_react14.useRef)(onNodesChange);
2458
+ const onEdgesChangeRefForInstance = (0, import_react14.useRef)(onEdgesChange);
2459
+ const onBeforeDeleteRef = (0, import_react14.useRef)(onBeforeDelete);
2460
+ const snapToGridRef = (0, import_react14.useRef)(snapToGrid);
2461
+ const gridSizeRef = (0, import_react14.useRef)(gridSize);
2462
+ const nodeCollisionGapRef = (0, import_react14.useRef)(nodeCollisionGap);
2463
+ const subflowCollisionGapRef = (0, import_react14.useRef)(subflowCollisionGap ?? nodeCollisionGap);
2464
+ (0, import_react14.useEffect)(() => {
2465
+ edgesRef.current = edges;
2466
+ }, [edges]);
2467
+ (0, import_react14.useEffect)(() => {
2468
+ onNodesChangeRefForInstance.current = onNodesChange;
2469
+ }, [onNodesChange]);
2470
+ (0, import_react14.useEffect)(() => {
2471
+ onEdgesChangeRefForInstance.current = onEdgesChange;
2472
+ }, [onEdgesChange]);
2473
+ (0, import_react14.useEffect)(() => {
2474
+ onBeforeDeleteRef.current = onBeforeDelete;
2475
+ }, [onBeforeDelete]);
2476
+ (0, import_react14.useEffect)(() => {
2477
+ snapToGridRef.current = snapToGrid;
2478
+ }, [snapToGrid]);
2479
+ (0, import_react14.useEffect)(() => {
2480
+ gridSizeRef.current = gridSize;
2481
+ }, [gridSize]);
2482
+ (0, import_react14.useEffect)(() => {
2483
+ nodeCollisionGapRef.current = nodeCollisionGap;
2484
+ }, [nodeCollisionGap]);
2485
+ (0, import_react14.useEffect)(() => {
2486
+ subflowCollisionGapRef.current = subflowCollisionGap ?? nodeCollisionGap;
2487
+ }, [subflowCollisionGap, nodeCollisionGap]);
2488
+ const onNodesChangeRef = (0, import_react14.useRef)(onNodesChange);
2489
+ const onConnectRef = (0, import_react14.useRef)(onConnect);
2490
+ const onConnectEndRef = (0, import_react14.useRef)(onConnectEnd);
2491
+ const isValidConnectionRef = (0, import_react14.useRef)(isValidConnection);
2492
+ (0, import_react14.useEffect)(() => {
2493
+ viewportRef.current = viewport;
2494
+ }, [viewport]);
2495
+ (0, import_react14.useEffect)(() => {
2496
+ nodesRef.current = nodes;
2497
+ }, [nodes]);
2498
+ (0, import_react14.useEffect)(() => {
2499
+ onNodesChangeRef.current = onNodesChange;
2500
+ }, [onNodesChange]);
2501
+ (0, import_react14.useEffect)(() => {
2502
+ onConnectRef.current = onConnect;
2503
+ }, [onConnect]);
2504
+ (0, import_react14.useEffect)(() => {
2505
+ onConnectEndRef.current = onConnectEnd;
2506
+ }, [onConnectEnd]);
2507
+ (0, import_react14.useEffect)(() => {
2508
+ isValidConnectionRef.current = isValidConnection;
2509
+ }, [isValidConnection]);
2510
+ (0, import_react14.useEffect)(() => {
2511
+ const onPointerMove = (e) => {
2512
+ const vp = viewportRef.current;
2513
+ const drag = dragRef.current;
2514
+ if (drag && drag.pointerId === e.pointerId) {
2515
+ const dx = (e.clientX - drag.startClientX) / vp.zoom;
2516
+ const dy = (e.clientY - drag.startClientY) / vp.zoom;
2517
+ drag.nextDelta = { dx, dy };
2518
+ if (!drag.rafScheduled) {
2519
+ drag.rafScheduled = true;
2520
+ requestAnimationFrame(() => {
2521
+ const d = dragRef.current;
2522
+ if (!d) return;
2523
+ d.rafScheduled = false;
2524
+ const delta = d.nextDelta;
2525
+ if (!delta) return;
2526
+ const dragNode = nodesRef.current.find((n) => n.id === d.nodeId);
2527
+ if (!dragNode) return;
2528
+ const proposed = {
2529
+ x: d.startPosition.x + delta.dx,
2530
+ y: d.startPosition.y + delta.dy
2531
+ };
2532
+ const clamped = d.altDetach ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2533
+ const isContainer = dragNode.type === "group" || dragNode.type === "forEach";
2534
+ const gap = isContainer ? subflowCollisionGapRef.current : nodeCollisionGapRef.current;
2535
+ const excludeIds = /* @__PURE__ */ new Set([d.nodeId, ...d.descendants.map((kid) => kid.id)]);
2536
+ const finalPos = resolveNodeCollisions(dragNode, clamped, nodesRef.current, {
2537
+ gap,
2538
+ exclude: excludeIds
2539
+ });
2540
+ const realDx = finalPos.x - d.startPosition.x;
2541
+ const realDy = finalPos.y - d.startPosition.y;
2542
+ const changes = [change.node.position(d.nodeId, finalPos, true)];
2543
+ for (const kid of d.descendants) {
2544
+ changes.push(
2545
+ change.node.position(
2546
+ kid.id,
2547
+ { x: kid.startPosition.x + realDx, y: kid.startPosition.y + realDy },
2548
+ true
2549
+ )
2550
+ );
2551
+ }
2552
+ onNodesChangeRef.current?.(changes);
2553
+ });
2554
+ }
2555
+ }
2556
+ const c = connRef.current;
2557
+ if (c && c.pointerId === e.pointerId) {
2558
+ const rect = containerRef.current?.getBoundingClientRect();
2559
+ if (rect) {
2560
+ const end = screenToFlow({ x: e.clientX - rect.left, y: e.clientY - rect.top }, vp);
2561
+ setConn({ ...c, end });
2562
+ }
2563
+ }
2564
+ };
2565
+ const onPointerUp = (e) => {
2566
+ const vp = viewportRef.current;
2567
+ const drag = dragRef.current;
2568
+ if (drag && drag.pointerId === e.pointerId) {
2569
+ const dx = (e.clientX - drag.startClientX) / vp.zoom;
2570
+ const dy = (e.clientY - drag.startClientY) / vp.zoom;
2571
+ const dragNode = nodesRef.current.find((n) => n.id === drag.nodeId);
2572
+ if (dragNode) {
2573
+ let proposed = {
2574
+ x: drag.startPosition.x + dx,
2575
+ y: drag.startPosition.y + dy
2576
+ };
2577
+ if (snapToGridRef.current) {
2578
+ const g = gridSizeRef.current;
2579
+ proposed = {
2580
+ x: Math.round(proposed.x / g) * g,
2581
+ y: Math.round(proposed.y / g) * g
2582
+ };
2583
+ }
2584
+ const clamped = drag.altDetach ? proposed : clampToParentExtent(dragNode, proposed, nodesRef.current);
2585
+ const isContainer = dragNode.type === "group" || dragNode.type === "forEach";
2586
+ const gap = isContainer ? subflowCollisionGapRef.current : nodeCollisionGapRef.current;
2587
+ const excludeIds = /* @__PURE__ */ new Set([
2588
+ drag.nodeId,
2589
+ ...drag.descendants.map((kid) => kid.id)
2590
+ ]);
2591
+ const finalPos = resolveNodeCollisions(dragNode, clamped, nodesRef.current, {
2592
+ gap,
2593
+ exclude: excludeIds
2594
+ });
2595
+ const realDx = finalPos.x - drag.startPosition.x;
2596
+ const realDy = finalPos.y - drag.startPosition.y;
2597
+ const changes = [change.node.position(drag.nodeId, finalPos, false)];
2598
+ for (const kid of drag.descendants) {
2599
+ changes.push(
2600
+ change.node.position(
2601
+ kid.id,
2602
+ { x: kid.startPosition.x + realDx, y: kid.startPosition.y + realDy },
2603
+ false
2604
+ )
2605
+ );
2606
+ }
2607
+ if (drag.altDetach && dragNode.parentId) {
2608
+ const targetGroup = findContainingGroup(
2609
+ {
2610
+ x: finalPos.x + (dragNode.width ?? 0) / 2,
2611
+ y: finalPos.y + (dragNode.height ?? 0) / 2
2612
+ },
2613
+ nodesRef.current,
2614
+ [drag.nodeId, ...drag.descendants.map((d) => d.id)]
2615
+ );
2616
+ const nextParentId = targetGroup?.id;
2617
+ const updated = {
2618
+ ...dragNode,
2619
+ position: finalPos,
2620
+ parentId: nextParentId,
2621
+ // Preserve extent only when staying in a group.
2622
+ extent: nextParentId ? dragNode.extent : void 0
2623
+ };
2624
+ changes.push(change.node.replace(drag.nodeId, updated));
2625
+ }
2626
+ onNodesChangeRef.current?.(changes);
2627
+ }
2628
+ dragRef.current = null;
2629
+ setDraggingId(null);
2630
+ }
2631
+ const c = connRef.current;
2632
+ if (c && c.pointerId === e.pointerId) {
2633
+ const target = e.target;
2634
+ const handleEl = target?.closest("[data-handle-id]");
2635
+ let connection = null;
2636
+ let connectedTo;
2637
+ if (handleEl) {
2638
+ const targetNodeId = handleEl.dataset.handleNodeId;
2639
+ const targetHandleId = handleEl.dataset.handleId;
2640
+ const targetType = handleEl.dataset.handleType;
2641
+ const connectableEnd = handleEl.dataset.handleConnectableEnd === "true";
2642
+ if (connectableEnd && (targetNodeId !== c.from.nodeId || targetHandleId !== c.from.handleId) && targetType !== c.from.handleType) {
2643
+ const source = c.from.handleType === "source" ? c.from : { nodeId: targetNodeId, handleId: targetHandleId, handleType: "source" };
2644
+ const target2 = c.from.handleType === "target" ? c.from : { nodeId: targetNodeId, handleId: targetHandleId, handleType: "target" };
2645
+ connection = {
2646
+ source: source.nodeId,
2647
+ sourceHandle: source.handleId,
2648
+ target: target2.nodeId,
2649
+ targetHandle: target2.handleId
2650
+ };
2651
+ connectedTo = {
2652
+ nodeId: targetNodeId,
2653
+ handleId: targetHandleId,
2654
+ handleType: targetType
2655
+ };
2656
+ const validator = isValidConnectionRef.current;
2657
+ if (validator && !validator(connection)) {
2658
+ connection = null;
2659
+ connectedTo = void 0;
2660
+ }
2661
+ }
2662
+ }
2663
+ const rect = containerRef.current?.getBoundingClientRect();
2664
+ const flowPos = rect ? screenToFlow({ x: e.clientX - rect.left, y: e.clientY - rect.top }, vp) : { x: 0, y: 0 };
2665
+ const endState = {
2666
+ cancelled: !connection,
2667
+ position: { x: e.clientX, y: e.clientY },
2668
+ flowPosition: flowPos,
2669
+ from: c.from,
2670
+ to: connectedTo
2671
+ };
2672
+ if (connection) onConnectRef.current?.(connection);
2673
+ onConnectEndRef.current?.(e, endState);
2674
+ setConn(null);
2675
+ store.setConnection(null);
2676
+ }
2677
+ };
2678
+ const onPointerCancel = () => {
2679
+ if (dragRef.current) {
2680
+ dragRef.current = null;
2681
+ setDraggingId(null);
2682
+ }
2683
+ if (connRef.current) {
2684
+ setConn(null);
2685
+ store.setConnection(null);
2686
+ }
2687
+ };
2688
+ window.addEventListener("pointermove", onPointerMove);
2689
+ window.addEventListener("pointerup", onPointerUp);
2690
+ window.addEventListener("pointercancel", onPointerCancel);
2691
+ return () => {
2692
+ window.removeEventListener("pointermove", onPointerMove);
2693
+ window.removeEventListener("pointerup", onPointerUp);
2694
+ window.removeEventListener("pointercancel", onPointerCancel);
2695
+ };
2696
+ }, [store]);
2697
+ const panRef = (0, import_react14.useRef)(null);
2698
+ const onCanvasPointerDown = (e) => {
2699
+ if (e.button !== 0) return;
2700
+ const t = e.target;
2701
+ if (t.closest("[data-node-id]") || t.closest("[data-edge-id]") || t.closest("[data-handle-id]")) {
2702
+ return;
2703
+ }
2704
+ if (!panOnDrag) {
2705
+ panRef.current = {
2706
+ pointerId: e.pointerId,
2707
+ startClientX: e.clientX,
2708
+ startClientY: e.clientY,
2709
+ startVp: viewport,
2710
+ moved: false
2711
+ };
2712
+ return;
2713
+ }
2714
+ panRef.current = {
2715
+ pointerId: e.pointerId,
2716
+ startClientX: e.clientX,
2717
+ startClientY: e.clientY,
2718
+ startVp: viewport,
2719
+ moved: false
2720
+ };
2721
+ };
2722
+ (0, import_react14.useEffect)(() => {
2723
+ const onMove = (e) => {
2724
+ const pan = panRef.current;
2725
+ if (!pan || pan.pointerId !== e.pointerId) return;
2726
+ const dx = e.clientX - pan.startClientX;
2727
+ const dy = e.clientY - pan.startClientY;
2728
+ if (Math.abs(dx) > paneClickDistance || Math.abs(dy) > paneClickDistance) {
2729
+ if (!pan.moved) setPanGesture(true);
2730
+ pan.moved = true;
2731
+ }
2732
+ if (pan.moved && panOnDrag) {
2733
+ setViewport({ ...pan.startVp, x: pan.startVp.x + dx, y: pan.startVp.y + dy });
2734
+ }
2735
+ };
2736
+ const onUp = () => {
2737
+ const pan = panRef.current;
2738
+ if (!pan) return;
2739
+ if (!pan.moved) {
2740
+ onPaneClick?.();
2741
+ if (paneClickClearsSelection) clearSelection();
2742
+ }
2743
+ panRef.current = null;
2744
+ setPanGesture(false);
2745
+ };
2746
+ window.addEventListener("pointermove", onMove);
2747
+ window.addEventListener("pointerup", onUp);
2748
+ window.addEventListener("pointercancel", onUp);
2749
+ return () => {
2750
+ window.removeEventListener("pointermove", onMove);
2751
+ window.removeEventListener("pointerup", onUp);
2752
+ window.removeEventListener("pointercancel", onUp);
2753
+ };
2754
+ }, [
2755
+ setViewport,
2756
+ paneClickDistance,
2757
+ panOnDrag,
2758
+ onPaneClick,
2759
+ clearSelection,
2760
+ paneClickClearsSelection
2761
+ ]);
2762
+ const handleWheel = (e) => {
2763
+ if (!zoomOnScroll) return;
2764
+ e.preventDefault();
2765
+ const rect = containerRef.current?.getBoundingClientRect();
2766
+ if (!rect) return;
2767
+ const px = e.clientX - rect.left;
2768
+ const py = e.clientY - rect.top;
2769
+ const factor = Math.exp(-e.deltaY * 1e-3);
2770
+ const nextZoom = Math.max(minZoom, Math.min(maxZoom, viewport.zoom * factor));
2771
+ const k = nextZoom / viewport.zoom;
2772
+ setViewport({
2773
+ zoom: nextZoom,
2774
+ x: px - (px - viewport.x) * k,
2775
+ y: py - (py - viewport.y) * k
2776
+ });
2777
+ };
2778
+ const dispatch = (0, import_react14.useCallback)(
2779
+ (a) => {
2780
+ if (a.type === "connection/start") {
2781
+ beginConnection(a.nodeId, a.handleId, a.handleType, a.pointerId, a.clientX, a.clientY);
2782
+ }
2783
+ },
2784
+ [beginConnection]
2785
+ );
2786
+ const reportDimensions = (0, import_react14.useCallback)(
2787
+ (nodeId, width2, height2) => {
2788
+ const node = nodes.find((n) => n.id === nodeId);
2789
+ if (!node) return;
2790
+ if (node.width === width2 && node.height === height2) return;
2791
+ onNodesChange?.([change.node.dimensions(nodeId, { width: width2, height: height2 })]);
2792
+ },
2793
+ [nodes, onNodesChange]
2794
+ );
2795
+ const toggleNodeCollapseImpl = (0, import_react14.useCallback)(
2796
+ (nodeId) => {
2797
+ const node = nodes.find((n) => n.id === nodeId);
2798
+ if (!node) return;
2799
+ const prevData = node.data ?? {};
2800
+ const nextNode = {
2801
+ ...node,
2802
+ data: { ...prevData, collapsed: !prevData.collapsed }
2803
+ };
2804
+ onNodesChange?.([change.node.replace(nodeId, nextNode)]);
2805
+ },
2806
+ [nodes, onNodesChange]
2807
+ );
2808
+ const deleteNodeImpl = (0, import_react14.useCallback)(
2809
+ (nodeId) => {
2810
+ const incidentEdgeIds = edgesRef.current.filter((e) => e.source === nodeId || e.target === nodeId).map((e) => e.id);
2811
+ if (incidentEdgeIds.length > 0) {
2812
+ onEdgesChange?.(incidentEdgeIds.map((id) => change.edge.remove(id)));
2813
+ }
2814
+ onNodesChange?.([change.node.remove(nodeId)]);
2815
+ },
2816
+ [onNodesChange, onEdgesChange]
2817
+ );
2818
+ const instance = (0, import_react14.useMemo)(
2819
+ () => ({
2820
+ // viewport
2821
+ getViewport: () => viewportRef.current,
2822
+ setViewport: (vp) => setViewport(vp),
2823
+ setCenter: (x, y, opts) => {
2824
+ const z = opts?.zoom ?? viewportRef.current.zoom;
2825
+ const rect = containerRef.current?.getBoundingClientRect();
2826
+ if (!rect) return;
2827
+ setViewport({
2828
+ zoom: z,
2829
+ x: rect.width / 2 - x * z,
2830
+ y: rect.height / 2 - y * z
2831
+ });
2832
+ },
2833
+ fitView: async (opts) => {
2834
+ const rect = containerRef.current?.getBoundingClientRect();
2835
+ if (!rect) return false;
2836
+ const padding = opts?.padding ?? 80;
2837
+ const targetNodes = opts?.nodes ? nodesRef.current.filter((n) => opts.nodes.some((x) => x.id === n.id)) : nodesRef.current.filter((n) => !n.hidden);
2838
+ if (targetNodes.length === 0) return false;
2839
+ let minX = Number.POSITIVE_INFINITY;
2840
+ let minY = Number.POSITIVE_INFINITY;
2841
+ let maxX = Number.NEGATIVE_INFINITY;
2842
+ let maxY = Number.NEGATIVE_INFINITY;
2843
+ for (const n of targetNodes) {
2844
+ const w = n.width ?? DEFAULT_NODE_WIDTH;
2845
+ const h = effectiveHeight(n);
2846
+ if (n.position.x < minX) minX = n.position.x;
2847
+ if (n.position.y < minY) minY = n.position.y;
2848
+ if (n.position.x + w > maxX) maxX = n.position.x + w;
2849
+ if (n.position.y + h > maxY) maxY = n.position.y + h;
2850
+ }
2851
+ const cw = maxX - minX;
2852
+ const ch = maxY - minY;
2853
+ const zx = (rect.width - padding * 2) / cw;
2854
+ const zy = (rect.height - padding * 2) / ch;
2855
+ const zoom = Math.max(
2856
+ opts?.minZoom ?? 0.25,
2857
+ Math.min(opts?.maxZoom ?? 1.5, Math.min(zx, zy))
2858
+ );
2859
+ setViewport({
2860
+ zoom,
2861
+ x: rect.width / 2 - (minX + maxX) / 2 * zoom,
2862
+ y: rect.height / 2 - (minY + maxY) / 2 * zoom
2863
+ });
2864
+ return true;
2865
+ },
2866
+ zoomIn: (opts) => {
2867
+ const step = opts?.step ?? 0.2;
2868
+ const next = Math.min(maxZoom, viewportRef.current.zoom * (1 + step));
2869
+ setViewport({ ...viewportRef.current, zoom: next });
2870
+ },
2871
+ zoomOut: (opts) => {
2872
+ const step = opts?.step ?? 0.2;
2873
+ const next = Math.max(minZoom, viewportRef.current.zoom / (1 + step));
2874
+ setViewport({ ...viewportRef.current, zoom: next });
2875
+ },
2876
+ zoomTo: (level) => {
2877
+ setViewport({
2878
+ ...viewportRef.current,
2879
+ zoom: Math.max(minZoom, Math.min(maxZoom, level))
2880
+ });
2881
+ },
2882
+ // transforms
2883
+ screenToFlowPosition: (p) => screenToFlow(p, viewportRef.current),
2884
+ flowToScreenPosition: (p) => flowToScreen(p, viewportRef.current),
2885
+ // reads
2886
+ getNodes: () => nodesRef.current.slice(),
2887
+ getEdges: () => edgesRef.current.slice(),
2888
+ getNode: (id) => nodesRef.current.find((n) => n.id === id),
2889
+ getEdge: (id) => edgesRef.current.find((e) => e.id === id),
2890
+ getNodesBounds: (subset) => {
2891
+ const pool = subset ? nodesRef.current.filter((n) => subset.some((x) => x.id === n.id)) : nodesRef.current.filter((n) => !n.hidden);
2892
+ if (pool.length === 0) return { x: 0, y: 0, width: 0, height: 0 };
2893
+ let minX = Number.POSITIVE_INFINITY;
2894
+ let minY = Number.POSITIVE_INFINITY;
2895
+ let maxX = Number.NEGATIVE_INFINITY;
2896
+ let maxY = Number.NEGATIVE_INFINITY;
2897
+ for (const n of pool) {
2898
+ const w = n.width ?? DEFAULT_NODE_WIDTH;
2899
+ const h = effectiveHeight(n);
2900
+ minX = Math.min(minX, n.position.x);
2901
+ minY = Math.min(minY, n.position.y);
2902
+ maxX = Math.max(maxX, n.position.x + w);
2903
+ maxY = Math.max(maxY, n.position.y + h);
2904
+ }
2905
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
2906
+ },
2907
+ getIntersectingNodes: (area, partially = true) => {
2908
+ return nodesRef.current.filter((n) => {
2909
+ if (n.hidden) return false;
2910
+ const w = n.width ?? DEFAULT_NODE_WIDTH;
2911
+ const h = effectiveHeight(n);
2912
+ if (partially) {
2913
+ return n.position.x < area.x + area.width && n.position.x + w > area.x && n.position.y < area.y + area.height && n.position.y + h > area.y;
2914
+ }
2915
+ return n.position.x >= area.x && n.position.y >= area.y && n.position.x + w <= area.x + area.width && n.position.y + h <= area.y + area.height;
2916
+ });
2917
+ },
2918
+ // mutations
2919
+ addNodes: (nodes2) => {
2920
+ const arr = Array.isArray(nodes2) ? nodes2 : [nodes2];
2921
+ onNodesChangeRefForInstance.current?.(arr.map((item) => change.node.add(item)));
2922
+ },
2923
+ addEdges: (edges2) => {
2924
+ const arr = Array.isArray(edges2) ? edges2 : [edges2];
2925
+ onEdgesChangeRefForInstance.current?.(arr.map((item) => change.edge.add(item)));
2926
+ },
2927
+ updateNodeData: (id, partial) => {
2928
+ const cur = nodesRef.current.find((n) => n.id === id);
2929
+ if (!cur) return;
2930
+ const nextData = { ...cur.data ?? {}, ...partial };
2931
+ onNodesChangeRefForInstance.current?.([
2932
+ change.node.replace(id, { ...cur, data: nextData })
2933
+ ]);
2934
+ },
2935
+ updateNode: (id, partial) => {
2936
+ const cur = nodesRef.current.find((n) => n.id === id);
2937
+ if (!cur) return;
2938
+ onNodesChangeRefForInstance.current?.([change.node.replace(id, { ...cur, ...partial })]);
2939
+ },
2940
+ deleteElements: async ({ nodes: ns, edges: es }) => {
2941
+ const nodesToDelete = (ns ?? []).map((x) => nodesRef.current.find((n) => n.id === x.id)).filter((n) => !!n);
2942
+ const edgesToDelete = (es ?? []).map((x) => edgesRef.current.find((e) => e.id === x.id)).filter((e) => !!e);
2943
+ const before = onBeforeDeleteRef.current;
2944
+ if (before) {
2945
+ const ok = await before({ nodes: nodesToDelete, edges: edgesToDelete });
2946
+ if (!ok) return false;
2947
+ }
2948
+ if (nodesToDelete.length > 0) {
2949
+ onNodesChangeRefForInstance.current?.(nodesToDelete.map((n) => change.node.remove(n.id)));
2950
+ }
2951
+ if (edgesToDelete.length > 0) {
2952
+ onEdgesChangeRefForInstance.current?.(edgesToDelete.map((e) => change.edge.remove(e.id)));
2953
+ }
2954
+ return true;
2955
+ }
2956
+ }),
2957
+ [setViewport, minZoom, maxZoom]
2958
+ );
2959
+ const initFiredRef = (0, import_react14.useRef)(false);
2960
+ (0, import_react14.useEffect)(() => {
2961
+ if (initFiredRef.current) return;
2962
+ initFiredRef.current = true;
2963
+ onInit?.(instance);
2964
+ }, [instance, onInit]);
2965
+ (0, import_react14.useEffect)(() => {
2966
+ const onKey = (e) => {
2967
+ if (e.key !== "Backspace" && e.key !== "Delete") return;
2968
+ const target = e.target;
2969
+ if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT" || target.isContentEditable)) {
2970
+ return;
2971
+ }
2972
+ if (!containerRef.current?.contains(target) && document.activeElement !== document.body) {
2973
+ return;
2974
+ }
2975
+ const sel = store.getSnapshot();
2976
+ if (sel.selectedNodeIds.size === 0 && sel.selectedEdgeIds.size === 0) return;
2977
+ e.preventDefault();
2978
+ void instance.deleteElements({
2979
+ nodes: Array.from(sel.selectedNodeIds, (id) => ({ id })),
2980
+ edges: Array.from(sel.selectedEdgeIds, (id) => ({ id }))
2981
+ });
2982
+ };
2983
+ window.addEventListener("keydown", onKey);
2984
+ return () => window.removeEventListener("keydown", onKey);
2985
+ }, [instance, store]);
2986
+ const bridge = (0, import_react14.useMemo)(
2987
+ () => ({
2988
+ beginNodeDrag,
2989
+ selectNode,
2990
+ notifyNodeClick,
2991
+ selectEdge,
2992
+ reportDimensions,
2993
+ deleteNode: deleteNodeImpl,
2994
+ toggleNodeCollapse: toggleNodeCollapseImpl
2995
+ }),
2996
+ [
2997
+ beginNodeDrag,
2998
+ selectNode,
2999
+ notifyNodeClick,
3000
+ selectEdge,
3001
+ reportDimensions,
3002
+ deleteNodeImpl,
3003
+ toggleNodeCollapseImpl
3004
+ ]
3005
+ );
3006
+ const [panGesture, setPanGesture] = (0, import_react14.useState)(false);
3007
+ const isEmpty = nodes.length === 0 && edges.length === 0;
3008
+ const isConnecting = conn !== null;
3009
+ const visibleNodes = (0, import_react14.useMemo)(() => nodes.filter((n) => !n.hidden), [nodes]);
3010
+ const visibleEdges = (0, import_react14.useMemo)(() => {
3011
+ if (visibleNodes.length === nodes.length) return edges;
3012
+ const visibleIds = new Set(visibleNodes.map((n) => n.id));
3013
+ return edges.filter((e) => visibleIds.has(e.source) && visibleIds.has(e.target));
3014
+ }, [edges, nodes, visibleNodes]);
3015
+ const orderedNodes = (0, import_react14.useMemo)(() => {
3016
+ const isContainer = (n) => n.type === "group" || n.type === "forEach";
3017
+ const depth = (n) => {
3018
+ let d = 0;
3019
+ let cursor = n.parentId;
3020
+ const seen = /* @__PURE__ */ new Set();
3021
+ while (cursor) {
3022
+ if (seen.has(cursor)) return Number.POSITIVE_INFINITY;
3023
+ seen.add(cursor);
3024
+ d++;
3025
+ cursor = visibleNodes.find((x) => x.id === cursor)?.parentId;
3026
+ }
3027
+ return d;
3028
+ };
3029
+ const containers = visibleNodes.filter(isContainer);
3030
+ const others = visibleNodes.filter((n) => !isContainer(n));
3031
+ containers.sort((a, b) => depth(a) - depth(b));
3032
+ return [...containers, ...others];
3033
+ }, [visibleNodes]);
3034
+ 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)(
3035
+ "div",
3036
+ {
3037
+ ref: containerRef,
3038
+ className: cn(
3039
+ "ods-flow-canvas-v2",
3040
+ panGesture && "ods-flow-canvas-v2--panning",
3041
+ isConnecting && "ods-flow-canvas-v2--connecting",
3042
+ draggingId && "ods-flow-canvas-v2--dragging",
3043
+ className
3044
+ ),
3045
+ style: {
3046
+ position: "relative",
3047
+ width,
3048
+ height,
3049
+ overflow: "hidden",
3050
+ userSelect: "none",
3051
+ touchAction: "none",
3052
+ // Cursor: state-driven so it flips instantly with the
3053
+ // gesture. CSS `:active` would lag behind pointer-capture.
3054
+ cursor: panGesture ? "grabbing" : isConnecting ? "crosshair" : panOnDrag ? "grab" : "default",
3055
+ ...style
3056
+ },
3057
+ onPointerDown: onCanvasPointerDown,
3058
+ onWheel: handleWheel,
3059
+ onContextMenu: (e) => {
3060
+ const t = e.target;
3061
+ const nodeEl = t.closest("[data-node-id]");
3062
+ if (nodeEl && onNodeContextMenu) {
3063
+ const id = nodeEl.dataset.nodeId;
3064
+ const node = nodes.find((n) => n.id === id);
3065
+ if (node) onNodeContextMenu(e, node);
3066
+ return;
3067
+ }
3068
+ const edgeEl = t.closest("[data-edge-id]");
3069
+ if (edgeEl && onEdgeContextMenu) {
3070
+ const id = edgeEl.dataset.edgeId;
3071
+ const edge = edges.find((x) => x.id === id);
3072
+ if (edge) onEdgeContextMenu(e, edge);
3073
+ return;
3074
+ }
3075
+ onPaneContextMenu?.(e);
3076
+ },
3077
+ "data-empty": isEmpty ? "true" : void 0,
3078
+ children: [
3079
+ background !== "none" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3080
+ "div",
3081
+ {
3082
+ className: cn(
3083
+ "ods-flow-canvas-v2__grid",
3084
+ `ods-flow-canvas-v2__grid--${background}`
3085
+ ),
3086
+ style: {
3087
+ // Custom property drives the four variants' SCSS.
3088
+ "--ods-flow-grid-size": `${gridSize * viewport.zoom}px`,
3089
+ backgroundPosition: `${viewport.x}px ${viewport.y}px`
3090
+ }
3091
+ }
3092
+ ),
3093
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3094
+ "div",
3095
+ {
3096
+ className: "ods-flow-canvas-v2__viewport",
3097
+ style: {
3098
+ position: "absolute",
3099
+ inset: 0,
3100
+ transform: `translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.zoom})`,
3101
+ transformOrigin: "0 0"
3102
+ },
3103
+ children: [
3104
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3105
+ EdgesLayer,
3106
+ {
3107
+ edges: visibleEdges,
3108
+ nodes: visibleNodes,
3109
+ onSelect: (id) => bridge.selectEdge(id, false),
3110
+ onDelete: (id) => onEdgesChangeRef(id, onEdgesChange),
3111
+ onLabelChange: onEdgeLabelChange,
3112
+ ghost: conn ? { start: conn.start, end: conn.end } : null,
3113
+ handleVersion
3114
+ }
3115
+ ),
3116
+ orderedNodes.map((node) => {
3117
+ const Kind = kinds[node.type] ?? kinds.action;
3118
+ if (!Kind) return null;
3119
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3120
+ FlowNode,
3121
+ {
3122
+ node,
3123
+ selected: selectedNodeIds.has(node.id),
3124
+ dragging: draggingId === node.id,
3125
+ isConnecting,
3126
+ Kind
3127
+ },
3128
+ node.id
3129
+ );
3130
+ })
3131
+ ]
3132
+ }
3133
+ ),
3134
+ isEmpty && emptyState && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ods-flow-canvas-v2__empty", children: emptyState }),
3135
+ children
3136
+ ]
3137
+ }
3138
+ ) }) }) }) }) });
3139
+ }
3140
+ function onEdgesChangeRef(id, cb) {
3141
+ cb?.([change.edge.remove(id)]);
3142
+ }
3143
+ var EdgesLayer = (0, import_react14.memo)(function EdgesLayer2({
3144
+ edges,
3145
+ nodes,
3146
+ onSelect,
3147
+ onDelete,
3148
+ onLabelChange,
3149
+ ghost,
3150
+ handleVersion: _handleVersion
3151
+ }) {
3152
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3153
+ "svg",
3154
+ {
3155
+ className: "ods-flow-canvas-v2__edges",
3156
+ style: { position: "absolute", inset: 0, overflow: "visible", pointerEvents: "none" },
3157
+ width: "100%",
3158
+ height: "100%",
3159
+ children: [
3160
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("g", { style: { pointerEvents: "auto" }, children: edges.map((edge) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3161
+ FlowEdge,
3162
+ {
3163
+ edge,
3164
+ nodes,
3165
+ onSelect,
3166
+ onDelete,
3167
+ onLabelChange,
3168
+ handleVersion: _handleVersion
3169
+ },
3170
+ edge.id
3171
+ )) }),
3172
+ ghost && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3173
+ "path",
3174
+ {
3175
+ d: `M ${ghost.start.x} ${ghost.start.y} L ${ghost.end.x} ${ghost.end.y}`,
3176
+ stroke: "var(--ods-accent, #4f46e5)",
3177
+ strokeWidth: 1.5,
3178
+ strokeDasharray: "4 4",
3179
+ fill: "none"
3180
+ }
3181
+ )
3182
+ ]
3183
+ }
3184
+ );
3185
+ });
3186
+
3187
+ // src/workflow/components/NodeResizer/NodeResizer.tsx
3188
+ var import_react15 = require("react");
3189
+ var import_jsx_runtime8 = require("react/jsx-runtime");
3190
+ function NodeResizer({
3191
+ isVisible,
3192
+ minWidth = 80,
3193
+ minHeight = 60,
3194
+ maxWidth,
3195
+ maxHeight,
3196
+ keepAspectRatio = false,
3197
+ onResize,
3198
+ onResizeEnd,
3199
+ color
3200
+ }) {
3201
+ const { node, selected } = useFlowNodeContext();
3202
+ const viewport = useViewport();
3203
+ const flow = useFlow();
3204
+ const dragRef = (0, import_react15.useRef)(null);
3205
+ const show = isVisible ?? selected;
3206
+ if (!show) return null;
3207
+ const beginResize = (e, corner) => {
3208
+ e.preventDefault();
3209
+ e.stopPropagation();
3210
+ e.target.setPointerCapture(e.pointerId);
3211
+ const w = node.width ?? DEFAULT_NODE_WIDTH;
3212
+ const h = node.height ?? DEFAULT_NODE_HEIGHT;
3213
+ dragRef.current = {
3214
+ pointerId: e.pointerId,
3215
+ corner,
3216
+ startClientX: e.clientX,
3217
+ startClientY: e.clientY,
3218
+ startWidth: w,
3219
+ startHeight: h,
3220
+ startX: node.position.x,
3221
+ startY: node.position.y,
3222
+ aspect: w / Math.max(1, h)
3223
+ };
3224
+ };
3225
+ const onMove = (e) => {
3226
+ const drag = dragRef.current;
3227
+ if (!drag || drag.pointerId !== e.pointerId) return;
3228
+ const dx = (e.clientX - drag.startClientX) / viewport.zoom;
3229
+ const dy = (e.clientY - drag.startClientY) / viewport.zoom;
3230
+ let nextW = drag.startWidth;
3231
+ let nextH = drag.startHeight;
3232
+ let nextX = drag.startX;
3233
+ let nextY = drag.startY;
3234
+ switch (drag.corner) {
3235
+ case "se":
3236
+ nextW = drag.startWidth + dx;
3237
+ nextH = drag.startHeight + dy;
3238
+ break;
3239
+ case "sw":
3240
+ nextW = drag.startWidth - dx;
3241
+ nextH = drag.startHeight + dy;
3242
+ nextX = drag.startX + dx;
3243
+ break;
3244
+ case "ne":
3245
+ nextW = drag.startWidth + dx;
3246
+ nextH = drag.startHeight - dy;
3247
+ nextY = drag.startY + dy;
3248
+ break;
3249
+ case "nw":
3250
+ nextW = drag.startWidth - dx;
3251
+ nextH = drag.startHeight - dy;
3252
+ nextX = drag.startX + dx;
3253
+ nextY = drag.startY + dy;
3254
+ break;
3255
+ }
3256
+ if (keepAspectRatio) {
3257
+ nextH = nextW / drag.aspect;
3258
+ if (drag.corner === "nw" || drag.corner === "ne") {
3259
+ nextY = drag.startY + (drag.startHeight - nextH);
3260
+ }
3261
+ }
3262
+ nextW = Math.max(minWidth, maxWidth ? Math.min(maxWidth, nextW) : nextW);
3263
+ nextH = Math.max(minHeight, maxHeight ? Math.min(maxHeight, nextH) : nextH);
3264
+ flow.updateNode(node.id, {
3265
+ width: nextW,
3266
+ height: nextH,
3267
+ position: { x: nextX, y: nextY }
3268
+ });
3269
+ onResize?.({ width: nextW, height: nextH });
3270
+ };
3271
+ const onUp = (e) => {
3272
+ if (dragRef.current?.pointerId === e.pointerId) {
3273
+ const cur = flow.getNode(node.id);
3274
+ if (cur) {
3275
+ onResizeEnd?.({
3276
+ width: cur.width ?? DEFAULT_NODE_WIDTH,
3277
+ height: cur.height ?? DEFAULT_NODE_HEIGHT
3278
+ });
3279
+ }
3280
+ dragRef.current = null;
3281
+ }
3282
+ };
3283
+ const handleColor = color ?? "var(--ods-accent)";
3284
+ const handleStyle = (corner) => {
3285
+ const base = {
3286
+ position: "absolute",
3287
+ width: 12,
3288
+ height: 12,
3289
+ background: "var(--ods-surface-canvas)",
3290
+ border: `2px solid ${handleColor}`,
3291
+ borderRadius: 2,
3292
+ cursor: cursorFor(corner),
3293
+ touchAction: "none",
3294
+ // Place each handle so its CENTRE sits on the corresponding corner.
3295
+ transform: "translate(-50%, -50%)"
3296
+ };
3297
+ switch (corner) {
3298
+ case "nw":
3299
+ return { ...base, top: 0, left: 0 };
3300
+ case "ne":
3301
+ return { ...base, top: 0, left: "100%" };
3302
+ case "sw":
3303
+ return { ...base, top: "100%", left: 0 };
3304
+ case "se":
3305
+ return { ...base, top: "100%", left: "100%" };
3306
+ }
3307
+ };
3308
+ 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)(
3309
+ "div",
3310
+ {
3311
+ style: handleStyle(corner),
3312
+ onPointerDown: (e) => beginResize(e, corner),
3313
+ onPointerMove: onMove,
3314
+ onPointerUp: onUp,
3315
+ onPointerCancel: onUp,
3316
+ "aria-label": `Resize ${corner}`
3317
+ },
3318
+ corner
3319
+ )) });
3320
+ }
3321
+ function cursorFor(corner) {
3322
+ switch (corner) {
3323
+ case "nw":
3324
+ case "se":
3325
+ return "nwse-resize";
3326
+ case "ne":
3327
+ case "sw":
3328
+ return "nesw-resize";
3329
+ }
3330
+ }
3331
+
3332
+ // src/workflow/components/NodeToolbar/NodeToolbar.tsx
3333
+ var import_jsx_runtime9 = require("react/jsx-runtime");
3334
+ function NodeToolbar({
3335
+ isVisible,
3336
+ position = "top",
3337
+ offset = 10,
3338
+ align = "center",
3339
+ children,
3340
+ className,
3341
+ style
3342
+ }) {
3343
+ const node = useFlowNodeContext();
3344
+ const viewport = useViewport();
3345
+ const show = isVisible ?? node.selected;
3346
+ if (!show) return null;
3347
+ const inverseScale = 1 / viewport.zoom;
3348
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3349
+ "div",
3350
+ {
3351
+ className: cn("ods-node-toolbar", `ods-node-toolbar--${position}`, className),
3352
+ "data-flow-no-drag": "true",
3353
+ style: {
3354
+ ...positionStyle(position, offset, align),
3355
+ ...style
3356
+ },
3357
+ onPointerDown: (e) => e.stopPropagation(),
3358
+ onMouseDown: (e) => e.stopPropagation(),
3359
+ onClick: (e) => e.stopPropagation(),
3360
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3361
+ "div",
3362
+ {
3363
+ className: "ods-node-toolbar__inner",
3364
+ style: {
3365
+ transform: `scale(${inverseScale})`,
3366
+ transformOrigin: transformOriginFor(position, align)
3367
+ },
3368
+ children
3369
+ }
3370
+ )
3371
+ }
3372
+ );
3373
+ }
3374
+ function positionStyle(side, offset, align) {
3375
+ const center = align === "center";
3376
+ const start = align === "start";
3377
+ switch (side) {
3378
+ case "top":
3379
+ return {
3380
+ position: "absolute",
3381
+ bottom: "100%",
3382
+ marginBottom: offset,
3383
+ left: start ? 0 : center ? "50%" : void 0,
3384
+ right: align === "end" ? 0 : void 0,
3385
+ transform: center ? "translateX(-50%)" : void 0
3386
+ };
3387
+ case "bottom":
3388
+ return {
3389
+ position: "absolute",
3390
+ top: "100%",
3391
+ marginTop: offset,
3392
+ left: start ? 0 : center ? "50%" : void 0,
3393
+ right: align === "end" ? 0 : void 0,
3394
+ transform: center ? "translateX(-50%)" : void 0
3395
+ };
3396
+ case "left":
3397
+ return {
3398
+ position: "absolute",
3399
+ right: "100%",
3400
+ marginRight: offset,
3401
+ top: start ? 0 : center ? "50%" : void 0,
3402
+ bottom: align === "end" ? 0 : void 0,
3403
+ transform: center ? "translateY(-50%)" : void 0
3404
+ };
3405
+ case "right":
3406
+ return {
3407
+ position: "absolute",
3408
+ left: "100%",
3409
+ marginLeft: offset,
3410
+ top: start ? 0 : center ? "50%" : void 0,
3411
+ bottom: align === "end" ? 0 : void 0,
3412
+ transform: center ? "translateY(-50%)" : void 0
3413
+ };
3414
+ }
3415
+ }
3416
+ function transformOriginFor(side, align) {
3417
+ if (side === "top")
3418
+ return align === "start" ? "left bottom" : align === "end" ? "right bottom" : "center bottom";
3419
+ if (side === "bottom")
3420
+ return align === "start" ? "left top" : align === "end" ? "right top" : "center top";
3421
+ if (side === "left")
3422
+ return align === "start" ? "right top" : align === "end" ? "right bottom" : "right center";
3423
+ return align === "start" ? "left top" : align === "end" ? "left bottom" : "left center";
3424
+ }
3425
+
3426
+ // src/workflow/utils/collapse.ts
3427
+ function collapseFor(nodes, edges) {
3428
+ const collapsedAncestorOf = /* @__PURE__ */ new Map();
3429
+ const parents = /* @__PURE__ */ new Map();
3430
+ for (const n of nodes) if (n.parentId) parents.set(n.id, n.parentId);
3431
+ const isCollapsed = (node) => {
3432
+ const d = node?.data;
3433
+ return !!d?.collapsed;
3434
+ };
3435
+ for (const n of nodes) {
3436
+ let cursor = n.id;
3437
+ let outermostCollapsed;
3438
+ const seen = /* @__PURE__ */ new Set([cursor]);
3439
+ while (cursor) {
3440
+ const p = parents.get(cursor);
3441
+ if (!p || seen.has(p)) break;
3442
+ seen.add(p);
3443
+ const parentNode = nodes.find((x) => x.id === p);
3444
+ if (isCollapsed(parentNode)) outermostCollapsed = p;
3445
+ cursor = p;
3446
+ }
3447
+ if (outermostCollapsed && outermostCollapsed !== n.id) {
3448
+ collapsedAncestorOf.set(n.id, outermostCollapsed);
3449
+ }
3450
+ }
3451
+ const outNodes = nodes.map((n) => {
3452
+ const anc = collapsedAncestorOf.get(n.id);
3453
+ if (anc) return { ...n, hidden: true };
3454
+ return n;
3455
+ });
3456
+ const BOUNDARY_DASH = "2 4";
3457
+ const outEdges = [];
3458
+ const hiddenCount = /* @__PURE__ */ new Map();
3459
+ for (const e of edges) {
3460
+ const srcAnc = collapsedAncestorOf.get(e.source);
3461
+ const tgtAnc = collapsedAncestorOf.get(e.target);
3462
+ if (srcAnc && tgtAnc && srcAnc === tgtAnc) continue;
3463
+ let src = e.source;
3464
+ let srcHandle = e.sourceHandle;
3465
+ let tgt = e.target;
3466
+ let tgtHandle = e.targetHandle;
3467
+ const rewritten = !!(srcAnc || tgtAnc);
3468
+ if (srcAnc) {
3469
+ src = srcAnc;
3470
+ srcHandle = "__group_out";
3471
+ }
3472
+ if (tgtAnc) {
3473
+ tgt = tgtAnc;
3474
+ tgtHandle = "__group_in";
3475
+ }
3476
+ if (src === tgt) continue;
3477
+ const nextEdge = {
3478
+ ...e,
3479
+ source: src,
3480
+ sourceHandle: srcHandle,
3481
+ target: tgt,
3482
+ targetHandle: tgtHandle
3483
+ };
3484
+ if (rewritten) {
3485
+ nextEdge.style = { ...e.style ?? {}, strokeDasharray: BOUNDARY_DASH };
3486
+ }
3487
+ outEdges.push(nextEdge);
3488
+ }
3489
+ for (const [_id, anc] of collapsedAncestorOf) {
3490
+ hiddenCount.set(anc, (hiddenCount.get(anc) ?? 0) + 1);
3491
+ }
3492
+ return { nodes: outNodes, edges: outEdges, hiddenCountByGroupId: hiddenCount };
3493
+ }
3494
+ function toggleGroupCollapse(groupId, nodes) {
3495
+ return nodes.map((n) => {
3496
+ if (n.id !== groupId) return n;
3497
+ const data = n.data ?? {};
3498
+ return { ...n, data: { ...data, collapsed: !data.collapsed } };
3499
+ });
3500
+ }
3501
+ // Annotate the CommonJS export names for ESM import in node:
3502
+ 0 && (module.exports = {
3503
+ ActionNode,
3504
+ ConditionNode,
3505
+ ConfigPanel,
3506
+ DEFAULT_NODE_HEIGHT,
3507
+ DEFAULT_NODE_KINDS,
3508
+ DEFAULT_NODE_WIDTH,
3509
+ ErrorNode,
3510
+ FlowCanvas,
3511
+ FlowEdge,
3512
+ FlowNode,
3513
+ ForEachNode,
3514
+ GroupNode,
3515
+ Handle,
3516
+ HttpRequestNode,
3517
+ NodeResizer,
3518
+ NodeToolbar,
3519
+ OutputNode,
3520
+ ParallelNode,
3521
+ StickyNode,
3522
+ TriggerNode,
3523
+ WaitNode,
3524
+ WebhookNode,
3525
+ applyEdgeChanges,
3526
+ applyNodeChanges,
3527
+ bezierPath,
3528
+ bezierPathFn,
3529
+ buildEdgePath,
3530
+ buildNodeKindRegistry,
3531
+ change,
3532
+ clampToParentExtent,
3533
+ collapseFor,
3534
+ createDagreEngine,
3535
+ createElkEngine,
3536
+ descendantsOf,
3537
+ findAncestor,
3538
+ findContainingGroup,
3539
+ flowToScreen,
3540
+ handleCentre,
3541
+ screenToFlow,
3542
+ smoothStepPath,
3543
+ stepPath,
3544
+ straightPath,
3545
+ toggleGroupCollapse,
3546
+ useAutoLayout,
3547
+ useConnection,
3548
+ useEdgeById,
3549
+ useEdges,
3550
+ useFlow,
3551
+ useFlowNodeContext,
3552
+ useFlowSelector,
3553
+ useFlowState,
3554
+ useIsEdgeSelected,
3555
+ useIsNodeSelected,
3556
+ useNodeById,
3557
+ useNodeData,
3558
+ useNodes,
3559
+ useSelection,
3560
+ useViewport
3561
+ });
3562
+ //# sourceMappingURL=workflow.cjs.map