1515
1616import { BlockSvg } from '../block_svg.js' ;
1717import { RenderedWorkspaceComment } from '../comments/rendered_workspace_comment.js' ;
18+ import { ConnectionType } from '../connection_type.js' ;
1819import { getFocusManager } from '../focus_manager.js' ;
1920import type { IFocusableNode } from '../interfaces/i_focusable_node.js' ;
2021import * as registry from '../registry.js' ;
22+ import { RenderedConnection } from '../rendered_connection.js' ;
2123import type { WorkspaceSvg } from '../workspace_svg.js' ;
2224import { Marker } from './marker.js' ;
2325
26+ /**
27+ * Representation of the direction of travel within a navigation context.
28+ */
29+ export enum NavigationDirection {
30+ NEXT ,
31+ PREVIOUS ,
32+ IN ,
33+ OUT ,
34+ }
35+
2436/**
2537 * Class for a line cursor.
2638 */
@@ -51,13 +63,7 @@ export class LineCursor extends Marker {
5163 }
5264 const newNode = this . getNextNode (
5365 curNode ,
54- ( candidate : IFocusableNode | null ) => {
55- return (
56- ( candidate instanceof BlockSvg &&
57- ! candidate . outputConnection ?. targetBlock ( ) ) ||
58- candidate instanceof RenderedWorkspaceComment
59- ) ;
60- } ,
66+ this . getValidationFunction ( NavigationDirection . NEXT ) ,
6167 true ,
6268 ) ;
6369
@@ -80,7 +86,11 @@ export class LineCursor extends Marker {
8086 return null ;
8187 }
8288
83- const newNode = this . getNextNode ( curNode , ( ) => true , true ) ;
89+ const newNode = this . getNextNode (
90+ curNode ,
91+ this . getValidationFunction ( NavigationDirection . IN ) ,
92+ true ,
93+ ) ;
8494
8595 if ( newNode ) {
8696 this . setCurNode ( newNode ) ;
@@ -101,13 +111,7 @@ export class LineCursor extends Marker {
101111 }
102112 const newNode = this . getPreviousNode (
103113 curNode ,
104- ( candidate : IFocusableNode | null ) => {
105- return (
106- ( candidate instanceof BlockSvg &&
107- ! candidate . outputConnection ?. targetBlock ( ) ) ||
108- candidate instanceof RenderedWorkspaceComment
109- ) ;
110- } ,
114+ this . getValidationFunction ( NavigationDirection . PREVIOUS ) ,
111115 true ,
112116 ) ;
113117
@@ -130,7 +134,11 @@ export class LineCursor extends Marker {
130134 return null ;
131135 }
132136
133- const newNode = this . getPreviousNode ( curNode , ( ) => true , true ) ;
137+ const newNode = this . getPreviousNode (
138+ curNode ,
139+ this . getValidationFunction ( NavigationDirection . OUT ) ,
140+ true ,
141+ ) ;
134142
135143 if ( newNode ) {
136144 this . setCurNode ( newNode ) ;
@@ -147,15 +155,14 @@ export class LineCursor extends Marker {
147155 atEndOfLine ( ) : boolean {
148156 const curNode = this . getCurNode ( ) ;
149157 if ( ! curNode ) return false ;
150- const inNode = this . getNextNode ( curNode , ( ) => true , true ) ;
158+ const inNode = this . getNextNode (
159+ curNode ,
160+ this . getValidationFunction ( NavigationDirection . IN ) ,
161+ true ,
162+ ) ;
151163 const nextNode = this . getNextNode (
152164 curNode ,
153- ( candidate : IFocusableNode | null ) => {
154- return (
155- candidate instanceof BlockSvg &&
156- ! candidate . outputConnection ?. targetBlock ( )
157- ) ;
158- } ,
165+ this . getValidationFunction ( NavigationDirection . NEXT ) ,
159166 true ,
160167 ) ;
161168
@@ -298,6 +305,92 @@ export class LineCursor extends Marker {
298305 return this . getRightMostChild ( newNode , stopIfFound ) ;
299306 }
300307
308+ /**
309+ * Returns a function that will be used to determine whether a candidate for
310+ * navigation is valid.
311+ *
312+ * @param direction The direction in which the user is navigating.
313+ * @returns A function that takes a proposed navigation candidate and returns
314+ * true if navigation should be allowed to proceed to it, or false to find
315+ * a different candidate.
316+ */
317+ getValidationFunction (
318+ direction : NavigationDirection ,
319+ ) : ( node : IFocusableNode | null ) => boolean {
320+ switch ( direction ) {
321+ case NavigationDirection . IN :
322+ case NavigationDirection . OUT :
323+ return ( ) => true ;
324+ case NavigationDirection . NEXT :
325+ case NavigationDirection . PREVIOUS :
326+ return ( candidate : IFocusableNode | null ) => {
327+ if (
328+ ( candidate instanceof BlockSvg &&
329+ ! candidate . outputConnection ?. targetBlock ( ) ) ||
330+ candidate instanceof RenderedWorkspaceComment ||
331+ ( candidate instanceof RenderedConnection &&
332+ ( candidate . type === ConnectionType . NEXT_STATEMENT ||
333+ ( candidate . type === ConnectionType . INPUT_VALUE &&
334+ candidate . getSourceBlock ( ) . statementInputCount &&
335+ candidate . getSourceBlock ( ) . inputList [ 0 ] !==
336+ candidate . getParentInput ( ) ) ) )
337+ ) {
338+ return true ;
339+ }
340+
341+ const current = this . getSourceBlockFromNode ( this . getCurNode ( ) ) ;
342+ if ( candidate instanceof BlockSvg && current instanceof BlockSvg ) {
343+ // If the candidate's parent uses inline inputs, disallow the
344+ // candidate; it follows that it must be on the same row as its
345+ // parent.
346+ if ( candidate . outputConnection ?. targetBlock ( ) ?. getInputsInline ( ) ) {
347+ return false ;
348+ }
349+
350+ const candidateParents = this . getParents ( candidate ) ;
351+ // If the candidate block is an (in)direct child of the current
352+ // block, disallow it; it cannot be on a different row than the
353+ // current block.
354+ if (
355+ current === this . getCurNode ( ) &&
356+ candidateParents . has ( current )
357+ ) {
358+ return false ;
359+ }
360+
361+ const currentParents = this . getParents ( current ) ;
362+
363+ const sharedParents = currentParents . intersection ( candidateParents ) ;
364+ // Allow the candidate if it and the current block have no parents
365+ // in common, or if they have a shared parent with external inputs.
366+ const result =
367+ ! sharedParents . size ||
368+ sharedParents . values ( ) . some ( ( block ) => ! block . getInputsInline ( ) ) ;
369+ return result ;
370+ }
371+
372+ return false ;
373+ } ;
374+ }
375+ }
376+
377+ /**
378+ * Returns a set of all of the parent blocks of the given block.
379+ *
380+ * @param block The block to retrieve the parents of.
381+ * @returns A set of the parents of the given block.
382+ */
383+ private getParents ( block : BlockSvg ) : Set < BlockSvg > {
384+ const parents = new Set < BlockSvg > ( ) ;
385+ let parent = block . getParent ( ) ;
386+ while ( parent ) {
387+ parents . add ( parent ) ;
388+ parent = parent . getParent ( ) ;
389+ }
390+
391+ return parents ;
392+ }
393+
301394 /**
302395 * Prepare for the deletion of a block by making a list of nodes we
303396 * could move the cursor to afterwards and save it to
0 commit comments