diff --git a/examples/vite-app/src/examples/Validation/validation.module.css b/examples/vite-app/src/examples/Validation/validation.module.css index 4ffc8b0b..37333aae 100644 --- a/examples/vite-app/src/examples/Validation/validation.module.css +++ b/examples/vite-app/src/examples/Validation/validation.module.css @@ -24,10 +24,10 @@ background: #fff; } -.validationflow :global .react-flow__handle-connecting { +.validationflow :global .connecting { background: #ff6060; } -.validationflow :global .react-flow__handle-valid { +.validationflow :global .valid { background: #55dd99; } diff --git a/packages/core/src/components/Handle/handler.ts b/packages/core/src/components/Handle/handler.ts index 03bf698c..5c35e48a 100644 --- a/packages/core/src/components/Handle/handler.ts +++ b/packages/core/src/components/Handle/handler.ts @@ -2,7 +2,7 @@ import type { MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } fro import { StoreApi } from 'zustand'; import { getHostForElement, calcAutoPan, getEventPosition } from '../../utils'; -import type { OnConnect, HandleType, ReactFlowState } from '../../types'; +import type { OnConnect, HandleType, ReactFlowState, Connection } from '../../types'; import { pointToRendererPoint, rendererPointToPoint } from '../../utils/graph'; import { ConnectionHandle, @@ -65,6 +65,8 @@ export function handlePointerDown({ let prevActiveHandle: Element; let connectionPosition = getEventPosition(event, containerBounds); let autoPanStarted = false; + let connection: Connection | null = null; + let isValid = false; const handleLookup = getHandleLookup({ nodes: getNodes(), @@ -108,23 +110,7 @@ export function handlePointerDown({ autoPanStarted = true; } - setState({ - connectionPosition: prevClosestHandle - ? rendererPointToPoint( - { - x: prevClosestHandle.x, - y: prevClosestHandle.y, - }, - transform - ) - : connectionPosition, - }); - - if (!prevClosestHandle) { - return resetRecentHandle(prevActiveHandle); - } - - const { connection, handleDomNode, isValid } = isValidHandle( + const { handleDomNode, ...result } = isValidHandle( event, prevClosestHandle, connectionMode, @@ -135,33 +121,39 @@ export function handlePointerDown({ doc ); + setState({ + connectionPosition: + prevClosestHandle && isValid + ? rendererPointToPoint( + { + x: prevClosestHandle.x, + y: prevClosestHandle.y, + }, + transform + ) + : connectionPosition, + }); + + if (!prevClosestHandle) { + return resetRecentHandle(prevActiveHandle); + } + + connection = result.connection; + isValid = result.isValid; + if (connection.source !== connection.target && handleDomNode) { resetRecentHandle(prevActiveHandle); prevActiveHandle = handleDomNode; - handleDomNode.classList.add('react-flow__handle-connecting'); + // @todo: remove the old class names "react-flow__handle-" in the next major version + handleDomNode.classList.add('connecting', 'react-flow__handle-connecting'); + handleDomNode.classList.toggle('valid', isValid); handleDomNode.classList.toggle('react-flow__handle-valid', isValid); } } function onPointerUp(event: MouseEvent | TouchEvent) { - cancelAnimationFrame(autoPanId); - autoPanStarted = false; - - if (prevClosestHandle) { - const { connection, isValid } = isValidHandle( - event, - prevClosestHandle, - connectionMode, - nodeId, - handleId, - isTarget ? 'target' : 'source', - isValidConnection, - doc - ); - - if (isValid) { - onConnect?.(connection); - } + if (connection && isValid) { + onConnect?.(connection); } onConnectEnd?.(event); @@ -171,8 +163,11 @@ export function handlePointerDown({ } resetRecentHandle(prevActiveHandle); - cancelConnection(); + cancelAnimationFrame(autoPanId); + autoPanStarted = false; + isValid = false; + connection = null; doc.removeEventListener('mousemove', onPointerMove as EventListener); doc.removeEventListener('mouseup', onPointerUp as EventListener); diff --git a/packages/core/src/components/Handle/utils.ts b/packages/core/src/components/Handle/utils.ts index 0b391e37..235abc92 100644 --- a/packages/core/src/components/Handle/utils.ts +++ b/packages/core/src/components/Handle/utils.ts @@ -61,10 +61,12 @@ type Result = { connection: Connection; }; +const nullConnection: Connection = { source: null, target: null, sourceHandle: null, targetHandle: null }; + // checks if and returns connection in fom of an object { source: 123, target: 312 } export function isValidHandle( event: MouseEvent | TouchEvent | ReactMouseEvent | ReactTouchEvent, - handle: Pick, + handle: Pick | null, connectionMode: ConnectionMode, fromNodeId: string, fromHandleId: string | null, @@ -72,6 +74,14 @@ export function isValidHandle( isValidConnection: ValidConnectionFunc, doc: Document | ShadowRoot ) { + if (!handle) { + return { + handleDomNode: null, + isValid: false, + connection: nullConnection, + }; + } + const isTarget = fromType === 'target'; const handleDomNode = doc.querySelector( `.react-flow__handle[data-id="${handle?.nodeId}-${handle?.id}-${handle?.type}"]` @@ -83,7 +93,7 @@ export function isValidHandle( const result: Result = { handleDomNode: handleToCheck, isValid: false, - connection: { source: null, target: null, sourceHandle: null, targetHandle: null }, + connection: nullConnection, }; if (handleToCheck) { @@ -155,6 +165,5 @@ export function getHandleType( } export function resetRecentHandle(handleDomNode: Element): void { - handleDomNode?.classList.remove('react-flow__handle-valid'); - handleDomNode?.classList.remove('react-flow__handle-connecting'); + handleDomNode?.classList.remove('valid', 'connecting', 'react-flow__handle-valid', 'react-flow__handle-connecting'); }