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 (

@@ -19,6 +28,15 @@ export function EdgeInspector({ edgeId, label, onChange, onDelete }: EdgeInspect
+
+ + +
+
+
+ + +
+