@bian-womp/spark-workbench 0.3.87 → 0.3.89

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 (175) hide show
  1. package/lib/cjs/index.cjs +72 -103
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  4. package/lib/esm/index.js +72 -103
  5. package/lib/esm/index.js.map +1 -1
  6. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  7. package/lib/src/adapters/cli/index.d.ts +22 -0
  8. package/lib/src/adapters/cli/index.d.ts.map +1 -0
  9. package/lib/src/adapters/cli/index.js +50 -0
  10. package/lib/src/adapters/cli/index.js.map +1 -0
  11. package/lib/src/core/AbstractWorkbench.d.ts +40 -0
  12. package/lib/src/core/AbstractWorkbench.d.ts.map +1 -0
  13. package/lib/src/core/AbstractWorkbench.js +15 -0
  14. package/lib/src/core/AbstractWorkbench.js.map +1 -0
  15. package/lib/src/core/InMemoryWorkbench.d.ts +304 -0
  16. package/lib/src/core/InMemoryWorkbench.d.ts.map +1 -0
  17. package/lib/src/core/InMemoryWorkbench.js +1016 -0
  18. package/lib/src/core/InMemoryWorkbench.js.map +1 -0
  19. package/lib/src/core/contracts.d.ts +172 -0
  20. package/lib/src/core/contracts.d.ts.map +1 -0
  21. package/lib/src/core/contracts.js +2 -0
  22. package/lib/src/core/contracts.js.map +1 -0
  23. package/lib/src/core/ui-extensions.d.ts +85 -0
  24. package/lib/src/core/ui-extensions.d.ts.map +1 -0
  25. package/lib/src/core/ui-extensions.js +111 -0
  26. package/lib/src/core/ui-extensions.js.map +1 -0
  27. package/lib/src/examples/cli.d.ts +2 -0
  28. package/lib/src/examples/cli.d.ts.map +1 -0
  29. package/lib/src/examples/cli.js +244 -0
  30. package/lib/src/examples/cli.js.map +1 -0
  31. package/lib/src/examples/reactflow/App.d.ts +2 -0
  32. package/lib/src/examples/reactflow/App.d.ts.map +1 -0
  33. package/lib/src/examples/reactflow/App.js +20 -0
  34. package/lib/src/examples/reactflow/App.js.map +1 -0
  35. package/lib/src/index.d.ts +30 -0
  36. package/lib/src/index.d.ts.map +1 -0
  37. package/lib/src/index.js +30 -0
  38. package/lib/src/index.js.map +1 -0
  39. package/lib/src/misc/DebugEvents.d.ts +7 -0
  40. package/lib/src/misc/DebugEvents.d.ts.map +1 -0
  41. package/lib/src/misc/DebugEvents.js +51 -0
  42. package/lib/src/misc/DebugEvents.js.map +1 -0
  43. package/lib/src/misc/DefaultEdge.d.ts +5 -0
  44. package/lib/src/misc/DefaultEdge.d.ts.map +1 -0
  45. package/lib/src/misc/DefaultEdge.js +15 -0
  46. package/lib/src/misc/DefaultEdge.js.map +1 -0
  47. package/lib/src/misc/DefaultNode.d.ts +5 -0
  48. package/lib/src/misc/DefaultNode.d.ts.map +1 -0
  49. package/lib/src/misc/DefaultNode.js +25 -0
  50. package/lib/src/misc/DefaultNode.js.map +1 -0
  51. package/lib/src/misc/DefaultNodeContent.d.ts +4 -0
  52. package/lib/src/misc/DefaultNodeContent.d.ts.map +1 -0
  53. package/lib/src/misc/DefaultNodeContent.js +58 -0
  54. package/lib/src/misc/DefaultNodeContent.js.map +1 -0
  55. package/lib/src/misc/DefaultNodeHeader.d.ts +13 -0
  56. package/lib/src/misc/DefaultNodeHeader.d.ts.map +1 -0
  57. package/lib/src/misc/DefaultNodeHeader.js +78 -0
  58. package/lib/src/misc/DefaultNodeHeader.js.map +1 -0
  59. package/lib/src/misc/Inspector.d.ts +12 -0
  60. package/lib/src/misc/Inspector.d.ts.map +1 -0
  61. package/lib/src/misc/Inspector.js +253 -0
  62. package/lib/src/misc/Inspector.js.map +1 -0
  63. package/lib/src/misc/IssueBadge.d.ts +7 -0
  64. package/lib/src/misc/IssueBadge.d.ts.map +1 -0
  65. package/lib/src/misc/IssueBadge.js +7 -0
  66. package/lib/src/misc/IssueBadge.js.map +1 -0
  67. package/lib/src/misc/KeyboardShortcutToast.d.ts +16 -0
  68. package/lib/src/misc/KeyboardShortcutToast.d.ts.map +1 -0
  69. package/lib/src/misc/KeyboardShortcutToast.js +40 -0
  70. package/lib/src/misc/KeyboardShortcutToast.js.map +1 -0
  71. package/lib/src/misc/NodeHandles.d.ts +18 -0
  72. package/lib/src/misc/NodeHandles.d.ts.map +1 -0
  73. package/lib/src/misc/NodeHandles.js +67 -0
  74. package/lib/src/misc/NodeHandles.js.map +1 -0
  75. package/lib/src/misc/SelectionActiveSync.d.ts +10 -0
  76. package/lib/src/misc/SelectionActiveSync.d.ts.map +1 -0
  77. package/lib/src/misc/SelectionActiveSync.js +21 -0
  78. package/lib/src/misc/SelectionActiveSync.js.map +1 -0
  79. package/lib/src/misc/WorkbenchCanvas.d.ts +23 -0
  80. package/lib/src/misc/WorkbenchCanvas.d.ts.map +1 -0
  81. package/lib/src/misc/WorkbenchCanvas.js +669 -0
  82. package/lib/src/misc/WorkbenchCanvas.js.map +1 -0
  83. package/lib/src/misc/WorkbenchStudio.d.ts +43 -0
  84. package/lib/src/misc/WorkbenchStudio.d.ts.map +1 -0
  85. package/lib/src/misc/WorkbenchStudio.js +463 -0
  86. package/lib/src/misc/WorkbenchStudio.js.map +1 -0
  87. package/lib/src/misc/constants.d.ts +4 -0
  88. package/lib/src/misc/constants.d.ts.map +1 -0
  89. package/lib/src/misc/constants.js +5 -0
  90. package/lib/src/misc/constants.js.map +1 -0
  91. package/lib/src/misc/context/WorkbenchContext.d.ts +133 -0
  92. package/lib/src/misc/context/WorkbenchContext.d.ts.map +1 -0
  93. package/lib/src/misc/context/WorkbenchContext.js +9 -0
  94. package/lib/src/misc/context/WorkbenchContext.js.map +1 -0
  95. package/lib/src/misc/context/WorkbenchContext.provider.d.ts +12 -0
  96. package/lib/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -0
  97. package/lib/src/misc/context/WorkbenchContext.provider.js +995 -0
  98. package/lib/src/misc/context/WorkbenchContext.provider.js.map +1 -0
  99. package/lib/src/misc/context-menu/ContextMenuButton.d.ts +8 -0
  100. package/lib/src/misc/context-menu/ContextMenuButton.d.ts.map +1 -0
  101. package/lib/src/misc/context-menu/ContextMenuButton.js +10 -0
  102. package/lib/src/misc/context-menu/ContextMenuButton.js.map +1 -0
  103. package/lib/src/misc/context-menu/ContextMenuHandlers.d.ts +85 -0
  104. package/lib/src/misc/context-menu/ContextMenuHandlers.d.ts.map +1 -0
  105. package/lib/src/misc/context-menu/ContextMenuHandlers.js +2 -0
  106. package/lib/src/misc/context-menu/ContextMenuHandlers.js.map +1 -0
  107. package/lib/src/misc/context-menu/ContextMenuHelpers.d.ts +45 -0
  108. package/lib/src/misc/context-menu/ContextMenuHelpers.d.ts.map +1 -0
  109. package/lib/src/misc/context-menu/ContextMenuHelpers.js +182 -0
  110. package/lib/src/misc/context-menu/ContextMenuHelpers.js.map +1 -0
  111. package/lib/src/misc/context-menu/DefaultContextMenu.d.ts +3 -0
  112. package/lib/src/misc/context-menu/DefaultContextMenu.d.ts.map +1 -0
  113. package/lib/src/misc/context-menu/DefaultContextMenu.js +111 -0
  114. package/lib/src/misc/context-menu/DefaultContextMenu.js.map +1 -0
  115. package/lib/src/misc/context-menu/NodeContextMenu.d.ts +3 -0
  116. package/lib/src/misc/context-menu/NodeContextMenu.d.ts.map +1 -0
  117. package/lib/src/misc/context-menu/NodeContextMenu.js +51 -0
  118. package/lib/src/misc/context-menu/NodeContextMenu.js.map +1 -0
  119. package/lib/src/misc/context-menu/SelectionContextMenu.d.ts +3 -0
  120. package/lib/src/misc/context-menu/SelectionContextMenu.d.ts.map +1 -0
  121. package/lib/src/misc/context-menu/SelectionContextMenu.js +47 -0
  122. package/lib/src/misc/context-menu/SelectionContextMenu.js.map +1 -0
  123. package/lib/src/misc/hooks.d.ts +18 -0
  124. package/lib/src/misc/hooks.d.ts.map +1 -0
  125. package/lib/src/misc/hooks.js +275 -0
  126. package/lib/src/misc/hooks.js.map +1 -0
  127. package/lib/src/misc/layout.d.ts +117 -0
  128. package/lib/src/misc/layout.d.ts.map +1 -0
  129. package/lib/src/misc/layout.js +205 -0
  130. package/lib/src/misc/layout.js.map +1 -0
  131. package/lib/src/misc/load.d.ts +5 -0
  132. package/lib/src/misc/load.d.ts.map +1 -0
  133. package/lib/src/misc/load.js +106 -0
  134. package/lib/src/misc/load.js.map +1 -0
  135. package/lib/src/misc/mapping.d.ts +128 -0
  136. package/lib/src/misc/mapping.d.ts.map +1 -0
  137. package/lib/src/misc/mapping.js +270 -0
  138. package/lib/src/misc/mapping.js.map +1 -0
  139. package/lib/src/misc/merge-utils.d.ts +12 -0
  140. package/lib/src/misc/merge-utils.d.ts.map +1 -0
  141. package/lib/src/misc/merge-utils.js +51 -0
  142. package/lib/src/misc/merge-utils.js.map +1 -0
  143. package/lib/src/misc/thumbnail-utils.d.ts +53 -0
  144. package/lib/src/misc/thumbnail-utils.d.ts.map +1 -0
  145. package/lib/src/misc/thumbnail-utils.js +629 -0
  146. package/lib/src/misc/thumbnail-utils.js.map +1 -0
  147. package/lib/src/misc/types.d.ts +18 -0
  148. package/lib/src/misc/types.d.ts.map +1 -0
  149. package/lib/src/misc/types.js +2 -0
  150. package/lib/src/misc/types.js.map +1 -0
  151. package/lib/src/misc/value.d.ts +16 -0
  152. package/lib/src/misc/value.d.ts.map +1 -0
  153. package/lib/src/misc/value.js +114 -0
  154. package/lib/src/misc/value.js.map +1 -0
  155. package/lib/src/misc/viewport-utils.d.ts +6 -0
  156. package/lib/src/misc/viewport-utils.d.ts.map +1 -0
  157. package/lib/src/misc/viewport-utils.js +18 -0
  158. package/lib/src/misc/viewport-utils.js.map +1 -0
  159. package/lib/src/runtime/AbstractGraphRunner.d.ts +61 -0
  160. package/lib/src/runtime/AbstractGraphRunner.d.ts.map +1 -0
  161. package/lib/src/runtime/AbstractGraphRunner.js +63 -0
  162. package/lib/src/runtime/AbstractGraphRunner.js.map +1 -0
  163. package/lib/src/runtime/IGraphRunner.d.ts +100 -0
  164. package/lib/src/runtime/IGraphRunner.d.ts.map +1 -0
  165. package/lib/src/runtime/IGraphRunner.js +2 -0
  166. package/lib/src/runtime/IGraphRunner.js.map +1 -0
  167. package/lib/src/runtime/LocalGraphRunner.d.ts +60 -0
  168. package/lib/src/runtime/LocalGraphRunner.d.ts.map +1 -0
  169. package/lib/src/runtime/LocalGraphRunner.js +294 -0
  170. package/lib/src/runtime/LocalGraphRunner.js.map +1 -0
  171. package/lib/src/runtime/RemoteGraphRunner.d.ts +109 -0
  172. package/lib/src/runtime/RemoteGraphRunner.d.ts.map +1 -0
  173. package/lib/src/runtime/RemoteGraphRunner.js +696 -0
  174. package/lib/src/runtime/RemoteGraphRunner.js.map +1 -0
  175. package/package.json +4 -4
@@ -0,0 +1,669 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useMemo, useState, useRef, useImperativeHandle, useCallback, useEffect } from "react";
3
+ import { ReactFlow, Background, Controls, MiniMap, BackgroundVariant, ReactFlowProvider, } from "@xyflow/react";
4
+ import lod from "lodash";
5
+ import { toReactFlow } from "./mapping";
6
+ import { useWorkbenchBridge } from "./hooks";
7
+ import { DefaultNode } from "./DefaultNode";
8
+ import { DefaultEdge } from "./DefaultEdge";
9
+ import { DefaultContextMenu } from "./context-menu/DefaultContextMenu";
10
+ import { NodeContextMenu } from "./context-menu/NodeContextMenu";
11
+ import { SelectionContextMenu } from "./context-menu/SelectionContextMenu";
12
+ import { useWorkbenchContext } from "./context/WorkbenchContext";
13
+ import { createNodeContextMenuHandlers, createSelectionContextMenuHandlers, createDefaultContextMenuHandlers, getBakeableOutputs, } from "./context-menu/ContextMenuHelpers";
14
+ import { KeyboardShortcutToast, useKeyboardShortcutToast } from "./KeyboardShortcutToast";
15
+ import SelectionActiveSync from "./SelectionActiveSync";
16
+ export const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
17
+ const { showValues, toString, toElement, getDefaultNodeSize, reactFlowProps, children } = props;
18
+ const { wb, handlesMap, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, registryVersion, runner, overrides, runNode, runFromHere, runMode, } = useWorkbenchContext();
19
+ const nodeValidation = validationByNode;
20
+ const edgeValidation = validationByEdge.errors;
21
+ const [historyState, setHistoryState] = useState(wb.getHistory());
22
+ const prevNodesRef = useRef([]);
23
+ const prevEdgesRef = useRef([]);
24
+ function retainStabilityById(prev, next, isSame) {
25
+ if (prev.length === 0)
26
+ return next;
27
+ const map = new Map();
28
+ for (const p of prev)
29
+ map.set(p.id, p);
30
+ const out = new Array(next.length);
31
+ for (let i = 0; i < next.length; i++) {
32
+ const n = next[i];
33
+ const p = map.get(n.id);
34
+ out[i] = p && isSame(p, n) ? p : n;
35
+ }
36
+ return out;
37
+ }
38
+ const isSameNode = (a, b) => {
39
+ // Compare the parts that affect rendering
40
+ const pick = (n) => ({
41
+ position: n.position,
42
+ type: n.type,
43
+ selected: n.selected,
44
+ measured: n.measured,
45
+ data: n.data && {
46
+ typeId: n.data.typeId,
47
+ handles: n.data.handles,
48
+ inputHandles: n.data.inputHandles,
49
+ outputHandles: n.data.outputHandles,
50
+ showValues: n.data.showValues,
51
+ inputValues: n.data.inputValues,
52
+ outputValues: n.data.outputValues,
53
+ status: n.data.status,
54
+ validation: n.data.validation,
55
+ inputConnected: n.data.inputConnected,
56
+ custom: n.data.custom,
57
+ },
58
+ });
59
+ return lod.isEqual(pick(a), pick(b));
60
+ };
61
+ const isSameEdge = (a, b) => {
62
+ const pick = (e) => ({
63
+ source: e.source,
64
+ target: e.target,
65
+ sourceHandle: e.sourceHandle,
66
+ targetHandle: e.targetHandle,
67
+ selected: e.selected,
68
+ animated: e.animated,
69
+ style: e.style,
70
+ label: e.label,
71
+ type: e.type,
72
+ data: e.data && {
73
+ edgeTypeId: e.data.edgeTypeId,
74
+ isRunning: e.data.isRunning,
75
+ hasError: e.data.hasError,
76
+ isInvalid: e.data.isInvalid,
77
+ isMissing: e.data.isMissing,
78
+ custom: e.data.custom,
79
+ },
80
+ });
81
+ return lod.isEqual(pick(a), pick(b));
82
+ };
83
+ // Expose imperative API
84
+ const rfInstanceRef = useRef(null);
85
+ const containerRef = useRef(null);
86
+ useImperativeHandle(ref, () => ({
87
+ fitView: () => {
88
+ try {
89
+ rfInstanceRef.current?.fitView();
90
+ }
91
+ catch (err) {
92
+ console.warn("Failed to fit view", err);
93
+ }
94
+ },
95
+ setViewport: (viewport) => {
96
+ if (rfInstanceRef.current) {
97
+ rfInstanceRef.current.setViewport(lod.clone(viewport));
98
+ }
99
+ },
100
+ }), []);
101
+ const { onConnect, onNodesChange, onEdgesChange, onEdgesDelete, onNodesDelete } = useWorkbenchBridge(wb, handlesMap);
102
+ const ui = useMemo(() => wb.getUI(), [wb, uiVersion]);
103
+ const { nodeTypes, resolveNodeType } = useMemo(() => {
104
+ // Build nodeTypes map using UI extension registry
105
+ const custom = new Map(); // Include all types present in registry AND current graph to avoid timing issues
106
+ const allNodeRenderers = ui.getAllNodeRenderers();
107
+ for (const typeId of Object.keys(allNodeRenderers)) {
108
+ custom.set(typeId, allNodeRenderers[typeId]);
109
+ }
110
+ const types = {
111
+ "spark-default": DefaultNode,
112
+ default: DefaultNode,
113
+ };
114
+ for (const [typeId, comp] of custom.entries()) {
115
+ types[`spark-${typeId}`] = comp;
116
+ }
117
+ const resolver = (nodeTypeId) => (custom.has(nodeTypeId) ? `spark-${nodeTypeId}` : "spark-default");
118
+ return { nodeTypes: types, resolveNodeType: resolver };
119
+ // Include uiVersion to recompute when custom renderers are registered
120
+ // Include registryVersion to recompute when registry enums/types change
121
+ }, [wb, wb.registry, registryVersion, ui, uiVersion]);
122
+ const edgeTypes = useMemo(() => {
123
+ // Use default edge renderer override if registered, otherwise use DefaultEdge
124
+ const customEdgeRenderer = ui.getEdgeRenderer();
125
+ return { default: customEdgeRenderer || DefaultEdge };
126
+ }, [uiVersion, ui]);
127
+ // Track selection state to prevent unnecessary re-renders
128
+ const [selection, setSelection] = useState(() => wb.getSelection());
129
+ useEffect(() => {
130
+ const off = wb.on("selectionChanged", (sel) => {
131
+ setSelection({ nodes: sel.nodes, edges: sel.edges });
132
+ });
133
+ return () => off();
134
+ }, [wb]);
135
+ const rfData = useMemo(() => {
136
+ const out = toReactFlow(wb.def, wb.getPositions(), wb.getSizes(), wb.registry, {
137
+ showValues,
138
+ inputs: inputsMap,
139
+ inputDefaults: inputDefaultsMap,
140
+ outputs: outputsMap,
141
+ handles: handlesMap,
142
+ resolveNodeType,
143
+ toString,
144
+ toElement,
145
+ nodeStatus,
146
+ edgeStatus,
147
+ nodeValidation,
148
+ edgeValidation,
149
+ selectedNodeIds: new Set(selection.nodes),
150
+ selectedEdgeIds: new Set(selection.edges),
151
+ getDefaultNodeSize,
152
+ ui,
153
+ customData: wb.getCustomData(),
154
+ });
155
+ // Retain references for unchanged items
156
+ const stableNodes = retainStabilityById(prevNodesRef.current, out.nodes, isSameNode);
157
+ const stableEdges = retainStabilityById(prevEdgesRef.current, out.edges, isSameEdge);
158
+ // Debug: log updates/additions/removals (use value equality, not reference)
159
+ try {
160
+ const prevNodeIds = new Set(prevNodesRef.current.map((n) => n.id));
161
+ const nextNodeIds = new Set(out.nodes.map((n) => n.id));
162
+ const addedNodeIds = out.nodes.filter((n) => !prevNodeIds.has(n.id)).map((n) => n.id);
163
+ const removedNodeIds = prevNodesRef.current.filter((n) => !nextNodeIds.has(n.id)).map((n) => n.id);
164
+ const prevNodeMap = new Map(prevNodesRef.current.map((n) => [n.id, n]));
165
+ const changedNodeIds = out.nodes
166
+ .filter((n) => {
167
+ const p = prevNodeMap.get(n.id);
168
+ return p ? !isSameNode(p, n) : false;
169
+ })
170
+ .map((n) => n.id);
171
+ // Detect handle updates (ids/length changes) for targeted debug
172
+ const toIds = (arr) => Array.isArray(arr)
173
+ ? arr.map((h) => (h && typeof h === "object" && "id" in h ? String(h.id) : "")).filter(Boolean)
174
+ : [];
175
+ const handlesEqual = (a, b) => {
176
+ const aIds = toIds(a);
177
+ const bIds = toIds(b);
178
+ if (aIds.length !== bIds.length)
179
+ return false;
180
+ for (let i = 0; i < aIds.length; i++) {
181
+ if (aIds[i] !== bIds[i])
182
+ return false;
183
+ }
184
+ return true;
185
+ };
186
+ const handleChanged = out.nodes
187
+ .filter((n) => {
188
+ const p = prevNodeMap.get(n.id);
189
+ if (!p)
190
+ return false;
191
+ const inChanged = !handlesEqual(p.data?.inputHandles, n.data?.inputHandles);
192
+ const outChanged = !handlesEqual(p.data?.outputHandles, n.data?.outputHandles);
193
+ return inChanged || outChanged;
194
+ })
195
+ .map((n) => n.id);
196
+ const prevEdgeIds = new Set(prevEdgesRef.current.map((e) => e.id));
197
+ const nextEdgeIds = new Set(out.edges.map((e) => e.id));
198
+ const addedEdgeIds = out.edges.filter((e) => !prevEdgeIds.has(e.id)).map((e) => e.id);
199
+ const removedEdgeIds = prevEdgesRef.current.filter((e) => !nextEdgeIds.has(e.id)).map((e) => e.id);
200
+ const prevEdgeMap = new Map(prevEdgesRef.current.map((e) => [e.id, e]));
201
+ const changedEdgeIds = out.edges
202
+ .filter((e) => {
203
+ const p = prevEdgeMap.get(e.id);
204
+ return p ? !isSameEdge(p, e) : false;
205
+ })
206
+ .map((e) => e.id);
207
+ if (addedNodeIds.length || removedNodeIds.length || changedNodeIds.length || handleChanged.length) {
208
+ // eslint-disable-next-line no-console
209
+ console.debug("[WorkbenchCanvas] node updates", {
210
+ added: addedNodeIds,
211
+ removed: removedNodeIds,
212
+ changed: changedNodeIds,
213
+ handleChanged,
214
+ });
215
+ }
216
+ if (addedEdgeIds.length || removedEdgeIds.length || changedEdgeIds.length) {
217
+ // eslint-disable-next-line no-console
218
+ console.debug("[WorkbenchCanvas] edge updates", {
219
+ added: addedEdgeIds,
220
+ removed: removedEdgeIds,
221
+ changed: changedEdgeIds,
222
+ });
223
+ }
224
+ }
225
+ catch { }
226
+ prevNodesRef.current = stableNodes;
227
+ prevEdgesRef.current = stableEdges;
228
+ return { nodes: stableNodes, edges: stableEdges };
229
+ }, [
230
+ showValues,
231
+ inputsMap,
232
+ inputDefaultsMap,
233
+ outputsMap,
234
+ handlesMap,
235
+ valuesTick,
236
+ toString,
237
+ toElement,
238
+ nodeStatus,
239
+ edgeStatus,
240
+ nodeValidation,
241
+ edgeValidation,
242
+ resolveNodeType,
243
+ selection,
244
+ ui,
245
+ getDefaultNodeSize,
246
+ wb,
247
+ uiVersion,
248
+ ]);
249
+ const [menuState, setMenuState] = useState(null);
250
+ // Compute the rectangular screen-space bounds of the current selection
251
+ const getSelectionScreenBounds = () => {
252
+ if (typeof document === "undefined")
253
+ return null;
254
+ const selection = wb.getSelection();
255
+ if (!selection.nodes.length)
256
+ return null;
257
+ let bounds = null;
258
+ for (const nodeId of selection.nodes) {
259
+ const el = document.querySelector(`.react-flow__node[data-id="${nodeId}"]`);
260
+ if (!el)
261
+ continue;
262
+ const rect = el.getBoundingClientRect();
263
+ if (!bounds) {
264
+ bounds = {
265
+ left: rect.left,
266
+ top: rect.top,
267
+ right: rect.right,
268
+ bottom: rect.bottom,
269
+ };
270
+ }
271
+ else {
272
+ bounds.left = Math.min(bounds.left, rect.left);
273
+ bounds.top = Math.min(bounds.top, rect.top);
274
+ bounds.right = Math.max(bounds.right, rect.right);
275
+ bounds.bottom = Math.max(bounds.bottom, rect.bottom);
276
+ }
277
+ }
278
+ return bounds;
279
+ };
280
+ const onContextMenu = (e) => {
281
+ e.preventDefault();
282
+ const selection = wb.getSelection();
283
+ const isSingleNodeSelected = selection.nodes.length === 1;
284
+ const hasMultipleNodesSelected = selection.nodes.length > 1;
285
+ // Determine if right-clicked over a node by hit-testing
286
+ const target = e.target?.closest(".react-flow__node");
287
+ if (target) {
288
+ // Resolve node id from data-id attribute React Flow sets
289
+ const nodeId = target.getAttribute("data-id");
290
+ if (!nodeId)
291
+ return;
292
+ const isSelected = selection.nodes.includes(nodeId);
293
+ if (isSelected && !isSingleNodeSelected) {
294
+ // Right-clicked on a node that's part of multi-selection - show selection menu
295
+ setMenuState({
296
+ type: "selection",
297
+ menuPos: { x: e.clientX, y: e.clientY },
298
+ });
299
+ return;
300
+ }
301
+ else {
302
+ // Right-clicked on a non-selected node - show node menu
303
+ setMenuState({
304
+ type: "node",
305
+ menuPos: { x: e.clientX, y: e.clientY },
306
+ nodeId,
307
+ });
308
+ return;
309
+ }
310
+ }
311
+ // Check if right-clicked on a selected edge
312
+ const edgeTarget = e.target?.closest(".react-flow__edge");
313
+ if (edgeTarget) {
314
+ const edgeId = edgeTarget.getAttribute("data-id");
315
+ const isSelected = edgeId && selection.edges.includes(edgeId);
316
+ if (isSelected && isSingleNodeSelected) {
317
+ // Right-clicked on an edge, but only one node is selected - show node menu
318
+ const nodeId = selection.nodes[0];
319
+ setMenuState({
320
+ type: "node",
321
+ menuPos: { x: e.clientX, y: e.clientY },
322
+ nodeId,
323
+ });
324
+ return;
325
+ }
326
+ else if (isSelected) {
327
+ // Right-clicked on a selected edge with multiple nodes - show selection menu
328
+ setMenuState({
329
+ type: "selection",
330
+ menuPos: { x: e.clientX, y: e.clientY },
331
+ });
332
+ return;
333
+ }
334
+ }
335
+ // Check if the cursor is inside the rectangular bounds of the current selection
336
+ // (for multi-selection when right-clicking on empty space within selection bounds)
337
+ const selectionBounds = getSelectionScreenBounds();
338
+ if (selectionBounds && hasMultipleNodesSelected) {
339
+ const { left, top, right, bottom } = selectionBounds;
340
+ if (e.clientX >= left && e.clientX <= right && e.clientY >= top && e.clientY <= bottom) {
341
+ // If only one node is selected (even with edges), show node menu for empty space clicks
342
+ if (isSingleNodeSelected) {
343
+ const nodeId = selection.nodes[0];
344
+ setMenuState({
345
+ type: "node",
346
+ menuPos: { x: e.clientX, y: e.clientY },
347
+ nodeId,
348
+ });
349
+ return;
350
+ }
351
+ setMenuState({
352
+ type: "selection",
353
+ menuPos: { x: e.clientX, y: e.clientY },
354
+ });
355
+ return;
356
+ }
357
+ }
358
+ // Right-clicked on empty space with no selection - show default menu
359
+ setMenuState({
360
+ type: "default",
361
+ menuPos: { x: e.clientX, y: e.clientY },
362
+ });
363
+ };
364
+ const addNodeAt = useCallback(async (typeId, opts) => wb.addNode({ typeId }, { inputs: opts.inputs, position: opts.position, commit: true }), [wb]);
365
+ const onCloseMenu = useCallback(() => {
366
+ setMenuState(null);
367
+ }, []);
368
+ const onCloseNodeMenu = useCallback(() => {
369
+ setMenuState(null);
370
+ }, []);
371
+ const onCloseSelectionMenu = useCallback(() => {
372
+ setMenuState(null);
373
+ }, []);
374
+ useEffect(() => {
375
+ const off = wb.on("historyChanged", (event) => {
376
+ setHistoryState(event.history);
377
+ });
378
+ return () => off();
379
+ }, [wb]);
380
+ const nodeIds = useMemo(() => Array.from(wb.registry.nodes.keys()), [wb.registry, registryVersion]);
381
+ const defaultContextMenuHandlers = useMemo(() => {
382
+ // Get storage from override or use workbench's internal storage
383
+ const storage = overrides?.getCopiedDataStorage
384
+ ? overrides.getCopiedDataStorage()
385
+ : {
386
+ get: () => wb.getCopiedData(),
387
+ set: (data) => wb.setCopiedData(data),
388
+ };
389
+ const baseHandlers = createDefaultContextMenuHandlers(addNodeAt, onCloseMenu, (position) => {
390
+ const data = storage.get();
391
+ if (!data)
392
+ return;
393
+ wb.pasteCopiedData(data, position, { commit: true, reason: "paste" });
394
+ onCloseMenu();
395
+ }, runner, () => storage.get(), () => storage.set(null), historyState, wb);
396
+ if (overrides?.getDefaultContextMenuHandlers) {
397
+ return overrides.getDefaultContextMenuHandlers(wb, baseHandlers);
398
+ }
399
+ return baseHandlers;
400
+ }, [addNodeAt, onCloseMenu, overrides, wb, runner, historyState]);
401
+ const selectionContextMenuHandlers = useMemo(() => {
402
+ // Get storage from override or use workbench's internal storage
403
+ const storage = overrides?.getCopiedDataStorage
404
+ ? overrides.getCopiedDataStorage()
405
+ : {
406
+ get: () => wb.getCopiedData(),
407
+ set: (data) => wb.setCopiedData(data),
408
+ };
409
+ const baseHandlers = createSelectionContextMenuHandlers(wb, onCloseSelectionMenu, overrides?.getDefaultNodeSize, (data) => {
410
+ storage.set(data);
411
+ }, runner);
412
+ if (overrides?.getSelectionContextMenuHandlers) {
413
+ const selection = wb.getSelection();
414
+ return overrides.getSelectionContextMenuHandlers(wb, selection, baseHandlers, {
415
+ getDefaultNodeSize: overrides.getDefaultNodeSize,
416
+ });
417
+ }
418
+ return baseHandlers;
419
+ }, [wb, runner, overrides, onCloseSelectionMenu]);
420
+ const nodeContextMenuHandlers = useMemo(() => {
421
+ if (menuState?.type !== "node")
422
+ return null;
423
+ const nodeAtMenu = menuState.nodeId;
424
+ // Get storage from override or use workbench's internal storage
425
+ const storage = overrides?.getCopiedDataStorage
426
+ ? overrides.getCopiedDataStorage()
427
+ : {
428
+ get: () => wb.getCopiedData(),
429
+ set: (data) => wb.setCopiedData(data),
430
+ };
431
+ const baseHandlers = createNodeContextMenuHandlers(nodeAtMenu, wb, runner, wb.registry, outputsMap, outputTypesMap, onCloseNodeMenu, overrides?.getDefaultNodeSize, (data) => {
432
+ storage.set(data);
433
+ }, runNode, runFromHere);
434
+ if (overrides?.getNodeContextMenuHandlers) {
435
+ return overrides.getNodeContextMenuHandlers(wb, nodeAtMenu, baseHandlers);
436
+ }
437
+ return baseHandlers;
438
+ }, [
439
+ menuState,
440
+ wb,
441
+ runner,
442
+ wb.registry,
443
+ registryVersion,
444
+ outputsMap,
445
+ outputTypesMap,
446
+ onCloseNodeMenu,
447
+ overrides?.getDefaultNodeSize,
448
+ overrides?.getNodeContextMenuHandlers,
449
+ overrides?.getCopiedDataStorage,
450
+ ]);
451
+ const bakeableOutputs = useMemo(() => {
452
+ if (menuState?.type !== "node")
453
+ return [];
454
+ return getBakeableOutputs(menuState.nodeId, wb, wb.registry, outputTypesMap);
455
+ }, [menuState, wb, wb.registry, registryVersion, outputTypesMap]);
456
+ // Keyboard shortcuts configuration
457
+ const keyboardShortcutEnables = useMemo(() => ({
458
+ undo: true,
459
+ redo: true,
460
+ copy: true,
461
+ paste: true,
462
+ duplicate: true,
463
+ selectAll: true,
464
+ ...(overrides?.enableKeyboardShortcuts ?? {}),
465
+ }), [overrides?.enableKeyboardShortcuts]);
466
+ const isShortcutEnabled = useCallback((action) => keyboardShortcutEnables[action] !== false, [keyboardShortcutEnables]);
467
+ const hasEnabledKeyboardShortcuts = useMemo(() => Object.values(keyboardShortcutEnables).some(Boolean), [keyboardShortcutEnables]);
468
+ const keyboardShortcuts = useMemo(() => overrides?.keyboardShortcuts || {
469
+ undo: "⌘/Ctrl + Z",
470
+ redo: "⌘/Ctrl + Shift + Z",
471
+ copy: "⌘/Ctrl + C",
472
+ paste: "⌘/Ctrl + V",
473
+ duplicate: "⌘/Ctrl + E",
474
+ selectAll: "⌘/Ctrl + A",
475
+ delete: "Delete",
476
+ }, [overrides?.keyboardShortcuts]);
477
+ // Toast notification for keyboard shortcuts
478
+ const { toast, showToast, hideToast } = useKeyboardShortcutToast();
479
+ // Keyboard shortcut handler
480
+ useEffect(() => {
481
+ if (!hasEnabledKeyboardShortcuts)
482
+ return;
483
+ const handleKeyDown = async (e) => {
484
+ // Check if target is inside WorkbenchCanvas container
485
+ const target = e.target;
486
+ if (!containerRef.current || !(containerRef.current.contains(target) || containerRef.current == target)) {
487
+ return;
488
+ }
489
+ // Ignore if typing in input/textarea
490
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) {
491
+ return;
492
+ }
493
+ // Detect Mac platform using userAgent (navigator.platform is deprecated)
494
+ const isMac = typeof navigator !== "undefined" && navigator.userAgent.toLowerCase().includes("mac");
495
+ const modKey = isMac ? e.metaKey : e.ctrlKey;
496
+ const key = e.key.toLowerCase();
497
+ // Undo: Cmd/Ctrl + Z
498
+ if (modKey && key === "z" && !e.shiftKey && !e.altKey) {
499
+ if (!isShortcutEnabled("undo"))
500
+ return;
501
+ e.preventDefault();
502
+ if (runner &&
503
+ "onUndo" in defaultContextMenuHandlers &&
504
+ defaultContextMenuHandlers.onUndo &&
505
+ defaultContextMenuHandlers.canUndo) {
506
+ if (defaultContextMenuHandlers.canUndo) {
507
+ const modKeyLabel = isMac ? "⌘" : "Ctrl";
508
+ showToast(`Undo (${modKeyLabel} + Z)`);
509
+ defaultContextMenuHandlers.onUndo();
510
+ }
511
+ }
512
+ return;
513
+ }
514
+ // Redo: Cmd/Ctrl + Shift + Z
515
+ if (modKey && e.shiftKey && key === "z" && !e.altKey) {
516
+ if (!isShortcutEnabled("redo"))
517
+ return;
518
+ e.preventDefault();
519
+ if (runner &&
520
+ "onRedo" in defaultContextMenuHandlers &&
521
+ defaultContextMenuHandlers.onRedo &&
522
+ defaultContextMenuHandlers.canRedo) {
523
+ if (defaultContextMenuHandlers.canRedo) {
524
+ const modKeyLabel = isMac ? "⌘" : "Ctrl";
525
+ showToast(`Redo (${modKeyLabel} + Shift + Z)`);
526
+ defaultContextMenuHandlers.onRedo();
527
+ }
528
+ }
529
+ return;
530
+ }
531
+ // Copy: Cmd/Ctrl + C
532
+ if (modKey && key === "c" && !e.shiftKey && !e.altKey) {
533
+ if (!isShortcutEnabled("copy"))
534
+ return;
535
+ const selection = wb.getSelection();
536
+ if (selection.nodes.length > 0 || selection.edges.length > 0) {
537
+ e.preventDefault();
538
+ const modKeyLabel = isMac ? "⌘" : "Ctrl";
539
+ showToast(`Copy (${modKeyLabel} + C)`);
540
+ // If single node selected, use node context menu handler; otherwise use selection handler
541
+ if (selection.nodes.length === 1 && nodeContextMenuHandlers?.onCopy) {
542
+ nodeContextMenuHandlers.onCopy();
543
+ }
544
+ else if (selectionContextMenuHandlers.onCopy) {
545
+ selectionContextMenuHandlers.onCopy();
546
+ }
547
+ }
548
+ return;
549
+ }
550
+ // Duplicate: Cmd/Ctrl + E
551
+ if (modKey && key === "e" && !e.shiftKey && !e.altKey) {
552
+ if (!isShortcutEnabled("duplicate"))
553
+ return;
554
+ const selection = wb.getSelection();
555
+ if (selection.nodes.length === 1 && nodeContextMenuHandlers?.onDuplicate) {
556
+ // Single node selected - use node context menu handler
557
+ e.preventDefault();
558
+ const modKeyLabel = isMac ? "⌘" : "Ctrl";
559
+ showToast(`Duplicate (${modKeyLabel} + E)`);
560
+ nodeContextMenuHandlers.onDuplicate();
561
+ }
562
+ else if (selection.nodes.length > 1 && selectionContextMenuHandlers.onDuplicate) {
563
+ // Multiple nodes selected - use selection context menu handler
564
+ e.preventDefault();
565
+ const modKeyLabel = isMac ? "⌘" : "Ctrl";
566
+ showToast(`Duplicate (${modKeyLabel} + E)`);
567
+ selectionContextMenuHandlers.onDuplicate();
568
+ }
569
+ return;
570
+ }
571
+ // Paste: Cmd/Ctrl + V
572
+ if (modKey && key === "v" && !e.shiftKey && !e.altKey) {
573
+ if (!isShortcutEnabled("paste"))
574
+ return;
575
+ e.preventDefault();
576
+ if ("hasPasteData" in defaultContextMenuHandlers &&
577
+ defaultContextMenuHandlers.hasPasteData &&
578
+ defaultContextMenuHandlers.hasPasteData() &&
579
+ "onPaste" in defaultContextMenuHandlers &&
580
+ defaultContextMenuHandlers.onPaste) {
581
+ const modKeyLabel = isMac ? "⌘" : "Ctrl";
582
+ showToast(`Paste (${modKeyLabel} + V)`);
583
+ const center = rfInstanceRef.current?.screenToFlowPosition({
584
+ x: window.innerWidth / 2,
585
+ y: window.innerHeight / 2,
586
+ }) || { x: 0, y: 0 };
587
+ defaultContextMenuHandlers.onPaste(center);
588
+ }
589
+ return;
590
+ }
591
+ // Select All: Cmd/Ctrl + A
592
+ if (modKey && key === "a" && !e.shiftKey && !e.altKey) {
593
+ if (!isShortcutEnabled("selectAll"))
594
+ return;
595
+ e.preventDefault();
596
+ if (defaultContextMenuHandlers.onSelectAll) {
597
+ const modKeyLabel = isMac ? "⌘" : "Ctrl";
598
+ showToast(`Select All (${modKeyLabel} + A)`);
599
+ defaultContextMenuHandlers.onSelectAll();
600
+ }
601
+ return;
602
+ }
603
+ // Note: Delete/Backspace is handled by ReactFlow's deleteKeyCode prop
604
+ // which triggers onNodesDelete/onEdgesDelete, so we don't need to handle it here
605
+ };
606
+ window.addEventListener("keydown", handleKeyDown);
607
+ return () => {
608
+ window.removeEventListener("keydown", handleKeyDown);
609
+ };
610
+ }, [
611
+ hasEnabledKeyboardShortcuts,
612
+ wb,
613
+ runner,
614
+ defaultContextMenuHandlers,
615
+ selectionContextMenuHandlers,
616
+ nodeContextMenuHandlers,
617
+ rfInstanceRef,
618
+ showToast,
619
+ isShortcutEnabled,
620
+ ]);
621
+ // Get custom renderers from UI extension registry (reactive to uiVersion changes)
622
+ const { BackgroundRenderer, MinimapRenderer, ControlsRenderer, DefaultContextMenuRenderer, NodeContextMenuRenderer, SelectionContextMenuRenderer, connectionLineRenderer, } = useMemo(() => {
623
+ return {
624
+ BackgroundRenderer: ui.getBackgroundRenderer(),
625
+ MinimapRenderer: ui.getMinimapRenderer(),
626
+ ControlsRenderer: ui.getControlsRenderer(),
627
+ DefaultContextMenuRenderer: ui.getDefaultContextMenuRenderer(),
628
+ NodeContextMenuRenderer: ui.getNodeContextMenuRenderer(),
629
+ SelectionContextMenuRenderer: ui.getSelectionContextMenuRenderer(),
630
+ connectionLineRenderer: ui.getConnectionLineRenderer(),
631
+ };
632
+ }, [ui, uiVersion]);
633
+ const onMoveEnd = useCallback(() => {
634
+ if (rfInstanceRef.current) {
635
+ const viewport = rfInstanceRef.current.getViewport();
636
+ const viewportData = lod.clone(viewport);
637
+ wb.setViewport(viewportData);
638
+ }
639
+ }, [wb]);
640
+ // Sync viewport when workbench fires graphUiChanged with viewport event
641
+ useEffect(() => {
642
+ const off = wb.on("graphUiChanged", (event) => {
643
+ if (event.change?.type === "viewport" && rfInstanceRef.current && event.init) {
644
+ const viewport = wb.getViewport();
645
+ if (viewport) {
646
+ rfInstanceRef.current.setViewport(lod.clone(viewport));
647
+ }
648
+ }
649
+ });
650
+ return () => off();
651
+ }, [wb]);
652
+ const { onInit: userOnInit, ...restReactFlowProps } = reactFlowProps || {};
653
+ return (_jsxs("div", { ref: containerRef, className: "w-full h-full relative overflow-hidden", tabIndex: 0, onContextMenu: onContextMenu, children: [_jsxs(ReactFlowProvider, { children: [_jsxs(ReactFlow, { ...restReactFlowProps, nodes: rfData.nodes, edges: rfData.edges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, connectionLineComponent: connectionLineRenderer, selectionOnDrag: true, onInit: (inst) => {
654
+ rfInstanceRef.current = inst;
655
+ const savedViewport = wb.getViewport();
656
+ if (savedViewport) {
657
+ inst.setViewport(lod.clone(savedViewport));
658
+ }
659
+ if (userOnInit) {
660
+ userOnInit(inst);
661
+ }
662
+ }, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onMoveEnd: onMoveEnd, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", children: [children, BackgroundRenderer ? (_jsx(BackgroundRenderer, {})) : (_jsx(Background, { id: "workbench-canvas-background", variant: BackgroundVariant.Dots, gap: 12, size: 1 })), MinimapRenderer ? _jsx(MinimapRenderer, {}) : _jsx(MiniMap, {}), ControlsRenderer ? _jsx(ControlsRenderer, {}) : _jsx(Controls, {}), menuState?.type === "default" &&
663
+ (DefaultContextMenuRenderer ? (_jsx(DefaultContextMenuRenderer, { open: true, clientPos: menuState.menuPos, handlers: defaultContextMenuHandlers, registry: wb.registry, nodeIds: nodeIds, keyboardShortcuts: keyboardShortcuts })) : (_jsx(DefaultContextMenu, { open: true, clientPos: menuState.menuPos, handlers: defaultContextMenuHandlers, registry: wb.registry, nodeIds: nodeIds, keyboardShortcuts: keyboardShortcuts }))), menuState?.type === "node" &&
664
+ nodeContextMenuHandlers &&
665
+ (NodeContextMenuRenderer ? (_jsx(NodeContextMenuRenderer, { open: true, clientPos: menuState.menuPos, nodeId: menuState.nodeId, handlers: nodeContextMenuHandlers, bakeableOutputs: bakeableOutputs, runMode: runMode, wb: wb, keyboardShortcuts: keyboardShortcuts })) : (_jsx(NodeContextMenu, { open: true, clientPos: menuState.menuPos, nodeId: menuState.nodeId, handlers: nodeContextMenuHandlers, bakeableOutputs: bakeableOutputs, runMode: runMode }))), menuState?.type === "selection" &&
666
+ (SelectionContextMenuRenderer ? (_jsx(SelectionContextMenuRenderer, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, keyboardShortcuts: keyboardShortcuts })) : (_jsx(SelectionContextMenu, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, keyboardShortcuts: keyboardShortcuts })))] }), _jsx(SelectionActiveSync, { selection: selection })] }), toast && _jsx(KeyboardShortcutToast, { message: toast.message, onClose: hideToast }, toast.id)] }));
667
+ });
668
+ export const WorkbenchCanvas = WorkbenchCanvasComponent;
669
+ //# sourceMappingURL=WorkbenchCanvas.js.map