feat(deletion): add onBeforeDelete handler

This commit is contained in:
moklick
2023-12-23 11:57:18 +01:00
parent 5e406875ea
commit 84147be699
19 changed files with 165 additions and 115 deletions
@@ -15,6 +15,8 @@ import {
Background, Background,
MiniMap, MiniMap,
ConnectionMode, ConnectionMode,
OnBeforeDelete,
OnDelete,
} from '@xyflow/react'; } from '@xyflow/react';
const onNodeDragStart = (_: ReactMouseEvent, node: Node, nodes: Node[]) => console.log('drag start', node, nodes); const onNodeDragStart = (_: ReactMouseEvent, node: Node, nodes: Node[]) => console.log('drag start', node, nodes);
@@ -45,8 +47,11 @@ const onEdgeMouseEnter = (_: ReactMouseEvent, edge: Edge) => console.log('edge m
const onEdgeMouseMove = (_: ReactMouseEvent, edge: Edge) => console.log('edge mouse move', edge); const onEdgeMouseMove = (_: ReactMouseEvent, edge: Edge) => console.log('edge mouse move', edge);
const onEdgeMouseLeave = (_: ReactMouseEvent, edge: Edge) => console.log('edge mouse leave', edge); const onEdgeMouseLeave = (_: ReactMouseEvent, edge: Edge) => console.log('edge mouse leave', edge);
const onEdgeDoubleClick = (_: ReactMouseEvent, edge: Edge) => console.log('edge double click', edge); const onEdgeDoubleClick = (_: ReactMouseEvent, edge: Edge) => console.log('edge double click', edge);
const onNodesDelete = (nodes: Node[]) => console.log('nodes delete', nodes); const onBeforeDelete: OnBeforeDelete = ({ nodes, edges }) => {
const onEdgesDelete = (edges: Edge[]) => console.log('edges delete', edges); console.log('on before delete', nodes, edges);
return true;
};
const onDelete: OnDelete = ({ nodes, edges }) => console.log('on delete', nodes, edges);
const onPaneMouseMove = (e: ReactMouseEvent) => console.log('pane move', e.clientX, e.clientY); const onPaneMouseMove = (e: ReactMouseEvent) => console.log('pane move', e.clientX, e.clientY);
const initialNodes: Node[] = [ const initialNodes: Node[] = [
@@ -226,8 +231,8 @@ const OverviewFlow = () => {
fitViewOptions={{ padding: 0.1 /*nodes: [{ id: '1' }]*/ }} fitViewOptions={{ padding: 0.1 /*nodes: [{ id: '1' }]*/ }}
attributionPosition="top-right" attributionPosition="top-right"
maxZoom={Infinity} maxZoom={Infinity}
onNodesDelete={onNodesDelete} onBeforeDelete={onBeforeDelete}
onEdgesDelete={onEdgesDelete} onDelete={onDelete}
onPaneMouseMove={onPaneMouseMove} onPaneMouseMove={onPaneMouseMove}
> >
<MiniMap nodeBorderRadius={2} /> <MiniMap nodeBorderRadius={2} />
@@ -16,6 +16,7 @@ import {
updateEdge, updateEdge,
Edge, Edge,
IsValidConnection, IsValidConnection,
OnBeforeDelete,
} from '@xyflow/react'; } from '@xyflow/react';
import ConnectionStatus from './ConnectionStatus'; import ConnectionStatus from './ConnectionStatus';
@@ -85,6 +86,10 @@ const ValidationFlow = () => {
[setEdges] [setEdges]
); );
const onBeforeDelete: OnBeforeDelete = useCallback(() => {
return true;
}, []);
return ( return (
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
@@ -99,6 +104,7 @@ const ValidationFlow = () => {
onConnectEnd={onConnectEnd} onConnectEnd={onConnectEnd}
onEdgeUpdate={onEdgeUpdate} onEdgeUpdate={onEdgeUpdate}
isValidConnection={isValidConnection} isValidConnection={isValidConnection}
onBeforeDelete={onBeforeDelete}
fitView fitView
> >
<ConnectionStatus /> <ConnectionStatus />
@@ -177,6 +177,10 @@
}} }}
on:selectionclick={(event) => console.log('on selection click', event)} on:selectionclick={(event) => console.log('on selection click', event)}
on:selectioncontextmenu={(event) => console.log('on selection contextmenu', event)} on:selectioncontextmenu={(event) => console.log('on selection contextmenu', event)}
onbeforedelete={({ nodes, edges }) => {
console.log('on before delete', nodes, edges);
return true;
}}
autoPanOnConnect autoPanOnConnect
autoPanOnNodeDrag autoPanOnNodeDrag
connectionMode={ConnectionMode.Strict} connectionMode={ConnectionMode.Strict}
@@ -64,6 +64,7 @@ const reactFlowFieldsToTrack = [
'isValidConnection', 'isValidConnection',
'selectNodesOnDrag', 'selectNodesOnDrag',
'nodeDragThreshold', 'nodeDragThreshold',
'onBeforeDelete',
] as const; ] as const;
type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number]; type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number];
@@ -70,6 +70,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
onSelectionContextMenu, onSelectionContextMenu,
onSelectionStart, onSelectionStart,
onSelectionEnd, onSelectionEnd,
onBeforeDelete,
connectionMode, connectionMode,
connectionLineType = ConnectionLineType.Bezier, connectionLineType = ConnectionLineType.Bezier,
connectionLineStyle, connectionLineStyle,
@@ -284,6 +285,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
isValidConnection={isValidConnection} isValidConnection={isValidConnection}
selectNodesOnDrag={selectNodesOnDrag} selectNodesOnDrag={selectNodesOnDrag}
nodeDragThreshold={nodeDragThreshold} nodeDragThreshold={nodeDragThreshold}
onBeforeDelete={onBeforeDelete}
/> />
<SelectionListener onSelectionChange={onSelectionChange} /> <SelectionListener onSelectionChange={onSelectionChange} />
{children} {children}
@@ -30,8 +30,8 @@ export default ({
useEffect(() => { useEffect(() => {
if (deleteKeyPressed) { if (deleteKeyPressed) {
const { edges, nodes } = store.getState(); const { edges, nodes, onBeforeDelete } = store.getState();
deleteElements({ nodes: nodes.filter(selected), edges: edges.filter(selected) }); deleteElements({ nodes: nodes.filter(selected), edges: edges.filter(selected), onBeforeDelete });
store.setState({ nodesSelectionActive: false }); store.setState({ nodesSelectionActive: false });
} }
}, [deleteKeyPressed]); }, [deleteKeyPressed]);
+43 -46
View File
@@ -12,7 +12,6 @@ import type {
EdgeResetChange, EdgeResetChange,
NodeRemoveChange, NodeRemoveChange,
EdgeRemoveChange, EdgeRemoveChange,
NodeChange,
Node, Node,
Edge, Edge,
} from '../types'; } 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 deleteElements = useCallback<Instance.DeleteElements>(
const { ({ nodes: nodesToRemove = [], edges: edgesToRemove = [], onBeforeDelete }) => {
nodes, const {
edges, nodes,
hasDefaultNodes, edges,
hasDefaultEdges, hasDefaultNodes,
onNodesDelete, hasDefaultEdges,
onEdgesDelete, onNodesDelete,
onNodesChange, onEdgesDelete,
onEdgesChange, onNodesChange,
onDelete, onEdgesChange,
} = store.getState(); onDelete,
const { matchingNodes, matchingEdges } = getElementsToRemove<Node, Edge>({ } = store.getState();
nodesToRemove: nodesDeleted || [], const { nodes: matchingNodes, edges: matchingEdges } = getElementsToRemove({
edgesToRemove: edgesDeleted || [], nodesToRemove,
nodes, edgesToRemove,
edges, nodes,
}); edges,
onBeforeDelete,
});
if (matchingNodes.length || matchingEdges.length) { const hasMatchingEdges = matchingEdges.length > 0;
if (hasDefaultEdges || hasDefaultNodes) { const hasMatchingNodes = matchingNodes.length > 0;
if (hasMatchingEdges) {
if (hasDefaultEdges) { if (hasDefaultEdges) {
store.setState({ store.setState({
edges: edges.filter((e) => !matchingEdges.some((mE) => mE.id === e.id)), 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) { if (hasDefaultNodes) {
store.setState({ store.setState({
nodes: nodes.filter((n) => !matchingNodes.some((mN) => mN.id === n.id)), 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) { if (hasMatchingNodes || hasMatchingEdges) {
onEdgesDelete?.(matchingEdges); onDelete?.({ nodes: matchingNodes, edges: matchingEdges });
if (onEdgesChange) {
onEdgesChange(
matchingEdges.map((edge) => ({
id: edge.id,
type: 'remove',
}))
);
}
} }
if (matchingNodes.length > 0) { return { deletedNodes: matchingNodes, deletedEdges: matchingEdges };
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 };
}, []);
const getNodeRect = useCallback( const getNodeRect = useCallback(
(nodeOrRect: NodeType | { id: Node['id'] } | Rect): [Rect | null, NodeType | null | undefined, boolean] => { (nodeOrRect: NodeType | { id: Node['id'] } | Rect): [Rect | null, NodeType | null | undefined, boolean] => {
+1
View File
@@ -79,6 +79,7 @@ export {
type ColorMode, type ColorMode,
type ColorModeClass, type ColorModeClass,
type HandleType, type HandleType,
type OnBeforeDelete,
} from '@xyflow/system'; } from '@xyflow/system';
// system utils // system utils
@@ -21,6 +21,7 @@ import type {
IsValidConnection, IsValidConnection,
ColorMode, ColorMode,
SnapGrid, SnapGrid,
OnBeforeDelete,
} from '@xyflow/system'; } from '@xyflow/system';
import type { import type {
@@ -100,6 +101,7 @@ export type ReactFlowProps = Omit<HTMLAttributes<HTMLDivElement>, 'onError'> & {
onPaneMouseEnter?: (event: ReactMouseEvent) => void; onPaneMouseEnter?: (event: ReactMouseEvent) => void;
onPaneMouseMove?: (event: ReactMouseEvent) => void; onPaneMouseMove?: (event: ReactMouseEvent) => void;
onPaneMouseLeave?: (event: ReactMouseEvent) => void; onPaneMouseLeave?: (event: ReactMouseEvent) => void;
onBeforeDelete?: OnBeforeDelete;
nodeTypes?: NodeTypes; nodeTypes?: NodeTypes;
edgeTypes?: EdgeTypes; edgeTypes?: EdgeTypes;
connectionLineType?: ConnectionLineType; connectionLineType?: ConnectionLineType;
+3 -2
View File
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-namespace */ /* 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 '.'; import type { Node, Edge, ViewportHelperFunctions } from '.';
export type ReactFlowJsonObject<NodeType extends Node = Node, EdgeType extends Edge = Edge> = { 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 = { export type DeleteElementsOptions = {
nodes?: (Node | { id: Node['id'] })[]; nodes?: (Node | { id: Node['id'] })[];
edges?: (Edge | { id: Edge['id'] })[]; edges?: (Edge | { id: Edge['id'] })[];
onBeforeDelete?: OnBeforeDelete;
}; };
export namespace Instance { export namespace Instance {
@@ -31,7 +32,7 @@ export namespace Instance {
NodeType, NodeType,
EdgeType EdgeType
>; >;
export type DeleteElements = ({ nodes, edges }: DeleteElementsOptions) => { export type DeleteElements = (params: DeleteElementsOptions) => {
deletedNodes: Node[]; deletedNodes: Node[];
deletedEdges: Edge[]; deletedEdges: Edge[];
}; };
+2
View File
@@ -27,6 +27,7 @@ import {
type EdgeLookup, type EdgeLookup,
type ConnectionLookup, type ConnectionLookup,
type NodeLookup, type NodeLookup,
OnBeforeDelete,
} from '@xyflow/system'; } from '@xyflow/system';
import type { import type {
@@ -132,6 +133,7 @@ export type ReactFlowStore = {
onViewportChangeStart?: OnViewportChange; onViewportChangeStart?: OnViewportChange;
onViewportChange?: OnViewportChange; onViewportChange?: OnViewportChange;
onViewportChangeEnd?: OnViewportChange; onViewportChangeEnd?: OnViewportChange;
onBeforeDelete?: OnBeforeDelete;
onSelectionChangeHandlers: OnSelectionChangeFunc[]; onSelectionChangeHandlers: OnSelectionChangeFunc[];
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { onMount, hasContext } from 'svelte'; import { onMount, hasContext } from 'svelte';
import { get } from 'svelte/store';
import cc from 'classcat'; import cc from 'classcat';
import { ConnectionMode, PanOnScrollMode, type Viewport } from '@xyflow/system'; import { ConnectionMode, PanOnScrollMode, type Viewport } from '@xyflow/system';
@@ -15,13 +16,7 @@
import { Attribution } from '$lib/components/Attribution'; import { Attribution } from '$lib/components/Attribution';
import { key, useStore, createStoreContext } from '$lib/store'; import { key, useStore, createStoreContext } from '$lib/store';
import type { SvelteFlowProps } from './types'; import type { SvelteFlowProps } from './types';
import { import { updateStore, updateStoreByKeys, type UpdatableStoreProps } from './utils';
updateStore,
updateStoreByKeys,
type UpdatableStoreProps,
getColorModeClass
} from './utils';
import { get } from 'svelte/store';
import { useColorModeClass } from '$lib/hooks/useColorModeClass'; import { useColorModeClass } from '$lib/hooks/useColorModeClass';
type $$Props = SvelteFlowProps; type $$Props = SvelteFlowProps;
@@ -81,6 +76,7 @@
export let onconnect: $$Props['onconnect'] = undefined; export let onconnect: $$Props['onconnect'] = undefined;
export let onconnectstart: $$Props['onconnectstart'] = undefined; export let onconnectstart: $$Props['onconnectstart'] = undefined;
export let onconnectend: $$Props['onconnectend'] = undefined; export let onconnectend: $$Props['onconnectend'] = undefined;
export let onbeforedelete: $$Props['onbeforedelete'] = undefined;
export let defaultMarkerColor = '#b1b1b7'; export let defaultMarkerColor = '#b1b1b7';
@@ -158,7 +154,8 @@
nodeDragThreshold, nodeDragThreshold,
onconnect, onconnect,
onconnectstart, onconnectstart,
onconnectend onconnectend,
onbeforedelete
}; };
updateStoreByKeys(store, updatableProps); updateStoreByKeys(store, updatableProps);
@@ -18,7 +18,8 @@ import type {
ColorMode, ColorMode,
OnConnect, OnConnect,
OnConnectStart, OnConnectStart,
OnConnectEnd OnConnectEnd,
OnBeforeDelete
} from '@xyflow/system'; } from '@xyflow/system';
import type { import type {
@@ -93,6 +94,7 @@ export type SvelteFlowProps = DOMAttributes<HTMLDivElement> & {
onMoveEnd?: OnMoveEnd; onMoveEnd?: OnMoveEnd;
onerror?: OnError; onerror?: OnError;
ondelete?: OnDelete; ondelete?: OnDelete;
onbeforedelete?: OnBeforeDelete;
onedgecreate?: OnEdgeCreate; onedgecreate?: OnEdgeCreate;
@@ -1,6 +1,6 @@
import type { SvelteFlowStore } from '$lib/store/types'; import type { SvelteFlowStore } from '$lib/store/types';
import type { EdgeTypes, NodeTypes } from '$lib/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'; import type { Writable } from 'svelte/store';
// this is helper function for updating the store // this is helper function for updating the store
@@ -69,6 +69,7 @@ export type UpdatableStoreProps = {
onconnect?: UnwrapWritable<SvelteFlowStore['onconnect']>; onconnect?: UnwrapWritable<SvelteFlowStore['onconnect']>;
onconnectstart?: UnwrapWritable<SvelteFlowStore['onconnectstart']>; onconnectstart?: UnwrapWritable<SvelteFlowStore['onconnectstart']>;
onconnectend?: UnwrapWritable<SvelteFlowStore['onconnectend']>; onconnectend?: UnwrapWritable<SvelteFlowStore['onconnectend']>;
onbeforedelete?: UnwrapWritable<SvelteFlowStore['onbeforedelete']>;
}; };
export function updateStoreByKeys(store: SvelteFlowStore, keys: UpdatableStoreProps) { 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';
}
+17 -10
View File
@@ -13,7 +13,8 @@ import {
type Rect, type Rect,
getViewportForBounds, getViewportForBounds,
getElementsToRemove, getElementsToRemove,
rendererPointToPoint rendererPointToPoint,
type OnBeforeDelete
} from '@xyflow/system'; } from '@xyflow/system';
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
@@ -48,7 +49,8 @@ export function useSvelteFlow(): {
fitBounds: (bounds: Rect, options?: FitBoundsOptions) => void; fitBounds: (bounds: Rect, options?: FitBoundsOptions) => void;
deleteElements: ( deleteElements: (
nodesToRemove?: (Node | { id: Node['id'] })[], nodesToRemove?: (Node | { id: Node['id'] })[],
edgesToRemove?: (Edge | { id: Edge['id'] })[] edgesToRemove?: (Edge | { id: Edge['id'] })[],
onBeforeDelete?: OnBeforeDelete
) => { deletedNodes: Node[]; deletedEdges: Edge[] }; ) => { deletedNodes: Node[]; deletedEdges: Edge[] };
screenToFlowPosition: (position: XYPosition) => XYPosition; screenToFlowPosition: (position: XYPosition) => XYPosition;
flowToScreenPosition: (position: XYPosition) => XYPosition; flowToScreenPosition: (position: XYPosition) => XYPosition;
@@ -69,6 +71,7 @@ export function useSvelteFlow(): {
zoomIn, zoomIn,
zoomOut, zoomOut,
fitView, fitView,
onbeforedelete,
snapGrid, snapGrid,
viewport, viewport,
width, width,
@@ -200,23 +203,27 @@ export function useSvelteFlow(): {
}, },
deleteElements: ( deleteElements: (
nodesToRemove: (Node | { id: Node['id'] })[] = [], nodesToRemove: (Node | { id: Node['id'] })[] = [],
edgesToRemove: (Edge | { id: Edge['id'] })[] = [] edgesToRemove: (Edge | { id: Edge['id'] })[] = [],
onBeforeDelete?: OnBeforeDelete
) => { ) => {
const _nodes = get(nodes); const { nodes: matchingNodes, edges: matchingEdges } = getElementsToRemove({
const _edges = get(edges);
const { matchingNodes, matchingEdges } = getElementsToRemove<Node, Edge>({
nodesToRemove, nodesToRemove,
edgesToRemove, edgesToRemove,
nodes: _nodes, nodes: get(nodes),
edges: _edges edges: get(edges),
onBeforeDelete
}); });
if (matchingNodes) { 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) { 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 { return {
+3 -2
View File
@@ -215,11 +215,12 @@ export function createStore({
const selectedNodes = nodes.filter((node) => node.selected); const selectedNodes = nodes.filter((node) => node.selected);
const selectedEdges = edges.filter((edge) => edge.selected); const selectedEdges = edges.filter((edge) => edge.selected);
const { matchingNodes, matchingEdges } = getElementsToRemove<Node, Edge>({ const { nodes: matchingNodes, edges: matchingEdges } = getElementsToRemove({
nodesToRemove: selectedNodes, nodesToRemove: selectedNodes,
edgesToRemove: selectedEdges, edgesToRemove: selectedEdges,
nodes, nodes,
edges edges,
onBeforeDelete: get(store.onbeforedelete)
}); });
if (matchingNodes.length || matchingEdges.length) { if (matchingNodes.length || matchingEdges.length) {
@@ -22,7 +22,8 @@ import {
type OnConnect, type OnConnect,
type OnConnectStart, type OnConnectStart,
type OnConnectEnd, type OnConnectEnd,
type NodeLookup type NodeLookup,
type OnBeforeDelete
} from '@xyflow/system'; } from '@xyflow/system';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte'; import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
@@ -148,6 +149,7 @@ export const getInitialStore = ({
onedgecreate: writable<OnEdgeCreate>(undefined), onedgecreate: writable<OnEdgeCreate>(undefined),
onconnect: writable<OnConnect>(undefined), onconnect: writable<OnConnect>(undefined),
onconnectstart: writable<OnConnectStart>(undefined), onconnectstart: writable<OnConnectStart>(undefined),
onconnectend: writable<OnConnectEnd>(undefined) onconnectend: writable<OnConnectEnd>(undefined),
onbeforedelete: writable<OnBeforeDelete>(undefined)
}; };
}; };
+8
View File
@@ -141,3 +141,11 @@ export type ColorModeClass = 'light' | 'dark';
export type ColorMode = ColorModeClass | 'system'; export type ColorMode = ColorModeClass | 'system';
export type ConnectionLookup = Map<string, Map<string, Connection>>; 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[];
}) => boolean | { nodes: NodeType[]; edges: EdgeType[] };
+48 -25
View File
@@ -22,6 +22,7 @@ import {
NodeDragItem, NodeDragItem,
CoordinateExtent, CoordinateExtent,
OnError, OnError,
OnBeforeDelete,
} from '../types'; } from '../types';
import { errorMessages } from '../constants'; import { errorMessages } from '../constants';
@@ -332,47 +333,69 @@ export function calcNextPosition<NodeType extends NodeBase>(
* @param param.edgesToRemove - The edges to remove * @param param.edgesToRemove - The edges to remove
* @param param.nodes - All nodes * @param param.nodes - All nodes
* @param param.edges - All edges * @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>({ export function getElementsToRemove<NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>({
nodesToRemove, nodesToRemove = [],
edgesToRemove, edgesToRemove = [],
nodes, nodes,
edges, edges,
onBeforeDelete,
}: { }: {
nodesToRemove: Partial<NodeType>[]; nodesToRemove: Partial<NodeType>[];
edgesToRemove: Partial<EdgeType>[]; edgesToRemove: Partial<EdgeType>[];
nodes: NodeType[]; nodes: NodeType[];
edges: EdgeType[]; edges: EdgeType[];
onBeforeDelete?: OnBeforeDelete;
}): { }): {
matchingNodes: NodeType[]; nodes: NodeType[];
matchingEdges: EdgeType[]; edges: EdgeType[];
} { } {
const nodeIds = nodesToRemove.map((node) => node.id); 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 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) => { for (const edge of deletableEdges) {
const parentHit = !nodeIds.includes(node.id) && node.parentNode && res.find((n) => n.id === node.parentNode); const isIncluded = edgeIds.includes(edge.id);
const deletable = typeof node.deletable === 'boolean' ? node.deletable : true;
if (deletable && (nodeIds.includes(node.id) || parentHit)) { if (isIncluded && !matchingEdges.find((e) => e.id === edge.id)) {
res.push(node); matchingEdges.push(edge);
} }
}
return res; if (!onBeforeDelete) {
}, []); return {
const deletableEdges = edges.filter((e) => (typeof e.deletable === 'boolean' ? e.deletable : true)); edges: matchingEdges,
const initialHitEdges = deletableEdges.filter((e) => edgeIds.includes(e.id)); nodes: matchingNodes,
const connectedEdges = getConnectedEdgesBase<NodeType, EdgeType>(matchingNodes, deletableEdges); };
const matchingEdges = connectedEdges.reduce((res, edge) => { }
if (!res.find((e) => e.id === edge.id)) {
res.push(edge);
}
return res; const onBeforeDeleteResult = onBeforeDelete({
}, initialHitEdges); nodes: matchingNodes,
edges: matchingEdges,
});
return { if (typeof onBeforeDeleteResult === 'boolean') {
matchingEdges, return onBeforeDeleteResult ? { edges: matchingEdges, nodes: matchingNodes } : { edges: [], nodes: [] };
matchingNodes, }
};
return onBeforeDeleteResult;
} }