@particle-academy/fancy-flow 0.18.0 → 0.20.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/{chunk-FSC64BPD.js → chunk-AW2QUEL2.js} +3 -3
- package/dist/{chunk-FSC64BPD.js.map → chunk-AW2QUEL2.js.map} +1 -1
- package/dist/{chunk-MFMRTRPO.js → chunk-KI4RI2N3.js} +115 -6
- package/dist/chunk-KI4RI2N3.js.map +1 -0
- package/dist/{chunk-NF6NPY5N.js → chunk-XETAGLH6.js} +31 -3
- package/dist/chunk-XETAGLH6.js.map +1 -0
- package/dist/index.cjs +379 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -3
- package/dist/index.d.ts +72 -3
- package/dist/index.js +242 -33
- package/dist/index.js.map +1 -1
- package/dist/registry.js +2 -2
- package/dist/runtime/index.d.cts +61 -1
- package/dist/runtime/index.d.ts +61 -1
- package/dist/runtime.cjs +112 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +2 -2
- package/package.json +6 -3
- package/dist/chunk-MFMRTRPO.js.map +0 -1
- package/dist/chunk-NF6NPY5N.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -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,
|
|
@@ -7509,6 +7531,12 @@ function addEdge2(edgeParams, edges, options = {}) {
|
|
|
7509
7531
|
onError: options.onError ?? defaultOnError
|
|
7510
7532
|
});
|
|
7511
7533
|
}
|
|
7534
|
+
function reconnectEdge2(oldEdge, newConnection, edges, options = { shouldReplaceId: true }) {
|
|
7535
|
+
return reconnectEdge(oldEdge, newConnection, edges, {
|
|
7536
|
+
...options,
|
|
7537
|
+
onError: options.onError ?? defaultOnError
|
|
7538
|
+
});
|
|
7539
|
+
}
|
|
7512
7540
|
var isNode = (element) => isNodeBase(element);
|
|
7513
7541
|
var isEdge = (element) => isEdgeBase(element);
|
|
7514
7542
|
function fixedForwardRef(render) {
|
|
@@ -10996,8 +11024,117 @@ function useFlowState(initial) {
|
|
|
10996
11024
|
const onConnect = ReactExports.useCallback((connection) => {
|
|
10997
11025
|
setEdges((es) => addEdge2(connection, es));
|
|
10998
11026
|
}, []);
|
|
11027
|
+
const setGraph = ReactExports.useCallback((graph) => {
|
|
11028
|
+
setNodes(graph.nodes);
|
|
11029
|
+
setEdges(graph.edges);
|
|
11030
|
+
}, []);
|
|
10999
11031
|
const toGraph = ReactExports.useCallback(() => ({ nodes, edges }), [nodes, edges]);
|
|
11000
|
-
return { nodes, edges, setNodes, setEdges, onNodesChange, onEdgesChange, onConnect, toGraph };
|
|
11032
|
+
return { nodes, edges, setNodes, setEdges, setGraph, onNodesChange, onEdgesChange, onConnect, toGraph };
|
|
11033
|
+
}
|
|
11034
|
+
|
|
11035
|
+
// src/runtime/history.ts
|
|
11036
|
+
function createHistory(limit = 100) {
|
|
11037
|
+
let past = [];
|
|
11038
|
+
let future = [];
|
|
11039
|
+
return {
|
|
11040
|
+
push(graph) {
|
|
11041
|
+
past.push(graph);
|
|
11042
|
+
if (past.length > limit) past.shift();
|
|
11043
|
+
future = [];
|
|
11044
|
+
},
|
|
11045
|
+
undo(current) {
|
|
11046
|
+
const prev = past.pop();
|
|
11047
|
+
if (prev === void 0) return null;
|
|
11048
|
+
future.push(current);
|
|
11049
|
+
return prev;
|
|
11050
|
+
},
|
|
11051
|
+
redo(current) {
|
|
11052
|
+
const next = future.pop();
|
|
11053
|
+
if (next === void 0) return null;
|
|
11054
|
+
past.push(current);
|
|
11055
|
+
return next;
|
|
11056
|
+
},
|
|
11057
|
+
canUndo: () => past.length > 0,
|
|
11058
|
+
canRedo: () => future.length > 0,
|
|
11059
|
+
clear() {
|
|
11060
|
+
past = [];
|
|
11061
|
+
future = [];
|
|
11062
|
+
},
|
|
11063
|
+
size: () => ({ past: past.length, future: future.length })
|
|
11064
|
+
};
|
|
11065
|
+
}
|
|
11066
|
+
|
|
11067
|
+
// src/runtime/use-flow-history.ts
|
|
11068
|
+
function useFlowHistory(flow) {
|
|
11069
|
+
const history = ReactExports.useRef(createHistory()).current;
|
|
11070
|
+
const restoring = ReactExports.useRef(false);
|
|
11071
|
+
const coalesced = ReactExports.useRef(false);
|
|
11072
|
+
const [, bump] = ReactExports.useState(0);
|
|
11073
|
+
const rerender = ReactExports.useCallback(() => bump((n) => n + 1), []);
|
|
11074
|
+
const graphRef = ReactExports.useRef({ nodes: flow.nodes, edges: flow.edges });
|
|
11075
|
+
graphRef.current = { nodes: flow.nodes, edges: flow.edges };
|
|
11076
|
+
const capture = ReactExports.useCallback(() => {
|
|
11077
|
+
if (restoring.current || coalesced.current) return;
|
|
11078
|
+
coalesced.current = true;
|
|
11079
|
+
queueMicrotask(() => {
|
|
11080
|
+
coalesced.current = false;
|
|
11081
|
+
});
|
|
11082
|
+
history.push(graphRef.current);
|
|
11083
|
+
rerender();
|
|
11084
|
+
}, [history, rerender]);
|
|
11085
|
+
const restore = ReactExports.useCallback(
|
|
11086
|
+
(g) => {
|
|
11087
|
+
if (!g) return;
|
|
11088
|
+
restoring.current = true;
|
|
11089
|
+
flow.setGraph(g);
|
|
11090
|
+
queueMicrotask(() => {
|
|
11091
|
+
restoring.current = false;
|
|
11092
|
+
});
|
|
11093
|
+
rerender();
|
|
11094
|
+
},
|
|
11095
|
+
[flow, rerender]
|
|
11096
|
+
);
|
|
11097
|
+
const undo = ReactExports.useCallback(() => restore(history.undo(graphRef.current)), [history, restore]);
|
|
11098
|
+
const redo = ReactExports.useCallback(() => restore(history.redo(graphRef.current)), [history, restore]);
|
|
11099
|
+
const wrapped = ReactExports.useMemo(
|
|
11100
|
+
() => ({
|
|
11101
|
+
...flow,
|
|
11102
|
+
setNodes: (next) => {
|
|
11103
|
+
capture();
|
|
11104
|
+
flow.setNodes(next);
|
|
11105
|
+
},
|
|
11106
|
+
setEdges: (next) => {
|
|
11107
|
+
capture();
|
|
11108
|
+
flow.setEdges(next);
|
|
11109
|
+
},
|
|
11110
|
+
setGraph: (g) => {
|
|
11111
|
+
capture();
|
|
11112
|
+
flow.setGraph(g);
|
|
11113
|
+
},
|
|
11114
|
+
onNodesChange: (changes) => {
|
|
11115
|
+
if (!restoring.current && changes.some((c) => c.type === "remove")) capture();
|
|
11116
|
+
flow.onNodesChange(changes);
|
|
11117
|
+
},
|
|
11118
|
+
onEdgesChange: (changes) => {
|
|
11119
|
+
if (!restoring.current && changes.some((c) => c.type === "remove")) capture();
|
|
11120
|
+
flow.onEdgesChange(changes);
|
|
11121
|
+
},
|
|
11122
|
+
onConnect: (conn) => {
|
|
11123
|
+
capture();
|
|
11124
|
+
flow.onConnect(conn);
|
|
11125
|
+
}
|
|
11126
|
+
}),
|
|
11127
|
+
[flow, capture]
|
|
11128
|
+
);
|
|
11129
|
+
return {
|
|
11130
|
+
flow: wrapped,
|
|
11131
|
+
undo,
|
|
11132
|
+
redo,
|
|
11133
|
+
canUndo: history.canUndo(),
|
|
11134
|
+
canRedo: history.canRedo(),
|
|
11135
|
+
capture,
|
|
11136
|
+
onNodeDragStart: capture
|
|
11137
|
+
};
|
|
11001
11138
|
}
|
|
11002
11139
|
function useFlowRun({ maxFeed = 200 } = {}) {
|
|
11003
11140
|
const [statuses, setStatuses] = ReactExports.useState({});
|
|
@@ -11414,6 +11551,65 @@ function duplicateNode(node, id2, offset = 40) {
|
|
|
11414
11551
|
data: JSON.parse(JSON.stringify(node.data ?? {}))
|
|
11415
11552
|
};
|
|
11416
11553
|
}
|
|
11554
|
+
function cloneSubgraph(nodes, edges, opts) {
|
|
11555
|
+
const offset = opts.offset ?? 40;
|
|
11556
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
11557
|
+
for (const n of nodes) idMap.set(n.id, opts.makeId());
|
|
11558
|
+
const clonedNodes = nodes.map((n) => {
|
|
11559
|
+
const cloned = duplicateNode(n, idMap.get(n.id), offset);
|
|
11560
|
+
const parentId = n.parentId;
|
|
11561
|
+
if (parentId && idMap.has(parentId)) cloned.parentId = idMap.get(parentId);
|
|
11562
|
+
else if (parentId) delete cloned.parentId;
|
|
11563
|
+
return cloned;
|
|
11564
|
+
});
|
|
11565
|
+
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) }));
|
|
11566
|
+
return { nodes: clonedNodes, edges: clonedEdges, idMap };
|
|
11567
|
+
}
|
|
11568
|
+
function reconnectEdge3(edges, oldEdge, newConnection) {
|
|
11569
|
+
return reconnectEdge2(oldEdge, newConnection, edges, { shouldReplaceId: false });
|
|
11570
|
+
}
|
|
11571
|
+
var nodeW = (n) => n.width ?? n.measured?.width ?? 0;
|
|
11572
|
+
var nodeH = (n) => n.height ?? n.measured?.height ?? 0;
|
|
11573
|
+
function alignNodes(nodes, edge) {
|
|
11574
|
+
if (nodes.length < 2) return nodes;
|
|
11575
|
+
const minL = Math.min(...nodes.map((n) => n.position.x));
|
|
11576
|
+
const maxR = Math.max(...nodes.map((n) => n.position.x + nodeW(n)));
|
|
11577
|
+
const minT = Math.min(...nodes.map((n) => n.position.y));
|
|
11578
|
+
const maxB = Math.max(...nodes.map((n) => n.position.y + nodeH(n)));
|
|
11579
|
+
const cx = (minL + maxR) / 2;
|
|
11580
|
+
const cy = (minT + maxB) / 2;
|
|
11581
|
+
return nodes.map((n) => {
|
|
11582
|
+
let { x, y } = n.position;
|
|
11583
|
+
if (edge === "left") x = minL;
|
|
11584
|
+
else if (edge === "right") x = maxR - nodeW(n);
|
|
11585
|
+
else if (edge === "hcenter") x = cx - nodeW(n) / 2;
|
|
11586
|
+
else if (edge === "top") y = minT;
|
|
11587
|
+
else if (edge === "bottom") y = maxB - nodeH(n);
|
|
11588
|
+
else if (edge === "vcenter") y = cy - nodeH(n) / 2;
|
|
11589
|
+
return { ...n, position: { x, y } };
|
|
11590
|
+
});
|
|
11591
|
+
}
|
|
11592
|
+
function distributeNodes(nodes, axis) {
|
|
11593
|
+
if (nodes.length < 3) return nodes;
|
|
11594
|
+
const size = (n) => axis === "h" ? nodeW(n) : nodeH(n);
|
|
11595
|
+
const coord = (n) => axis === "h" ? n.position.x : n.position.y;
|
|
11596
|
+
const sorted = [...nodes].sort((a, b) => coord(a) - coord(b));
|
|
11597
|
+
const start2 = coord(sorted[0]);
|
|
11598
|
+
const last = sorted[sorted.length - 1];
|
|
11599
|
+
const end = coord(last) + size(last);
|
|
11600
|
+
const totalSize = sorted.reduce((s, n) => s + size(n), 0);
|
|
11601
|
+
const gap = (end - start2 - totalSize) / (sorted.length - 1);
|
|
11602
|
+
const posById = /* @__PURE__ */ new Map();
|
|
11603
|
+
let cursor = start2;
|
|
11604
|
+
for (const n of sorted) {
|
|
11605
|
+
posById.set(n.id, cursor);
|
|
11606
|
+
cursor += size(n) + gap;
|
|
11607
|
+
}
|
|
11608
|
+
return nodes.map((n) => {
|
|
11609
|
+
const p = posById.get(n.id);
|
|
11610
|
+
return { ...n, position: axis === "h" ? { ...n.position, x: p } : { ...n.position, y: p } };
|
|
11611
|
+
});
|
|
11612
|
+
}
|
|
11417
11613
|
var FlowEditorContext = ReactExports.createContext(null);
|
|
11418
11614
|
var FlowEditorProvider = FlowEditorContext.Provider;
|
|
11419
11615
|
function useFlowEditor() {
|
|
@@ -11453,13 +11649,16 @@ function FlowEditorInner({
|
|
|
11453
11649
|
onSelectionChange,
|
|
11454
11650
|
onDelete,
|
|
11455
11651
|
onEdgeDelete,
|
|
11652
|
+
confirmDelete,
|
|
11456
11653
|
apiRef
|
|
11457
11654
|
}) {
|
|
11458
11655
|
const internal = useFlowState(initial);
|
|
11459
11656
|
const runner = useFlowRun();
|
|
11460
11657
|
const rf = useReactFlow();
|
|
11461
11658
|
const controlled = value !== void 0;
|
|
11462
|
-
const
|
|
11659
|
+
const baseFlow = controlled ? makeControlledFlowAdapter(value, onChange) : internal;
|
|
11660
|
+
const hist = useFlowHistory(baseFlow);
|
|
11661
|
+
const flow = hist.flow;
|
|
11463
11662
|
const [, force] = ReactExports.useState(0);
|
|
11464
11663
|
ReactExports.useEffect(() => onNodeKindsChanged(() => force((n) => n + 1)), []);
|
|
11465
11664
|
const nodeTypes = ReactExports.useMemo(() => buildNodeTypes(), [listNodeKinds().length]);
|
|
@@ -11512,6 +11711,23 @@ function FlowEditorInner({
|
|
|
11512
11711
|
window.removeEventListener("keydown", onKey);
|
|
11513
11712
|
};
|
|
11514
11713
|
}, [menu, labelEdit]);
|
|
11714
|
+
ReactExports.useEffect(() => {
|
|
11715
|
+
const onKey = (e) => {
|
|
11716
|
+
const t = e.target;
|
|
11717
|
+
if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable)) return;
|
|
11718
|
+
if (!(e.ctrlKey || e.metaKey)) return;
|
|
11719
|
+
const key = e.key.toLowerCase();
|
|
11720
|
+
if (key === "z" && !e.shiftKey) {
|
|
11721
|
+
e.preventDefault();
|
|
11722
|
+
hist.undo();
|
|
11723
|
+
} else if (key === "z" && e.shiftKey || key === "y") {
|
|
11724
|
+
e.preventDefault();
|
|
11725
|
+
hist.redo();
|
|
11726
|
+
}
|
|
11727
|
+
};
|
|
11728
|
+
window.addEventListener("keydown", onKey);
|
|
11729
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
11730
|
+
}, [hist]);
|
|
11515
11731
|
ReactExports.useEffect(() => {
|
|
11516
11732
|
if (!controlled) onChange?.({ nodes: flow.nodes, edges: flow.edges });
|
|
11517
11733
|
}, [flow.nodes, flow.edges, onChange, controlled]);
|
|
@@ -11536,16 +11752,116 @@ function FlowEditorInner({
|
|
|
11536
11752
|
[flow]
|
|
11537
11753
|
);
|
|
11538
11754
|
const deleteNodes = ReactExports.useCallback(
|
|
11539
|
-
(ids) => {
|
|
11755
|
+
async (ids) => {
|
|
11540
11756
|
if (ids.length === 0) return;
|
|
11757
|
+
const targets = flow.nodes.filter((n) => ids.includes(n.id));
|
|
11758
|
+
if (targets.length === 0) return;
|
|
11759
|
+
if (confirmDelete) {
|
|
11760
|
+
const attached = flow.edges.filter((e) => ids.includes(e.source) || ids.includes(e.target));
|
|
11761
|
+
if (!await confirmDelete({ nodes: targets, edges: attached })) return;
|
|
11762
|
+
}
|
|
11541
11763
|
const doomed = new Set(ids);
|
|
11542
|
-
flow.
|
|
11543
|
-
flow.setEdges((all) => removeNodes({ nodes: [], edges: all }, ids).edges);
|
|
11764
|
+
flow.setGraph(removeNodes({ nodes: flow.nodes, edges: flow.edges }, ids));
|
|
11544
11765
|
setSelectedId((cur) => cur !== null && doomed.has(cur) ? null : cur);
|
|
11545
11766
|
onDelete?.(ids);
|
|
11546
11767
|
},
|
|
11547
|
-
[flow, onDelete]
|
|
11768
|
+
[flow, onDelete, confirmDelete]
|
|
11769
|
+
);
|
|
11770
|
+
const deleteEdges = ReactExports.useCallback(
|
|
11771
|
+
async (ids) => {
|
|
11772
|
+
if (ids.length === 0) return;
|
|
11773
|
+
const targets = flow.edges.filter((e) => ids.includes(e.id));
|
|
11774
|
+
if (targets.length === 0) return;
|
|
11775
|
+
if (confirmDelete && !await confirmDelete({ nodes: [], edges: targets })) return;
|
|
11776
|
+
flow.setEdges((all) => removeEdges(all, ids));
|
|
11777
|
+
setSelectedEdgeId((cur) => cur !== null && ids.includes(cur) ? null : cur);
|
|
11778
|
+
onEdgeDelete?.(ids);
|
|
11779
|
+
},
|
|
11780
|
+
[flow, onEdgeDelete, confirmDelete]
|
|
11781
|
+
);
|
|
11782
|
+
const selectedNodes = ReactExports.useMemo(() => flow.nodes.filter((n) => n.selected), [flow.nodes]);
|
|
11783
|
+
const selectedIds = ReactExports.useMemo(() => selectedNodes.map((n) => n.id), [selectedNodes]);
|
|
11784
|
+
const clipboard = ReactExports.useRef(null);
|
|
11785
|
+
const copySelection = ReactExports.useCallback(() => {
|
|
11786
|
+
const sel = flow.nodes.filter((n) => n.selected);
|
|
11787
|
+
if (sel.length === 0) return;
|
|
11788
|
+
const ids = new Set(sel.map((n) => n.id));
|
|
11789
|
+
const edges = flow.edges.filter((e) => ids.has(e.source) && ids.has(e.target));
|
|
11790
|
+
clipboard.current = { nodes: sel.map((n) => ({ ...n })), edges: edges.map((e) => ({ ...e })) };
|
|
11791
|
+
}, [flow]);
|
|
11792
|
+
const pasteClipboard = ReactExports.useCallback(
|
|
11793
|
+
(at) => {
|
|
11794
|
+
const clip = clipboard.current;
|
|
11795
|
+
if (!clip || clip.nodes.length === 0) return;
|
|
11796
|
+
const { nodes: clones, edges: cloneEdges } = cloneSubgraph(clip.nodes, clip.edges, { makeId: newNodeId, offset: 40 });
|
|
11797
|
+
let placed = clones;
|
|
11798
|
+
if (at) {
|
|
11799
|
+
const minX = Math.min(...clones.map((n) => n.position.x));
|
|
11800
|
+
const minY = Math.min(...clones.map((n) => n.position.y));
|
|
11801
|
+
placed = clones.map((n) => ({ ...n, position: { x: n.position.x - minX + at.x, y: n.position.y - minY + at.y } }));
|
|
11802
|
+
}
|
|
11803
|
+
flow.setGraph({
|
|
11804
|
+
nodes: [...flow.nodes.map((n) => ({ ...n, selected: false })), ...placed.map((n) => ({ ...n, selected: true }))],
|
|
11805
|
+
edges: [...flow.edges, ...cloneEdges]
|
|
11806
|
+
});
|
|
11807
|
+
},
|
|
11808
|
+
[flow]
|
|
11809
|
+
);
|
|
11810
|
+
const duplicateSelected = ReactExports.useCallback(() => {
|
|
11811
|
+
const sel = flow.nodes.filter((n) => n.selected);
|
|
11812
|
+
if (sel.length === 0) return;
|
|
11813
|
+
const ids = new Set(sel.map((n) => n.id));
|
|
11814
|
+
const internal2 = flow.edges.filter((e) => ids.has(e.source) && ids.has(e.target));
|
|
11815
|
+
const { nodes: clones, edges: cloneEdges } = cloneSubgraph(sel, internal2, { makeId: newNodeId, offset: 40 });
|
|
11816
|
+
flow.setGraph({
|
|
11817
|
+
nodes: [...flow.nodes.map((n) => ({ ...n, selected: false })), ...clones.map((n) => ({ ...n, selected: true }))],
|
|
11818
|
+
edges: [...flow.edges, ...cloneEdges]
|
|
11819
|
+
});
|
|
11820
|
+
}, [flow]);
|
|
11821
|
+
const alignSelected = ReactExports.useCallback(
|
|
11822
|
+
(edge) => {
|
|
11823
|
+
const sel = flow.nodes.filter((n) => n.selected);
|
|
11824
|
+
if (sel.length < 2) return;
|
|
11825
|
+
const byId = new Map(alignNodes(sel, edge).map((n) => [n.id, n]));
|
|
11826
|
+
flow.setNodes((all) => all.map((n) => byId.get(n.id) ?? n));
|
|
11827
|
+
},
|
|
11828
|
+
[flow]
|
|
11829
|
+
);
|
|
11830
|
+
const distributeSelected = ReactExports.useCallback(
|
|
11831
|
+
(axis) => {
|
|
11832
|
+
const sel = flow.nodes.filter((n) => n.selected);
|
|
11833
|
+
if (sel.length < 3) return;
|
|
11834
|
+
const byId = new Map(distributeNodes(sel, axis).map((n) => [n.id, n]));
|
|
11835
|
+
flow.setNodes((all) => all.map((n) => byId.get(n.id) ?? n));
|
|
11836
|
+
},
|
|
11837
|
+
[flow]
|
|
11838
|
+
);
|
|
11839
|
+
const onReconnect = ReactExports.useCallback(
|
|
11840
|
+
(oldEdge, conn) => flow.setEdges((eds) => reconnectEdge3(eds, oldEdge, conn)),
|
|
11841
|
+
[flow]
|
|
11548
11842
|
);
|
|
11843
|
+
ReactExports.useEffect(() => {
|
|
11844
|
+
const onKey = (e) => {
|
|
11845
|
+
const t = e.target;
|
|
11846
|
+
if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable)) return;
|
|
11847
|
+
if (!(e.ctrlKey || e.metaKey)) return;
|
|
11848
|
+
const key = e.key.toLowerCase();
|
|
11849
|
+
if (key === "c") copySelection();
|
|
11850
|
+
else if (key === "x") {
|
|
11851
|
+
e.preventDefault();
|
|
11852
|
+
copySelection();
|
|
11853
|
+
deleteNodes(selectedIds);
|
|
11854
|
+
} else if (key === "v") {
|
|
11855
|
+
e.preventDefault();
|
|
11856
|
+
pasteClipboard();
|
|
11857
|
+
} else if (key === "d") {
|
|
11858
|
+
e.preventDefault();
|
|
11859
|
+
duplicateSelected();
|
|
11860
|
+
}
|
|
11861
|
+
};
|
|
11862
|
+
window.addEventListener("keydown", onKey);
|
|
11863
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
11864
|
+
}, [copySelection, pasteClipboard, duplicateSelected, deleteNodes, selectedIds]);
|
|
11549
11865
|
const api = ReactExports.useMemo(() => {
|
|
11550
11866
|
const toWorkflow = () => exportWorkflow({ nodes: flow.nodes, edges: flow.edges }, metadata);
|
|
11551
11867
|
return {
|
|
@@ -11556,6 +11872,8 @@ function FlowEditorInner({
|
|
|
11556
11872
|
selected: selected2,
|
|
11557
11873
|
selectedEdgeId,
|
|
11558
11874
|
selectedEdge,
|
|
11875
|
+
selectedIds,
|
|
11876
|
+
selectedNodes,
|
|
11559
11877
|
running: runner.running,
|
|
11560
11878
|
statuses: runner.statuses,
|
|
11561
11879
|
select: setSelectedId,
|
|
@@ -11564,18 +11882,8 @@ function FlowEditorInner({
|
|
|
11564
11882
|
updateNode: (next) => flow.setNodes((all) => all.map((x) => x.id === next.id ? next : x)),
|
|
11565
11883
|
deleteNodes,
|
|
11566
11884
|
deleteSelected: () => deleteNodes(selectedId ? [selectedId] : []),
|
|
11567
|
-
deleteEdges
|
|
11568
|
-
|
|
11569
|
-
flow.setEdges((all) => removeEdges(all, ids));
|
|
11570
|
-
setSelectedEdgeId((cur) => cur !== null && ids.includes(cur) ? null : cur);
|
|
11571
|
-
onEdgeDelete?.(ids);
|
|
11572
|
-
},
|
|
11573
|
-
deleteSelectedEdge: () => {
|
|
11574
|
-
if (!selectedEdgeId) return;
|
|
11575
|
-
flow.setEdges((all) => removeEdges(all, [selectedEdgeId]));
|
|
11576
|
-
setSelectedEdgeId(null);
|
|
11577
|
-
onEdgeDelete?.([selectedEdgeId]);
|
|
11578
|
-
},
|
|
11885
|
+
deleteEdges,
|
|
11886
|
+
deleteSelectedEdge: () => deleteEdges(selectedEdgeId ? [selectedEdgeId] : []),
|
|
11579
11887
|
setEdgeLabel: (id2, label) => flow.setEdges((all) => setEdgeLabel(all, id2, label)),
|
|
11580
11888
|
duplicateNode: (id2) => {
|
|
11581
11889
|
const src = flow.nodes.find((n) => n.id === id2);
|
|
@@ -11585,22 +11893,29 @@ function FlowEditorInner({
|
|
|
11585
11893
|
setSelectedId(copy.id);
|
|
11586
11894
|
return copy.id;
|
|
11587
11895
|
},
|
|
11588
|
-
setGraph: (graph) =>
|
|
11589
|
-
|
|
11590
|
-
|
|
11896
|
+
setGraph: (graph) => flow.setGraph(graph),
|
|
11897
|
+
duplicateSelected,
|
|
11898
|
+
alignSelected,
|
|
11899
|
+
distributeSelected,
|
|
11900
|
+
copy: copySelection,
|
|
11901
|
+
cut: () => {
|
|
11902
|
+
copySelection();
|
|
11903
|
+
deleteNodes(selectedIds);
|
|
11591
11904
|
},
|
|
11905
|
+
paste: pasteClipboard,
|
|
11592
11906
|
run: () => runner.run({ nodes: flow.nodes, edges: flow.edges }, executors),
|
|
11593
11907
|
cancel: runner.cancel,
|
|
11594
11908
|
reset: runner.reset,
|
|
11595
11909
|
toWorkflow,
|
|
11596
11910
|
exportWorkflow: () => downloadWorkflow(toWorkflow(), metadata),
|
|
11597
|
-
importWorkflow: () => pickWorkflow((graph) =>
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
11601
|
-
|
|
11911
|
+
importWorkflow: () => pickWorkflow((graph) => flow.setGraph(graph)),
|
|
11912
|
+
fitView: () => rf.fitView({ padding: 0.2 }),
|
|
11913
|
+
undo: hist.undo,
|
|
11914
|
+
redo: hist.redo,
|
|
11915
|
+
canUndo: hist.canUndo,
|
|
11916
|
+
canRedo: hist.canRedo
|
|
11602
11917
|
};
|
|
11603
|
-
}, [flow, selectedId, selected2, runner, executors, metadata, addNode, deleteNodes, rf]);
|
|
11918
|
+
}, [flow, selectedId, selected2, selectedEdgeId, selectedEdge, selectedIds, selectedNodes, runner, executors, metadata, addNode, deleteNodes, deleteEdges, duplicateSelected, alignSelected, distributeSelected, copySelection, pasteClipboard, hist, rf]);
|
|
11604
11919
|
ReactExports.useImperativeHandle(apiRef, () => api, [api]);
|
|
11605
11920
|
const dropHandlers = paletteDropHandlers((kindName, evt) => {
|
|
11606
11921
|
const point = rf.screenToFlowPosition({ x: evt.clientX, y: evt.clientY });
|
|
@@ -11611,6 +11926,31 @@ function FlowEditorInner({
|
|
|
11611
11926
|
const toolbar = slots.toolbar ? slots.toolbar(api) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
11612
11927
|
startActions.map((a) => renderAction(a, api)),
|
|
11613
11928
|
builtins.run !== false && /* @__PURE__ */ jsxRuntime.jsx(FlowRunControls, { running: api.running, onRun: api.run, onCancel: api.cancel, onReset: api.reset }),
|
|
11929
|
+
builtins.history !== false && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
11930
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-editor__sep" }),
|
|
11931
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11932
|
+
"button",
|
|
11933
|
+
{
|
|
11934
|
+
className: "ff-editor__btn",
|
|
11935
|
+
"data-action": "undo",
|
|
11936
|
+
title: "Undo (Ctrl+Z)",
|
|
11937
|
+
disabled: !api.canUndo,
|
|
11938
|
+
onClick: api.undo,
|
|
11939
|
+
children: "\u21B6 Undo"
|
|
11940
|
+
}
|
|
11941
|
+
),
|
|
11942
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11943
|
+
"button",
|
|
11944
|
+
{
|
|
11945
|
+
className: "ff-editor__btn",
|
|
11946
|
+
"data-action": "redo",
|
|
11947
|
+
title: "Redo (Ctrl+Shift+Z)",
|
|
11948
|
+
disabled: !api.canRedo,
|
|
11949
|
+
onClick: api.redo,
|
|
11950
|
+
children: "\u21B7 Redo"
|
|
11951
|
+
}
|
|
11952
|
+
)
|
|
11953
|
+
] }),
|
|
11614
11954
|
(builtins.export !== false || builtins.import !== false) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-editor__sep" }),
|
|
11615
11955
|
builtins.export !== false && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "ff-editor__btn", "data-action": "export", onClick: api.exportWorkflow, children: "\u2193 Export" }),
|
|
11616
11956
|
builtins.import !== false && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "ff-editor__btn", "data-action": "import", onClick: api.importWorkflow, children: "\u2191 Import" }),
|
|
@@ -11635,12 +11975,16 @@ function FlowEditorInner({
|
|
|
11635
11975
|
onNodesChange: flow.onNodesChange,
|
|
11636
11976
|
onEdgesChange: flow.onEdgesChange,
|
|
11637
11977
|
onConnect: flow.onConnect,
|
|
11978
|
+
onReconnect,
|
|
11979
|
+
edgesReconnectable: true,
|
|
11980
|
+
onNodeDragStart: hist.onNodeDragStart,
|
|
11638
11981
|
onNodeClick: handleNodeClick2,
|
|
11639
11982
|
onNodeContextMenu: builtins.contextMenu === false ? void 0 : handleNodeContextMenu,
|
|
11640
11983
|
onEdgeClick: handleEdgeClick,
|
|
11641
11984
|
onEdgeContextMenu: builtins.edgeContextMenu === false ? void 0 : handleEdgeContextMenu,
|
|
11642
11985
|
onEdgesDelete: (deleted) => onEdgeDelete?.(deleted.map((e) => e.id)),
|
|
11643
11986
|
onNodesDelete: (deleted) => onDelete?.(deleted.map((n) => n.id)),
|
|
11987
|
+
onBeforeDelete: confirmDelete ? async ({ nodes, edges }) => confirmDelete({ nodes, edges }) : void 0,
|
|
11644
11988
|
deleteKeyCode: ["Delete", "Backspace"],
|
|
11645
11989
|
height: "100%",
|
|
11646
11990
|
toolbar,
|
|
@@ -11811,6 +12155,8 @@ function makeControlledFlowAdapter(value, onChange) {
|
|
|
11811
12155
|
const nextEdges = typeof next === "function" ? next(value.edges) : next;
|
|
11812
12156
|
apply({ nodes: value.nodes, edges: nextEdges });
|
|
11813
12157
|
},
|
|
12158
|
+
// Atomic both-at-once commit — the reason this exists (see UseFlowStateReturn).
|
|
12159
|
+
setGraph: (graph) => apply(graph),
|
|
11814
12160
|
onNodesChange: (changes) => {
|
|
11815
12161
|
apply({ nodes: applyNodeChanges(changes, value.nodes), edges: value.edges });
|
|
11816
12162
|
},
|
|
@@ -11920,15 +12266,19 @@ exports.SubgraphNode = SubgraphNode;
|
|
|
11920
12266
|
exports.TriggerNode = TriggerNode;
|
|
11921
12267
|
exports.WORKFLOW_SCHEMA_URL = WORKFLOW_SCHEMA_URL;
|
|
11922
12268
|
exports.WORKFLOW_SCHEMA_VERSION = WORKFLOW_SCHEMA_VERSION;
|
|
12269
|
+
exports.alignNodes = alignNodes;
|
|
11923
12270
|
exports.applyStatusesToNodes = applyStatusesToNodes;
|
|
11924
12271
|
exports.buildNodeTypes = buildNodeTypes;
|
|
11925
12272
|
exports.categoryAccent = categoryAccent;
|
|
12273
|
+
exports.cloneSubgraph = cloneSubgraph;
|
|
11926
12274
|
exports.createConnectionValidator = createConnectionValidator;
|
|
12275
|
+
exports.createHistory = createHistory;
|
|
11927
12276
|
exports.decodePause = decodePause;
|
|
11928
12277
|
exports.defaultConfigFor = defaultConfigFor;
|
|
11929
12278
|
exports.defaultNodeTypes = defaultNodeTypes;
|
|
11930
12279
|
exports.defaultPortCompatibility = defaultPortCompatibility;
|
|
11931
12280
|
exports.defineNode = defineNode;
|
|
12281
|
+
exports.distributeNodes = distributeNodes;
|
|
11932
12282
|
exports.encodePause = encodePause;
|
|
11933
12283
|
exports.exportWorkflow = exportWorkflow;
|
|
11934
12284
|
exports.getNodeKind = getNodeKind;
|
|
@@ -11941,6 +12291,7 @@ exports.onNodeKindsChanged = onNodeKindsChanged;
|
|
|
11941
12291
|
exports.onRichInputAdapterChanged = onRichInputAdapterChanged;
|
|
11942
12292
|
exports.paletteDropHandlers = paletteDropHandlers;
|
|
11943
12293
|
exports.pauseForHuman = pauseForHuman;
|
|
12294
|
+
exports.reconnectEdge = reconnectEdge3;
|
|
11944
12295
|
exports.registerBuiltinKinds = registerBuiltinKinds;
|
|
11945
12296
|
exports.registerNodeKind = registerNodeKind;
|
|
11946
12297
|
exports.registerRichInputAdapter = registerRichInputAdapter;
|
|
@@ -11949,6 +12300,7 @@ exports.resolvePortSpec = resolvePortSpec;
|
|
|
11949
12300
|
exports.runFlow = runFlow;
|
|
11950
12301
|
exports.useFlowEditor = useFlowEditor;
|
|
11951
12302
|
exports.useFlowEditorOptional = useFlowEditorOptional;
|
|
12303
|
+
exports.useFlowHistory = useFlowHistory;
|
|
11952
12304
|
exports.useFlowRun = useFlowRun;
|
|
11953
12305
|
exports.useFlowState = useFlowState;
|
|
11954
12306
|
exports.validateConfig = validateConfig;
|