From d823b3df47e2be4e26e059c7712ef65b0d4e892b Mon Sep 17 00:00:00 2001 From: NikitaKalitov Date: Mon, 20 Jul 2026 13:49:44 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D1=83=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B8=20=D1=83=D0=B4=D0=B0=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=83=D0=B7=D0=BB=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DiagramEditor.tsx | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/DiagramEditor.tsx b/src/DiagramEditor.tsx index f81bba6..22f8829 100644 --- a/src/DiagramEditor.tsx +++ b/src/DiagramEditor.tsx @@ -259,17 +259,25 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) { setSelectedEdgeId(null); }, []); - const onNodesChange = useCallback( - (changes: NodeChange[]) => { - const withChildren = new Set(nodes.filter((n) => n.parentId).map((n) => n.parentId!)); - const filteredChanges = changes.filter((change) => { - if (change.type !== 'remove' || !withChildren.has(change.id)) return true; + // Runs before React Flow computes any removal changes at all (for both + // nodes and their connected edges), so blocking here — unlike filtering + // inside onNodesChange — stops the connected edges from being deleted too. + const onBeforeDelete = useCallback( + async ({ nodes: nodesToDelete }: { nodes: Node[]; edges: Edge[] }) => { + const hasContainerNode = nodesToDelete.some((node) => nodes.some((n) => n.parentId === node.id)); + if (hasContainerNode) { window.alert('Сначала удалите вложенные узлы — у этого узла есть под-процесс.'); return false; - }); + } + return true; + }, + [nodes], + ); - const positionChanges = filteredChanges.filter((c) => c.type === 'position' && c.position); - let adjustedChanges = filteredChanges; + const onNodesChange = useCallback( + (changes: NodeChange[]) => { + const positionChanges = changes.filter((c) => c.type === 'position' && c.position); + let adjustedChanges = changes; if (positionChanges.length === 1) { const change = positionChanges[0]; const draggedNode = change.type === 'position' ? nodes.find((n) => n.id === change.id) : undefined; @@ -286,7 +294,7 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) { }; const snapped = computeSnapAndGuides(dragged, others); setAlignmentGuides(change.dragging === false ? [] : snapped.guides); - adjustedChanges = filteredChanges.map((c) => + adjustedChanges = changes.map((c) => c === change ? { ...c, position: { x: snapped.x, y: snapped.y } } : c, ); } @@ -294,7 +302,7 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) { setAlignmentGuides([]); } - const isHistoryWorthy = filteredChanges.some( + const isHistoryWorthy = changes.some( (change) => change.type === 'remove' || (change.type === 'position' && change.dragging === false) || @@ -308,7 +316,7 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) { // emitChange — filtering edges here too (redundantly with onEdgesChange) // guarantees a removed node never leaves a dangling edge behind. const removedIds = new Set( - filteredChanges.filter((change) => change.type === 'remove').map((change) => change.id), + changes.filter((change) => change.type === 'remove').map((change) => change.id), ); const nextEdges = removedIds.size ? edges.filter((edge) => !removedIds.has(edge.source) && !removedIds.has(edge.target)) @@ -574,6 +582,7 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) { onEdgesChange={onEdgesChange} onConnect={onConnect} onReconnect={onReconnect} + onBeforeDelete={onBeforeDelete} edgesReconnectable={mode === 'edit'} onNodeClick={onNodeClick} onEdgeClick={onEdgeClick}