mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-01 14:59:19 +08:00
improvement(workflow): use DOM hit-testing for edge drop-on-block detection (#3851)
This commit is contained in:
@@ -263,7 +263,7 @@ const WorkflowContent = React.memo(
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
const reactFlowInstance = useReactFlow()
|
||||
const { screenToFlowPosition, getNodes, setNodes, getIntersectingNodes } = reactFlowInstance
|
||||
const { screenToFlowPosition, getNodes, setNodes } = reactFlowInstance
|
||||
const { fitViewToBounds, getViewportCenter } = useCanvasViewport(reactFlowInstance, {
|
||||
embedded,
|
||||
})
|
||||
@@ -2849,38 +2849,29 @@ const WorkflowContent = React.memo(
|
||||
)
|
||||
|
||||
/**
|
||||
* Finds the best node at a given flow position for drop-on-block connection.
|
||||
* Skips subflow containers as they have their own connection logic.
|
||||
* Finds the node under the cursor using DOM hit-testing for pixel-perfect
|
||||
* detection that matches exactly what the user sees on screen.
|
||||
* Uses the same approach as ReactFlow's internal handle detection.
|
||||
*/
|
||||
const findNodeAtPosition = useCallback(
|
||||
(position: { x: number; y: number }) => {
|
||||
const cursorRect = {
|
||||
x: position.x - 1,
|
||||
y: position.y - 1,
|
||||
width: 2,
|
||||
height: 2,
|
||||
const findNodeAtScreenPosition = useCallback(
|
||||
(clientX: number, clientY: number) => {
|
||||
const elements = document.elementsFromPoint(clientX, clientY)
|
||||
const nodes = getNodes()
|
||||
|
||||
for (const el of elements) {
|
||||
const nodeEl = el.closest('.react-flow__node') as HTMLElement | null
|
||||
if (!nodeEl) continue
|
||||
|
||||
const nodeId = nodeEl.getAttribute('data-id')
|
||||
if (!nodeId) continue
|
||||
|
||||
const node = nodes.find((n) => n.id === nodeId)
|
||||
if (node && node.type !== 'subflowNode') return node
|
||||
}
|
||||
|
||||
const intersecting = getIntersectingNodes(cursorRect, true).filter(
|
||||
(node) => node.type !== 'subflowNode'
|
||||
)
|
||||
|
||||
if (intersecting.length === 0) return undefined
|
||||
if (intersecting.length === 1) return intersecting[0]
|
||||
|
||||
return intersecting.reduce((closest, node) => {
|
||||
const getDistance = (n: Node) => {
|
||||
const absPos = getNodeAbsolutePosition(n.id)
|
||||
const dims = getBlockDimensions(n.id)
|
||||
const centerX = absPos.x + dims.width / 2
|
||||
const centerY = absPos.y + dims.height / 2
|
||||
return Math.hypot(position.x - centerX, position.y - centerY)
|
||||
}
|
||||
|
||||
return getDistance(node) < getDistance(closest) ? node : closest
|
||||
})
|
||||
return undefined
|
||||
},
|
||||
[getIntersectingNodes, getNodeAbsolutePosition, getBlockDimensions]
|
||||
[getNodes]
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -3005,15 +2996,9 @@ const WorkflowContent = React.memo(
|
||||
return
|
||||
}
|
||||
|
||||
// Get cursor position in flow coordinates
|
||||
// Find node under cursor using DOM hit-testing
|
||||
const clientPos = 'changedTouches' in event ? event.changedTouches[0] : event
|
||||
const flowPosition = screenToFlowPosition({
|
||||
x: clientPos.clientX,
|
||||
y: clientPos.clientY,
|
||||
})
|
||||
|
||||
// Find node under cursor
|
||||
const targetNode = findNodeAtPosition(flowPosition)
|
||||
const targetNode = findNodeAtScreenPosition(clientPos.clientX, clientPos.clientY)
|
||||
|
||||
// Create connection if valid target found (handle-to-body case)
|
||||
if (targetNode && targetNode.id !== source.nodeId) {
|
||||
@@ -3027,7 +3012,7 @@ const WorkflowContent = React.memo(
|
||||
|
||||
connectionSourceRef.current = null
|
||||
},
|
||||
[screenToFlowPosition, findNodeAtPosition, onConnect]
|
||||
[findNodeAtScreenPosition, onConnect]
|
||||
)
|
||||
|
||||
/** Handles node drag to detect container intersections and update highlighting. */
|
||||
|
||||
Reference in New Issue
Block a user