@@ -600,6 +600,9 @@ const WorkflowContent = React.memo(
600600 /** Tracks whether onConnect successfully handled the connection (ReactFlow pattern). */
601601 const connectionCompletedRef = useRef ( false )
602602
603+ /** Set when Escape aborts an in-flight connection drag so no edge or selector results. */
604+ const connectionCancelledRef = useRef ( false )
605+
603606 /** Stores start positions for multi-node drag undo/redo recording. */
604607 const multiNodeDragStartRef = useRef < Map < string , { x : number ; y : number ; parentId ?: string } > > (
605608 new Map ( )
@@ -3214,6 +3217,32 @@ const WorkflowContent = React.memo(
32143217 [ getNodes ]
32153218 )
32163219
3220+ /**
3221+ * Aborts an in-flight connection drag on Escape.
3222+ *
3223+ * React Flow only tears a handle drag down on pointer release, so a synthetic
3224+ * mouseup is dispatched to run its own cleanup: it stops auto-panning, clears
3225+ * the connection line and handle highlights, and detaches its document
3226+ * listeners. `connectionCancelledRef` turns the resulting `onConnect` and
3227+ * `onConnectEnd` into no-ops so the drag leaves behind neither an edge nor the
3228+ * block selector, and the real mouseup that follows is inert.
3229+ *
3230+ * Listens in the capture phase and stops propagation so Escape mid-drag only
3231+ * cancels the edge and never reaches an unrelated Escape handler.
3232+ */
3233+ const handleConnectionEscape = useCallback ( ( event : KeyboardEvent ) => {
3234+ if ( event . key !== 'Escape' || ! connectionSourceRef . current ) return
3235+ event . preventDefault ( )
3236+ event . stopPropagation ( )
3237+ connectionCancelledRef . current = true
3238+ document . dispatchEvent ( new MouseEvent ( 'mouseup' , { bubbles : true } ) )
3239+ } , [ ] )
3240+
3241+ useEffect (
3242+ ( ) => ( ) => window . removeEventListener ( 'keydown' , handleConnectionEscape , true ) ,
3243+ [ handleConnectionEscape ]
3244+ )
3245+
32173246 /**
32183247 * Captures the source handle when a connection drag starts.
32193248 * Resets connectionCompletedRef to track if onConnect handles this connection.
@@ -3232,13 +3261,16 @@ const WorkflowContent = React.memo(
32323261 handleId : params ?. handleId ,
32333262 }
32343263 connectionCompletedRef . current = false
3264+ connectionCancelledRef . current = false
3265+ window . addEventListener ( 'keydown' , handleConnectionEscape , true )
32353266 } ,
3236- [ closeConnectionBlockSelector ]
3267+ [ closeConnectionBlockSelector , handleConnectionEscape ]
32373268 )
32383269
32393270 /** Handles new edge connections with container boundary validation. */
32403271 const onConnect = useCallback (
32413272 ( connection : any ) => {
3273+ if ( connectionCancelledRef . current ) return
32423274 if ( connection . source && connection . target ) {
32433275 const normalizedConnection = {
32443276 ...connection ,
@@ -3336,11 +3368,12 @@ const WorkflowContent = React.memo(
33363368 */
33373369 const onConnectEnd = useCallback (
33383370 ( event : MouseEvent | TouchEvent ) => {
3371+ window . removeEventListener ( 'keydown' , handleConnectionEscape , true )
33393372 canvasContainerRef . current ?. setAttribute ( 'data-connection-line' , 'default' )
33403373 canvasContainerRef . current ?. setAttribute ( 'data-connection-active' , 'false' )
33413374
33423375 const source = connectionSourceRef . current
3343- if ( ! source ?. nodeId ) {
3376+ if ( ! source ?. nodeId || connectionCancelledRef . current ) {
33443377 connectionSourceRef . current = null
33453378 return
33463379 }
@@ -3417,7 +3450,14 @@ const WorkflowContent = React.memo(
34173450
34183451 connectionSourceRef . current = null
34193452 } ,
3420- [ findNodeAtScreenPosition , onConnect , blocks , reactFlowInstance , screenToFlowPosition ]
3453+ [
3454+ findNodeAtScreenPosition ,
3455+ onConnect ,
3456+ blocks ,
3457+ reactFlowInstance ,
3458+ screenToFlowPosition ,
3459+ handleConnectionEscape ,
3460+ ]
34213461 )
34223462
34233463 /** Handles node drag to detect container intersections and update highlighting. */
0 commit comments