diff --git a/examples/vite-app/src/examples/Basic/index.tsx b/examples/vite-app/src/examples/Basic/index.tsx index 37c5d010..f3f74dda 100644 --- a/examples/vite-app/src/examples/Basic/index.tsx +++ b/examples/vite-app/src/examples/Basic/index.tsx @@ -78,6 +78,10 @@ const BasicFlow = () => { ); }; + const deleteElements = () => { + instance.deleteSelectedElements() + } + return ( { - + + ); diff --git a/packages/core/src/hooks/useGlobalKeyHandler.ts b/packages/core/src/hooks/useGlobalKeyHandler.ts index 151c9786..3d563941 100644 --- a/packages/core/src/hooks/useGlobalKeyHandler.ts +++ b/packages/core/src/hooks/useGlobalKeyHandler.ts @@ -2,8 +2,8 @@ import { useEffect } from 'react'; import { useStoreApi } from '../hooks/useStore'; import useKeyPress from './useKeyPress'; -import { getConnectedEdges } from '../utils/graph'; -import { KeyCode, NodeChange, Node } from '../types'; +import { KeyCode } from '../types'; +import useReactFlow from './useReactFlow'; interface HookParams { deleteKeyCode: KeyCode | null; @@ -12,88 +12,14 @@ interface HookParams { export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => { const store = useStoreApi(); + const { deleteSelectedElements } = 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); - } - } - - store.setState({ nodesSelectionActive: false }); + if (deleteKeyPressed) { + deleteSelectedElements(); } }, [deleteKeyPressed]); diff --git a/packages/core/src/hooks/useReactFlow.ts b/packages/core/src/hooks/useReactFlow.ts index ba826ddf..76043a2e 100644 --- a/packages/core/src/hooks/useReactFlow.ts +++ b/packages/core/src/hooks/useReactFlow.ts @@ -11,7 +11,10 @@ import { 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,84 @@ export default function useReactFlow(): ReactFlo }; }, []); + const deleteSelectedElements = useCallback(() => { + 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); + } + } + + store.setState({ nodesSelectionActive: false }); + } + }, []); + return useMemo(() => { return { ...viewportHelper, @@ -123,6 +204,19 @@ export default function useReactFlow(): ReactFlo addNodes, addEdges, toObject, + deleteSelectedElements, }; - }, [viewportHelper, getNodes, getNode, getEdges, getEdge, setNodes, setEdges, addNodes, addEdges, toObject]); + }, [ + viewportHelper, + getNodes, + getNode, + getEdges, + getEdge, + setNodes, + setEdges, + addNodes, + addEdges, + toObject, + deleteSelectedElements, + ]); } diff --git a/packages/core/src/types/instance.ts b/packages/core/src/types/instance.ts index f5fe2848..058da326 100644 --- a/packages/core/src/types/instance.ts +++ b/packages/core/src/types/instance.ts @@ -24,6 +24,7 @@ export namespace Instance { export type GetEdge = (id: string) => Edge | undefined; export type AddEdges = (payload: Edge[] | Edge) => void; export type ToObject = () => ReactFlowJsonObject; + export type DeleteSelectedElements = () => void; } export type ReactFlowInstance = { @@ -36,5 +37,6 @@ export type ReactFlowInstance = { addEdges: Instance.AddEdges; getEdge: Instance.GetEdge; toObject: Instance.ToObject; + deleteSelectedElements: Instance.DeleteSelectedElements; viewportInitialized: boolean; } & Omit;