Merge pull request #3741 from xyflow/feat/on-before-delete
Feat(deletions): add onBeforeDelete handler
This commit is contained in:
@@ -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}
|
||||
>
|
||||
<MiniMap nodeBorderRadius={2} />
|
||||
|
||||
@@ -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 (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
@@ -99,6 +104,7 @@ const ValidationFlow = () => {
|
||||
onConnectEnd={onConnectEnd}
|
||||
onEdgeUpdate={onEdgeUpdate}
|
||||
isValidConnection={isValidConnection}
|
||||
onBeforeDelete={onBeforeDelete}
|
||||
fitView
|
||||
>
|
||||
<ConnectionStatus />
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -64,6 +64,7 @@ const reactFlowFieldsToTrack = [
|
||||
'isValidConnection',
|
||||
'selectNodesOnDrag',
|
||||
'nodeDragThreshold',
|
||||
'onBeforeDelete',
|
||||
] as const;
|
||||
|
||||
type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number];
|
||||
|
||||
@@ -70,6 +70,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
onSelectionContextMenu,
|
||||
onSelectionStart,
|
||||
onSelectionEnd,
|
||||
onBeforeDelete,
|
||||
connectionMode,
|
||||
connectionLineType = ConnectionLineType.Bezier,
|
||||
connectionLineStyle,
|
||||
@@ -284,6 +285,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
isValidConnection={isValidConnection}
|
||||
selectNodesOnDrag={selectNodesOnDrag}
|
||||
nodeDragThreshold={nodeDragThreshold}
|
||||
onBeforeDelete={onBeforeDelete}
|
||||
/>
|
||||
<SelectionListener onSelectionChange={onSelectionChange} />
|
||||
{children}
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -12,7 +12,6 @@ import type {
|
||||
EdgeResetChange,
|
||||
NodeRemoveChange,
|
||||
EdgeRemoveChange,
|
||||
NodeChange,
|
||||
Node,
|
||||
Edge,
|
||||
} from '../types';
|
||||
@@ -118,67 +117,65 @@ export default function useReactFlow<NodeType extends Node = Node, EdgeType exte
|
||||
};
|
||||
}, []);
|
||||
|
||||
const deleteElements = useCallback<Instance.DeleteElements>(({ nodes: nodesDeleted, edges: edgesDeleted }) => {
|
||||
const {
|
||||
nodes,
|
||||
edges,
|
||||
hasDefaultNodes,
|
||||
hasDefaultEdges,
|
||||
onNodesDelete,
|
||||
onEdgesDelete,
|
||||
onNodesChange,
|
||||
onEdgesChange,
|
||||
onDelete,
|
||||
} = store.getState();
|
||||
const { matchingNodes, matchingEdges } = getElementsToRemove<Node, Edge>({
|
||||
nodesToRemove: nodesDeleted || [],
|
||||
edgesToRemove: edgesDeleted || [],
|
||||
nodes,
|
||||
edges,
|
||||
});
|
||||
const deleteElements = useCallback<Instance.DeleteElements>(
|
||||
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] => {
|
||||
|
||||
@@ -79,6 +79,7 @@ export {
|
||||
type ColorMode,
|
||||
type ColorModeClass,
|
||||
type HandleType,
|
||||
type OnBeforeDelete,
|
||||
} from '@xyflow/system';
|
||||
|
||||
// system utils
|
||||
|
||||
@@ -21,6 +21,7 @@ import type {
|
||||
IsValidConnection,
|
||||
ColorMode,
|
||||
SnapGrid,
|
||||
OnBeforeDelete,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import type {
|
||||
@@ -100,6 +101,7 @@ export type ReactFlowProps = Omit<HTMLAttributes<HTMLDivElement>, 'onError'> & {
|
||||
onPaneMouseEnter?: (event: ReactMouseEvent) => void;
|
||||
onPaneMouseMove?: (event: ReactMouseEvent) => void;
|
||||
onPaneMouseLeave?: (event: ReactMouseEvent) => void;
|
||||
onBeforeDelete?: OnBeforeDelete;
|
||||
nodeTypes?: NodeTypes;
|
||||
edgeTypes?: EdgeTypes;
|
||||
connectionLineType?: ConnectionLineType;
|
||||
|
||||
@@ -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<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
||||
@@ -12,6 +12,7 @@ export type ReactFlowJsonObject<NodeType extends Node = Node, EdgeType extends E
|
||||
export type DeleteElementsOptions = {
|
||||
nodes?: (Node | { id: Node['id'] })[];
|
||||
edges?: (Edge | { id: Edge['id'] })[];
|
||||
onBeforeDelete?: OnBeforeDelete;
|
||||
};
|
||||
|
||||
export namespace Instance {
|
||||
@@ -31,10 +32,10 @@ export namespace Instance {
|
||||
NodeType,
|
||||
EdgeType
|
||||
>;
|
||||
export type DeleteElements = ({ nodes, edges }: DeleteElementsOptions) => {
|
||||
export type DeleteElements = (params: DeleteElementsOptions) => Promise<{
|
||||
deletedNodes: Node[];
|
||||
deletedEdges: Edge[];
|
||||
};
|
||||
}>;
|
||||
export type GetIntersectingNodes<NodeType extends Node = Node> = (
|
||||
node: NodeType | { id: Node['id'] } | Rect,
|
||||
partially?: boolean,
|
||||
|
||||
@@ -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[];
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount, hasContext } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import cc from 'classcat';
|
||||
import { ConnectionMode, PanOnScrollMode, type Viewport } from '@xyflow/system';
|
||||
|
||||
@@ -15,13 +16,7 @@
|
||||
import { Attribution } from '$lib/components/Attribution';
|
||||
import { key, useStore, createStoreContext } from '$lib/store';
|
||||
import type { SvelteFlowProps } from './types';
|
||||
import {
|
||||
updateStore,
|
||||
updateStoreByKeys,
|
||||
type UpdatableStoreProps,
|
||||
getColorModeClass
|
||||
} from './utils';
|
||||
import { get } from 'svelte/store';
|
||||
import { updateStore, updateStoreByKeys, type UpdatableStoreProps } from './utils';
|
||||
import { useColorModeClass } from '$lib/hooks/useColorModeClass';
|
||||
|
||||
type $$Props = SvelteFlowProps;
|
||||
@@ -81,6 +76,7 @@
|
||||
export let onconnect: $$Props['onconnect'] = undefined;
|
||||
export let onconnectstart: $$Props['onconnectstart'] = undefined;
|
||||
export let onconnectend: $$Props['onconnectend'] = undefined;
|
||||
export let onbeforedelete: $$Props['onbeforedelete'] = undefined;
|
||||
|
||||
export let defaultMarkerColor = '#b1b1b7';
|
||||
|
||||
@@ -158,7 +154,8 @@
|
||||
nodeDragThreshold,
|
||||
onconnect,
|
||||
onconnectstart,
|
||||
onconnectend
|
||||
onconnectend,
|
||||
onbeforedelete
|
||||
};
|
||||
|
||||
updateStoreByKeys(store, updatableProps);
|
||||
|
||||
@@ -18,7 +18,8 @@ import type {
|
||||
ColorMode,
|
||||
OnConnect,
|
||||
OnConnectStart,
|
||||
OnConnectEnd
|
||||
OnConnectEnd,
|
||||
OnBeforeDelete
|
||||
} from '@xyflow/system';
|
||||
|
||||
import type {
|
||||
@@ -93,6 +94,7 @@ export type SvelteFlowProps = DOMAttributes<HTMLDivElement> & {
|
||||
onMoveEnd?: OnMoveEnd;
|
||||
onerror?: OnError;
|
||||
ondelete?: OnDelete;
|
||||
onbeforedelete?: OnBeforeDelete;
|
||||
|
||||
onedgecreate?: OnEdgeCreate;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { SvelteFlowStore } from '$lib/store/types';
|
||||
import type { EdgeTypes, NodeTypes } from '$lib/types';
|
||||
import type { ColorMode, CoordinateExtent } from '@xyflow/system';
|
||||
import type { CoordinateExtent } from '@xyflow/system';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
// this is helper function for updating the store
|
||||
@@ -69,6 +69,7 @@ export type UpdatableStoreProps = {
|
||||
onconnect?: UnwrapWritable<SvelteFlowStore['onconnect']>;
|
||||
onconnectstart?: UnwrapWritable<SvelteFlowStore['onconnectstart']>;
|
||||
onconnectend?: UnwrapWritable<SvelteFlowStore['onconnectend']>;
|
||||
onbeforedelete?: UnwrapWritable<SvelteFlowStore['onbeforedelete']>;
|
||||
};
|
||||
|
||||
export function updateStoreByKeys(store: SvelteFlowStore, keys: UpdatableStoreProps) {
|
||||
@@ -81,15 +82,3 @@ export function updateStoreByKeys(store: SvelteFlowStore, keys: UpdatableStorePr
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function getColorModeClass(colorMode?: ColorMode) {
|
||||
if (colorMode !== 'system') {
|
||||
return colorMode;
|
||||
}
|
||||
|
||||
if (!colorMode || typeof window === 'undefined' || !window.matchMedia) {
|
||||
return 'light';
|
||||
}
|
||||
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
type Rect,
|
||||
getViewportForBounds,
|
||||
getElementsToRemove,
|
||||
rendererPointToPoint
|
||||
rendererPointToPoint,
|
||||
type OnBeforeDelete
|
||||
} from '@xyflow/system';
|
||||
|
||||
import { useStore } from '$lib/store';
|
||||
@@ -48,8 +49,9 @@ export function useSvelteFlow(): {
|
||||
fitBounds: (bounds: Rect, options?: FitBoundsOptions) => void;
|
||||
deleteElements: (
|
||||
nodesToRemove?: (Node | { id: Node['id'] })[],
|
||||
edgesToRemove?: (Edge | { id: Edge['id'] })[]
|
||||
) => { deletedNodes: Node[]; deletedEdges: Edge[] };
|
||||
edgesToRemove?: (Edge | { id: Edge['id'] })[],
|
||||
onBeforeDelete?: OnBeforeDelete
|
||||
) => Promise<{ deletedNodes: Node[]; deletedEdges: Edge[] }>;
|
||||
screenToFlowPosition: (position: XYPosition) => XYPosition;
|
||||
flowToScreenPosition: (position: XYPosition) => XYPosition;
|
||||
viewport: Writable<Viewport>;
|
||||
@@ -69,6 +71,7 @@ export function useSvelteFlow(): {
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
fitView,
|
||||
onbeforedelete,
|
||||
snapGrid,
|
||||
viewport,
|
||||
width,
|
||||
@@ -198,25 +201,29 @@ export function useSvelteFlow(): {
|
||||
|
||||
return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
|
||||
},
|
||||
deleteElements: (
|
||||
deleteElements: async (
|
||||
nodesToRemove: (Node | { id: Node['id'] })[] = [],
|
||||
edgesToRemove: (Edge | { id: Edge['id'] })[] = []
|
||||
edgesToRemove: (Edge | { id: Edge['id'] })[] = [],
|
||||
onBeforeDelete?: OnBeforeDelete
|
||||
) => {
|
||||
const _nodes = get(nodes);
|
||||
const _edges = get(edges);
|
||||
const { matchingNodes, matchingEdges } = getElementsToRemove<Node, Edge>({
|
||||
const { nodes: matchingNodes, edges: matchingEdges } = await getElementsToRemove({
|
||||
nodesToRemove,
|
||||
edgesToRemove,
|
||||
nodes: _nodes,
|
||||
edges: _edges
|
||||
nodes: get(nodes),
|
||||
edges: get(edges),
|
||||
onBeforeDelete
|
||||
});
|
||||
|
||||
if (matchingNodes) {
|
||||
nodes.set(_nodes.filter((node) => !matchingNodes.some(({ id }) => id === node.id)));
|
||||
nodes.update((nds) =>
|
||||
nds.filter((node) => !matchingNodes.some(({ id }) => id === node.id))
|
||||
);
|
||||
}
|
||||
|
||||
if (matchingEdges) {
|
||||
edges.set(_edges.filter((edge) => !matchingEdges.some(({ id }) => id === edge.id)));
|
||||
edges.update((eds) =>
|
||||
eds.filter((edge) => !matchingEdges.some(({ id }) => id === edge.id))
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -208,18 +208,19 @@ export function createStore({
|
||||
}
|
||||
}
|
||||
|
||||
store.deleteKeyPressed.subscribe((deleteKeyPressed) => {
|
||||
store.deleteKeyPressed.subscribe(async (deleteKeyPressed) => {
|
||||
if (deleteKeyPressed) {
|
||||
const nodes = get(store.nodes);
|
||||
const edges = get(store.edges);
|
||||
const selectedNodes = nodes.filter((node) => node.selected);
|
||||
const selectedEdges = edges.filter((edge) => edge.selected);
|
||||
|
||||
const { matchingNodes, matchingEdges } = getElementsToRemove<Node, Edge>({
|
||||
const { nodes: matchingNodes, edges: matchingEdges } = await getElementsToRemove({
|
||||
nodesToRemove: selectedNodes,
|
||||
edgesToRemove: selectedEdges,
|
||||
nodes,
|
||||
edges
|
||||
edges,
|
||||
onBeforeDelete: get(store.onbeforedelete)
|
||||
});
|
||||
|
||||
if (matchingNodes.length || matchingEdges.length) {
|
||||
|
||||
@@ -22,7 +22,8 @@ import {
|
||||
type OnConnect,
|
||||
type OnConnectStart,
|
||||
type OnConnectEnd,
|
||||
type NodeLookup
|
||||
type NodeLookup,
|
||||
type OnBeforeDelete
|
||||
} from '@xyflow/system';
|
||||
|
||||
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
||||
@@ -148,6 +149,7 @@ export const getInitialStore = ({
|
||||
onedgecreate: writable<OnEdgeCreate>(undefined),
|
||||
onconnect: writable<OnConnect>(undefined),
|
||||
onconnectstart: writable<OnConnectStart>(undefined),
|
||||
onconnectend: writable<OnConnectEnd>(undefined)
|
||||
onconnectend: writable<OnConnectEnd>(undefined),
|
||||
onbeforedelete: writable<OnBeforeDelete>(undefined)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -141,3 +141,11 @@ export type ColorModeClass = 'light' | 'dark';
|
||||
export type ColorMode = ColorModeClass | 'system';
|
||||
|
||||
export type ConnectionLookup = Map<string, Map<string, Connection>>;
|
||||
|
||||
export type OnBeforeDelete = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>({
|
||||
nodes,
|
||||
edges,
|
||||
}: {
|
||||
nodes: NodeType[];
|
||||
edges: EdgeType[];
|
||||
}) => Promise<boolean | { nodes: NodeType[]; edges: EdgeType[] }>;
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
NodeDragItem,
|
||||
CoordinateExtent,
|
||||
OnError,
|
||||
OnBeforeDelete,
|
||||
} from '../types';
|
||||
import { errorMessages } from '../constants';
|
||||
|
||||
@@ -332,47 +333,69 @@ export function calcNextPosition<NodeType extends NodeBase>(
|
||||
* @param param.edgesToRemove - The edges to remove
|
||||
* @param param.nodes - All nodes
|
||||
* @param param.edges - All edges
|
||||
* @returns matchingNodes: nodes that can be deleted, matchingEdges: edges that can be deleted
|
||||
* @param param.onBeforeDelete - Callback to check which nodes and edges can be deleted
|
||||
* @returns nodes: nodes that can be deleted, edges: edges that can be deleted
|
||||
*/
|
||||
export function getElementsToRemove<NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>({
|
||||
nodesToRemove,
|
||||
edgesToRemove,
|
||||
export async function getElementsToRemove<NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>({
|
||||
nodesToRemove = [],
|
||||
edgesToRemove = [],
|
||||
nodes,
|
||||
edges,
|
||||
onBeforeDelete,
|
||||
}: {
|
||||
nodesToRemove: Partial<NodeType>[];
|
||||
edgesToRemove: Partial<EdgeType>[];
|
||||
nodes: NodeType[];
|
||||
edges: EdgeType[];
|
||||
}): {
|
||||
matchingNodes: NodeType[];
|
||||
matchingEdges: EdgeType[];
|
||||
} {
|
||||
onBeforeDelete?: OnBeforeDelete;
|
||||
}): Promise<{
|
||||
nodes: NodeType[];
|
||||
edges: EdgeType[];
|
||||
}> {
|
||||
const nodeIds = nodesToRemove.map((node) => node.id);
|
||||
const matchingNodes: NodeType[] = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
if (node.deletable === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isIncluded = nodeIds.includes(node.id);
|
||||
const parentHit = !isIncluded && node.parentNode && matchingNodes.find((n) => n.id === node.parentNode);
|
||||
|
||||
if (isIncluded || parentHit) {
|
||||
matchingNodes.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
const edgeIds = edgesToRemove.map((edge) => edge.id);
|
||||
const deletableEdges = edges.filter((edge) => edge.deletable !== false);
|
||||
const connectedEdges = getConnectedEdgesBase(matchingNodes, deletableEdges);
|
||||
const matchingEdges: EdgeType[] = connectedEdges;
|
||||
|
||||
const matchingNodes = nodes.reduce<NodeType[]>((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);
|
||||
for (const edge of deletableEdges) {
|
||||
const isIncluded = edgeIds.includes(edge.id);
|
||||
|
||||
if (isIncluded && !matchingEdges.find((e) => e.id === edge.id)) {
|
||||
matchingEdges.push(edge);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}, []);
|
||||
const deletableEdges = edges.filter((e) => (typeof e.deletable === 'boolean' ? e.deletable : true));
|
||||
const initialHitEdges = deletableEdges.filter((e) => edgeIds.includes(e.id));
|
||||
const connectedEdges = getConnectedEdgesBase<NodeType, EdgeType>(matchingNodes, deletableEdges);
|
||||
const matchingEdges = connectedEdges.reduce((res, edge) => {
|
||||
if (!res.find((e) => e.id === edge.id)) {
|
||||
res.push(edge);
|
||||
}
|
||||
if (!onBeforeDelete) {
|
||||
return {
|
||||
edges: matchingEdges,
|
||||
nodes: matchingNodes,
|
||||
};
|
||||
}
|
||||
|
||||
return res;
|
||||
}, initialHitEdges);
|
||||
const onBeforeDeleteResult = await onBeforeDelete({
|
||||
nodes: matchingNodes,
|
||||
edges: matchingEdges,
|
||||
});
|
||||
|
||||
return {
|
||||
matchingEdges,
|
||||
matchingNodes,
|
||||
};
|
||||
if (typeof onBeforeDeleteResult === 'boolean') {
|
||||
return onBeforeDeleteResult ? { edges: matchingEdges, nodes: matchingNodes } : { edges: [], nodes: [] };
|
||||
}
|
||||
|
||||
return onBeforeDeleteResult;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user