feat(connection-line): add status class (valid or invalid) while in connection radius
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 }),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user