diff --git a/packages/core/src/composables/useHandle.ts b/packages/core/src/composables/useHandle.ts index 4a1c61a7..ce1eb36a 100644 --- a/packages/core/src/composables/useHandle.ts +++ b/packages/core/src/composables/useHandle.ts @@ -65,7 +65,7 @@ export default function useHandle({ isValidConnectionHandler = (!isTarget ? node.isValidTargetPos : node.isValidSourcePos) || alwaysValid } - let prevClosestHandle: ConnectionHandle | null + let closestHandle: ConnectionHandle | null let autoPanId = 0 @@ -119,7 +119,7 @@ export default function useHandle({ function onPointerMove(event: MouseTouchEvent) { connectionPosition = getEventPosition(event, containerBounds) - prevClosestHandle = getClosestHandle( + closestHandle = getClosestHandle( pointToRendererPoint(connectionPosition, viewport.value, false, [1, 1]), connectionRadius.value, handleLookup, @@ -132,7 +132,7 @@ export default function useHandle({ const result = isValidHandle( event, - prevClosestHandle, + closestHandle, connectionMode.value, resolveUnref(nodeId), resolveUnref(handleId), @@ -148,20 +148,20 @@ export default function useHandle({ handleDomNode = result.handleDomNode updateConnection( - prevClosestHandle && isValid + closestHandle && isValid ? rendererPointToPoint( { - x: prevClosestHandle.x, - y: prevClosestHandle.y, + x: closestHandle.x, + y: closestHandle.y, }, viewport.value, ) : connectionPosition, result.endHandle, - getConnectionStatus(!!prevClosestHandle, isValid), + getConnectionStatus(!!closestHandle, isValid), ) - if (!prevClosestHandle && !isValid && !handleDomNode) { + if (!closestHandle && !isValid && !handleDomNode) { return resetRecentHandle(prevActiveHandle) } @@ -179,7 +179,7 @@ export default function useHandle({ } function onPointerUp(event: MouseTouchEvent) { - if ((prevClosestHandle || handleDomNode) && connection && isValid) { + if ((closestHandle || handleDomNode) && connection && isValid) { if (!onEdgeUpdate) { emits.connect(connection) } else { diff --git a/packages/core/src/utils/handle.ts b/packages/core/src/utils/handle.ts index 85d890c6..53e7a0d4 100644 --- a/packages/core/src/utils/handle.ts +++ b/packages/core/src/utils/handle.ts @@ -57,18 +57,31 @@ export function getClosestHandle( connectionRadius: number, handles: ConnectionHandle[], ): ConnectionHandle | null { - let closestHandle: ConnectionHandle | null = null + let closestHandles: ConnectionHandle[] = [] let minDistance = Infinity handles.forEach((handle) => { const distance = Math.sqrt((handle.x - pos.x) ** 2 + (handle.y - pos.y) ** 2) - if (distance <= connectionRadius && distance < minDistance) { + if (distance <= connectionRadius) { + if (distance < minDistance) { + closestHandles = [handle] + } else if (distance === minDistance) { + // when multiple handles are on the same distance we collect all of them + closestHandles.push(handle) + } + minDistance = distance - closestHandle = handle } }) - return closestHandle + if (!closestHandles.length) { + return null + } + + return closestHandles.length === 1 + ? closestHandles[0] + : // if multiple handles are layout on top of each other we take the one with type = target because it's more likely that the user wants to connect to this one + closestHandles.find((handle) => handle.type === 'target') || closestHandles[0] } // checks if and returns connection in fom of an object { source: 123, target: 312 } @@ -89,6 +102,9 @@ export function isValidHandle( const handleDomNode = doc.querySelector(`.vue-flow__handle[data-id="${handle?.nodeId}-${handle?.id}-${handle?.type}"]`) const { x, y } = getEventPosition(event) const handleBelow = doc.elementFromPoint(x, y) + + // we always want to prioritize the handle below the mouse cursor over the closest distance handle, + // because it could be that the center of another handle is closer to the mouse pointer than the handle below the cursor const handleToCheck = handleBelow?.classList.contains('vue-flow__handle') ? handleBelow : handleDomNode const result: ValidHandleResult = {