@@ -8,6 +8,8 @@ import {getFocusManager} from '../../focus_manager.js';
88import type { IFocusableNode } from '../../interfaces/i_focusable_node.js' ;
99import { isSelectableToolboxItem } from '../../interfaces/i_selectable_toolbox_item.js' ;
1010import type { IToolbox } from '../../interfaces/i_toolbox.js' ;
11+ import { Position } from '../../utils/toolbox.js' ;
12+ import type { WorkspaceSvg } from '../../workspace_svg.js' ;
1113import { ToolboxItemNavigationPolicy } from '../navigation_policies/toolbox_item_navigation_policy.js' ;
1214import { Navigator } from './navigator.js' ;
1315
@@ -21,20 +23,118 @@ export class ToolboxNavigator extends Navigator {
2123 }
2224
2325 /**
24- * Returns the flyout's first item when navigating to the right in a toolbox
25- * from a toolbox item that has a flyout.
26+ * Returns the flyout's first item (if any) or next toolbox item when
27+ * navigating in (right arrow) from a toolbox.
28+ *
29+ * @param node The toolbox item to navigate relative to.
30+ * @param bypassAdjustments True to skip adjusting navigation based on the
31+ * toolbox's layout; false (default) to take it into account.
2632 */
2733 override getInNode (
28- current = getFocusManager ( ) . getFocusedNode ( ) ,
34+ node = getFocusManager ( ) . getFocusedNode ( ) ,
35+ bypassAdjustments = false ,
2936 ) : IFocusableNode | null {
30- if ( isSelectableToolboxItem ( current ) && ! current . getContents ( ) . length ) {
37+ const position = getPhysicalToolboxPosition ( this . toolbox . getWorkspace ( ) ) ;
38+ if ( ! bypassAdjustments ) {
39+ switch ( position ) {
40+ case Position . TOP :
41+ case Position . BOTTOM :
42+ return this . getNextNode ( node , true ) ;
43+ case Position . RIGHT :
44+ return this . getOutNode ( node , true ) ;
45+ }
46+ }
47+
48+ if ( isSelectableToolboxItem ( node ) && ! node . getContents ( ) . length ) {
3149 return null ;
3250 }
3351
34- return (
35- this . toolbox . getFlyout ( ) ?. getWorkspace ( ) . getRestoredFocusableNode ( null ) ??
36- null
37- ) ;
52+ const flyoutNavigator = this . toolbox
53+ . getFlyout ( )
54+ ?. getWorkspace ( )
55+ . getNavigator ( ) ;
56+ if ( ! flyoutNavigator ) return null ;
57+
58+ return this . toolbox . getWorkspace ( ) . RTL &&
59+ ( position === Position . TOP || position === Position . BOTTOM )
60+ ? flyoutNavigator . getLastNode ( )
61+ : flyoutNavigator . getFirstNode ( ) ;
62+ }
63+
64+ /**
65+ * Returns the flyout's first item (if any) or previous toolbox item when
66+ * navigating out (left arrow) from a toolbox.
67+ *
68+ * @param node The toolbox item to navigate relative to.
69+ * @param bypassAdjustments True to skip adjusting navigation based on the
70+ * toolbox's layout; false (default) to take it into account.
71+ */
72+ override getOutNode (
73+ node ?: IFocusableNode | null ,
74+ bypassAdjustments = false ,
75+ ) : IFocusableNode | null {
76+ if ( ! bypassAdjustments ) {
77+ const position = getPhysicalToolboxPosition ( this . toolbox . getWorkspace ( ) ) ;
78+ switch ( position ) {
79+ case Position . TOP :
80+ case Position . BOTTOM :
81+ return this . getPreviousNode ( node , true ) ;
82+ case Position . RIGHT :
83+ return this . getInNode ( node , true ) ;
84+ }
85+ }
86+
87+ return super . getOutNode ( node ) ;
88+ }
89+
90+ /**
91+ * Returns the flyout's first item (if any) or next toolbox item when
92+ * navigating next (down arrow) from a toolbox.
93+ *
94+ * @param node The toolbox item to navigate relative to.
95+ * @param bypassAdjustments True to skip adjusting navigation based on the
96+ * toolbox's layout; false (default) to take it into account.
97+ */
98+ override getNextNode (
99+ node ?: IFocusableNode | null ,
100+ bypassAdjustments = false ,
101+ ) : IFocusableNode | null {
102+ if ( ! bypassAdjustments ) {
103+ const position = getPhysicalToolboxPosition ( this . toolbox . getWorkspace ( ) ) ;
104+ switch ( position ) {
105+ case Position . TOP :
106+ return this . getInNode ( node , true ) ;
107+ case Position . BOTTOM :
108+ return this . getOutNode ( node , true ) ;
109+ }
110+ }
111+
112+ return super . getNextNode ( node ) ;
113+ }
114+
115+ /**
116+ * Returns the flyout's first item (if any) or previous toolbox item when
117+ * navigating previous (up arrow) from a toolbox.
118+ *
119+ * @param node The toolbox item to navigate relative to.
120+ * @param bypassAdjustments True to skip adjusting navigation based on the
121+ * toolbox's layout; false (default) to take it into account.
122+ */
123+ override getPreviousNode (
124+ node ?: IFocusableNode | null ,
125+ bypassAdjustments = false ,
126+ ) : IFocusableNode | null {
127+ if ( ! bypassAdjustments ) {
128+ const position = getPhysicalToolboxPosition ( this . toolbox . getWorkspace ( ) ) ;
129+ switch ( position ) {
130+ case Position . TOP :
131+ return this . getOutNode ( node , true ) ;
132+ case Position . BOTTOM :
133+ return this . getInNode ( node , true ) ;
134+ }
135+ }
136+
137+ return super . getPreviousNode ( node ) ;
38138 }
39139
40140 /**
@@ -44,3 +144,26 @@ export class ToolboxNavigator extends Navigator {
44144 return this . toolbox . getToolboxItems ( ) ;
45145 }
46146}
147+
148+ /**
149+ * Although developers specify the toolbox position as "start" or "end", this
150+ * gets normalized by the injection options parser based on RTL, such that "end"
151+ * in RTL means the left. When dealing with arrow keys, we want the actual/
152+ * physical position on screen, not the logical position. This function converts
153+ * the stored logical position to the physical position.
154+ *
155+ * @internal
156+ * @param workspace The workspace to use injection options from.
157+ * @returns The physical location of the toolbox/flyout on screen.
158+ */
159+ export function getPhysicalToolboxPosition ( workspace : WorkspaceSvg ) : Position {
160+ const logicalPosition = workspace . options . toolboxPosition ;
161+ if (
162+ workspace . options . RTL &&
163+ ! ( logicalPosition === Position . TOP || logicalPosition === Position . BOTTOM )
164+ ) {
165+ return logicalPosition === Position . LEFT ? Position . RIGHT : Position . LEFT ;
166+ }
167+
168+ return logicalPosition ;
169+ }
0 commit comments