Merge pull request #2803 from wbkd/feat/status-connectionline

feat(connection): add status class (valid or invalid) while in connection radius
This commit is contained in:
Moritz Klack
2023-02-04 11:11:30 +01:00
committed by GitHub
9 changed files with 60 additions and 8 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@reactflow/core': patch
---
connection: add status class (valid or invalid) while in connection radius
@@ -31,3 +31,11 @@
.validationflow :global .valid { .validationflow :global .valid {
background: #55dd99; background: #55dd99;
} }
.validationflow :global .valid .react-flow__connection-path {
stroke: #55dd99;
}
.validationflow :global .invalid .react-flow__connection-path {
stroke: #ff6060;
}
@@ -1,12 +1,19 @@
import { CSSProperties, useCallback } from 'react'; import { CSSProperties, useCallback } from 'react';
import { shallow } from 'zustand/shallow'; import { shallow } from 'zustand/shallow';
import cc from 'classcat';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { getBezierPath } from '../Edges/BezierEdge'; import { getBezierPath } from '../Edges/BezierEdge';
import { getSmoothStepPath } from '../Edges/SmoothStepEdge'; import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
import { getSimpleBezierPath } from '../Edges/SimpleBezierEdge'; import { getSimpleBezierPath } from '../Edges/SimpleBezierEdge';
import { internalsSymbol } from '../../utils'; import { internalsSymbol } from '../../utils';
import type { ConnectionLineComponent, HandleType, ReactFlowState, ReactFlowStore } from '../../types'; import type {
ConnectionLineComponent,
ConnectionStatus,
HandleType,
ReactFlowState,
ReactFlowStore,
} from '../../types';
import { Position, ConnectionLineType, ConnectionMode } from '../../types'; import { Position, ConnectionLineType, ConnectionMode } from '../../types';
type ConnectionLineProps = { type ConnectionLineProps = {
@@ -15,6 +22,7 @@ type ConnectionLineProps = {
type: ConnectionLineType; type: ConnectionLineType;
style?: CSSProperties; style?: CSSProperties;
CustomComponent?: ConnectionLineComponent; CustomComponent?: ConnectionLineComponent;
connectionStatus: ConnectionStatus | null;
}; };
const oppositePosition = { const oppositePosition = {
@@ -30,6 +38,7 @@ const ConnectionLine = ({
style, style,
type = ConnectionLineType.Bezier, type = ConnectionLineType.Bezier,
CustomComponent, CustomComponent,
connectionStatus,
}: ConnectionLineProps) => { }: ConnectionLineProps) => {
const { fromNode, handleId, toX, toY, connectionMode } = useStore( const { fromNode, handleId, toX, toY, connectionMode } = useStore(
useCallback( useCallback(
@@ -80,6 +89,7 @@ const ConnectionLine = ({
toY={toY} toY={toY}
fromPosition={fromPosition} fromPosition={fromPosition}
toPosition={toPosition} toPosition={toPosition}
connectionStatus={connectionStatus}
/> />
); );
} }
@@ -127,12 +137,13 @@ const selector = (s: ReactFlowState) => ({
nodeId: s.connectionNodeId, nodeId: s.connectionNodeId,
handleType: s.connectionHandleType, handleType: s.connectionHandleType,
nodesConnectable: s.nodesConnectable, nodesConnectable: s.nodesConnectable,
connectionStatus: s.connectionStatus,
width: s.width, width: s.width,
height: s.height, height: s.height,
}); });
function ConnectionLineWrapper({ containerStyle, style, type, component }: ConnectionLineWrapperProps) { function ConnectionLineWrapper({ containerStyle, style, type, component }: ConnectionLineWrapperProps) {
const { nodeId, handleType, nodesConnectable, width, height } = useStore(selector, shallow); const { nodeId, handleType, nodesConnectable, width, height, connectionStatus } = useStore(selector, shallow);
const isValid = !!(nodeId && handleType && width && nodesConnectable); const isValid = !!(nodeId && handleType && width && nodesConnectable);
if (!isValid) { if (!isValid) {
@@ -146,8 +157,15 @@ function ConnectionLineWrapper({ containerStyle, style, type, component }: Conne
height={height} height={height}
className="react-flow__edges react-flow__connectionline react-flow__container" className="react-flow__edges react-flow__connectionline react-flow__container"
> >
<g className="react-flow__connection"> <g className={cc(['react-flow__connection', connectionStatus])}>
<ConnectionLine nodeId={nodeId} handleType={handleType} style={style} type={type} CustomComponent={component} /> <ConnectionLine
nodeId={nodeId}
handleType={handleType}
style={style}
type={type}
CustomComponent={component}
connectionStatus={connectionStatus}
/>
</g> </g>
</svg> </svg>
); );
@@ -2,11 +2,12 @@ import type { MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } fro
import { StoreApi } from 'zustand'; import { StoreApi } from 'zustand';
import { getHostForElement, calcAutoPan, getEventPosition } from '../../utils'; import { getHostForElement, calcAutoPan, getEventPosition } from '../../utils';
import type { OnConnect, HandleType, ReactFlowState, Connection } from '../../types'; import type { OnConnect, HandleType, ReactFlowState, Connection, ConnectionStatus } from '../../types';
import { pointToRendererPoint, rendererPointToPoint } from '../../utils/graph'; import { pointToRendererPoint, rendererPointToPoint } from '../../utils/graph';
import { import {
ConnectionHandle, ConnectionHandle,
getClosestHandle, getClosestHandle,
getConnectionStatus,
getHandleLookup, getHandleLookup,
getHandleType, getHandleType,
isValidHandle, isValidHandle,
@@ -91,6 +92,7 @@ export function handlePointerDown({
connectionNodeId: nodeId, connectionNodeId: nodeId,
connectionHandleId: handleId, connectionHandleId: handleId,
connectionHandleType: handleType, connectionHandleType: handleType,
connectionStatus: null,
}); });
onConnectStart?.(event, { nodeId, handleId, handleType }); onConnectStart?.(event, { nodeId, handleId, handleType });
@@ -123,7 +125,7 @@ export function handlePointerDown({
setState({ setState({
connectionPosition: connectionPosition:
prevClosestHandle && isValid prevClosestHandle && result.isValid
? rendererPointToPoint( ? rendererPointToPoint(
{ {
x: prevClosestHandle.x, x: prevClosestHandle.x,
@@ -132,6 +134,7 @@ export function handlePointerDown({
transform transform
) )
: connectionPosition, : connectionPosition,
connectionStatus: getConnectionStatus(!!prevClosestHandle, result.isValid),
}); });
if (!prevClosestHandle) { if (!prevClosestHandle) {
+13 -1
View File
@@ -1,6 +1,6 @@
import { MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } from 'react'; import { MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } from 'react';
import { ConnectionMode } from '../../types'; import { ConnectionMode, ConnectionStatus } from '../../types';
import { getEventPosition, internalsSymbol } from '../../utils'; import { getEventPosition, internalsSymbol } from '../../utils';
import type { Connection, HandleType, XYPosition, Node, NodeHandleBounds } from '../../types'; import type { Connection, HandleType, XYPosition, Node, NodeHandleBounds } from '../../types';
@@ -167,3 +167,15 @@ export function getHandleType(
export function resetRecentHandle(handleDomNode: Element): void { export function resetRecentHandle(handleDomNode: Element): void {
handleDomNode?.classList.remove('valid', 'connecting', 'react-flow__handle-valid', 'react-flow__handle-connecting'); handleDomNode?.classList.remove('valid', 'connecting', 'react-flow__handle-valid', 'react-flow__handle-connecting');
} }
export function getConnectionStatus(isInsideConnectionRadius: boolean, isHandleValid: boolean) {
let connectionStatus = null;
if (isHandleValid) {
connectionStatus = 'valid';
} else if (isInsideConnectionRadius && !isHandleValid) {
connectionStatus = 'invalid';
}
return connectionStatus as ConnectionStatus;
}
+1
View File
@@ -274,6 +274,7 @@ const createRFStore = () =>
connectionNodeId: initialState.connectionNodeId, connectionNodeId: initialState.connectionNodeId,
connectionHandleId: initialState.connectionHandleId, connectionHandleId: initialState.connectionHandleId,
connectionHandleType: initialState.connectionHandleType, connectionHandleType: initialState.connectionHandleType,
connectionStatus: initialState.connectionStatus,
}), }),
reset: () => set({ ...initialState }), reset: () => set({ ...initialState }),
})); }));
+1
View File
@@ -32,6 +32,7 @@ const initialState: ReactFlowStore = {
connectionHandleId: null, connectionHandleId: null,
connectionHandleType: 'source', connectionHandleType: 'source',
connectionPosition: { x: 0, y: 0 }, connectionPosition: { x: 0, y: 0 },
connectionStatus: null,
connectionMode: ConnectionMode.Strict, connectionMode: ConnectionMode.Strict,
domNode: null, domNode: null,
paneDragging: false, paneDragging: false,
+2 -1
View File
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import type { CSSProperties, ComponentType, HTMLAttributes, ReactNode, MouseEvent as ReactMouseEvent } from 'react'; import type { CSSProperties, ComponentType, HTMLAttributes, ReactNode, MouseEvent as ReactMouseEvent } from 'react';
import { Position } from '.'; import { ConnectionStatus, Position } from '.';
import type { Connection, HandleElement, HandleType, Node } from '.'; import type { Connection, HandleElement, HandleType, Node } from '.';
type EdgeLabelOptions = { type EdgeLabelOptions = {
@@ -155,6 +155,7 @@ export type ConnectionLineComponentProps = {
toY: number; toY: number;
fromPosition: Position; fromPosition: Position;
toPosition: Position; toPosition: Position;
connectionStatus: ConnectionStatus | null;
}; };
export type ConnectionLineComponent = ComponentType<ConnectionLineComponentProps>; export type ConnectionLineComponent = ComponentType<ConnectionLineComponentProps>;
+3
View File
@@ -61,6 +61,8 @@ export interface Connection {
targetHandle: string | null; targetHandle: string | null;
} }
export type ConnectionStatus = 'valid' | 'invalid';
export enum ConnectionMode { export enum ConnectionMode {
Strict = 'strict', Strict = 'strict',
Loose = 'loose', Loose = 'loose',
@@ -166,6 +168,7 @@ export type ReactFlowStore = {
connectionHandleId: string | null; connectionHandleId: string | null;
connectionHandleType: HandleType | null; connectionHandleType: HandleType | null;
connectionPosition: XYPosition; connectionPosition: XYPosition;
connectionStatus: ConnectionStatus | null;
connectionMode: ConnectionMode; connectionMode: ConnectionMode;
snapToGrid: boolean; snapToGrid: boolean;