diff --git a/src/DiagramEditor.tsx b/src/DiagramEditor.tsx
index 336822c..ac92f4f 100644
--- a/src/DiagramEditor.tsx
+++ b/src/DiagramEditor.tsx
@@ -12,10 +12,12 @@ import {
ViewportPortal,
useReactFlow,
addEdge,
+ reconnectEdge,
applyNodeChanges,
applyEdgeChanges,
type Node,
type Edge,
+ type Connection,
type OnConnect,
type NodeChange,
type EdgeChange,
@@ -132,6 +134,7 @@ function toRfNodes(doc: DiagramDocument | null): Node[] {
data: node.data,
width: node.width ?? defaults.width,
height: node.height ?? defaults.height,
+ zIndex: node.zIndex,
...(node.parentId ? { parentId: node.parentId } : {}),
};
});
@@ -147,6 +150,7 @@ function toRfEdges(doc: DiagramDocument | null): Edge[] {
type: edge.type ?? DEFAULT_EDGE_TYPE,
markerEnd: DEFAULT_MARKER_END,
label: edge.label,
+ zIndex: edge.zIndex,
data: edge.data,
}));
}
@@ -330,6 +334,17 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) {
[nodes, edges, emitChange, pushHistory],
);
+ const onReconnect = useCallback(
+ (oldEdge: Edge, connection: Connection) => {
+ pushHistory(nodes, edges);
+ // Keep the original edge id stable so selection/history don't go stale mid-drag.
+ const next = reconnectEdge(oldEdge, connection, edges, { shouldReplaceId: false });
+ setEdges(next);
+ emitChange(nodes, next);
+ },
+ [nodes, edges, emitChange, pushHistory],
+ );
+
const onNodeClick: NodeMouseHandler = useCallback((_event, node) => {
setSelectedNodeId(node.id);
setSelectedEdgeId(null);
@@ -462,6 +477,53 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) {
setSelectedEdgeId(null);
}, [selectedEdgeId, nodes, edges, emitChange, pushHistory]);
+ // Nodes and edges share one z-index space (zIndexMode="manual"), so front/back
+ // always means "above/below everything currently on the canvas", not just siblings.
+ const maxZIndex = useCallback(
+ () => Math.max(0, ...nodes.map((n) => n.zIndex ?? 0), ...edges.map((e) => e.zIndex ?? 0)),
+ [nodes, edges],
+ );
+ const minZIndex = useCallback(
+ () => Math.min(0, ...nodes.map((n) => n.zIndex ?? 0), ...edges.map((e) => e.zIndex ?? 0)),
+ [nodes, edges],
+ );
+
+ const bringSelectedNodeToFront = useCallback(() => {
+ if (!selectedNodeId) return;
+ const z = maxZIndex() + 1;
+ pushHistory(nodes, edges);
+ const next = nodes.map((n) => (n.id === selectedNodeId ? { ...n, zIndex: z } : n));
+ setNodes(next);
+ emitChange(next, edges);
+ }, [selectedNodeId, nodes, edges, emitChange, pushHistory, maxZIndex]);
+
+ const sendSelectedNodeToBack = useCallback(() => {
+ if (!selectedNodeId) return;
+ const z = minZIndex() - 1;
+ pushHistory(nodes, edges);
+ const next = nodes.map((n) => (n.id === selectedNodeId ? { ...n, zIndex: z } : n));
+ setNodes(next);
+ emitChange(next, edges);
+ }, [selectedNodeId, nodes, edges, emitChange, pushHistory, minZIndex]);
+
+ const bringSelectedEdgeToFront = useCallback(() => {
+ if (!selectedEdgeId) return;
+ const z = maxZIndex() + 1;
+ pushHistory(nodes, edges);
+ const next = edges.map((e) => (e.id === selectedEdgeId ? { ...e, zIndex: z } : e));
+ setEdges(next);
+ emitChange(nodes, next);
+ }, [selectedEdgeId, nodes, edges, emitChange, pushHistory, maxZIndex]);
+
+ const sendSelectedEdgeToBack = useCallback(() => {
+ if (!selectedEdgeId) return;
+ const z = minZIndex() - 1;
+ pushHistory(nodes, edges);
+ const next = edges.map((e) => (e.id === selectedEdgeId ? { ...e, zIndex: z } : e));
+ setEdges(next);
+ emitChange(nodes, next);
+ }, [selectedEdgeId, nodes, edges, emitChange, pushHistory, minZIndex]);
+
useEffect(() => {
function onKeyDown(event: KeyboardEvent) {
if (mode !== 'edit' || isEditableTarget(event.target)) return;
@@ -500,6 +562,8 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) {
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
+ onReconnect={onReconnect}
+ edgesReconnectable={mode === 'edit'}
onNodeClick={onNodeClick}
onEdgeClick={onEdgeClick}
onPaneClick={onPaneClick}
@@ -508,6 +572,7 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) {
deleteKeyCode={mode === 'edit' ? undefined : null}
zoomOnDoubleClick={false}
connectionMode={ConnectionMode.Loose}
+ zIndexMode="manual"
className={mode === 'view' ? 'diagram-flow--view-mode' : undefined}
fitView
>
@@ -589,6 +654,8 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) {
onEnterSubProcess={enterSubProcess}
onDuplicate={duplicateSelectedNode}
onDelete={deleteSelectedNode}
+ onBringToFront={bringSelectedNodeToFront}
+ onSendToBack={sendSelectedNodeToBack}
/>
) : selectedEdge ? (
diff --git a/src/EdgeInspector.tsx b/src/EdgeInspector.tsx index 0f81ca4..c9f0ec3 100644 --- a/src/EdgeInspector.tsx +++ b/src/EdgeInspector.tsx @@ -3,9 +3,18 @@ interface EdgeInspectorProps { label: string; onChange: (label: string) => void; onDelete: () => void; + onBringToFront: () => void; + onSendToBack: () => void; } -export function EdgeInspector({ edgeId, label, onChange, onDelete }: EdgeInspectorProps) { +export function EdgeInspector({ + edgeId, + label, + onChange, + onDelete, + onBringToFront, + onSendToBack, +}: EdgeInspectorProps) { return (