feat(deletion): add onBeforeDelete handler
This commit is contained in:
@@ -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]);
|
||||||
|
|||||||
@@ -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] => {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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[];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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';
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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[] };
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user