From 99ec9f0f034e67799075e5334c4a364091d1eba6 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 2 Mar 2023 17:42:07 +0100 Subject: [PATCH 1/3] feat(reactflow): add isValidConnection prop closes #2876 --- .../vite-app/src/examples/Validation/index.tsx | 15 ++++++++++++--- .../core/src/components/Edges/wrapEdge.tsx | 8 ++++++-- packages/core/src/components/Handle/index.tsx | 18 ++++++++++++------ .../core/src/components/StoreUpdater/index.tsx | 3 +++ .../core/src/container/ReactFlow/index.tsx | 2 ++ packages/core/src/store/index.ts | 5 +++-- packages/core/src/store/initialState.ts | 1 + packages/core/src/types/component-props.ts | 2 ++ packages/core/src/types/general.ts | 4 ++++ 9 files changed, 45 insertions(+), 13 deletions(-) diff --git a/examples/vite-app/src/examples/Validation/index.tsx b/examples/vite-app/src/examples/Validation/index.tsx index 1488b48c..c55d8178 100644 --- a/examples/vite-app/src/examples/Validation/index.tsx +++ b/examples/vite-app/src/examples/Validation/index.tsx @@ -12,6 +12,8 @@ import ReactFlow, { OnConnectStart, OnConnectEnd, OnConnect, + updateEdge, + Edge, } from 'reactflow'; import styles from './validation.module.css'; @@ -28,15 +30,15 @@ const isValidConnection = (connection: Connection) => connection.target === 'B'; const CustomInput: FC = () => ( <>
Only connectable with B
- + ); const CustomNode: FC = ({ id }) => ( <> - +
{id}
- + ); @@ -74,6 +76,11 @@ const ValidationFlow = () => { [value] ); + const onEdgeUpdate = useCallback( + (oldEdge: Edge, newConnection: Connection) => setEdges((els) => updateEdge(oldEdge, newConnection, els)), + [setEdges] + ); + return ( { nodeTypes={nodeTypes} onConnectStart={onConnectStart} onConnectEnd={onConnectEnd} + onEdgeUpdate={onEdgeUpdate} + isValidConnection={isValidConnection} fitView /> ); diff --git a/packages/core/src/components/Edges/wrapEdge.tsx b/packages/core/src/components/Edges/wrapEdge.tsx index 80a6be7d..607f2c5a 100644 --- a/packages/core/src/components/Edges/wrapEdge.tsx +++ b/packages/core/src/components/Edges/wrapEdge.tsx @@ -11,6 +11,8 @@ import { getMouseHandler } from './utils'; import { elementSelectionKeys } from '../../utils'; import type { EdgeProps, WrapEdgeProps, Connection } from '../../types'; +const alwaysValidConnection = () => true; + export default (EdgeComponent: ComponentType) => { const EdgeWrapper = ({ id, @@ -94,12 +96,14 @@ export default (EdgeComponent: ComponentType) => { return; } + const { edges, isValidConnection: isValidConnectionStore } = store.getState(); const nodeId = isSourceHandle ? target : source; const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null; const handleType = isSourceHandle ? 'target' : 'source'; - const isValidConnection = () => true; + const isValidConnection = isValidConnectionStore || alwaysValidConnection; + const isTarget = isSourceHandle; - const edge = store.getState().edges.find((e) => e.id === id)!; + const edge = edges.find((e) => e.id === id)!; setUpdating(true); onEdgeUpdateStart?.(event, edge, handleType); diff --git a/packages/core/src/components/Handle/index.tsx b/packages/core/src/components/Handle/index.tsx index dab16653..e2d57d4d 100644 --- a/packages/core/src/components/Handle/index.tsx +++ b/packages/core/src/components/Handle/index.tsx @@ -27,7 +27,7 @@ const Handle = forwardRef( { type = 'source', position = Position.Top, - isValidConnection = alwaysValid, + isValidConnection, isConnectable = true, id, onConnect, @@ -61,8 +61,8 @@ const Handle = forwardRef( ...params, }; if (hasDefaultEdges) { - const { edges } = store.getState(); - store.setState({ edges: addEdge(edgeParams, edges) }); + const { edges, setEdges } = store.getState(); + setEdges(addEdge(edgeParams, edges)); } onConnectAction?.(edgeParams); @@ -81,7 +81,7 @@ const Handle = forwardRef( isTarget, getState: store.getState, setState: store.setState, - isValidConnection, + isValidConnection: isValidConnection || store.getState().isValidConnection || alwaysValid, }); } @@ -93,7 +93,12 @@ const Handle = forwardRef( }; const onClick = (event: ReactMouseEvent) => { - const { onClickConnectStart, onClickConnectEnd, connectionMode } = store.getState(); + const { + onClickConnectStart, + onClickConnectEnd, + connectionMode, + isValidConnection: isValidConnectionStore, + } = store.getState(); if (!connectionStartHandle) { onClickConnectStart?.(event, { nodeId, handleId, handleType: type }); store.setState({ connectionStartHandle: { nodeId, type, handleId } }); @@ -101,6 +106,7 @@ const Handle = forwardRef( } const doc = getHostForElement(event.target as HTMLElement); + const isValidConnectionHandler = isValidConnection || isValidConnectionStore || alwaysValid; const { connection, isValid } = isValidHandle( event, { @@ -112,7 +118,7 @@ const Handle = forwardRef( connectionStartHandle.nodeId, connectionStartHandle.handleId || null, connectionStartHandle.type, - isValidConnection, + isValidConnectionHandler, doc ); diff --git a/packages/core/src/components/StoreUpdater/index.tsx b/packages/core/src/components/StoreUpdater/index.tsx index febd3b0c..4491bb94 100644 --- a/packages/core/src/components/StoreUpdater/index.tsx +++ b/packages/core/src/components/StoreUpdater/index.tsx @@ -49,6 +49,7 @@ type StoreUpdaterProps = Pick< | 'autoPanOnNodeDrag' | 'onError' | 'connectionRadius' + | 'isValidConnection' > & { rfId: string }; const selector = (s: ReactFlowState) => ({ @@ -127,6 +128,7 @@ const StoreUpdater = ({ autoPanOnNodeDrag, onError, connectionRadius, + isValidConnection, }: StoreUpdaterProps) => { const { setNodes, @@ -184,6 +186,7 @@ const StoreUpdater = ({ useDirectStoreUpdater('autoPanOnNodeDrag', autoPanOnNodeDrag, store.setState); useDirectStoreUpdater('onError', onError, store.setState); useDirectStoreUpdater('connectionRadius', connectionRadius, store.setState); + useDirectStoreUpdater('isValidConnection', isValidConnection, store.setState); useStoreUpdater(nodes, setNodes); useStoreUpdater(edges, setEdges); diff --git a/packages/core/src/container/ReactFlow/index.tsx b/packages/core/src/container/ReactFlow/index.tsx index 9e7b7a49..9fa23f9c 100644 --- a/packages/core/src/container/ReactFlow/index.tsx +++ b/packages/core/src/container/ReactFlow/index.tsx @@ -163,6 +163,7 @@ const ReactFlow = forwardRef( autoPanOnConnect = true, autoPanOnNodeDrag = true, connectionRadius = 20, + isValidConnection, onError, style, id, @@ -295,6 +296,7 @@ const ReactFlow = forwardRef( autoPanOnNodeDrag={autoPanOnNodeDrag} onError={onError} connectionRadius={connectionRadius} + isValidConnection={isValidConnection} /> {children} diff --git a/packages/core/src/store/index.ts b/packages/core/src/store/index.ts index c85225db..79d95193 100644 --- a/packages/core/src/store/index.ts +++ b/packages/core/src/store/index.ts @@ -33,8 +33,9 @@ const createRFStore = () => return Array.from(get().nodeInternals.values()); }, setEdges: (edges: Edge[]) => { - const { defaultEdgeOptions = {} } = get(); - set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) }); + const { defaultEdgeOptions = null, isValidConnection } = get(); + const validEdges = isValidConnection ? edges.filter(isValidConnection) : edges; + set({ edges: defaultEdgeOptions ? validEdges.map((e) => ({ ...defaultEdgeOptions, ...e })) : edges }); }, setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => { const hasDefaultNodes = typeof nodes !== 'undefined'; diff --git a/packages/core/src/store/initialState.ts b/packages/core/src/store/initialState.ts index 99e9d298..55aca27f 100644 --- a/packages/core/src/store/initialState.ts +++ b/packages/core/src/store/initialState.ts @@ -62,6 +62,7 @@ const initialState: ReactFlowStore = { autoPanOnNodeDrag: true, connectionRadius: 20, onError: devWarn, + isValidConnection: undefined, }; export default initialState; diff --git a/packages/core/src/types/component-props.ts b/packages/core/src/types/component-props.ts index 1614805a..cae9da3b 100644 --- a/packages/core/src/types/component-props.ts +++ b/packages/core/src/types/component-props.ts @@ -38,6 +38,7 @@ import type { SelectionMode, OnError, } from '.'; +import { ValidConnectionFunc } from '../components/Handle/utils'; export type ReactFlowProps = HTMLAttributes & { nodes?: Node[]; @@ -144,6 +145,7 @@ export type ReactFlowProps = HTMLAttributes & { autoPanOnConnect?: boolean; connectionRadius?: number; onError?: OnError; + isValidConnection?: ValidConnectionFunc; }; export type ReactFlowRefType = HTMLDivElement; diff --git a/packages/core/src/types/general.ts b/packages/core/src/types/general.ts index f628727e..a34f4a73 100644 --- a/packages/core/src/types/general.ts +++ b/packages/core/src/types/general.ts @@ -61,6 +61,8 @@ export interface Connection { targetHandle: string | null; } +export type IsValidConnection = (edge: Edge | Connection) => boolean; + export type ConnectionStatus = 'valid' | 'invalid'; export enum ConnectionMode { @@ -223,6 +225,8 @@ export type ReactFlowStore = { autoPanOnConnect: boolean; autoPanOnNodeDrag: boolean; connectionRadius: number; + + isValidConnection?: IsValidConnection; }; export type ReactFlowActions = { From f783bdc25fbe7ca102c7c02a08688434a7121aec Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 6 Mar 2023 14:58:32 +0100 Subject: [PATCH 2/3] refactor(store): dont use isValidConn for edges --- packages/core/src/store/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/core/src/store/index.ts b/packages/core/src/store/index.ts index 79d95193..056957c3 100644 --- a/packages/core/src/store/index.ts +++ b/packages/core/src/store/index.ts @@ -33,9 +33,8 @@ const createRFStore = () => return Array.from(get().nodeInternals.values()); }, setEdges: (edges: Edge[]) => { - const { defaultEdgeOptions = null, isValidConnection } = get(); - const validEdges = isValidConnection ? edges.filter(isValidConnection) : edges; - set({ edges: defaultEdgeOptions ? validEdges.map((e) => ({ ...defaultEdgeOptions, ...e })) : edges }); + const { defaultEdgeOptions = null } = get(); + set({ edges: defaultEdgeOptions ? edges.map((e) => ({ ...defaultEdgeOptions, ...e })) : edges }); }, setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => { const hasDefaultNodes = typeof nodes !== 'undefined'; From b88865140c72fa7e92a883498768000cb2cc96a7 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 6 Mar 2023 14:59:59 +0100 Subject: [PATCH 3/3] chore(changeset): add --- .changeset/thick-garlics-tap.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/thick-garlics-tap.md diff --git a/.changeset/thick-garlics-tap.md b/.changeset/thick-garlics-tap.md new file mode 100644 index 00000000..14705425 --- /dev/null +++ b/.changeset/thick-garlics-tap.md @@ -0,0 +1,5 @@ +--- +'@reactflow/core': minor +--- + +add isValidConnection prop for ReactFlow component