@@ -58,6 +58,9 @@ export class KeyboardDragStrategy extends dragging.BlockDragStrategy {
5858 this . block . moveDuringDrag (
5959 new utils . Coordinate ( neighbour . x + 10 , neighbour . y + 10 ) ,
6060 ) ;
61+ } else {
62+ // Handle the case when unconstrained drag was far from any candidate.
63+ this . searchNode = null ;
6164 }
6265 }
6366
@@ -71,58 +74,96 @@ export class KeyboardDragStrategy extends dragging.BlockDragStrategy {
7174 */
7275 private getConstrainedConnectionCandidate (
7376 draggingBlock : BlockSvg ,
77+ ) : ConnectionCandidate | null {
78+ // @ts -expect-error getLocalConnections is private.
79+ const localConns = this . getLocalConnections ( draggingBlock ) ;
80+
81+ let candidateConnection = this . findTraversalCandidate (
82+ draggingBlock ,
83+ localConns ,
84+ ) ;
85+ // Fall back on a coordinate-based search if there was no good starting
86+ // point for the search.
87+ if ( ! candidateConnection && ! this . searchNode ) {
88+ candidateConnection = this . findNearestCandidate ( localConns ) ;
89+ }
90+ return candidateConnection ;
91+ }
92+
93+ /**
94+ * Get the nearest valid candidate connection, regardless of direction.
95+ * TODO(github.com/google/blockly/issues/8869): Replace with an
96+ * override of `getSearchRadius` when implemented in core.
97+ *
98+ * @param localConns The list of connections on the dragging block(s) that are
99+ * available to connect to.
100+ * @returns A candidate connection and radius, or null if none was found.
101+ */
102+ findNearestCandidate (
103+ localConns : RenderedConnection [ ] ,
104+ ) : ConnectionCandidate | null {
105+ let radius = Infinity ;
106+ let candidate = null ;
107+ const dxy = new utils . Coordinate ( 0 , 0 ) ;
108+
109+ for ( const conn of localConns ) {
110+ const { connection : neighbour , radius : rad } = conn . closest ( radius , dxy ) ;
111+ if ( neighbour ) {
112+ candidate = {
113+ local : conn ,
114+ neighbour : neighbour ,
115+ distance : rad ,
116+ } ;
117+ radius = rad ;
118+ }
119+ }
120+ return candidate ;
121+ }
122+
123+ /**
124+ * Get the nearest valid candidate connection in traversal order.
125+ *
126+ * @param draggingBlock The root block being dragged.
127+ * @param localConns The list of connections on the dragging block(s) that are
128+ * available to connect to.
129+ * @returns A candidate connection and radius, or null if none was found.
130+ */
131+ findTraversalCandidate (
132+ draggingBlock : BlockSvg ,
133+ localConns : RenderedConnection [ ] ,
74134 ) : ConnectionCandidate | null {
75135 // TODO(#385): Make sure this works for any cursor, not just LineCursor.
76136 const cursor = draggingBlock . workspace . getCursor ( ) as LineCursor ;
137+ if ( ! cursor ) return null ;
77138
78- const initialNode = this . searchNode ;
79- if ( ! initialNode || ! cursor ) return null ;
80-
81- // @ts -expect-error getLocalConnections is private.
82- const localConns = this . getLocalConnections ( draggingBlock ) ;
83139 const connectionChecker = draggingBlock . workspace . connectionChecker ;
84-
85140 let candidateConnection : ConnectionCandidate | null = null ;
86-
87- let potential : ASTNode | null = initialNode ;
141+ let potential : ASTNode | null = this . searchNode ;
142+ const dir = this . currentDragDirection ;
88143 while ( potential && ! candidateConnection ) {
89- if (
90- this . currentDragDirection === Direction . Up ||
91- this . currentDragDirection === Direction . Left
92- ) {
144+ if ( dir === Direction . Up || dir === Direction . Left ) {
93145 potential = cursor . getPreviousNode ( potential , ( node ) => {
94146 // @ts -expect-error isConnectionType is private.
95147 return node && ASTNode . isConnectionType ( node . getType ( ) ) ;
96148 } ) ;
97- } else if (
98- this . currentDragDirection === Direction . Down ||
99- this . currentDragDirection === Direction . Right
100- ) {
149+ } else if ( dir === Direction . Down || dir === Direction . Right ) {
101150 potential = cursor . getNextNode ( potential , ( node ) => {
102151 // @ts -expect-error isConnectionType is private.
103152 return node && ASTNode . isConnectionType ( node . getType ( ) ) ;
104153 } ) ;
105154 }
106155
107156 localConns . forEach ( ( conn : RenderedConnection ) => {
108- const potentialLocation =
109- potential ?. getLocation ( ) as RenderedConnection ;
110- if (
111- connectionChecker . canConnect ( conn , potentialLocation , true , Infinity )
112- ) {
157+ const location = potential ?. getLocation ( ) as RenderedConnection ;
158+ if ( connectionChecker . canConnect ( conn , location , true , Infinity ) ) {
113159 candidateConnection = {
114160 local : conn ,
115- neighbour : potentialLocation ,
161+ neighbour : location ,
116162 distance : 0 ,
117163 } ;
118164 }
119165 } ) ;
120166 }
121- if ( candidateConnection ) {
122- this . searchNode = ASTNode . createConnectionNode (
123- ( candidateConnection as ConnectionCandidate ) . neighbour ,
124- ) ;
125- }
126167 return candidateConnection ;
127168 }
128169
0 commit comments