@@ -77,6 +77,7 @@ interface FocusableComponent {
7777 isFocusBoundary : boolean ;
7878 focusBoundaryDirections ?: string [ ] ;
7979 autoRestoreFocus : boolean ;
80+ forceFocus : boolean ;
8081 lastFocusedChildKey ?: string ;
8182 layout ?: FocusableComponentLayout ;
8283 layoutUpdated ?: boolean ;
@@ -873,6 +874,11 @@ class SpatialNavigationService {
873874 return ;
874875 }
875876
877+ const isVerticalDirection =
878+ direction === DIRECTION_DOWN || direction === DIRECTION_UP ;
879+ const isIncrementalDirection =
880+ direction === DIRECTION_DOWN || direction === DIRECTION_RIGHT ;
881+
876882 this . log ( 'smartNavigate' , 'direction' , direction ) ;
877883 this . log ( 'smartNavigate' , 'fromParentFocusKey' , fromParentFocusKey ) ;
878884 this . log ( 'smartNavigate' , 'this.focusKey' , this . focusKey ) ;
@@ -887,6 +893,15 @@ class SpatialNavigationService {
887893 const currentComponent =
888894 this . focusableComponents [ fromParentFocusKey || this . focusKey ] ;
889895
896+ /**
897+ * When there's no currently focused component, an attempt is made, to force focus one of
898+ * the Focusable Containers, that have "forceFocus" flag enabled.
899+ */
900+ if ( ! fromParentFocusKey && ! currentComponent ) {
901+ this . setFocus ( this . getForcedFocusKey ( ) ) ;
902+ return ;
903+ }
904+
890905 this . log (
891906 'smartNavigate' ,
892907 'currentComponent' ,
@@ -899,11 +914,6 @@ class SpatialNavigationService {
899914 this . updateLayout ( currentComponent . focusKey ) ;
900915 const { parentFocusKey, focusKey, layout } = currentComponent ;
901916
902- const isVerticalDirection =
903- direction === DIRECTION_DOWN || direction === DIRECTION_UP ;
904- const isIncrementalDirection =
905- direction === DIRECTION_DOWN || direction === DIRECTION_RIGHT ;
906-
907917 const currentCutoffCoordinate =
908918 SpatialNavigationService . getCutoffCoordinate (
909919 isVerticalDirection ,
@@ -1031,6 +1041,30 @@ class SpatialNavigationService {
10311041 return this . focusKey ;
10321042 }
10331043
1044+ /**
1045+ * Returns the focus key to which focus can be forced if there are force-focusable components.
1046+ * A component closest to the top left viewport corner (0,0) is returned.
1047+ */
1048+ getForcedFocusKey ( ) : string | undefined {
1049+ const forceFocusableComponents = filter (
1050+ this . focusableComponents ,
1051+ ( component ) => component . focusable && component . forceFocus
1052+ ) ;
1053+
1054+ /**
1055+ * Searching of the top level component that is closest to the top left viewport corner (0,0).
1056+ * To achieve meaningful and coherent results, 'down' direction is forced.
1057+ */
1058+ const sortedForceFocusableComponents = this . sortSiblingsByPriority (
1059+ forceFocusableComponents ,
1060+ { x :0 , y :0 , width :0 , height : 0 , left : 0 , top :0 , node : null } ,
1061+ 'down' ,
1062+ ROOT_FOCUS_KEY
1063+ ) ;
1064+
1065+ return first ( sortedForceFocusableComponents ) ?. focusKey ;
1066+ }
1067+
10341068 /**
10351069 * This function tries to determine the next component to Focus
10361070 * It's either the target node OR the one down by the Tree if node has children components
@@ -1133,6 +1167,7 @@ class SpatialNavigationService {
11331167 onUpdateHasFocusedChild,
11341168 preferredChildFocusKey,
11351169 autoRestoreFocus,
1170+ forceFocus,
11361171 focusable,
11371172 isFocusBoundary,
11381173 focusBoundaryDirections
@@ -1155,6 +1190,7 @@ class SpatialNavigationService {
11551190 isFocusBoundary,
11561191 focusBoundaryDirections,
11571192 autoRestoreFocus,
1193+ forceFocus,
11581194 lastFocusedChildKey : null ,
11591195 layout : {
11601196 x : 0 ,
@@ -1453,6 +1489,16 @@ class SpatialNavigationService {
14531489
14541490 this . log ( 'setFocus' , 'focusKey' , focusKey ) ;
14551491
1492+ /**
1493+ * When focusKey is not provided or is equal to `ROOT_FOCUS_KEY`, an attempt is made,
1494+ * to force focus one of the Focusable Containers, that have "forceFocus" flag enabled.
1495+ * A component closest to the top left viewport corner (0,0) is force-focused.
1496+ */
1497+ if ( ! focusKey || focusKey === ROOT_FOCUS_KEY ) {
1498+ // eslint-disable-next-line no-param-reassign
1499+ focusKey = this . getForcedFocusKey ( ) ;
1500+ }
1501+
14561502 const newFocusKey = this . getNextFocusKey ( focusKey ) ;
14571503
14581504 this . log ( 'setFocus' , 'newFocusKey' , newFocusKey ) ;
0 commit comments