@@ -248,6 +248,9 @@ const WorkflowContent = React.memo(() => {
248248 } ) )
249249 )
250250
251+ /** Stores source node/handle info when a connection drag starts for drop-on-block detection. */
252+ const connectionSourceRef = useRef < { nodeId : string ; handleId : string } | null > ( null )
253+
251254 /** Re-applies diff markers when blocks change after socket rehydration. */
252255 const blocksRef = useRef ( blocks )
253256 useEffect ( ( ) => {
@@ -1702,20 +1705,43 @@ const WorkflowContent = React.memo(() => {
17021705 [ removeEdge ]
17031706 )
17041707
1708+ /**
1709+ * Finds a node at a given flow position for drop-on-block connection.
1710+ * Skips subflow containers as they have their own connection logic.
1711+ */
1712+ const findNodeAtPosition = useCallback (
1713+ ( position : { x : number ; y : number } ) => {
1714+ const nodes = getNodes ( )
1715+
1716+ return nodes . find ( ( node ) => {
1717+ // Skip subflow containers for drop targets
1718+ if ( node . type === 'subflowNode' ) return false
1719+
1720+ const absPos = getNodeAbsolutePosition ( node . id )
1721+ const dims = getBlockDimensions ( node . id )
1722+
1723+ return (
1724+ position . x >= absPos . x &&
1725+ position . x <= absPos . x + dims . width &&
1726+ position . y >= absPos . y &&
1727+ position . y <= absPos . y + dims . height
1728+ )
1729+ } )
1730+ } ,
1731+ [ getNodes , getNodeAbsolutePosition , getBlockDimensions ]
1732+ )
1733+
17051734 /**
17061735 * Captures the source handle when a connection drag starts
17071736 */
17081737 const onConnectStart = useCallback ( ( _event : any , params : any ) => {
17091738 const handleId : string | undefined = params ?. handleId
17101739 // Treat explicit error handle (id === 'error') as error connection
17111740 setIsErrorConnectionDrag ( handleId === 'error' )
1712- } , [ ] )
1713-
1714- /**
1715- * Resets the source handle when connection drag ends
1716- */
1717- const onConnectEnd = useCallback ( ( ) => {
1718- setIsErrorConnectionDrag ( false )
1741+ connectionSourceRef . current = {
1742+ nodeId : params ?. nodeId ,
1743+ handleId : params ?. handleId ,
1744+ }
17191745 } , [ ] )
17201746
17211747 /** Handles new edge connections with container boundary validation. */
@@ -1806,7 +1832,46 @@ const WorkflowContent = React.memo(() => {
18061832 } )
18071833 }
18081834 } ,
1809- [ addEdge , getNodes ]
1835+ [ addEdge , getNodes , blocks ]
1836+ )
1837+
1838+ /**
1839+ * Handles connection drag end. Detects if the edge was dropped over a block
1840+ * and automatically creates a connection to that block's target handle.
1841+ */
1842+ const onConnectEnd = useCallback (
1843+ ( event : MouseEvent | TouchEvent ) => {
1844+ setIsErrorConnectionDrag ( false )
1845+
1846+ const source = connectionSourceRef . current
1847+ if ( ! source ?. nodeId ) {
1848+ connectionSourceRef . current = null
1849+ return
1850+ }
1851+
1852+ // Get cursor position in flow coordinates
1853+ const clientPos = 'changedTouches' in event ? event . changedTouches [ 0 ] : event
1854+ const flowPosition = screenToFlowPosition ( {
1855+ x : clientPos . clientX ,
1856+ y : clientPos . clientY ,
1857+ } )
1858+
1859+ // Find node under cursor
1860+ const targetNode = findNodeAtPosition ( flowPosition )
1861+
1862+ // Create connection if valid target found
1863+ if ( targetNode && targetNode . id !== source . nodeId ) {
1864+ onConnect ( {
1865+ source : source . nodeId ,
1866+ sourceHandle : source . handleId ,
1867+ target : targetNode . id ,
1868+ targetHandle : 'target' ,
1869+ } )
1870+ }
1871+
1872+ connectionSourceRef . current = null
1873+ } ,
1874+ [ screenToFlowPosition , findNodeAtPosition , onConnect ]
18101875 )
18111876
18121877 /** Handles node drag to detect container intersections and update highlighting. */
0 commit comments