From 00726085be1f828ea8c2262082a467b4cc27ad42 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 19 Jan 2023 17:35:06 +0100 Subject: [PATCH] refactor(connections): cleanup, naming --- .../vite-app/src/examples/Subflow/index.tsx | 1 + .../vite-app/src/examples/Switch/index.tsx | 2 +- .../core/src/components/Handle/handler.ts | 72 +++++++-------- packages/core/src/components/Handle/utils.ts | 88 +++++++++---------- 4 files changed, 82 insertions(+), 81 deletions(-) diff --git a/examples/vite-app/src/examples/Subflow/index.tsx b/examples/vite-app/src/examples/Subflow/index.tsx index 75bd3629..c3f10b46 100644 --- a/examples/vite-app/src/examples/Subflow/index.tsx +++ b/examples/vite-app/src/examples/Subflow/index.tsx @@ -12,6 +12,7 @@ import ReactFlow, { MiniMap, Background, Panel, + NodeOrigin, } from 'reactflow'; import DebugNode from './DebugNode'; diff --git a/examples/vite-app/src/examples/Switch/index.tsx b/examples/vite-app/src/examples/Switch/index.tsx index 7fde6bbd..658082ed 100644 --- a/examples/vite-app/src/examples/Switch/index.tsx +++ b/examples/vite-app/src/examples/Switch/index.tsx @@ -1,5 +1,5 @@ import { MouseEvent, useCallback } from 'react'; -import ReactFlow, { addEdge, Node, Connection, Edge, useNodesState, useEdgesState } from 'reactflow'; +import ReactFlow, { addEdge, Node, Connection, Edge, useNodesState, useEdgesState, NodeOrigin } from 'reactflow'; const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); diff --git a/packages/core/src/components/Handle/handler.ts b/packages/core/src/components/Handle/handler.ts index 51cfd32d..86aab175 100644 --- a/packages/core/src/components/Handle/handler.ts +++ b/packages/core/src/components/Handle/handler.ts @@ -4,11 +4,18 @@ import { StoreApi } from 'zustand'; import { getHostForElement, calcAutoPanVelocity } from '../../utils'; import type { OnConnect, HandleType, ReactFlowState } from '../../types'; import { pointToRendererPoint, rendererPointToPoint } from '../../utils/graph'; -import { ConnectionHandle, getClosestHandle, getHandleLookup, isValidHandle, ValidConnectionFunc } from './utils'; +import { + ConnectionHandle, + getClosestHandle, + getConnectionPosition, + getHandleLookup, + isValidHandle, + ValidConnectionFunc, +} from './utils'; -function resetRecentHandle(hoveredHandle: Element): void { - hoveredHandle?.classList.remove('react-flow__handle-valid'); - hoveredHandle?.classList.remove('react-flow__handle-connecting'); +function resetRecentHandle(handleDomNode: Element): void { + handleDomNode?.classList.remove('react-flow__handle-valid'); + handleDomNode?.classList.remove('react-flow__handle-connecting'); } export function handleMouseDown({ @@ -39,7 +46,7 @@ export function handleMouseDown({ const { connectionMode, domNode, autoPanOnConnect, connectionRadius, onConnectStart, onConnectEnd, panBy, getNodes } = getState(); let autoPanId = 0; - let prevClosestHandle: ConnectionHandle | undefined; + let prevClosestHandle: ConnectionHandle | null; const clickedElement = doc?.elementFromPoint(event.clientX, event.clientY); const elementIsTarget = clickedElement?.classList.contains('target'); @@ -51,11 +58,8 @@ export function handleMouseDown({ const handleType = elementEdgeUpdaterType ? elementEdgeUpdaterType : elementIsTarget ? 'target' : 'source'; const containerBounds = domNode.getBoundingClientRect(); - let recentHoveredHandle: Element; - let connectionPosition = { - x: event.clientX - containerBounds.left, - y: event.clientY - containerBounds.top, - }; + let prevActiveHandle: Element; + let connectionPosition = getConnectionPosition(event, containerBounds); const handleLookup = getHandleLookup({ nodes: getNodes(), @@ -90,10 +94,7 @@ export function handleMouseDown({ function onMouseMove(event: MouseEvent) { const { transform } = getState(); - connectionPosition = { - x: event.clientX - containerBounds.left, - y: event.clientY - containerBounds.top, - }; + connectionPosition = getConnectionPosition(event, containerBounds); prevClosestHandle = getClosestHandle( pointToRendererPoint(connectionPosition, transform, false, [1, 1]), @@ -105,35 +106,33 @@ export function handleMouseDown({ connectionPosition: prevClosestHandle ? rendererPointToPoint( { - x: prevClosestHandle.absX + prevClosestHandle.width / 2, - y: prevClosestHandle.absY + prevClosestHandle.height / 2, + x: prevClosestHandle.x, + y: prevClosestHandle.y, }, transform ) : connectionPosition, }); - if (prevClosestHandle) { - const { connection, elementBelow, isValid, isHoveringHandle } = isValidHandle( - prevClosestHandle, - connectionMode, - nodeId, - handleId, - isTarget ? 'target' : 'source', - isValidConnection, - doc - ); + if (!prevClosestHandle) { + return resetRecentHandle(prevActiveHandle); + } - if (!isHoveringHandle) { - return resetRecentHandle(recentHoveredHandle); - } + const { connection, handleDomNode, isValid } = isValidHandle( + prevClosestHandle, + connectionMode, + nodeId, + handleId, + isTarget ? 'target' : 'source', + isValidConnection, + doc + ); - if (connection.source !== connection.target && elementBelow) { - resetRecentHandle(recentHoveredHandle); - recentHoveredHandle = elementBelow; - elementBelow.classList.add('react-flow__handle-connecting'); - elementBelow.classList.toggle('react-flow__handle-valid', isValid); - } + if (connection.source !== connection.target && handleDomNode) { + resetRecentHandle(prevActiveHandle); + prevActiveHandle = handleDomNode; + handleDomNode.classList.add('react-flow__handle-connecting'); + handleDomNode.classList.toggle('react-flow__handle-valid', isValid); } } @@ -162,7 +161,8 @@ export function handleMouseDown({ onEdgeUpdateEnd?.(event); } - resetRecentHandle(recentHoveredHandle); + resetRecentHandle(prevActiveHandle); + setState({ connectionNodeId: null, connectionHandleId: null, diff --git a/packages/core/src/components/Handle/utils.ts b/packages/core/src/components/Handle/utils.ts index a476c515..69d6f68c 100644 --- a/packages/core/src/components/Handle/utils.ts +++ b/packages/core/src/components/Handle/utils.ts @@ -1,19 +1,21 @@ -import { ConnectionMode, Node, NodeHandleBounds } from '../../types'; -import type { Connection, HandleType, XYPosition } from '../../types'; +import { MouseEvent as ReactMouseEvent } from 'react'; + +import { ConnectionMode } from '../../types'; +import type { Connection, HandleType, XYPosition, Node, NodeHandleBounds } from '../../types'; import { internalsSymbol } from '../../utils'; export type ConnectionHandle = { id: string | null; type: HandleType; nodeId: string; - absX: number; - absY: number; - width: number; - height: number; + x: number; + y: number; }; export type ValidConnectionFunc = (connection: Connection) => boolean; +// this functions collects all handles and adds an absolute position +// so that we can later find the closest handle to the mouse position export function getHandles( node: Node, handleBounds: NodeHandleBounds, @@ -26,22 +28,24 @@ export function getHandles( id: h.id || null, type, nodeId: node.id, - absX: (node.positionAbsolute?.x ?? 0) + h.x, - absY: (node.positionAbsolute?.y ?? 0) + h.y, - width: h.width, - height: h.height, + x: (node.positionAbsolute?.x ?? 0) + h.x + h.width / 2, + y: (node.positionAbsolute?.y ?? 0) + h.y + h.height / 2, }); } return res; }, []); } -export function getClosestHandle(pos: XYPosition, connectionRadius: number, handles: ConnectionHandle[]) { - let closestHandle: ConnectionHandle | undefined; +export function getClosestHandle( + pos: XYPosition, + connectionRadius: number, + handles: ConnectionHandle[] +): ConnectionHandle | null { + let closestHandle: ConnectionHandle | null = null; let minDistance = Infinity; handles.forEach((handle) => { - const distance = Math.sqrt(Math.pow(handle.absX - pos.x, 2) + Math.pow(handle.absY - pos.y, 2)); + const distance = Math.sqrt(Math.pow(handle.x - pos.x, 2) + Math.pow(handle.y - pos.y, 2)); if (distance <= connectionRadius && distance < minDistance) { minDistance = distance; closestHandle = handle; @@ -52,10 +56,9 @@ export function getClosestHandle(pos: XYPosition, connectionRadius: number, hand } type Result = { - elementBelow: Element | null; + handleDomNode: Element | null; isValid: boolean; connection: Connection; - isHoveringHandle: boolean; }; // checks if and returns connection in fom of an object { source: 123, target: 312 } @@ -68,46 +71,36 @@ export function isValidHandle( isValidConnection: ValidConnectionFunc, doc: Document | ShadowRoot ) { - const result: Result = { - elementBelow: null, - isValid: false, - connection: { source: null, target: null, sourceHandle: null, targetHandle: null }, - isHoveringHandle: false, - }; const isTarget = fromType === 'target'; - - const elementBelow = doc.querySelector( + const handleDomNode = doc.querySelector( `.react-flow__handle[data-id="${handle?.nodeId}-${handle?.id}-${handle?.type}"]` ); + const result: Result = { + handleDomNode, + isValid: false, + connection: { source: null, target: null, sourceHandle: null, targetHandle: null }, + }; - if (elementBelow) { - const elementBelowIsTarget = handle.type === 'target'; - const elementBelowIsSource = handle.type === 'source'; - result.isHoveringHandle = true; + if (handleDomNode) { + const handleIsTarget = handle.type === 'target'; + const handleIsSource = handle.type === 'source'; + const handleNodeId = handleDomNode.getAttribute('data-nodeid'); + const handleId = handleDomNode.getAttribute('data-handleid'); - const elementBelowNodeId = elementBelow.getAttribute('data-nodeid'); - const elementBelowHandleId = elementBelow.getAttribute('data-handleid'); - const connection: Connection = isTarget - ? { - source: handle.nodeId, - sourceHandle: handle.id, - target: fromNodeId, - targetHandle: fromHandleId, - } - : { - source: fromNodeId, - sourceHandle: fromHandleId, - target: handle.nodeId, - targetHandle: handle.id, - }; + const connection: Connection = { + source: isTarget ? handle.nodeId : fromNodeId, + sourceHandle: isTarget ? handle.id : fromHandleId, + target: isTarget ? fromNodeId : handle.nodeId, + targetHandle: isTarget ? fromHandleId : handle.id, + }; result.connection = connection; // in strict mode we don't allow target to target or source to source connections const isValid = connectionMode === ConnectionMode.Strict - ? (isTarget && elementBelowIsSource) || (!isTarget && elementBelowIsTarget) - : elementBelowNodeId !== handle.nodeId || elementBelowHandleId !== handle.id; + ? (isTarget && handleIsSource) || (!isTarget && handleIsTarget) + : handleNodeId !== handle.nodeId || handleId !== handle.id; if (isValid) { result.isValid = isValidConnection(connection); @@ -141,3 +134,10 @@ export function getHandleLookup({ nodes, nodeId, handleId, handleType }: GetHand return res; }, []); } + +export function getConnectionPosition(event: MouseEvent | ReactMouseEvent, bounds: DOMRect): XYPosition { + return { + x: event.clientX - bounds.left, + y: event.clientY - bounds.top, + }; +}