{
if (!autoPanOnConnect.value) {
@@ -127,10 +130,37 @@ export function useHandle({
autoPanId = requestAnimationFrame(autoPan)
}
+ // Stays the same for all consecutive pointermove events
+ const fromHandle: HandleElement = {
+ ...fromHandleInternal,
+ nodeId: toValue(nodeId),
+ type: handleType,
+ position: fromHandleInternal.position,
+ }
+
+ const fromNodeInternal = nodeLookup.value.get(toValue(nodeId))!
+
+ const from = getHandlePosition(fromNodeInternal, fromHandle, Position.Left, true)
+
+ const newConnection: ConnectionInProgress = {
+ inProgress: true,
+ isValid: null,
+
+ from,
+ fromHandle,
+ fromPosition: fromHandle.position,
+ fromNode: fromNodeInternal,
+
+ to: connectionPosition,
+ toHandle: null,
+ toPosition: oppositePosition[fromHandle.position],
+ toNode: null,
+ }
+
startConnection(
{
nodeId: toValue(nodeId),
- handleId: toValue(handleId),
+ id: toValue(handleId),
type: handleType,
position: (clickedHandle?.getAttribute('data-handlepos') as Position) || Position.Top,
},
@@ -142,52 +172,71 @@ export function useHandle({
emits.connectStart({ event, nodeId: toValue(nodeId), handleId: toValue(handleId), handleType })
+ let previousConnection: ConnectionInProgress = newConnection
+
function onPointerMove(event: MouseTouchEvent) {
connectionPosition = getEventPosition(event, containerBounds)
- const { handle, validHandleResult } = getClosestHandle(
- event,
- doc,
+ closestHandle = getClosestHandle(
pointToRendererPoint(connectionPosition, viewport.value, false, [1, 1]),
connectionRadius.value,
- handleLookup,
- (handle) =>
- isValidHandle(
- event,
- handle,
- connectionMode.value,
- toValue(nodeId),
- toValue(handleId),
- isTarget ? 'target' : 'source',
- isValidConnectionHandler,
- doc,
- edges.value,
- nodes.value,
- findNode,
- ),
+ nodeLookup.value,
+ fromHandle,
)
- closestHandle = handle
-
if (!autoPanStarted) {
autoPan()
autoPanStarted = true
}
- connection = validHandleResult.connection
- isValid = validHandleResult.isValid
- handleDomNode = validHandleResult.handleDomNode
+ const result = isValidHandle(
+ event,
+ {
+ handle: closestHandle,
+ connectionMode: connectionMode.value,
+ fromNodeId: toValue(nodeId),
+ fromHandleId: toValue(handleId),
+ fromType: isTarget ? 'target' : 'source',
+ isValidConnection: isValidConnectionHandler,
+ doc,
+ lib: 'vue',
+ flowId,
+ nodeLookup: nodeLookup.value,
+ },
+ edges.value,
+ nodes.value,
+ findNode,
+ )
+
+ handleDomNode = result.handleDomNode
+ connection = result.connection
+ isValid = isConnectionValid(!!closestHandle, result.isValid)
+
+ const newConnection: ConnectionInProgress = {
+ // from stays the same
+ ...previousConnection,
+ isValid,
+ to:
+ closestHandle && isValid
+ ? rendererPointToPoint({ x: closestHandle.x, y: closestHandle.y }, viewport.value)
+ : connectionPosition,
+ toHandle: result.toHandle,
+ toPosition: isValid && result.toHandle ? result.toHandle.position : oppositePosition[fromHandle.position],
+ toNode: result.toHandle ? nodeLookup.value.get(result.toHandle.nodeId)! : null,
+ }
// we don't want to trigger an update when the connection
// is snapped to the same handle as before
if (
isValid &&
closestHandle &&
- previousConnection?.endHandle &&
- validHandleResult.endHandle &&
- previousConnection.endHandle.type === validHandleResult.endHandle.type &&
- previousConnection.endHandle.nodeId === validHandleResult.endHandle.nodeId &&
- previousConnection.endHandle.handleId === validHandleResult.endHandle.handleId
+ previousConnection?.toHandle &&
+ newConnection.toHandle &&
+ previousConnection.toHandle.type === newConnection.toHandle.type &&
+ previousConnection.toHandle.nodeId === newConnection.toHandle.nodeId &&
+ previousConnection.toHandle.id === newConnection.toHandle.id &&
+ previousConnection.to.x === newConnection.to.x &&
+ previousConnection.to.y === newConnection.to.y
) {
return
}
@@ -202,11 +251,11 @@ export function useHandle({
viewport.value,
)
: connectionPosition,
- validHandleResult.endHandle,
+ result.toHandle,
getConnectionStatus(!!closestHandle, isValid),
)
- previousConnection = validHandleResult
+ previousConnection = newConnection
if (!closestHandle && !isValid && !handleDomNode) {
return resetRecentHandle(prevActiveHandle)
@@ -219,9 +268,9 @@ export function useHandle({
// todo: remove `vue-flow__handle-connecting` in next major version
handleDomNode.classList.add('connecting', 'vue-flow__handle-connecting')
- handleDomNode.classList.toggle('valid', isValid)
+ handleDomNode.classList.toggle('valid', !!isValid)
// todo: remove this in next major version
- handleDomNode.classList.toggle('vue-flow__handle-valid', isValid)
+ handleDomNode.classList.toggle('vue-flow__handle-valid', !!isValid)
}
}
@@ -275,50 +324,62 @@ export function useHandle({
if (!connectionClickStartHandle.value) {
emits.clickConnectStart({ event, nodeId: toValue(nodeId), handleId: toValue(handleId) })
- startConnection({ nodeId: toValue(nodeId), type: toValue(type), handleId: toValue(handleId) }, undefined, true)
- } else {
- let isValidConnectionHandler = toValue(isValidConnection) || isValidConnectionProp.value || alwaysValid
+ startConnection(
+ { nodeId: toValue(nodeId), type: toValue(type), id: toValue(handleId), position: Position.Top },
+ undefined,
+ true,
+ )
- const node = findNode(toValue(nodeId))
+ return
+ }
- if (!isValidConnectionHandler && node) {
- isValidConnectionHandler = (!isTarget ? node.isValidTargetPos : node.isValidSourcePos) || alwaysValid
- }
+ let isValidConnectionHandler = toValue(isValidConnection) || isValidConnectionProp.value || alwaysValid
- if (node && (typeof node.connectable === 'undefined' ? nodesConnectable.value : node.connectable) === false) {
- return
- }
+ const node = findNode(toValue(nodeId))
- const doc = getHostForElement(event.target as HTMLElement)
+ if (!isValidConnectionHandler && node) {
+ isValidConnectionHandler = (!isTarget ? node.isValidTargetPos : node.isValidSourcePos) || alwaysValid
+ }
- const { connection, isValid } = isValidHandle(
- event,
- {
+ if (node && (typeof node.connectable === 'undefined' ? nodesConnectable.value : node.connectable) === false) {
+ return
+ }
+
+ const doc = getHostForElement(event.target as HTMLElement)
+
+ const result = isValidHandle(
+ event,
+ {
+ handle: {
nodeId: toValue(nodeId),
id: toValue(handleId),
type: toValue(type),
+ position: Position.Top,
},
- connectionMode.value,
- connectionClickStartHandle.value.nodeId,
- connectionClickStartHandle.value.handleId || null,
- connectionClickStartHandle.value.type,
- isValidConnectionHandler,
+ connectionMode: connectionMode.value,
+ fromNodeId: connectionClickStartHandle.value.nodeId,
+ fromHandleId: connectionClickStartHandle.value.id || null,
+ fromType: connectionClickStartHandle.value.type,
+ isValidConnection: isValidConnectionHandler,
doc,
- edges.value,
- nodes.value,
- findNode,
- )
-
- const isOwnHandle = connection.source === connection.target
-
- if (isValid && !isOwnHandle) {
- emits.connect(connection)
- }
+ lib: 'vue',
+ flowId,
+ nodeLookup: nodeLookup.value,
+ },
+ edges.value,
+ nodes.value,
+ findNode,
+ )
+
+ const isOwnHandle = result.connection?.source === result.connection?.target
+
+ if (result.isValid && result.connection && !isOwnHandle) {
+ emits.connect(result.connection)
+ }
- emits.clickConnectEnd(event)
+ emits.clickConnectEnd(event)
- endConnection(event, true)
- }
+ endConnection(event, true)
}
return {
diff --git a/packages/core/src/store/actions.ts b/packages/core/src/store/actions.ts
index 5e3050d21..6c29b1356 100644
--- a/packages/core/src/store/actions.ts
+++ b/packages/core/src/store/actions.ts
@@ -157,8 +157,8 @@ export function useActions(state: State, nodeLookup: ComputedRef, ed
if (doUpdate) {
const nodeBounds = update.nodeElement.getBoundingClientRect()
node.dimensions = dimensions
- node.handleBounds.source = getHandleBounds('.source', update.nodeElement, nodeBounds, zoom)
- node.handleBounds.target = getHandleBounds('.target', update.nodeElement, nodeBounds, zoom)
+ node.handleBounds.source = getHandleBounds('source', update.nodeElement, nodeBounds, zoom)
+ node.handleBounds.target = getHandleBounds('target', update.nodeElement, nodeBounds, zoom)
changes.push({
id: node.id,
diff --git a/packages/core/src/types/handle.ts b/packages/core/src/types/handle.ts
index 55b04b471..f88f318da 100644
--- a/packages/core/src/types/handle.ts
+++ b/packages/core/src/types/handle.ts
@@ -1,13 +1,16 @@
import type { Dimensions, Position, XYPosition } from './flow'
-import type { Connection } from './connection'
+import type { Connection, ConnectionMode } from './connection'
import type { GraphEdge } from './edge'
import type { GraphNode } from './node'
+import type { NodeLookup } from './store'
export type HandleType = 'source' | 'target'
export interface HandleElement extends XYPosition, Dimensions {
id?: string | null
position: Position
+ type: HandleType
+ nodeId: string
}
export interface ConnectionHandle extends XYPosition {
@@ -16,18 +19,11 @@ export interface ConnectionHandle extends XYPosition {
nodeId: string
}
-export interface ValidHandleResult {
- endHandle: ConnectingHandle | null
- handleDomNode: Element | null
- isValid: boolean
- connection: Connection
-}
-
export interface ConnectingHandle {
nodeId: string
type: HandleType
- handleId?: string | null
- position?: Position | null
+ id?: string | null
+ position: Position
}
/** A valid connection function can determine if an attempted connection is valid or not, i.e. abort creating a new edge */
@@ -63,3 +59,36 @@ export interface HandleProps {
/** Can this handle be used to *end* a connection */
connectableEnd?: boolean
}
+
+export interface IsValidParams {
+ handle: ConnectingHandle | null
+ connectionMode: ConnectionMode
+ fromNodeId: string
+ fromHandleId: string | null
+ fromType: HandleType
+ isValidConnection?: ValidConnectionFunc
+ doc: Document | ShadowRoot
+ lib: string
+ flowId: string | null
+ nodeLookup: NodeLookup
+}
+
+export interface Result {
+ handleDomNode: Element | null
+ isValid: boolean
+ connection: Connection | null
+ toHandle: ConnectingHandle | null
+}
+
+export interface ConnectionInProgress {
+ inProgress: true
+ isValid: boolean | null
+ from: XYPosition
+ fromHandle: HandleElement
+ fromPosition: Position
+ fromNode: NodeType
+ to: XYPosition
+ toHandle: ConnectingHandle | null
+ toPosition: Position
+ toNode: NodeType | null
+}
diff --git a/packages/core/src/utils/edge.ts b/packages/core/src/utils/edge.ts
index b95e53d97..584c33582 100644
--- a/packages/core/src/utils/edge.ts
+++ b/packages/core/src/utils/edge.ts
@@ -6,41 +6,36 @@ export function getHandlePosition(
node: GraphNode,
handle: HandleElement | null,
fallbackPosition: Position = Position.Left,
+ center = false,
): XYPosition {
const x = (handle?.x ?? 0) + node.computedPosition.x
const y = (handle?.y ?? 0) + node.computedPosition.y
const { width, height } = handle ?? getNodeDimensions(node)
+
+ if (center) {
+ return { x: x + width / 2, y: y + height / 2 }
+ }
+
const position = handle?.position ?? fallbackPosition
switch (position) {
case Position.Top:
- return {
- x: x + width / 2,
- y,
- }
+ return { x: x + width / 2, y }
case Position.Right:
- return {
- x: x + width,
- y: y + height / 2,
- }
+ return { x: x + width, y: y + height / 2 }
case Position.Bottom:
- return {
- x: x + width / 2,
- y: y + height,
- }
+ return { x: x + width / 2, y: y + height }
case Position.Left:
- return {
- x,
- y: y + height / 2,
- }
+ return { x, y: y + height / 2 }
}
}
-export function getHandle(bounds: HandleElement[] = [], handleId?: string | null): HandleElement | null {
- if (!bounds.length) {
+export function getEdgeHandle(bounds: HandleElement[] | undefined, handleId?: string | null): HandleElement | null {
+ if (!bounds) {
return null
}
+ // if no handleId is given, we use the first handle, otherwise we check for the id
return (!handleId ? bounds[0] : bounds.find((d) => d.id === handleId)) || null
}
diff --git a/packages/core/src/utils/handle.ts b/packages/core/src/utils/handle.ts
index c28e38242..ac1799217 100644
--- a/packages/core/src/utils/handle.ts
+++ b/packages/core/src/utils/handle.ts
@@ -1,4 +1,4 @@
-import { ConnectionMode } from '../types'
+import { ConnectionMode, Position } from '../types'
import type {
Actions,
Connection,
@@ -6,23 +6,17 @@ import type {
ConnectionStatus,
GraphEdge,
GraphNode,
+ HandleElement,
HandleType,
+ IsValidParams,
NodeHandleBounds,
- Position,
- ValidConnectionFunc,
- ValidHandleResult,
+ NodeLookup,
+ Result,
XYPosition,
} from '../types'
-import { getEventPosition, getHandlePosition } from '.'
+import { getEventPosition, getHandlePosition, getOverlappingArea, nodeToRect } from '.'
-function defaultValidHandleResult(): ValidHandleResult {
- return {
- handleDomNode: null,
- isValid: false,
- connection: { source: '', target: '', sourceHandle: null, targetHandle: null },
- endHandle: null,
- }
-}
+const alwaysValid = () => true
export function resetRecentHandle(handleDomNode: Element): void {
handleDomNode?.classList.remove('valid', 'connecting', 'vue-flow__handle-valid', 'vue-flow__handle-connecting')
@@ -55,126 +49,126 @@ export function getHandles(
return connectionHandles
}
-export function getClosestHandle(
- event: MouseEvent | TouchEvent,
- doc: Document | ShadowRoot,
- pos: XYPosition,
- connectionRadius: number,
- handles: ConnectionHandle[],
- validator: (handle: Pick) => ValidHandleResult,
-) {
- // 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 { x, y } = getEventPosition(event)
- const domNodes = doc.elementsFromPoint(x, y)
-
- const handleBelow = domNodes.find((el) => el.classList.contains('vue-flow__handle'))
-
- if (handleBelow) {
- const handleNodeId = handleBelow.getAttribute('data-nodeid')
-
- if (handleNodeId) {
- const handleType = getHandleType(undefined, handleBelow)
- const handleId = handleBelow.getAttribute('data-handleid')
- const validHandleResult = validator({ nodeId: handleNodeId, id: handleId, type: handleType })
-
- if (validHandleResult) {
- const handle = handles.find((h) => h.nodeId === handleNodeId && h.type === handleType && h.id === handleId)
-
- return {
- handle: {
- id: handleId,
- type: handleType,
- nodeId: handleNodeId,
- x: handle?.x || pos.x,
- y: handle?.y || pos.y,
- },
- validHandleResult,
- }
- }
+function getNodesWithinDistance(position: XYPosition, nodeLookup: NodeLookup, distance: number): GraphNode[] {
+ const nodes: GraphNode[] = []
+ const rect = {
+ x: position.x - distance,
+ y: position.y - distance,
+ width: distance * 2,
+ height: distance * 2,
+ }
+
+ for (const node of nodeLookup.values()) {
+ if (getOverlappingArea(rect, nodeToRect(node)) > 0) {
+ nodes.push(node)
}
}
- // if we couldn't find a handle below the mouse cursor we look for the closest distance based on the connectionRadius
- let closestHandles: { handle: ConnectionHandle; validHandleResult: ValidHandleResult }[] = []
+ return nodes
+}
+
+// this distance is used for the area around the user pointer
+// while doing a connection for finding the closest nodes
+const ADDITIONAL_DISTANCE = 250
+
+export function getClosestHandle(
+ position: XYPosition,
+ connectionRadius: number,
+ nodeLookup: NodeLookup,
+ fromHandle: { nodeId: string; type: HandleType; id?: string | null },
+): HandleElement | null {
+ let closestHandles: HandleElement[] = []
let minDistance = Number.POSITIVE_INFINITY
- for (const handle of handles) {
- const distance = Math.sqrt((handle.x - pos.x) ** 2 + (handle.y - pos.y) ** 2)
+ const closeNodes = getNodesWithinDistance(position, nodeLookup, connectionRadius + ADDITIONAL_DISTANCE)
+
+ for (const node of closeNodes) {
+ const allHandles = [...(node.handleBounds?.source ?? []), ...(node.handleBounds?.target ?? [])]
+
+ for (const handle of allHandles) {
+ // if the handle is the same as the fromHandle we skip it
+ if (fromHandle.nodeId === handle.nodeId && fromHandle.type === handle.type && fromHandle.id === handle.id) {
+ continue
+ }
- if (distance <= connectionRadius) {
- const validHandleResult = validator(handle)
+ // determine absolute position of the handle
+ const { x, y } = getHandlePosition(node, handle, handle.position, true)
- if (distance <= minDistance) {
- 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,
- })
- }
+ const distance = Math.sqrt((x - position.x) ** 2 + (y - position.y) ** 2)
+ if (distance > connectionRadius) {
+ continue
+ }
+ if (distance < minDistance) {
+ closestHandles = [{ ...handle, x, y }]
minDistance = distance
+ } else if (distance === minDistance) {
+ // when multiple handles are on the same distance we collect all of them
+ closestHandles.push({ ...handle, x, y })
}
}
}
if (!closestHandles.length) {
- return { handle: null, validHandleResult: defaultValidHandleResult() }
+ return null
}
- if (closestHandles.length === 1) {
- return closestHandles[0]
+ // when multiple handles overlay each other we prefer the opposite handle
+ if (closestHandles.length > 1) {
+ const oppositeHandleType = fromHandle.type === 'source' ? 'target' : 'source'
+ return closestHandles.find((handle) => handle.type === oppositeHandleType) ?? closestHandles[0]
}
- const hasValidHandle = closestHandles.some(({ validHandleResult }) => validHandleResult.isValid)
- const hasTargetHandle = closestHandles.some(({ handle }) => handle.type === 'target')
-
- // if multiple handles are layout on top of each other we prefer the one with type = target and the one that is valid
- return (
- closestHandles.find(({ handle, validHandleResult }) =>
- hasTargetHandle ? handle.type === 'target' : hasValidHandle ? validHandleResult.isValid : true,
- ) || closestHandles[0]
- )
+ return closestHandles[0]
}
-// checks if and returns connection in fom of an object { source: 123, target: 312 }
+// checks if and returns connection in form of an object { source: 123, target: 312 }
export function isValidHandle(
event: MouseEvent | TouchEvent,
- handle: Pick | null,
- connectionMode: ConnectionMode,
- fromNodeId: string,
- fromHandleId: string | null,
- fromType: HandleType,
- isValidConnection: ValidConnectionFunc,
- doc: Document | ShadowRoot,
+ {
+ handle,
+ connectionMode,
+ fromNodeId,
+ fromHandleId,
+ fromType,
+ doc,
+ lib,
+ flowId,
+ isValidConnection = alwaysValid,
+ }: IsValidParams,
edges: GraphEdge[],
nodes: GraphNode[],
findNode: Actions['findNode'],
) {
const isTarget = fromType === 'target'
+ const handleDomNode = handle
+ ? doc.querySelector(`.${lib}-flow__handle[data-id="${flowId}-${handle?.nodeId}-${handle?.id}-${handle?.type}"]`)
+ : null
- const handleDomNode = doc.querySelector(`.vue-flow__handle[data-id="${handle?.nodeId}-${handle?.id}-${handle?.type}"]`)
const { x, y } = getEventPosition(event)
- const elementBelow = doc.elementFromPoint(x, y)
-
+ 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 = elementBelow?.classList.contains('vue-flow__handle') ? elementBelow : handleDomNode
+ const handleToCheck = handleBelow?.classList.contains(`${lib}-flow__handle`) ? handleBelow : handleDomNode
- const result = defaultValidHandleResult()
+ const result: Result = {
+ handleDomNode: handleToCheck,
+ isValid: false,
+ connection: null,
+ toHandle: null,
+ }
if (handleToCheck) {
- result.handleDomNode = handleToCheck
-
const handleType = getHandleType(undefined, handleToCheck)
- const handleNodeId = handleToCheck.getAttribute('data-nodeid')!
+ const handleNodeId = handleToCheck.getAttribute('data-nodeid')
const handleId = handleToCheck.getAttribute('data-handleid')
const connectable = handleToCheck.classList.contains('connectable')
const connectableEnd = handleToCheck.classList.contains('connectableend')
+ if (!handleNodeId || !handleType) {
+ return result
+ }
+
const connection: Connection = {
source: isTarget ? handleNodeId : fromNodeId,
sourceHandle: isTarget ? handleId : fromHandleId,
@@ -185,7 +179,6 @@ export function isValidHandle(
result.connection = connection
const isConnectable = connectable && connectableEnd
-
// in strict mode we don't allow target to target or source to source connections
const isValid =
isConnectable &&
@@ -193,54 +186,21 @@ export function isValidHandle(
? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target')
: handleNodeId !== fromNodeId || handleId !== fromHandleId)
- if (isValid) {
- result.isValid = isValidConnection(connection, {
- edges,
+ result.isValid =
+ isValid &&
+ isValidConnection(connection, {
nodes,
- sourceNode: findNode(connection.source)!,
- targetNode: findNode(connection.target)!,
+ edges,
+ sourceNode: findNode(fromNodeId)!,
+ targetNode: findNode(handleNodeId)!,
})
- result.endHandle = {
- nodeId: handleNodeId,
- handleId,
- type: handleType as HandleType,
- position: result.isValid ? (handleToCheck.getAttribute('data-handlepos') as Position) : null,
- }
- }
+ result.toHandle = handle
}
return result
}
-interface GetHandleLookupParams {
- nodes: GraphNode[]
- nodeId: string
- handleId: string | null
- handleType: string
-}
-
-export function getHandleLookup({ nodes, nodeId, handleId, handleType }: GetHandleLookupParams) {
- const handleLookup: ConnectionHandle[] = []
- for (let i = 0; i < nodes.length; i++) {
- const node = nodes[i]
-
- const { handleBounds } = node
-
- let sourceHandles: ConnectionHandle[] = []
- let targetHandles: ConnectionHandle[] = []
-
- if (handleBounds) {
- sourceHandles = getHandles(node, handleBounds, 'source', `${nodeId}-${handleId}-${handleType}`)
- targetHandles = getHandles(node, handleBounds, 'target', `${nodeId}-${handleId}-${handleType}`)
- }
-
- handleLookup.push(...sourceHandles, ...targetHandles)
- }
-
- return handleLookup
-}
-
export function getHandleType(edgeUpdaterType: HandleType | undefined, handleDomNode: Element | null): HandleType | null {
if (edgeUpdaterType) {
return edgeUpdaterType
@@ -253,7 +213,7 @@ export function getHandleType(edgeUpdaterType: HandleType | undefined, handleDom
return null
}
-export function getConnectionStatus(isInsideConnectionRadius: boolean, isHandleValid: boolean) {
+export function getConnectionStatus(isInsideConnectionRadius: boolean, isHandleValid: boolean | null) {
let connectionStatus: ConnectionStatus | null = null
if (isHandleValid) {
@@ -264,3 +224,44 @@ export function getConnectionStatus(isInsideConnectionRadius: boolean, isHandleV
return connectionStatus
}
+
+export function isConnectionValid(isInsideConnectionRadius: boolean, isHandleValid: boolean) {
+ let isValid: boolean | null = null
+
+ if (isHandleValid) {
+ isValid = true
+ } else if (isInsideConnectionRadius && !isHandleValid) {
+ isValid = false
+ }
+
+ return isValid
+}
+
+export function getHandle(
+ nodeId: string,
+ handleType: HandleType,
+ handleId: string | null,
+ nodeLookup: NodeLookup,
+ connectionMode: ConnectionMode,
+ withAbsolutePosition = false,
+): HandleElement | null {
+ const node = nodeLookup.get(nodeId)
+ if (!node) {
+ return null
+ }
+
+ const handles =
+ connectionMode === ConnectionMode.Strict
+ ? node.handleBounds?.[handleType]
+ : [...(node.handleBounds?.source ?? []), ...(node.handleBounds?.target ?? [])]
+ const handle = (handleId ? handles?.find((h) => h.id === handleId) : handles?.[0]) ?? null
+
+ return handle && withAbsolutePosition ? { ...handle, ...getHandlePosition(node, handle, handle.position, true) } : handle
+}
+
+export const oppositePosition = {
+ [Position.Left]: Position.Right,
+ [Position.Right]: Position.Left,
+ [Position.Top]: Position.Bottom,
+ [Position.Bottom]: Position.Top,
+}
diff --git a/packages/core/src/utils/node.ts b/packages/core/src/utils/node.ts
index 698460250..2ee8afb53 100644
--- a/packages/core/src/utils/node.ts
+++ b/packages/core/src/utils/node.ts
@@ -1,15 +1,15 @@
import type { Ref } from 'vue'
import { nextTick } from 'vue'
-import type { Actions, GraphNode, HandleElement, Position } from '../types'
+import type { Actions, GraphNode, HandleElement, HandleType, Position } from '../types'
import { getDimensions } from '.'
export function getHandleBounds(
- selector: string,
+ type: HandleType,
nodeElement: HTMLDivElement,
nodeBounds: DOMRect,
zoom: number,
): HandleElement[] {
- const handles = nodeElement.querySelectorAll(`.vue-flow__handle${selector}`)
+ const handles = nodeElement.querySelectorAll(`.vue-flow__handle.${type}`)
const handlesArray = Array.from(handles) as HTMLDivElement[]
@@ -19,6 +19,8 @@ export function getHandleBounds(
return {
id: handle.getAttribute('data-handleid'),
position: handle.getAttribute('data-handlepos') as unknown as Position,
+ nodeId: handle.getAttribute('data-nodeid') as string,
+ type,
x: (handleBounds.left - nodeBounds.left) / zoom,
y: (handleBounds.top - nodeBounds.top) / zoom,
...getDimensions(handle),
diff --git a/tests/cypress/component/2-vue-flow/updateEdge.cy.ts b/tests/cypress/component/2-vue-flow/updateEdge.cy.ts
index 7361de088..e43937ea1 100644
--- a/tests/cypress/component/2-vue-flow/updateEdge.cy.ts
+++ b/tests/cypress/component/2-vue-flow/updateEdge.cy.ts
@@ -50,13 +50,13 @@ describe('Check if edges are updatable', () => {
view: win,
})
.trigger('mousemove', {
- clientX: x + 5,
- clientY: y + 5,
+ clientX: x,
+ clientY: y,
force: true,
})
.trigger('mouseup', {
- clientX: x + 5,
- clientY: y + 5,
+ clientX: x,
+ clientY: y,
force: true,
view: win,
})
diff --git a/tests/package.json b/tests/package.json
index 08e1ecb20..c9024d4c0 100644
--- a/tests/package.json
+++ b/tests/package.json
@@ -3,7 +3,7 @@
"version": "0.0.0",
"private": true,
"scripts": {
- "test": "cypress run --component",
+ "test": "cypress run --component --browser chrome",
"open": "cypress open",
"lint": "eslint --ext .js,.ts ./"
},