diff --git a/examples/react/src/examples/Overview/index.tsx b/examples/react/src/examples/Overview/index.tsx
index e4879ace..3f5c72d0 100644
--- a/examples/react/src/examples/Overview/index.tsx
+++ b/examples/react/src/examples/Overview/index.tsx
@@ -15,6 +15,8 @@ import {
Background,
MiniMap,
ConnectionMode,
+ OnBeforeDelete,
+ OnDelete,
} from '@xyflow/react';
const onNodeDragStart = (_: ReactMouseEvent, node: Node, nodes: Node[]) => console.log('drag start', node, nodes);
@@ -45,8 +47,12 @@ const onEdgeMouseEnter = (_: ReactMouseEvent, edge: Edge) => console.log('edge m
const onEdgeMouseMove = (_: ReactMouseEvent, edge: Edge) => console.log('edge mouse move', edge);
const onEdgeMouseLeave = (_: ReactMouseEvent, edge: Edge) => console.log('edge mouse leave', edge);
const onEdgeDoubleClick = (_: ReactMouseEvent, edge: Edge) => console.log('edge double click', edge);
-const onNodesDelete = (nodes: Node[]) => console.log('nodes delete', nodes);
-const onEdgesDelete = (edges: Edge[]) => console.log('edges delete', edges);
+const onBeforeDelete: OnBeforeDelete = async ({ nodes, edges }) => {
+ console.log('on before delete', nodes, edges);
+ const deleteElements = confirm('Do you want to remove the selected elements?');
+ return deleteElements;
+};
+const onDelete: OnDelete = ({ nodes, edges }) => console.log('on delete', nodes, edges);
const onPaneMouseMove = (e: ReactMouseEvent) => console.log('pane move', e.clientX, e.clientY);
const initialNodes: Node[] = [
@@ -226,8 +232,8 @@ const OverviewFlow = () => {
fitViewOptions={{ padding: 0.1 /*nodes: [{ id: '1' }]*/ }}
attributionPosition="top-right"
maxZoom={Infinity}
- onNodesDelete={onNodesDelete}
- onEdgesDelete={onEdgesDelete}
+ onBeforeDelete={onBeforeDelete}
+ onDelete={onDelete}
onPaneMouseMove={onPaneMouseMove}
>
diff --git a/examples/react/src/examples/Validation/index.tsx b/examples/react/src/examples/Validation/index.tsx
index 864c3c08..3e89b3f8 100644
--- a/examples/react/src/examples/Validation/index.tsx
+++ b/examples/react/src/examples/Validation/index.tsx
@@ -16,6 +16,7 @@ import {
updateEdge,
Edge,
IsValidConnection,
+ OnBeforeDelete,
} from '@xyflow/react';
import ConnectionStatus from './ConnectionStatus';
@@ -85,6 +86,10 @@ const ValidationFlow = () => {
[setEdges]
);
+ const onBeforeDelete: OnBeforeDelete = useCallback(async () => {
+ return true;
+ }, []);
+
return (
{
onConnectEnd={onConnectEnd}
onEdgeUpdate={onEdgeUpdate}
isValidConnection={isValidConnection}
+ onBeforeDelete={onBeforeDelete}
fitView
>
diff --git a/examples/svelte/src/routes/examples/overview/+page.svelte b/examples/svelte/src/routes/examples/overview/+page.svelte
index d8293666..44d54544 100644
--- a/examples/svelte/src/routes/examples/overview/+page.svelte
+++ b/examples/svelte/src/routes/examples/overview/+page.svelte
@@ -177,6 +177,11 @@
}}
on:selectionclick={(event) => console.log('on selection click', event)}
on:selectioncontextmenu={(event) => console.log('on selection contextmenu', event)}
+ onbeforedelete={async ({ nodes, edges }) => {
+ console.log('on before delete', nodes, edges);
+ const deleteElements = confirm('Are you sure you want to delete the selected elements?');
+ return deleteElements;
+ }}
autoPanOnConnect
autoPanOnNodeDrag
connectionMode={ConnectionMode.Strict}
diff --git a/packages/react/src/components/StoreUpdater/index.tsx b/packages/react/src/components/StoreUpdater/index.tsx
index 948c50fc..344c718f 100644
--- a/packages/react/src/components/StoreUpdater/index.tsx
+++ b/packages/react/src/components/StoreUpdater/index.tsx
@@ -64,6 +64,7 @@ const reactFlowFieldsToTrack = [
'isValidConnection',
'selectNodesOnDrag',
'nodeDragThreshold',
+ 'onBeforeDelete',
] as const;
type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number];
diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx
index 48ff13c6..bfe3530c 100644
--- a/packages/react/src/container/ReactFlow/index.tsx
+++ b/packages/react/src/container/ReactFlow/index.tsx
@@ -70,6 +70,7 @@ const ReactFlow = forwardRef(
onSelectionContextMenu,
onSelectionStart,
onSelectionEnd,
+ onBeforeDelete,
connectionMode,
connectionLineType = ConnectionLineType.Bezier,
connectionLineStyle,
@@ -284,6 +285,7 @@ const ReactFlow = forwardRef(
isValidConnection={isValidConnection}
selectNodesOnDrag={selectNodesOnDrag}
nodeDragThreshold={nodeDragThreshold}
+ onBeforeDelete={onBeforeDelete}
/>
{children}
diff --git a/packages/react/src/hooks/useGlobalKeyHandler.ts b/packages/react/src/hooks/useGlobalKeyHandler.ts
index 786ed870..2409558f 100644
--- a/packages/react/src/hooks/useGlobalKeyHandler.ts
+++ b/packages/react/src/hooks/useGlobalKeyHandler.ts
@@ -30,8 +30,8 @@ export default ({
useEffect(() => {
if (deleteKeyPressed) {
- const { edges, nodes } = store.getState();
- deleteElements({ nodes: nodes.filter(selected), edges: edges.filter(selected) });
+ const { edges, nodes, onBeforeDelete } = store.getState();
+ deleteElements({ nodes: nodes.filter(selected), edges: edges.filter(selected), onBeforeDelete });
store.setState({ nodesSelectionActive: false });
}
}, [deleteKeyPressed]);
diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts
index 271e0ebf..eb07cc6a 100644
--- a/packages/react/src/hooks/useReactFlow.ts
+++ b/packages/react/src/hooks/useReactFlow.ts
@@ -12,7 +12,6 @@ import type {
EdgeResetChange,
NodeRemoveChange,
EdgeRemoveChange,
- NodeChange,
Node,
Edge,
} from '../types';
@@ -118,67 +117,65 @@ export default function useReactFlow(({ nodes: nodesDeleted, edges: edgesDeleted }) => {
- const {
- nodes,
- edges,
- hasDefaultNodes,
- hasDefaultEdges,
- onNodesDelete,
- onEdgesDelete,
- onNodesChange,
- onEdgesChange,
- onDelete,
- } = store.getState();
- const { matchingNodes, matchingEdges } = getElementsToRemove({
- nodesToRemove: nodesDeleted || [],
- edgesToRemove: edgesDeleted || [],
- nodes,
- edges,
- });
+ const deleteElements = useCallback(
+ async ({ nodes: nodesToRemove = [], edges: edgesToRemove = [], onBeforeDelete }) => {
+ const {
+ nodes,
+ edges,
+ hasDefaultNodes,
+ hasDefaultEdges,
+ onNodesDelete,
+ onEdgesDelete,
+ onNodesChange,
+ onEdgesChange,
+ onDelete,
+ } = store.getState();
+ const { nodes: matchingNodes, edges: matchingEdges } = await getElementsToRemove({
+ nodesToRemove,
+ edgesToRemove,
+ nodes,
+ edges,
+ onBeforeDelete,
+ });
- if (matchingNodes.length || matchingEdges.length) {
- if (hasDefaultEdges || hasDefaultNodes) {
+ const hasMatchingEdges = matchingEdges.length > 0;
+ const hasMatchingNodes = matchingNodes.length > 0;
+
+ if (hasMatchingEdges) {
if (hasDefaultEdges) {
store.setState({
edges: edges.filter((e) => !matchingEdges.some((mE) => mE.id === e.id)),
});
}
+ onEdgesDelete?.(matchingEdges);
+ onEdgesChange?.(
+ matchingEdges.map((edge) => ({
+ id: edge.id,
+ type: 'remove',
+ }))
+ );
+ }
+
+ if (hasMatchingNodes) {
if (hasDefaultNodes) {
store.setState({
nodes: nodes.filter((n) => !matchingNodes.some((mN) => mN.id === n.id)),
});
}
+
+ onNodesDelete?.(matchingNodes);
+ onNodesChange?.(matchingNodes.map((node) => ({ id: node.id, type: 'remove' })));
}
- if (matchingEdges.length > 0) {
- onEdgesDelete?.(matchingEdges);
-
- if (onEdgesChange) {
- onEdgesChange(
- matchingEdges.map((edge) => ({
- id: edge.id,
- type: 'remove',
- }))
- );
- }
+ if (hasMatchingNodes || hasMatchingEdges) {
+ onDelete?.({ nodes: matchingNodes, edges: matchingEdges });
}
- if (matchingNodes.length > 0) {
- onNodesDelete?.(matchingNodes as Node[]);
-
- if (onNodesChange) {
- const nodeChanges: NodeChange[] = matchingNodes.map((node) => ({ id: node.id, type: 'remove' }));
- onNodesChange(nodeChanges);
- }
- }
-
- onDelete?.({ nodes: matchingNodes, edges: matchingEdges });
- }
-
- return { deletedNodes: matchingNodes, deletedEdges: matchingEdges };
- }, []);
+ return { deletedNodes: matchingNodes, deletedEdges: matchingEdges };
+ },
+ []
+ );
const getNodeRect = useCallback(
(nodeOrRect: NodeType | { id: Node['id'] } | Rect): [Rect | null, NodeType | null | undefined, boolean] => {
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index f1997caa..9f30289d 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -79,6 +79,7 @@ export {
type ColorMode,
type ColorModeClass,
type HandleType,
+ type OnBeforeDelete,
} from '@xyflow/system';
// system utils
diff --git a/packages/react/src/types/component-props.ts b/packages/react/src/types/component-props.ts
index 43dc5e90..bea7166b 100644
--- a/packages/react/src/types/component-props.ts
+++ b/packages/react/src/types/component-props.ts
@@ -21,6 +21,7 @@ import type {
IsValidConnection,
ColorMode,
SnapGrid,
+ OnBeforeDelete,
} from '@xyflow/system';
import type {
@@ -100,6 +101,7 @@ export type ReactFlowProps = Omit, 'onError'> & {
onPaneMouseEnter?: (event: ReactMouseEvent) => void;
onPaneMouseMove?: (event: ReactMouseEvent) => void;
onPaneMouseLeave?: (event: ReactMouseEvent) => void;
+ onBeforeDelete?: OnBeforeDelete;
nodeTypes?: NodeTypes;
edgeTypes?: EdgeTypes;
connectionLineType?: ConnectionLineType;
diff --git a/packages/react/src/types/instance.ts b/packages/react/src/types/instance.ts
index 878e85a8..c9c4708d 100644
--- a/packages/react/src/types/instance.ts
+++ b/packages/react/src/types/instance.ts
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-namespace */
-import type { Rect, Viewport } from '@xyflow/system';
+import type { OnBeforeDelete, Rect, Viewport } from '@xyflow/system';
import type { Node, Edge, ViewportHelperFunctions } from '.';
export type ReactFlowJsonObject = {
@@ -12,6 +12,7 @@ export type ReactFlowJsonObject;
- export type DeleteElements = ({ nodes, edges }: DeleteElementsOptions) => {
+ export type DeleteElements = (params: DeleteElementsOptions) => Promise<{
deletedNodes: Node[];
deletedEdges: Edge[];
- };
+ }>;
export type GetIntersectingNodes = (
node: NodeType | { id: Node['id'] } | Rect,
partially?: boolean,
diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts
index a4cad399..b138fc9d 100644
--- a/packages/react/src/types/store.ts
+++ b/packages/react/src/types/store.ts
@@ -27,6 +27,7 @@ import {
type EdgeLookup,
type ConnectionLookup,
type NodeLookup,
+ OnBeforeDelete,
} from '@xyflow/system';
import type {
@@ -132,6 +133,7 @@ export type ReactFlowStore = {
onViewportChangeStart?: OnViewportChange;
onViewportChange?: OnViewportChange;
onViewportChangeEnd?: OnViewportChange;
+ onBeforeDelete?: OnBeforeDelete;
onSelectionChangeHandlers: OnSelectionChangeFunc[];
diff --git a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte
index 3e09a460..2d2df3a6 100644
--- a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte
+++ b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte
@@ -1,5 +1,6 @@