feature(deleteSelectedElements): add deleteSelectedElements method
This commit is contained in:
@@ -56,6 +56,10 @@ const BasicFlow = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteElements = () => {
|
||||||
|
instance.deleteSelectedElements()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
defaultNodes={initialNodes}
|
defaultNodes={initialNodes}
|
||||||
@@ -82,7 +86,8 @@ const BasicFlow = () => {
|
|||||||
<button onClick={toggleClassnames} style={{ marginRight: 5 }}>
|
<button onClick={toggleClassnames} style={{ marginRight: 5 }}>
|
||||||
toggle classnames
|
toggle classnames
|
||||||
</button>
|
</button>
|
||||||
<button onClick={logToObject}>toObject</button>
|
<button onClick={logToObject} style={{ marginRight: 5 }}>toObject</button>
|
||||||
|
<button onClick={deleteElements}>delete selected elements</button>
|
||||||
</div>
|
</div>
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,94 +1,27 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import shallow from 'zustand/shallow';
|
|
||||||
|
|
||||||
import { useStore, useStoreApi } from '../store';
|
import { useStoreApi } from '../store';
|
||||||
import useKeyPress from './useKeyPress';
|
import useKeyPress from './useKeyPress';
|
||||||
import { getConnectedEdges } from '../utils/graph';
|
import { KeyCode } from '../types';
|
||||||
import { EdgeChange, KeyCode, NodeChange, Node, ReactFlowState } from '../types';
|
import useReactFlow from './useReactFlow';
|
||||||
|
|
||||||
interface HookParams {
|
interface HookParams {
|
||||||
deleteKeyCode: KeyCode | null;
|
deleteKeyCode: KeyCode | null;
|
||||||
multiSelectionKeyCode: KeyCode | null;
|
multiSelectionKeyCode: KeyCode | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
|
||||||
onNodesChange: s.onNodesChange,
|
|
||||||
onEdgesChange: s.onEdgesChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
|
export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const { onNodesChange, onEdgesChange } = useStore(selector, shallow);
|
const { deleteSelectedElements } = useReactFlow();
|
||||||
|
|
||||||
const deleteKeyPressed = useKeyPress(deleteKeyCode);
|
const deleteKeyPressed = useKeyPress(deleteKeyCode);
|
||||||
const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode);
|
const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { nodeInternals, edges, hasDefaultNodes, hasDefaultEdges, onNodesDelete, onEdgesDelete } = store.getState();
|
if (deleteKeyPressed) {
|
||||||
const nodes = Array.from(nodeInternals.values());
|
deleteSelectedElements();
|
||||||
const nodesToRemove = nodes.reduce<Node[]>((res, node) => {
|
|
||||||
if (!node.selected && node.parentNode && res.find((n) => n.id === node.parentNode)) {
|
|
||||||
res.push(node);
|
|
||||||
} else if (node.selected) {
|
|
||||||
res.push(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}, []);
|
|
||||||
const selectedEdges = edges.filter((e) => e.selected);
|
|
||||||
|
|
||||||
if (deleteKeyPressed && (nodesToRemove || selectedEdges)) {
|
|
||||||
const connectedEdges = getConnectedEdges(nodesToRemove, edges);
|
|
||||||
const edgesToRemove = [...selectedEdges, ...connectedEdges];
|
|
||||||
const edgeIdsToRemove = edgesToRemove.reduce<string[]>((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) {
|
|
||||||
const edgeChanges: EdgeChange[] = edgeIdsToRemove.map((id) => ({
|
|
||||||
id,
|
|
||||||
type: 'remove',
|
|
||||||
}));
|
|
||||||
onEdgesChange(edgeChanges);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 });
|
|
||||||
}
|
}
|
||||||
}, [deleteKeyPressed, onNodesChange, onEdgesChange]);
|
}, [deleteKeyPressed]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
store.setState({ multiSelectionActive: multiSelectionKeyPressed });
|
store.setState({ multiSelectionActive: multiSelectionKeyPressed });
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import useViewportHelper from './useViewportHelper';
|
import useViewportHelper from './useViewportHelper';
|
||||||
import { useStoreApi } from '../store';
|
import { useStore, useStoreApi } from '../store';
|
||||||
import {
|
import {
|
||||||
ReactFlowInstance,
|
ReactFlowInstance,
|
||||||
Instance,
|
Instance,
|
||||||
@@ -11,12 +11,25 @@ import {
|
|||||||
EdgeResetChange,
|
EdgeResetChange,
|
||||||
NodeRemoveChange,
|
NodeRemoveChange,
|
||||||
EdgeRemoveChange,
|
EdgeRemoveChange,
|
||||||
|
ReactFlowState,
|
||||||
|
EdgeChange,
|
||||||
|
NodeChange,
|
||||||
|
Node,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
import { getConnectedEdges } from '../utils/graph';
|
||||||
|
import shallow from 'zustand/shallow';
|
||||||
|
|
||||||
|
const selector = (s: ReactFlowState) => ({
|
||||||
|
onNodesChange: s.onNodesChange,
|
||||||
|
onEdgesChange: s.onEdgesChange,
|
||||||
|
});
|
||||||
|
|
||||||
export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlowInstance<NodeData, EdgeData> {
|
export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlowInstance<NodeData, EdgeData> {
|
||||||
const viewportHelper = useViewportHelper();
|
const viewportHelper = useViewportHelper();
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
|
|
||||||
|
const { onNodesChange, onEdgesChange } = useStore(selector, shallow);
|
||||||
|
|
||||||
const getNodes = useCallback<Instance.GetNodes<NodeData>>(() => {
|
const getNodes = useCallback<Instance.GetNodes<NodeData>>(() => {
|
||||||
const { nodeInternals } = store.getState();
|
const { nodeInternals } = store.getState();
|
||||||
const nodes = Array.from(nodeInternals.values());
|
const nodes = Array.from(nodeInternals.values());
|
||||||
@@ -110,6 +123,73 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const deleteSelectedElements = useCallback<Instance.DeleteSelectedElements>(() => {
|
||||||
|
const { nodeInternals, edges, hasDefaultNodes, hasDefaultEdges, onNodesDelete, onEdgesDelete } = store.getState();
|
||||||
|
const nodes = Array.from(nodeInternals.values());
|
||||||
|
const nodesToRemove = nodes.reduce<Node[]>((res, node) => {
|
||||||
|
if (!node.selected && node.parentNode && res.find((n) => n.id === node.parentNode)) {
|
||||||
|
res.push(node);
|
||||||
|
} else if (node.selected) {
|
||||||
|
res.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}, []);
|
||||||
|
const selectedEdges = edges.filter((e) => e.selected);
|
||||||
|
|
||||||
|
if (nodesToRemove || selectedEdges) {
|
||||||
|
const connectedEdges = getConnectedEdges(nodesToRemove, edges);
|
||||||
|
const edgesToRemove = [...selectedEdges, ...connectedEdges];
|
||||||
|
const edgeIdsToRemove = edgesToRemove.reduce<string[]>((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) {
|
||||||
|
const edgeChanges: EdgeChange[] = edgeIdsToRemove.map((id) => ({
|
||||||
|
id,
|
||||||
|
type: 'remove',
|
||||||
|
}));
|
||||||
|
onEdgesChange(edgeChanges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 useMemo(() => {
|
||||||
return {
|
return {
|
||||||
...viewportHelper,
|
...viewportHelper,
|
||||||
@@ -122,6 +202,19 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
|
|||||||
addNodes,
|
addNodes,
|
||||||
addEdges,
|
addEdges,
|
||||||
toObject,
|
toObject,
|
||||||
|
deleteSelectedElements,
|
||||||
};
|
};
|
||||||
}, [viewportHelper, getNodes, getNode, getEdges, getEdge, setNodes, setEdges, addNodes, addEdges, toObject]);
|
}, [
|
||||||
|
viewportHelper,
|
||||||
|
getNodes,
|
||||||
|
getNode,
|
||||||
|
getEdges,
|
||||||
|
getEdge,
|
||||||
|
setNodes,
|
||||||
|
setEdges,
|
||||||
|
addNodes,
|
||||||
|
addEdges,
|
||||||
|
toObject,
|
||||||
|
deleteSelectedElements,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export namespace Instance {
|
|||||||
export type GetEdge<EdgeData> = (id: string) => Edge<EdgeData> | undefined;
|
export type GetEdge<EdgeData> = (id: string) => Edge<EdgeData> | undefined;
|
||||||
export type AddEdges<EdgeData> = (payload: Edge<EdgeData>[] | Edge<EdgeData>) => void;
|
export type AddEdges<EdgeData> = (payload: Edge<EdgeData>[] | Edge<EdgeData>) => void;
|
||||||
export type ToObject<NodeData = any, EdgeData = any> = () => ReactFlowJsonObject<NodeData, EdgeData>;
|
export type ToObject<NodeData = any, EdgeData = any> = () => ReactFlowJsonObject<NodeData, EdgeData>;
|
||||||
|
export type DeleteSelectedElements = () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ReactFlowInstance<NodeData = any, EdgeData = any> = {
|
export type ReactFlowInstance<NodeData = any, EdgeData = any> = {
|
||||||
@@ -34,5 +35,6 @@ export type ReactFlowInstance<NodeData = any, EdgeData = any> = {
|
|||||||
addEdges: Instance.AddEdges<EdgeData>;
|
addEdges: Instance.AddEdges<EdgeData>;
|
||||||
getEdge: Instance.GetEdge<EdgeData>;
|
getEdge: Instance.GetEdge<EdgeData>;
|
||||||
toObject: Instance.ToObject<NodeData, EdgeData>;
|
toObject: Instance.ToObject<NodeData, EdgeData>;
|
||||||
|
deleteSelectedElements: Instance.DeleteSelectedElements;
|
||||||
viewportInitialized: boolean;
|
viewportInitialized: boolean;
|
||||||
} & Omit<ViewportHelperFunctions, 'initialized'>;
|
} & Omit<ViewportHelperFunctions, 'initialized'>;
|
||||||
|
|||||||
Reference in New Issue
Block a user