diff --git a/packages/core/src/composables/useHandle.ts b/packages/core/src/composables/useHandle.ts index 2e0148e7..dc5b2df9 100644 --- a/packages/core/src/composables/useHandle.ts +++ b/packages/core/src/composables/useHandle.ts @@ -1,7 +1,7 @@ import type { MaybeRefOrGetter } from '@vueuse/core' import { toValue } from '@vueuse/core' import { useVueFlow } from './useVueFlow' -import type { Connection, ConnectionHandle, HandleType, MouseTouchEvent, ValidConnectionFunc, ValidHandleResult } from '~/types' +import type { Connection, ConnectionHandle, HandleType, MouseTouchEvent, ValidConnectionFunc } from '~/types' import { calcAutoPan, getClosestHandle, @@ -135,20 +135,12 @@ export function useHandle({ function onPointerMove(event: MouseTouchEvent) { connectionPosition = getEventPosition(event, containerBounds) - let result: ValidHandleResult | null = { - handleDomNode: null, - isValid: false, - connection: { source: '', target: '', sourceHandle: null, targetHandle: null }, - endHandle: null, - } - - closestHandle = - getClosestHandle( - pointToRendererPoint(connectionPosition, viewport.value, false, [1, 1]), - connectionRadius.value, - handleLookup, - )?.find((handle) => { - const validHandleResult = isValidHandle( + const { handle, validHandleResult } = getClosestHandle( + pointToRendererPoint(connectionPosition, viewport.value, false, [1, 1]), + connectionRadius.value, + handleLookup, + (handle) => + isValidHandle( event, handle, connectionMode.value, @@ -159,27 +151,19 @@ export function useHandle({ doc, edges.value, findNode, - ) + ), + ) - if (validHandleResult.isValid) { - result = validHandleResult - } - - return validHandleResult.isValid - }) || null + closestHandle = handle if (!autoPanStarted) { autoPan() autoPanStarted = true } - if (!result) { - return - } - - connection = result.connection - isValid = result.isValid - handleDomNode = result.handleDomNode + connection = validHandleResult.connection + isValid = validHandleResult.isValid + handleDomNode = validHandleResult.handleDomNode updateConnection( closestHandle && isValid @@ -191,7 +175,7 @@ export function useHandle({ viewport.value, ) : connectionPosition, - result.endHandle, + validHandleResult.endHandle, getConnectionStatus(!!closestHandle, isValid), ) diff --git a/packages/core/src/utils/handle.ts b/packages/core/src/utils/handle.ts index cb51c740..22020389 100644 --- a/packages/core/src/utils/handle.ts +++ b/packages/core/src/utils/handle.ts @@ -21,6 +21,15 @@ export interface ConnectionHandle { y: number } +function defaultValidHandleResult(): ValidHandleResult { + return { + handleDomNode: null, + isValid: false, + connection: { source: '', target: '', sourceHandle: null, targetHandle: null }, + endHandle: null, + } +} + export function resetRecentHandle(handleDomNode: Element): void { handleDomNode?.classList.remove('valid', 'connecting', 'vue-flow__handle-valid', 'vue-flow__handle-connecting') } @@ -53,29 +62,45 @@ export function getHandles( }, []) } -export function getClosestHandle(pos: XYPosition, connectionRadius: number, handles: ConnectionHandle[]) { - let closestHandles: ConnectionHandle[] = [] +export function getClosestHandle( + pos: XYPosition, + connectionRadius: number, + handles: ConnectionHandle[], + validator: (handle: ConnectionHandle) => ValidHandleResult, +) { + let closestHandles: { handle: ConnectionHandle; validHandleResult: ValidHandleResult }[] = [] let minDistance = Infinity handles.forEach((handle) => { const distance = Math.sqrt((handle.x - pos.x) ** 2 + (handle.y - pos.y) ** 2) - 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 + if (distance <= connectionRadius) { + const validHandleResult = validator(handle) + + if (distance <= minDistance && validHandleResult.isValid) { + if (distance < minDistance) { + closestHandles = [{ handle, validHandleResult }] + } else if (distance === minDistance) { + // when multiple handles are on the same distance we collect all of them + closestHandles.push({ + handle, + validHandleResult, + }) + } + + minDistance = distance + } } }) if (!closestHandles.length) { - return null + return { handle: null, validHandleResult: defaultValidHandleResult() } } - return closestHandles + 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 } @@ -101,12 +126,7 @@ export function isValidHandle( // 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 = { - handleDomNode: handleToCheck, - isValid: false, - connection: { source: '', target: '', sourceHandle: null, targetHandle: null }, - endHandle: null, - } + const result = defaultValidHandleResult() if (handleToCheck) { const handleType = getHandleType(undefined, handleToCheck)