diff --git a/examples/vite-app/src/examples/Basic/index.tsx b/examples/vite-app/src/examples/Basic/index.tsx
index b59e21b3..d6f3950f 100644
--- a/examples/vite-app/src/examples/Basic/index.tsx
+++ b/examples/vite-app/src/examples/Basic/index.tsx
@@ -106,7 +106,7 @@ const BasicFlow = () => {
-
+
);
diff --git a/examples/vite-app/src/examples/UseReactFlow/index.tsx b/examples/vite-app/src/examples/UseReactFlow/index.tsx
index 7133cec9..6fca9627 100644
--- a/examples/vite-app/src/examples/UseReactFlow/index.tsx
+++ b/examples/vite-app/src/examples/UseReactFlow/index.tsx
@@ -43,6 +43,7 @@ const initialNodes: Node[] = [
const initialEdges: Edge[] = [
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
+ // { id: 'e3-4', source: '3', target: '4' }
];
let id = 5;
@@ -64,6 +65,7 @@ const UseZoomPanHelperFlow = () => {
addEdges,
getNodes,
getEdges,
+ deleteElements
} = useReactFlow();
const onPaneClick = useCallback(
@@ -111,6 +113,16 @@ const UseZoomPanHelperFlow = () => {
console.log('edges', getEdges());
}, [getNodes, getEdges]);
+ const deleteSelectedElements = useCallback(() => {
+ const selectedNodes = nodes.filter(node => node.selected);
+ const selectedEdges = edges.filter(edge => edge.selected);
+ deleteElements({ nodes: selectedNodes, edges: selectedEdges });
+ }, [deleteElements, nodes, edges])
+
+ const deleteSomeElements = useCallback(() => {
+ deleteElements({ nodes: [{ id: '2' }], edges: [{ id: 'e1-3' }] })
+ }, [])
+
useEffect(() => {
addEdges({ id: 'e3-4', source: '3', target: '4' });
}, [addEdges]);
@@ -137,6 +149,8 @@ const UseZoomPanHelperFlow = () => {
+
+
diff --git a/packages/core/src/hooks/useGlobalKeyHandler.ts b/packages/core/src/hooks/useGlobalKeyHandler.ts
index 23cb2a7d..f2b206e7 100644
--- a/packages/core/src/hooks/useGlobalKeyHandler.ts
+++ b/packages/core/src/hooks/useGlobalKeyHandler.ts
@@ -2,9 +2,8 @@ import { useEffect } from 'react';
import { useStoreApi } from '../hooks/useStore';
import useKeyPress from './useKeyPress';
-import { getConnectedEdges } from '../utils/graph';
-import type { KeyCode, NodeChange, Node } from '../types';
-
+import type { KeyCode } from '../types';
+import useReactFlow from './useReactFlow';
interface HookParams {
deleteKeyCode: KeyCode | null;
multiSelectionKeyCode: KeyCode | null;
@@ -12,87 +11,18 @@ interface HookParams {
export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
const store = useStoreApi();
+ const { deleteElements } = useReactFlow();
+
const deleteKeyPressed = useKeyPress(deleteKeyCode);
const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode);
useEffect(() => {
- if (!deleteKeyPressed) {
- return;
- }
-
- const {
- nodeInternals,
- edges,
- hasDefaultNodes,
- hasDefaultEdges,
- onNodesDelete,
- onEdgesDelete,
- onNodesChange,
- onEdgesChange,
- } = store.getState();
- const nodes = Array.from(nodeInternals.values());
- const nodesToRemove = nodes.reduce((res, node) => {
- const parentSelected = !node.selected && node.parentNode && res.find((n) => n.id === node.parentNode);
- const deletable = typeof node.deletable === 'boolean' ? node.deletable : true;
- if (deletable && (node.selected || parentSelected)) {
- res.push(node);
- }
-
- return res;
- }, []);
- const deletableEdges = edges.filter((e) => (typeof e.deletable === 'boolean' ? e.deletable : true));
- const selectedEdges = deletableEdges.filter((e) => e.selected);
-
- if (nodesToRemove || selectedEdges) {
- const connectedEdges = getConnectedEdges(nodesToRemove, deletableEdges);
- const edgesToRemove = [...selectedEdges, ...connectedEdges];
- const edgeIdsToRemove = edgesToRemove.reduce((res, edge) => {
- if (!res.includes(edge.id)) {
- res.push(edge.id);
- }
- return res;
- }, []);
-
- if (hasDefaultEdges || hasDefaultNodes) {
- if (hasDefaultEdges) {
- store.setState({
- edges: edges.filter((e) => !edgeIdsToRemove.includes(e.id)),
- });
- }
-
- if (hasDefaultNodes) {
- nodesToRemove.forEach((node) => {
- nodeInternals.delete(node.id);
- });
-
- store.setState({
- nodeInternals: new Map(nodeInternals),
- });
- }
- }
-
- if (edgeIdsToRemove.length > 0) {
- onEdgesDelete?.(edgesToRemove);
-
- if (onEdgesChange) {
- onEdgesChange(
- edgeIdsToRemove.map((id) => ({
- id,
- type: 'remove',
- }))
- );
- }
- }
-
- if (nodesToRemove.length > 0) {
- onNodesDelete?.(nodesToRemove);
-
- if (onNodesChange) {
- const nodeChanges: NodeChange[] = nodesToRemove.map((n) => ({ id: n.id, type: 'remove' }));
- onNodesChange(nodeChanges);
- }
- }
-
+ if (deleteKeyPressed) {
+ const { nodeInternals, edges } = store.getState();
+ const nodes = Array.from(nodeInternals.values());
+ const selectedNodes = nodes.filter((node) => node.selected);
+ const selectedEdges = edges.filter((edge) => edge.selected);
+ deleteElements({nodes: selectedNodes, edges: selectedEdges});
store.setState({ nodesSelectionActive: false });
}
}, [deleteKeyPressed]);
diff --git a/packages/core/src/hooks/useReactFlow.ts b/packages/core/src/hooks/useReactFlow.ts
index 2cba0fae..77415b9b 100644
--- a/packages/core/src/hooks/useReactFlow.ts
+++ b/packages/core/src/hooks/useReactFlow.ts
@@ -11,7 +11,10 @@ import type {
EdgeResetChange,
NodeRemoveChange,
EdgeRemoveChange,
+ NodeChange,
+ Node,
} from '../types';
+import { getConnectedEdges } from '../utils/graph';
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export default function useReactFlow(): ReactFlowInstance {
@@ -111,6 +114,83 @@ export default function useReactFlow(): ReactFlo
};
}, []);
+ const deleteElements = useCallback(({ nodes: nodesDeleted, edges: edgesDeleted }) => {
+ const {
+ nodeInternals,
+ edges,
+ hasDefaultNodes,
+ hasDefaultEdges,
+ onNodesDelete,
+ onEdgesDelete,
+ onNodesChange,
+ onEdgesChange,
+ } = store.getState();
+ const nodes = Array.from(nodeInternals.values());
+ const nodeIds = (nodesDeleted || []).map((node) => node.id);
+ const edgeIds = (edgesDeleted || []).map((edge) => edge.id);
+ const nodesToRemove = nodes.reduce((res, node) => {
+ const parentHit = !nodeIds.includes(node.id) && node.parentNode && res.find((n) => n.id === node.parentNode);
+ const deletable = typeof node.deletable === 'boolean' ? node.deletable : true;
+ if (deletable && (nodeIds.includes(node.id) || parentHit)) {
+ res.push(node);
+ }
+
+ return res;
+ }, []);
+ const deletableEdges = edges.filter((e) => (typeof e.deletable === 'boolean' ? e.deletable : true));
+ const initialHitEdges = deletableEdges.filter((e) => edgeIds.includes(e.id));
+ if (nodesToRemove || initialHitEdges) {
+ const connectedEdges = getConnectedEdges(nodesToRemove, deletableEdges);
+ const edgesToRemove = [...initialHitEdges, ...connectedEdges];
+ const edgeIdsToRemove = edgesToRemove.reduce((res, edge) => {
+ if (!res.includes(edge.id)) {
+ res.push(edge.id);
+ }
+ return res;
+ }, []);
+
+ if (hasDefaultEdges || hasDefaultNodes) {
+ if (hasDefaultEdges) {
+ store.setState({
+ edges: edges.filter((e) => !edgeIdsToRemove.includes(e.id)),
+ });
+ }
+
+ if (hasDefaultNodes) {
+ nodesToRemove.forEach((node) => {
+ nodeInternals.delete(node.id);
+ });
+
+ store.setState({
+ nodeInternals: new Map(nodeInternals),
+ });
+ }
+ }
+
+ if (edgeIdsToRemove.length > 0) {
+ onEdgesDelete?.(edgesToRemove);
+
+ if (onEdgesChange) {
+ onEdgesChange(
+ edgeIdsToRemove.map((id) => ({
+ id,
+ type: 'remove',
+ }))
+ );
+ }
+ }
+
+ if (nodesToRemove.length > 0) {
+ onNodesDelete?.(nodesToRemove);
+
+ if (onNodesChange) {
+ const nodeChanges: NodeChange[] = nodesToRemove.map((n) => ({ id: n.id, type: 'remove' }));
+ onNodesChange(nodeChanges);
+ }
+ }
+ }
+ }, []);
+
return useMemo(() => {
return {
...viewportHelper,
@@ -123,6 +203,19 @@ export default function useReactFlow(): ReactFlo
addNodes,
addEdges,
toObject,
+ deleteElements,
};
- }, [viewportHelper, getNodes, getNode, getEdges, getEdge, setNodes, setEdges, addNodes, addEdges, toObject]);
+ }, [
+ viewportHelper,
+ getNodes,
+ getNode,
+ getEdges,
+ getEdge,
+ setNodes,
+ setEdges,
+ addNodes,
+ addEdges,
+ toObject,
+ deleteElements,
+ ]);
}
diff --git a/packages/core/src/types/instance.ts b/packages/core/src/types/instance.ts
index dab4299e..0c7200d2 100644
--- a/packages/core/src/types/instance.ts
+++ b/packages/core/src/types/instance.ts
@@ -8,6 +8,10 @@ export type ReactFlowJsonObject = {
viewport: Viewport;
};
+export type DeleteElementsOptions = {
+ nodes?: (Partial & { id: Node['id'] })[],
+ edges?: (Partial & { id: Edge['id'] })[]
+};
export namespace Instance {
export type GetNodes = () => Node[];
export type SetNodes = (
@@ -22,6 +26,7 @@ export namespace Instance {
export type GetEdge = (id: string) => Edge | undefined;
export type AddEdges = (payload: Edge[] | Edge) => void;
export type ToObject = () => ReactFlowJsonObject;
+ export type DeleteElements = ({ nodes, edges }: DeleteElementsOptions) => void;
}
export type ReactFlowInstance = {
@@ -34,5 +39,6 @@ export type ReactFlowInstance = {
addEdges: Instance.AddEdges;
getEdge: Instance.GetEdge;
toObject: Instance.ToObject;
+ deleteElements: Instance.DeleteElements;
viewportInitialized: boolean;
} & Omit;