Skip to content

Commit 245dd82

Browse files
committed
fix(core): prioritize handle below during handle lookup
1 parent 0b60cc3 commit 245dd82

File tree

3 files changed

+55
-12
lines changed

3 files changed

+55
-12
lines changed

packages/core/src/composables/useHandle.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ export function useHandle({
136136
connectionPosition = getEventPosition(event, containerBounds)
137137

138138
const { handle, validHandleResult } = getClosestHandle(
139+
event,
140+
doc,
139141
pointToRendererPoint(connectionPosition, viewport.value, false, [1, 1]),
140142
connectionRadius.value,
141143
handleLookup,
@@ -154,7 +156,7 @@ export function useHandle({
154156
),
155157
)
156158

157-
closestHandle = handle || handleLookup.find((handle) => handle.id === validHandleResult.endHandle?.handleId) || null
159+
closestHandle = handle
158160

159161
if (!autoPanStarted) {
160162
autoPan()

packages/core/src/types/handle.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export interface HandleElement extends XYPosition, Dimensions {
1212

1313
export interface ConnectionHandle {
1414
id: string | null
15-
type: HandleType
15+
type: HandleType | null
1616
nodeId: string
1717
x: number
1818
y: number

packages/core/src/utils/handle.ts

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import type {
1616

1717
export interface ConnectionHandle extends XYPosition, Dimensions {
1818
id: string | null
19-
type: HandleType
19+
type: HandleType | null
2020
nodeId: string
2121
}
2222

@@ -58,22 +58,54 @@ export function getHandles(
5858
}
5959

6060
export function getClosestHandle(
61+
event: MouseEvent | TouchEvent,
62+
doc: Document | ShadowRoot,
6163
pos: XYPosition,
6264
connectionRadius: number,
6365
handles: ConnectionHandle[],
64-
validator: (handle: ConnectionHandle | null) => ValidHandleResult,
66+
validator: (handle: Pick<ConnectionHandle, 'nodeId' | 'id' | 'type'>) => ValidHandleResult,
6567
) {
68+
// we always want to prioritize the handle below the mouse cursor over the closest distance handle,
69+
// because it could be that the center of another handle is closer to the mouse pointer than the handle below the cursor
70+
const { x, y } = getEventPosition(event)
71+
const domNodes = doc.elementsFromPoint(x, y)
72+
73+
const handleBelow = domNodes.find((el) => el.classList.contains('vue-flow__handle'))
74+
75+
if (handleBelow) {
76+
const handleNodeId = handleBelow.getAttribute('data-nodeid')
77+
78+
if (handleNodeId) {
79+
const handleType = getHandleType(undefined, handleBelow)
80+
const handleId = handleBelow.getAttribute('data-handleid')
81+
const validHandleResult = validator({ nodeId: handleNodeId, id: handleId, type: handleType })
82+
83+
if (validHandleResult) {
84+
return {
85+
handle: {
86+
id: handleId,
87+
type: handleType,
88+
nodeId: handleNodeId,
89+
x: pos.x,
90+
y: pos.y,
91+
},
92+
validHandleResult,
93+
}
94+
}
95+
}
96+
}
97+
98+
// if we couldn't find a handle below the mouse cursor we look for the closest distance based on the connectionRadius
6699
let closestHandles: { handle: ConnectionHandle; validHandleResult: ValidHandleResult }[] = []
67100
let minDistance = Infinity
68101

69102
handles.forEach((handle) => {
70-
// calculate distance from mouse position to center of handle while considering handle width and height as well as x and y position
71-
const distance = Math.sqrt((handle.x - pos.x - handle.width / 2) ** 2 + (handle.y - pos.y - handle.height / 2) ** 2)
103+
const distance = Math.sqrt((handle.x - pos.x) ** 2 + (handle.y - pos.y) ** 2)
72104

73105
if (distance <= connectionRadius) {
74106
const validHandleResult = validator(handle)
75107

76-
if (distance <= minDistance && validHandleResult.isValid) {
108+
if (distance <= minDistance) {
77109
if (distance < minDistance) {
78110
closestHandles = [{ handle, validHandleResult }]
79111
} else if (distance === minDistance) {
@@ -90,13 +122,22 @@ export function getClosestHandle(
90122
})
91123

92124
if (!closestHandles.length) {
93-
return { handle: null, validHandleResult: validator(null) }
125+
return { handle: null, validHandleResult: defaultValidHandleResult() }
94126
}
95127

96-
return closestHandles.length === 1
97-
? closestHandles[0]
98-
: // 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
99-
closestHandles.find(({ handle }) => handle.type === 'target') || closestHandles[0]
128+
if (closestHandles.length === 1) {
129+
return closestHandles[0]
130+
}
131+
132+
const hasValidHandle = closestHandles.some(({ validHandleResult }) => validHandleResult.isValid)
133+
const hasTargetHandle = closestHandles.some(({ handle }) => handle.type === 'target')
134+
135+
// if multiple handles are layouted on top of each other we prefer the one with type = target and the one that is valid
136+
return (
137+
closestHandles.find(({ handle, validHandleResult }) =>
138+
hasTargetHandle ? handle.type === 'target' : hasValidHandle ? validHandleResult.isValid : true,
139+
) || closestHandles[0]
140+
)
100141
}
101142

102143
// checks if and returns connection in fom of an object { source: 123, target: 312 }

0 commit comments

Comments
 (0)