@particle-academy/fancy-flow 0.19.0 → 0.21.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.
package/dist/index.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  var ReactExports = require('react');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
- require('react-dom');
5
+ var reactDom = require('react-dom');
6
6
 
7
7
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
8
 
@@ -1388,9 +1388,9 @@ function creator_default(name) {
1388
1388
  // node_modules/d3-selection/src/selector.js
1389
1389
  function none() {
1390
1390
  }
1391
- function selector_default(selector) {
1392
- return selector == null ? none : function() {
1393
- return this.querySelector(selector);
1391
+ function selector_default(selector2) {
1392
+ return selector2 == null ? none : function() {
1393
+ return this.querySelector(selector2);
1394
1394
  };
1395
1395
  }
1396
1396
 
@@ -1417,9 +1417,9 @@ function array(x) {
1417
1417
  function empty() {
1418
1418
  return [];
1419
1419
  }
1420
- function selectorAll_default(selector) {
1421
- return selector == null ? empty : function() {
1422
- return this.querySelectorAll(selector);
1420
+ function selectorAll_default(selector2) {
1421
+ return selector2 == null ? empty : function() {
1422
+ return this.querySelectorAll(selector2);
1423
1423
  };
1424
1424
  }
1425
1425
 
@@ -1444,14 +1444,14 @@ function selectAll_default(select) {
1444
1444
  }
1445
1445
 
1446
1446
  // node_modules/d3-selection/src/matcher.js
1447
- function matcher_default(selector) {
1447
+ function matcher_default(selector2) {
1448
1448
  return function() {
1449
- return this.matches(selector);
1449
+ return this.matches(selector2);
1450
1450
  };
1451
1451
  }
1452
- function childMatcher(selector) {
1452
+ function childMatcher(selector2) {
1453
1453
  return function(node) {
1454
- return node.matches(selector);
1454
+ return node.matches(selector2);
1455
1455
  };
1456
1456
  }
1457
1457
 
@@ -1520,11 +1520,11 @@ EnterNode.prototype = {
1520
1520
  insertBefore: function(child, next) {
1521
1521
  return this._parent.insertBefore(child, next);
1522
1522
  },
1523
- querySelector: function(selector) {
1524
- return this._parent.querySelector(selector);
1523
+ querySelector: function(selector2) {
1524
+ return this._parent.querySelector(selector2);
1525
1525
  },
1526
- querySelectorAll: function(selector) {
1527
- return this._parent.querySelectorAll(selector);
1526
+ querySelectorAll: function(selector2) {
1527
+ return this._parent.querySelectorAll(selector2);
1528
1528
  }
1529
1529
  };
1530
1530
 
@@ -2135,8 +2135,8 @@ Selection.prototype = selection.prototype = {
2135
2135
  var selection_default = selection;
2136
2136
 
2137
2137
  // node_modules/d3-selection/src/select.js
2138
- function select_default2(selector) {
2139
- return typeof selector === "string" ? new Selection([[document.querySelector(selector)]], [document.documentElement]) : new Selection([[selector]], root);
2138
+ function select_default2(selector2) {
2139
+ return typeof selector2 === "string" ? new Selection([[document.querySelector(selector2)]], [document.documentElement]) : new Selection([[selector2]], root);
2140
2140
  }
2141
2141
 
2142
2142
  // node_modules/d3-selection/src/sourceEvent.js
@@ -4871,6 +4871,28 @@ var addEdge = (edgeParams, edges, options = {}) => {
4871
4871
  }
4872
4872
  return edges.concat(edge);
4873
4873
  };
4874
+ var reconnectEdge = (oldEdge, newConnection, edges, options = { shouldReplaceId: true }) => {
4875
+ const { id: oldEdgeId, ...rest } = oldEdge;
4876
+ if (!newConnection.source || !newConnection.target) {
4877
+ options.onError?.("006", errorMessages["error006"]());
4878
+ return edges;
4879
+ }
4880
+ const foundEdge = edges.find((e) => e.id === oldEdge.id);
4881
+ if (!foundEdge) {
4882
+ options.onError?.("007", errorMessages["error007"](oldEdgeId));
4883
+ return edges;
4884
+ }
4885
+ const edgeIdGenerator = options.getEdgeId || getEdgeId;
4886
+ const edge = {
4887
+ ...rest,
4888
+ id: options.shouldReplaceId ? edgeIdGenerator(newConnection) : oldEdgeId,
4889
+ source: newConnection.source,
4890
+ target: newConnection.target,
4891
+ sourceHandle: newConnection.sourceHandle,
4892
+ targetHandle: newConnection.targetHandle
4893
+ };
4894
+ return edges.filter((e) => e.id !== oldEdgeId).concat(edge);
4895
+ };
4874
4896
  function getStraightPath({ sourceX, sourceY, targetX, targetY }) {
4875
4897
  const [labelX, labelY, offsetX, offsetY] = getEdgeCenter({
4876
4898
  sourceX,
@@ -5128,6 +5150,40 @@ function createMarkerIds(edges, { id: id2, defaultColor, defaultMarkerStart, def
5128
5150
  return markers;
5129
5151
  }, []).sort((a, b) => a.id.localeCompare(b.id));
5130
5152
  }
5153
+ function getNodeToolbarTransform(nodeRect, viewport, position, offset, align) {
5154
+ let alignmentOffset = 0.5;
5155
+ if (align === "start") {
5156
+ alignmentOffset = 0;
5157
+ } else if (align === "end") {
5158
+ alignmentOffset = 1;
5159
+ }
5160
+ let pos = [
5161
+ (nodeRect.x + nodeRect.width * alignmentOffset) * viewport.zoom + viewport.x,
5162
+ nodeRect.y * viewport.zoom + viewport.y - offset
5163
+ ];
5164
+ let shift = [-100 * alignmentOffset, -100];
5165
+ switch (position) {
5166
+ case Position.Right:
5167
+ pos = [
5168
+ (nodeRect.x + nodeRect.width) * viewport.zoom + viewport.x + offset,
5169
+ (nodeRect.y + nodeRect.height * alignmentOffset) * viewport.zoom + viewport.y
5170
+ ];
5171
+ shift = [0, -100 * alignmentOffset];
5172
+ break;
5173
+ case Position.Bottom:
5174
+ pos[1] = (nodeRect.y + nodeRect.height) * viewport.zoom + viewport.y + offset;
5175
+ shift[1] = 0;
5176
+ break;
5177
+ case Position.Left:
5178
+ pos = [
5179
+ nodeRect.x * viewport.zoom + viewport.x - offset,
5180
+ (nodeRect.y + nodeRect.height * alignmentOffset) * viewport.zoom + viewport.y
5181
+ ];
5182
+ shift = [-100, -100 * alignmentOffset];
5183
+ break;
5184
+ }
5185
+ return `translate(${pos[0]}px, ${pos[1]}px) translate(${shift[0]}%, ${shift[1]}%)`;
5186
+ }
5131
5187
  var SELECTED_NODE_Z = 1e3;
5132
5188
  var ROOT_PARENT_Z_INCREMENT = 10;
5133
5189
  var defaultOptions = {
@@ -5495,10 +5551,10 @@ function isParentSelected(node, nodeLookup) {
5495
5551
  }
5496
5552
  return isParentSelected(parentNode, nodeLookup);
5497
5553
  }
5498
- function hasSelector(target, selector, domNode) {
5554
+ function hasSelector(target, selector2, domNode) {
5499
5555
  let current = target;
5500
5556
  do {
5501
- if (current?.matches?.(selector))
5557
+ if (current?.matches?.(selector2))
5502
5558
  return true;
5503
5559
  if (current === domNode)
5504
5560
  return false;
@@ -6456,6 +6512,8 @@ var ResizeControlVariant;
6456
6512
  ResizeControlVariant2["Line"] = "line";
6457
6513
  ResizeControlVariant2["Handle"] = "handle";
6458
6514
  })(ResizeControlVariant || (ResizeControlVariant = {}));
6515
+ var XY_RESIZER_HANDLE_POSITIONS = ["top-left", "top-right", "bottom-left", "bottom-right"];
6516
+ var XY_RESIZER_LINE_POSITIONS = ["top", "right", "bottom", "left"];
6459
6517
  function getResizeDirection({ width, prevWidth, height, prevHeight, affectsX, affectsY }) {
6460
6518
  const deltaWidth = width - prevWidth;
6461
6519
  const deltaHeight = height - prevHeight;
@@ -6812,7 +6870,7 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
6812
6870
  destroy
6813
6871
  };
6814
6872
  }
6815
- function useSyncExternalStoreWithSelector(subscribe, getSnapshot, getServerSnapshot, selector, isEqual) {
6873
+ function useSyncExternalStoreWithSelector(subscribe, getSnapshot, getServerSnapshot, selector2, isEqual) {
6816
6874
  const instRef = ReactExports.useRef(null);
6817
6875
  let inst;
6818
6876
  if (instRef.current === null) {
@@ -6829,7 +6887,7 @@ function useSyncExternalStoreWithSelector(subscribe, getSnapshot, getServerSnaps
6829
6887
  if (!hasMemo) {
6830
6888
  hasMemo = true;
6831
6889
  memoizedSnapshot = nextSnapshot;
6832
- const nextSelection2 = selector(nextSnapshot);
6890
+ const nextSelection2 = selector2(nextSnapshot);
6833
6891
  if (isEqual !== void 0 && inst.hasValue) {
6834
6892
  const currentSelection = inst.value;
6835
6893
  if (isEqual(currentSelection, nextSelection2)) {
@@ -6845,7 +6903,7 @@ function useSyncExternalStoreWithSelector(subscribe, getSnapshot, getServerSnaps
6845
6903
  if (Object.is(prevSnapshot, nextSnapshot)) {
6846
6904
  return prevSelection;
6847
6905
  }
6848
- const nextSelection = selector(nextSnapshot);
6906
+ const nextSelection = selector2(nextSnapshot);
6849
6907
  if (isEqual !== void 0 && isEqual(prevSelection, nextSelection)) {
6850
6908
  memoizedSnapshot = nextSnapshot;
6851
6909
  return prevSelection;
@@ -6858,7 +6916,7 @@ function useSyncExternalStoreWithSelector(subscribe, getSnapshot, getServerSnaps
6858
6916
  const getSnapshotWithSelector = () => memoizedSelector(getSnapshot());
6859
6917
  const getServerSnapshotWithSelector = maybeGetServerSnapshot === null ? void 0 : () => memoizedSelector(maybeGetServerSnapshot());
6860
6918
  return [getSnapshotWithSelector, getServerSnapshotWithSelector];
6861
- }, [getSnapshot, getServerSnapshot, selector, isEqual]);
6919
+ }, [getSnapshot, getServerSnapshot, selector2, isEqual]);
6862
6920
  const value = ReactExports.useSyncExternalStore(subscribe, getSelection, getServerSelection);
6863
6921
  ReactExports.useEffect(() => {
6864
6922
  inst.hasValue = true;
@@ -6905,12 +6963,12 @@ var createStore = (createState) => createState ? createStoreImpl(createState) :
6905
6963
  var { useDebugValue: useDebugValue2 } = ReactExports__default.default;
6906
6964
  var { useSyncExternalStoreWithSelector: useSyncExternalStoreWithSelector2 } = use_sync_external_store_with_selector_default;
6907
6965
  var identity3 = (arg) => arg;
6908
- function useStoreWithEqualityFn(api, selector = identity3, equalityFn) {
6966
+ function useStoreWithEqualityFn(api, selector2 = identity3, equalityFn) {
6909
6967
  const slice = useSyncExternalStoreWithSelector2(
6910
6968
  api.subscribe,
6911
6969
  api.getState,
6912
6970
  api.getServerState || api.getInitialState,
6913
- selector,
6971
+ selector2,
6914
6972
  equalityFn
6915
6973
  );
6916
6974
  useDebugValue2(slice);
@@ -6918,7 +6976,7 @@ function useStoreWithEqualityFn(api, selector = identity3, equalityFn) {
6918
6976
  }
6919
6977
  var createWithEqualityFnImpl = (createState, defaultEqualityFn) => {
6920
6978
  const api = createStore(createState);
6921
- const useBoundStoreWithEqualityFn = (selector, equalityFn = defaultEqualityFn) => useStoreWithEqualityFn(api, selector, equalityFn);
6979
+ const useBoundStoreWithEqualityFn = (selector2, equalityFn = defaultEqualityFn) => useStoreWithEqualityFn(api, selector2, equalityFn);
6922
6980
  Object.assign(useBoundStoreWithEqualityFn, api);
6923
6981
  return useBoundStoreWithEqualityFn;
6924
6982
  };
@@ -6964,12 +7022,12 @@ function shallow$1(objA, objB) {
6964
7022
  var StoreContext = ReactExports.createContext(null);
6965
7023
  var Provider$1 = StoreContext.Provider;
6966
7024
  var zustandErrorMessage = errorMessages["error001"]("react");
6967
- function useStore(selector, equalityFn) {
7025
+ function useStore(selector2, equalityFn) {
6968
7026
  const store = ReactExports.useContext(StoreContext);
6969
7027
  if (store === null) {
6970
7028
  throw new Error(zustandErrorMessage);
6971
7029
  }
6972
- return useStoreWithEqualityFn(store, selector, equalityFn);
7030
+ return useStoreWithEqualityFn(store, selector2, equalityFn);
6973
7031
  }
6974
7032
  function useStoreApi() {
6975
7033
  const store = ReactExports.useContext(StoreContext);
@@ -7509,6 +7567,12 @@ function addEdge2(edgeParams, edges, options = {}) {
7509
7567
  onError: options.onError ?? defaultOnError
7510
7568
  });
7511
7569
  }
7570
+ function reconnectEdge2(oldEdge, newConnection, edges, options = { shouldReplaceId: true }) {
7571
+ return reconnectEdge(oldEdge, newConnection, edges, {
7572
+ ...options,
7573
+ onError: options.onError ?? defaultOnError
7574
+ });
7575
+ }
7512
7576
  var isNode = (element) => isNodeBase(element);
7513
7577
  var isEdge = (element) => isEdgeBase(element);
7514
7578
  function fixedForwardRef(render) {
@@ -10161,7 +10225,69 @@ function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle
10161
10225
  ...color2 && { [isHandleControl ? "backgroundColor" : "borderColor"]: color2 }
10162
10226
  }, children: children2 });
10163
10227
  }
10164
- ReactExports.memo(ResizeControl);
10228
+ var NodeResizeControl = ReactExports.memo(ResizeControl);
10229
+ function NodeResizer({ nodeId, isVisible = true, handleClassName, handleStyle, lineClassName, lineStyle, color: color2, minWidth = 10, minHeight = 10, maxWidth = Number.MAX_VALUE, maxHeight = Number.MAX_VALUE, keepAspectRatio = false, autoScale = true, shouldResize, onResizeStart, onResize, onResizeEnd }) {
10230
+ if (!isVisible) {
10231
+ return null;
10232
+ }
10233
+ return jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [XY_RESIZER_LINE_POSITIONS.map((position) => jsxRuntime.jsx(NodeResizeControl, { className: lineClassName, style: lineStyle, nodeId, position, variant: ResizeControlVariant.Line, color: color2, minWidth, minHeight, maxWidth, maxHeight, onResizeStart, keepAspectRatio, autoScale, shouldResize, onResize, onResizeEnd }, position)), XY_RESIZER_HANDLE_POSITIONS.map((position) => jsxRuntime.jsx(NodeResizeControl, { className: handleClassName, style: handleStyle, nodeId, position, color: color2, minWidth, minHeight, maxWidth, maxHeight, onResizeStart, keepAspectRatio, autoScale, shouldResize, onResize, onResizeEnd }, position))] });
10234
+ }
10235
+ var selector = (state) => state.domNode?.querySelector(".react-flow__renderer");
10236
+ function NodeToolbarPortal({ children: children2 }) {
10237
+ const wrapperRef = useStore(selector);
10238
+ if (!wrapperRef) {
10239
+ return null;
10240
+ }
10241
+ return reactDom.createPortal(children2, wrapperRef);
10242
+ }
10243
+ var nodeEqualityFn = (a, b) => a?.internals.positionAbsolute.x !== b?.internals.positionAbsolute.x || a?.internals.positionAbsolute.y !== b?.internals.positionAbsolute.y || a?.measured.width !== b?.measured.width || a?.measured.height !== b?.measured.height || a?.selected !== b?.selected || a?.internals.z !== b?.internals.z;
10244
+ var nodesEqualityFn = (a, b) => {
10245
+ if (a.size !== b.size) {
10246
+ return false;
10247
+ }
10248
+ for (const [key, node] of a) {
10249
+ if (nodeEqualityFn(node, b.get(key))) {
10250
+ return false;
10251
+ }
10252
+ }
10253
+ return true;
10254
+ };
10255
+ var storeSelector = (state) => ({
10256
+ x: state.transform[0],
10257
+ y: state.transform[1],
10258
+ zoom: state.transform[2],
10259
+ selectedNodesCount: state.nodes.filter((node) => node.selected).length
10260
+ });
10261
+ function NodeToolbar({ nodeId, children: children2, className, style: style2, isVisible, position = Position.Top, offset = 10, align = "center", ...rest }) {
10262
+ const contextNodeId = useNodeId();
10263
+ const nodesSelector = ReactExports.useCallback((state) => {
10264
+ const nodeIds = Array.isArray(nodeId) ? nodeId : [nodeId || contextNodeId || ""];
10265
+ const internalNodes = nodeIds.reduce((res, id2) => {
10266
+ const node = state.nodeLookup.get(id2);
10267
+ if (node) {
10268
+ res.set(node.id, node);
10269
+ }
10270
+ return res;
10271
+ }, /* @__PURE__ */ new Map());
10272
+ return internalNodes;
10273
+ }, [nodeId, contextNodeId]);
10274
+ const nodes = useStore(nodesSelector, nodesEqualityFn);
10275
+ const { x, y, zoom, selectedNodesCount } = useStore(storeSelector, shallow$1);
10276
+ const isActive = typeof isVisible === "boolean" ? isVisible : nodes.size === 1 && nodes.values().next().value?.selected && selectedNodesCount === 1;
10277
+ if (!isActive || !nodes.size) {
10278
+ return null;
10279
+ }
10280
+ const nodeRect = getInternalNodesBounds(nodes);
10281
+ const nodesArray = Array.from(nodes.values());
10282
+ const zIndex = Math.max(...nodesArray.map((node) => node.internals.z + 1));
10283
+ const wrapperStyle2 = {
10284
+ position: "absolute",
10285
+ transform: getNodeToolbarTransform(nodeRect, { x, y, zoom }, position, offset, align),
10286
+ zIndex,
10287
+ ...style2
10288
+ };
10289
+ return jsxRuntime.jsx(NodeToolbarPortal, { children: jsxRuntime.jsx("div", { style: wrapperStyle2, className: cc(["react-flow__node-toolbar", className]), ...rest, "data-id": nodesArray.reduce((acc, node) => `${acc}${node.id} `, "").trim(), children: children2 }) });
10290
+ }
10165
10291
  function NodeShellInner({
10166
10292
  node,
10167
10293
  accent,
@@ -11219,6 +11345,7 @@ function toSchemaNode(n) {
11219
11345
  const data = n.data ?? {};
11220
11346
  const kindName = data.kind ?? n.type ?? "custom";
11221
11347
  const ports = resolveNodePorts(n, getNodeKind(kindName) ?? void 0);
11348
+ const node = n;
11222
11349
  return {
11223
11350
  id: n.id,
11224
11351
  kind: kindName,
@@ -11227,7 +11354,14 @@ function toSchemaNode(n) {
11227
11354
  description: data.description,
11228
11355
  config: data.config,
11229
11356
  inputs: ports.inputs,
11230
- outputs: ports.outputs
11357
+ outputs: ports.outputs,
11358
+ // Visual layout — only when explicitly set (never persist auto-`measured`
11359
+ // dimensions, which are derived, not authored).
11360
+ ...node.parentId ? { parentId: node.parentId } : {},
11361
+ ...node.extent ? { extent: node.extent } : {},
11362
+ ...typeof node.width === "number" ? { width: node.width } : {},
11363
+ ...typeof node.height === "number" ? { height: node.height } : {},
11364
+ ...node.style ? { style: node.style } : {}
11231
11365
  };
11232
11366
  }
11233
11367
  function toSchemaEdge(e) {
@@ -11240,9 +11374,13 @@ function toSchemaEdge(e) {
11240
11374
  label: typeof e.label === "string" ? e.label : void 0
11241
11375
  };
11242
11376
  }
11377
+ function migrateSchema(schema) {
11378
+ return schema;
11379
+ }
11243
11380
  function importWorkflow(schema, options = {}) {
11244
11381
  const issues = [];
11245
11382
  const lenient = options.lenient === true;
11383
+ schema = migrateSchema(schema);
11246
11384
  if (!schema || typeof schema !== "object") {
11247
11385
  return { ok: false, graph: { nodes: [], edges: [] }, issues: [{ level: "error", message: "Schema is not an object." }] };
11248
11386
  }
@@ -11276,6 +11414,12 @@ function importWorkflow(schema, options = {}) {
11276
11414
  id: n.id,
11277
11415
  type: kindId,
11278
11416
  position: { x: n.position?.x ?? 0, y: n.position?.y ?? 0 },
11417
+ // Rehydrate visual layout onto the node's top level (where xyflow reads it).
11418
+ ...n.parentId ? { parentId: n.parentId } : {},
11419
+ ...n.extent ? { extent: n.extent } : {},
11420
+ ...typeof n.width === "number" ? { width: n.width } : {},
11421
+ ...typeof n.height === "number" ? { height: n.height } : {},
11422
+ ...n.style ? { style: n.style } : {},
11279
11423
  data: {
11280
11424
  kind: kindId,
11281
11425
  label: n.label ?? kind?.label ?? n.kind,
@@ -11380,10 +11524,20 @@ function RegistryNodeInner(props) {
11380
11524
  "ff-node",
11381
11525
  `ff-node--status-${status}`,
11382
11526
  `ff-node--cat-${kind.category}`,
11383
- props.selected ? "ff-node--selected" : ""
11527
+ props.selected ? "ff-node--selected" : "",
11528
+ kind.resizable ? "ff-node--resizable" : ""
11384
11529
  ].filter(Boolean).join(" "),
11385
11530
  style: { borderColor: props.selected ? accent : void 0 },
11386
11531
  children: [
11532
+ kind.resizable && /* @__PURE__ */ jsxRuntime.jsx(
11533
+ NodeResizer,
11534
+ {
11535
+ isVisible: props.selected ?? false,
11536
+ color: accent,
11537
+ ...typeof kind.resizable === "object" ? kind.resizable : {}
11538
+ }
11539
+ ),
11540
+ kind.toolbar && /* @__PURE__ */ jsxRuntime.jsx(NodeToolbar, { isVisible: props.selected ?? false, children: kind.toolbar({ nodeId: props.id, config, selected: props.selected ?? false }) }),
11387
11541
  /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "ff-node__header", style: { background: accent }, children: [
11388
11542
  kind.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-node__icon", "aria-hidden": true, children: kind.icon }),
11389
11543
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-node__tag", children: kind.label.toUpperCase() }),
@@ -11523,6 +11677,65 @@ function duplicateNode(node, id2, offset = 40) {
11523
11677
  data: JSON.parse(JSON.stringify(node.data ?? {}))
11524
11678
  };
11525
11679
  }
11680
+ function cloneSubgraph(nodes, edges, opts) {
11681
+ const offset = opts.offset ?? 40;
11682
+ const idMap = /* @__PURE__ */ new Map();
11683
+ for (const n of nodes) idMap.set(n.id, opts.makeId());
11684
+ const clonedNodes = nodes.map((n) => {
11685
+ const cloned = duplicateNode(n, idMap.get(n.id), offset);
11686
+ const parentId = n.parentId;
11687
+ if (parentId && idMap.has(parentId)) cloned.parentId = idMap.get(parentId);
11688
+ else if (parentId) delete cloned.parentId;
11689
+ return cloned;
11690
+ });
11691
+ const clonedEdges = edges.filter((e) => idMap.has(e.source) && idMap.has(e.target)).map((e) => ({ ...e, id: opts.makeId(), source: idMap.get(e.source), target: idMap.get(e.target) }));
11692
+ return { nodes: clonedNodes, edges: clonedEdges, idMap };
11693
+ }
11694
+ function reconnectEdge3(edges, oldEdge, newConnection) {
11695
+ return reconnectEdge2(oldEdge, newConnection, edges, { shouldReplaceId: false });
11696
+ }
11697
+ var nodeW = (n) => n.width ?? n.measured?.width ?? 0;
11698
+ var nodeH = (n) => n.height ?? n.measured?.height ?? 0;
11699
+ function alignNodes(nodes, edge) {
11700
+ if (nodes.length < 2) return nodes;
11701
+ const minL = Math.min(...nodes.map((n) => n.position.x));
11702
+ const maxR = Math.max(...nodes.map((n) => n.position.x + nodeW(n)));
11703
+ const minT = Math.min(...nodes.map((n) => n.position.y));
11704
+ const maxB = Math.max(...nodes.map((n) => n.position.y + nodeH(n)));
11705
+ const cx = (minL + maxR) / 2;
11706
+ const cy = (minT + maxB) / 2;
11707
+ return nodes.map((n) => {
11708
+ let { x, y } = n.position;
11709
+ if (edge === "left") x = minL;
11710
+ else if (edge === "right") x = maxR - nodeW(n);
11711
+ else if (edge === "hcenter") x = cx - nodeW(n) / 2;
11712
+ else if (edge === "top") y = minT;
11713
+ else if (edge === "bottom") y = maxB - nodeH(n);
11714
+ else if (edge === "vcenter") y = cy - nodeH(n) / 2;
11715
+ return { ...n, position: { x, y } };
11716
+ });
11717
+ }
11718
+ function distributeNodes(nodes, axis) {
11719
+ if (nodes.length < 3) return nodes;
11720
+ const size = (n) => axis === "h" ? nodeW(n) : nodeH(n);
11721
+ const coord = (n) => axis === "h" ? n.position.x : n.position.y;
11722
+ const sorted = [...nodes].sort((a, b) => coord(a) - coord(b));
11723
+ const start2 = coord(sorted[0]);
11724
+ const last = sorted[sorted.length - 1];
11725
+ const end = coord(last) + size(last);
11726
+ const totalSize = sorted.reduce((s, n) => s + size(n), 0);
11727
+ const gap = (end - start2 - totalSize) / (sorted.length - 1);
11728
+ const posById = /* @__PURE__ */ new Map();
11729
+ let cursor = start2;
11730
+ for (const n of sorted) {
11731
+ posById.set(n.id, cursor);
11732
+ cursor += size(n) + gap;
11733
+ }
11734
+ return nodes.map((n) => {
11735
+ const p = posById.get(n.id);
11736
+ return { ...n, position: axis === "h" ? { ...n.position, x: p } : { ...n.position, y: p } };
11737
+ });
11738
+ }
11526
11739
  var FlowEditorContext = ReactExports.createContext(null);
11527
11740
  var FlowEditorProvider = FlowEditorContext.Provider;
11528
11741
  function useFlowEditor() {
@@ -11692,6 +11905,89 @@ function FlowEditorInner({
11692
11905
  },
11693
11906
  [flow, onEdgeDelete, confirmDelete]
11694
11907
  );
11908
+ const selectedNodes = ReactExports.useMemo(() => flow.nodes.filter((n) => n.selected), [flow.nodes]);
11909
+ const selectedIds = ReactExports.useMemo(() => selectedNodes.map((n) => n.id), [selectedNodes]);
11910
+ const clipboard = ReactExports.useRef(null);
11911
+ const copySelection = ReactExports.useCallback(() => {
11912
+ const sel = flow.nodes.filter((n) => n.selected);
11913
+ if (sel.length === 0) return;
11914
+ const ids = new Set(sel.map((n) => n.id));
11915
+ const edges = flow.edges.filter((e) => ids.has(e.source) && ids.has(e.target));
11916
+ clipboard.current = { nodes: sel.map((n) => ({ ...n })), edges: edges.map((e) => ({ ...e })) };
11917
+ }, [flow]);
11918
+ const pasteClipboard = ReactExports.useCallback(
11919
+ (at) => {
11920
+ const clip = clipboard.current;
11921
+ if (!clip || clip.nodes.length === 0) return;
11922
+ const { nodes: clones, edges: cloneEdges } = cloneSubgraph(clip.nodes, clip.edges, { makeId: newNodeId, offset: 40 });
11923
+ let placed = clones;
11924
+ if (at) {
11925
+ const minX = Math.min(...clones.map((n) => n.position.x));
11926
+ const minY = Math.min(...clones.map((n) => n.position.y));
11927
+ placed = clones.map((n) => ({ ...n, position: { x: n.position.x - minX + at.x, y: n.position.y - minY + at.y } }));
11928
+ }
11929
+ flow.setGraph({
11930
+ nodes: [...flow.nodes.map((n) => ({ ...n, selected: false })), ...placed.map((n) => ({ ...n, selected: true }))],
11931
+ edges: [...flow.edges, ...cloneEdges]
11932
+ });
11933
+ },
11934
+ [flow]
11935
+ );
11936
+ const duplicateSelected = ReactExports.useCallback(() => {
11937
+ const sel = flow.nodes.filter((n) => n.selected);
11938
+ if (sel.length === 0) return;
11939
+ const ids = new Set(sel.map((n) => n.id));
11940
+ const internal2 = flow.edges.filter((e) => ids.has(e.source) && ids.has(e.target));
11941
+ const { nodes: clones, edges: cloneEdges } = cloneSubgraph(sel, internal2, { makeId: newNodeId, offset: 40 });
11942
+ flow.setGraph({
11943
+ nodes: [...flow.nodes.map((n) => ({ ...n, selected: false })), ...clones.map((n) => ({ ...n, selected: true }))],
11944
+ edges: [...flow.edges, ...cloneEdges]
11945
+ });
11946
+ }, [flow]);
11947
+ const alignSelected = ReactExports.useCallback(
11948
+ (edge) => {
11949
+ const sel = flow.nodes.filter((n) => n.selected);
11950
+ if (sel.length < 2) return;
11951
+ const byId = new Map(alignNodes(sel, edge).map((n) => [n.id, n]));
11952
+ flow.setNodes((all) => all.map((n) => byId.get(n.id) ?? n));
11953
+ },
11954
+ [flow]
11955
+ );
11956
+ const distributeSelected = ReactExports.useCallback(
11957
+ (axis) => {
11958
+ const sel = flow.nodes.filter((n) => n.selected);
11959
+ if (sel.length < 3) return;
11960
+ const byId = new Map(distributeNodes(sel, axis).map((n) => [n.id, n]));
11961
+ flow.setNodes((all) => all.map((n) => byId.get(n.id) ?? n));
11962
+ },
11963
+ [flow]
11964
+ );
11965
+ const onReconnect = ReactExports.useCallback(
11966
+ (oldEdge, conn) => flow.setEdges((eds) => reconnectEdge3(eds, oldEdge, conn)),
11967
+ [flow]
11968
+ );
11969
+ ReactExports.useEffect(() => {
11970
+ const onKey = (e) => {
11971
+ const t = e.target;
11972
+ if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable)) return;
11973
+ if (!(e.ctrlKey || e.metaKey)) return;
11974
+ const key = e.key.toLowerCase();
11975
+ if (key === "c") copySelection();
11976
+ else if (key === "x") {
11977
+ e.preventDefault();
11978
+ copySelection();
11979
+ deleteNodes(selectedIds);
11980
+ } else if (key === "v") {
11981
+ e.preventDefault();
11982
+ pasteClipboard();
11983
+ } else if (key === "d") {
11984
+ e.preventDefault();
11985
+ duplicateSelected();
11986
+ }
11987
+ };
11988
+ window.addEventListener("keydown", onKey);
11989
+ return () => window.removeEventListener("keydown", onKey);
11990
+ }, [copySelection, pasteClipboard, duplicateSelected, deleteNodes, selectedIds]);
11695
11991
  const api = ReactExports.useMemo(() => {
11696
11992
  const toWorkflow = () => exportWorkflow({ nodes: flow.nodes, edges: flow.edges }, metadata);
11697
11993
  return {
@@ -11702,6 +11998,8 @@ function FlowEditorInner({
11702
11998
  selected: selected2,
11703
11999
  selectedEdgeId,
11704
12000
  selectedEdge,
12001
+ selectedIds,
12002
+ selectedNodes,
11705
12003
  running: runner.running,
11706
12004
  statuses: runner.statuses,
11707
12005
  select: setSelectedId,
@@ -11722,6 +12020,15 @@ function FlowEditorInner({
11722
12020
  return copy.id;
11723
12021
  },
11724
12022
  setGraph: (graph) => flow.setGraph(graph),
12023
+ duplicateSelected,
12024
+ alignSelected,
12025
+ distributeSelected,
12026
+ copy: copySelection,
12027
+ cut: () => {
12028
+ copySelection();
12029
+ deleteNodes(selectedIds);
12030
+ },
12031
+ paste: pasteClipboard,
11725
12032
  run: () => runner.run({ nodes: flow.nodes, edges: flow.edges }, executors),
11726
12033
  cancel: runner.cancel,
11727
12034
  reset: runner.reset,
@@ -11734,7 +12041,7 @@ function FlowEditorInner({
11734
12041
  canUndo: hist.canUndo,
11735
12042
  canRedo: hist.canRedo
11736
12043
  };
11737
- }, [flow, selectedId, selected2, selectedEdgeId, selectedEdge, runner, executors, metadata, addNode, deleteNodes, deleteEdges, hist, rf]);
12044
+ }, [flow, selectedId, selected2, selectedEdgeId, selectedEdge, selectedIds, selectedNodes, runner, executors, metadata, addNode, deleteNodes, deleteEdges, duplicateSelected, alignSelected, distributeSelected, copySelection, pasteClipboard, hist, rf]);
11738
12045
  ReactExports.useImperativeHandle(apiRef, () => api, [api]);
11739
12046
  const dropHandlers = paletteDropHandlers((kindName, evt) => {
11740
12047
  const point = rf.screenToFlowPosition({ x: evt.clientX, y: evt.clientY });
@@ -11794,6 +12101,8 @@ function FlowEditorInner({
11794
12101
  onNodesChange: flow.onNodesChange,
11795
12102
  onEdgesChange: flow.onEdgesChange,
11796
12103
  onConnect: flow.onConnect,
12104
+ onReconnect,
12105
+ edgesReconnectable: true,
11797
12106
  onNodeDragStart: hist.onNodeDragStart,
11798
12107
  onNodeClick: handleNodeClick2,
11799
12108
  onNodeContextMenu: builtins.contextMenu === false ? void 0 : handleNodeContextMenu,
@@ -12083,9 +12392,11 @@ exports.SubgraphNode = SubgraphNode;
12083
12392
  exports.TriggerNode = TriggerNode;
12084
12393
  exports.WORKFLOW_SCHEMA_URL = WORKFLOW_SCHEMA_URL;
12085
12394
  exports.WORKFLOW_SCHEMA_VERSION = WORKFLOW_SCHEMA_VERSION;
12395
+ exports.alignNodes = alignNodes;
12086
12396
  exports.applyStatusesToNodes = applyStatusesToNodes;
12087
12397
  exports.buildNodeTypes = buildNodeTypes;
12088
12398
  exports.categoryAccent = categoryAccent;
12399
+ exports.cloneSubgraph = cloneSubgraph;
12089
12400
  exports.createConnectionValidator = createConnectionValidator;
12090
12401
  exports.createHistory = createHistory;
12091
12402
  exports.decodePause = decodePause;
@@ -12093,6 +12404,7 @@ exports.defaultConfigFor = defaultConfigFor;
12093
12404
  exports.defaultNodeTypes = defaultNodeTypes;
12094
12405
  exports.defaultPortCompatibility = defaultPortCompatibility;
12095
12406
  exports.defineNode = defineNode;
12407
+ exports.distributeNodes = distributeNodes;
12096
12408
  exports.encodePause = encodePause;
12097
12409
  exports.exportWorkflow = exportWorkflow;
12098
12410
  exports.getNodeKind = getNodeKind;
@@ -12101,10 +12413,12 @@ exports.importWorkflow = importWorkflow;
12101
12413
  exports.isPause = isPause;
12102
12414
  exports.isRichInputEnabled = isRichInputEnabled;
12103
12415
  exports.listNodeKinds = listNodeKinds;
12416
+ exports.migrateSchema = migrateSchema;
12104
12417
  exports.onNodeKindsChanged = onNodeKindsChanged;
12105
12418
  exports.onRichInputAdapterChanged = onRichInputAdapterChanged;
12106
12419
  exports.paletteDropHandlers = paletteDropHandlers;
12107
12420
  exports.pauseForHuman = pauseForHuman;
12421
+ exports.reconnectEdge = reconnectEdge3;
12108
12422
  exports.registerBuiltinKinds = registerBuiltinKinds;
12109
12423
  exports.registerNodeKind = registerNodeKind;
12110
12424
  exports.registerRichInputAdapter = registerRichInputAdapter;