Merge pull request #2877 from wbkd/feat/isValidConnection
feat(reactflow): add isValidConnection prop
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@reactflow/core': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
add isValidConnection prop for ReactFlow component
|
||||||
@@ -12,6 +12,8 @@ import ReactFlow, {
|
|||||||
OnConnectStart,
|
OnConnectStart,
|
||||||
OnConnectEnd,
|
OnConnectEnd,
|
||||||
OnConnect,
|
OnConnect,
|
||||||
|
updateEdge,
|
||||||
|
Edge,
|
||||||
} from 'reactflow';
|
} from 'reactflow';
|
||||||
|
|
||||||
import styles from './validation.module.css';
|
import styles from './validation.module.css';
|
||||||
@@ -28,15 +30,15 @@ const isValidConnection = (connection: Connection) => connection.target === 'B';
|
|||||||
const CustomInput: FC<NodeProps> = () => (
|
const CustomInput: FC<NodeProps> = () => (
|
||||||
<>
|
<>
|
||||||
<div>Only connectable with B</div>
|
<div>Only connectable with B</div>
|
||||||
<Handle type="source" position={Position.Right} isValidConnection={isValidConnection} />
|
<Handle type="source" position={Position.Right} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const CustomNode: FC<NodeProps> = ({ id }) => (
|
const CustomNode: FC<NodeProps> = ({ id }) => (
|
||||||
<>
|
<>
|
||||||
<Handle type="target" position={Position.Left} isValidConnection={isValidConnection} />
|
<Handle type="target" position={Position.Left} />
|
||||||
<div>{id}</div>
|
<div>{id}</div>
|
||||||
<Handle type="source" position={Position.Right} isValidConnection={isValidConnection} />
|
<Handle type="source" position={Position.Right} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -74,6 +76,11 @@ const ValidationFlow = () => {
|
|||||||
[value]
|
[value]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onEdgeUpdate = useCallback(
|
||||||
|
(oldEdge: Edge, newConnection: Connection) => setEdges((els) => updateEdge(oldEdge, newConnection, els)),
|
||||||
|
[setEdges]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
@@ -86,6 +93,8 @@ const ValidationFlow = () => {
|
|||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
onConnectStart={onConnectStart}
|
onConnectStart={onConnectStart}
|
||||||
onConnectEnd={onConnectEnd}
|
onConnectEnd={onConnectEnd}
|
||||||
|
onEdgeUpdate={onEdgeUpdate}
|
||||||
|
isValidConnection={isValidConnection}
|
||||||
fitView
|
fitView
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { getMouseHandler } from './utils';
|
|||||||
import { elementSelectionKeys } from '../../utils';
|
import { elementSelectionKeys } from '../../utils';
|
||||||
import type { EdgeProps, WrapEdgeProps, Connection } from '../../types';
|
import type { EdgeProps, WrapEdgeProps, Connection } from '../../types';
|
||||||
|
|
||||||
|
const alwaysValidConnection = () => true;
|
||||||
|
|
||||||
export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||||
const EdgeWrapper = ({
|
const EdgeWrapper = ({
|
||||||
id,
|
id,
|
||||||
@@ -94,12 +96,14 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { edges, isValidConnection: isValidConnectionStore } = store.getState();
|
||||||
const nodeId = isSourceHandle ? target : source;
|
const nodeId = isSourceHandle ? target : source;
|
||||||
const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null;
|
const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null;
|
||||||
const handleType = isSourceHandle ? 'target' : 'source';
|
const handleType = isSourceHandle ? 'target' : 'source';
|
||||||
const isValidConnection = () => true;
|
const isValidConnection = isValidConnectionStore || alwaysValidConnection;
|
||||||
|
|
||||||
const isTarget = isSourceHandle;
|
const isTarget = isSourceHandle;
|
||||||
const edge = store.getState().edges.find((e) => e.id === id)!;
|
const edge = edges.find((e) => e.id === id)!;
|
||||||
|
|
||||||
setUpdating(true);
|
setUpdating(true);
|
||||||
onEdgeUpdateStart?.(event, edge, handleType);
|
onEdgeUpdateStart?.(event, edge, handleType);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
{
|
{
|
||||||
type = 'source',
|
type = 'source',
|
||||||
position = Position.Top,
|
position = Position.Top,
|
||||||
isValidConnection = alwaysValid,
|
isValidConnection,
|
||||||
isConnectable = true,
|
isConnectable = true,
|
||||||
id,
|
id,
|
||||||
onConnect,
|
onConnect,
|
||||||
@@ -61,8 +61,8 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
...params,
|
...params,
|
||||||
};
|
};
|
||||||
if (hasDefaultEdges) {
|
if (hasDefaultEdges) {
|
||||||
const { edges } = store.getState();
|
const { edges, setEdges } = store.getState();
|
||||||
store.setState({ edges: addEdge(edgeParams, edges) });
|
setEdges(addEdge(edgeParams, edges));
|
||||||
}
|
}
|
||||||
|
|
||||||
onConnectAction?.(edgeParams);
|
onConnectAction?.(edgeParams);
|
||||||
@@ -81,7 +81,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
isTarget,
|
isTarget,
|
||||||
getState: store.getState,
|
getState: store.getState,
|
||||||
setState: store.setState,
|
setState: store.setState,
|
||||||
isValidConnection,
|
isValidConnection: isValidConnection || store.getState().isValidConnection || alwaysValid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +93,12 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onClick = (event: ReactMouseEvent) => {
|
const onClick = (event: ReactMouseEvent) => {
|
||||||
const { onClickConnectStart, onClickConnectEnd, connectionMode } = store.getState();
|
const {
|
||||||
|
onClickConnectStart,
|
||||||
|
onClickConnectEnd,
|
||||||
|
connectionMode,
|
||||||
|
isValidConnection: isValidConnectionStore,
|
||||||
|
} = store.getState();
|
||||||
if (!connectionStartHandle) {
|
if (!connectionStartHandle) {
|
||||||
onClickConnectStart?.(event, { nodeId, handleId, handleType: type });
|
onClickConnectStart?.(event, { nodeId, handleId, handleType: type });
|
||||||
store.setState({ connectionStartHandle: { nodeId, type, handleId } });
|
store.setState({ connectionStartHandle: { nodeId, type, handleId } });
|
||||||
@@ -101,6 +106,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const doc = getHostForElement(event.target as HTMLElement);
|
const doc = getHostForElement(event.target as HTMLElement);
|
||||||
|
const isValidConnectionHandler = isValidConnection || isValidConnectionStore || alwaysValid;
|
||||||
const { connection, isValid } = isValidHandle(
|
const { connection, isValid } = isValidHandle(
|
||||||
event,
|
event,
|
||||||
{
|
{
|
||||||
@@ -112,7 +118,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
connectionStartHandle.nodeId,
|
connectionStartHandle.nodeId,
|
||||||
connectionStartHandle.handleId || null,
|
connectionStartHandle.handleId || null,
|
||||||
connectionStartHandle.type,
|
connectionStartHandle.type,
|
||||||
isValidConnection,
|
isValidConnectionHandler,
|
||||||
doc
|
doc
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ type StoreUpdaterProps = Pick<
|
|||||||
| 'autoPanOnNodeDrag'
|
| 'autoPanOnNodeDrag'
|
||||||
| 'onError'
|
| 'onError'
|
||||||
| 'connectionRadius'
|
| 'connectionRadius'
|
||||||
|
| 'isValidConnection'
|
||||||
> & { rfId: string };
|
> & { rfId: string };
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
@@ -127,6 +128,7 @@ const StoreUpdater = ({
|
|||||||
autoPanOnNodeDrag,
|
autoPanOnNodeDrag,
|
||||||
onError,
|
onError,
|
||||||
connectionRadius,
|
connectionRadius,
|
||||||
|
isValidConnection,
|
||||||
}: StoreUpdaterProps) => {
|
}: StoreUpdaterProps) => {
|
||||||
const {
|
const {
|
||||||
setNodes,
|
setNodes,
|
||||||
@@ -184,6 +186,7 @@ const StoreUpdater = ({
|
|||||||
useDirectStoreUpdater('autoPanOnNodeDrag', autoPanOnNodeDrag, store.setState);
|
useDirectStoreUpdater('autoPanOnNodeDrag', autoPanOnNodeDrag, store.setState);
|
||||||
useDirectStoreUpdater('onError', onError, store.setState);
|
useDirectStoreUpdater('onError', onError, store.setState);
|
||||||
useDirectStoreUpdater('connectionRadius', connectionRadius, store.setState);
|
useDirectStoreUpdater('connectionRadius', connectionRadius, store.setState);
|
||||||
|
useDirectStoreUpdater('isValidConnection', isValidConnection, store.setState);
|
||||||
|
|
||||||
useStoreUpdater<Node[]>(nodes, setNodes);
|
useStoreUpdater<Node[]>(nodes, setNodes);
|
||||||
useStoreUpdater<Edge[]>(edges, setEdges);
|
useStoreUpdater<Edge[]>(edges, setEdges);
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
autoPanOnConnect = true,
|
autoPanOnConnect = true,
|
||||||
autoPanOnNodeDrag = true,
|
autoPanOnNodeDrag = true,
|
||||||
connectionRadius = 20,
|
connectionRadius = 20,
|
||||||
|
isValidConnection,
|
||||||
onError,
|
onError,
|
||||||
style,
|
style,
|
||||||
id,
|
id,
|
||||||
@@ -295,6 +296,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
autoPanOnNodeDrag={autoPanOnNodeDrag}
|
autoPanOnNodeDrag={autoPanOnNodeDrag}
|
||||||
onError={onError}
|
onError={onError}
|
||||||
connectionRadius={connectionRadius}
|
connectionRadius={connectionRadius}
|
||||||
|
isValidConnection={isValidConnection}
|
||||||
/>
|
/>
|
||||||
<SelectionListener onSelectionChange={onSelectionChange} />
|
<SelectionListener onSelectionChange={onSelectionChange} />
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ const createRFStore = () =>
|
|||||||
return Array.from(get().nodeInternals.values());
|
return Array.from(get().nodeInternals.values());
|
||||||
},
|
},
|
||||||
setEdges: (edges: Edge[]) => {
|
setEdges: (edges: Edge[]) => {
|
||||||
const { defaultEdgeOptions = {} } = get();
|
const { defaultEdgeOptions = null } = get();
|
||||||
set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) });
|
set({ edges: defaultEdgeOptions ? edges.map((e) => ({ ...defaultEdgeOptions, ...e })) : edges });
|
||||||
},
|
},
|
||||||
setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => {
|
setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => {
|
||||||
const hasDefaultNodes = typeof nodes !== 'undefined';
|
const hasDefaultNodes = typeof nodes !== 'undefined';
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ const initialState: ReactFlowStore = {
|
|||||||
autoPanOnNodeDrag: true,
|
autoPanOnNodeDrag: true,
|
||||||
connectionRadius: 20,
|
connectionRadius: 20,
|
||||||
onError: devWarn,
|
onError: devWarn,
|
||||||
|
isValidConnection: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default initialState;
|
export default initialState;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import type {
|
|||||||
SelectionMode,
|
SelectionMode,
|
||||||
OnError,
|
OnError,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
import { ValidConnectionFunc } from '../components/Handle/utils';
|
||||||
|
|
||||||
export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
|
export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
nodes?: Node[];
|
nodes?: Node[];
|
||||||
@@ -144,6 +145,7 @@ export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
|
|||||||
autoPanOnConnect?: boolean;
|
autoPanOnConnect?: boolean;
|
||||||
connectionRadius?: number;
|
connectionRadius?: number;
|
||||||
onError?: OnError;
|
onError?: OnError;
|
||||||
|
isValidConnection?: ValidConnectionFunc;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReactFlowRefType = HTMLDivElement;
|
export type ReactFlowRefType = HTMLDivElement;
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ export interface Connection {
|
|||||||
targetHandle: string | null;
|
targetHandle: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IsValidConnection = (edge: Edge | Connection) => boolean;
|
||||||
|
|
||||||
export type ConnectionStatus = 'valid' | 'invalid';
|
export type ConnectionStatus = 'valid' | 'invalid';
|
||||||
|
|
||||||
export enum ConnectionMode {
|
export enum ConnectionMode {
|
||||||
@@ -223,6 +225,8 @@ export type ReactFlowStore = {
|
|||||||
autoPanOnConnect: boolean;
|
autoPanOnConnect: boolean;
|
||||||
autoPanOnNodeDrag: boolean;
|
autoPanOnNodeDrag: boolean;
|
||||||
connectionRadius: number;
|
connectionRadius: number;
|
||||||
|
|
||||||
|
isValidConnection?: IsValidConnection;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReactFlowActions = {
|
export type ReactFlowActions = {
|
||||||
|
|||||||
Reference in New Issue
Block a user