@@ -36,24 +36,25 @@ function clickMenuItem(items: MenuItemPattern<any>[], index: number, mods?: Modi
3636 } as unknown as PointerEvent ;
3737}
3838
39- function getMenuTriggerPattern ( ) {
39+ function getMenuTriggerPattern ( opts ?: { textDirection : 'ltr' | 'rtl' } ) {
4040 const element = signal ( document . createElement ( 'button' ) ) ;
4141 const submenu = signal < MenuPattern < string > | undefined > ( undefined ) ;
4242 const trigger = new MenuTriggerPattern < string > ( {
43+ textDirection : signal ( opts ?. textDirection || 'ltr' ) ,
4344 element,
4445 menu : submenu ,
4546 } ) ;
4647 return trigger ;
4748}
4849
49- function getMenuBarPattern ( values : string [ ] ) {
50+ function getMenuBarPattern ( values : string [ ] , opts ?: { textDirection : 'ltr' | 'rtl' } ) {
5051 const items = signal < TestMenuItem [ ] > ( [ ] ) ;
5152
5253 const menubar = new MenuBarPattern < string > ( {
5354 items : items ,
5455 activeItem : signal ( undefined ) ,
5556 orientation : signal ( 'horizontal' ) ,
56- textDirection : signal ( 'ltr' ) ,
57+ textDirection : signal ( opts ?. textDirection || 'ltr' ) ,
5758 multi : signal ( false ) ,
5859 selectionMode : signal ( 'explicit' ) ,
5960 value : signal ( [ ] ) ,
@@ -86,6 +87,7 @@ function getMenuBarPattern(values: string[]) {
8687function getMenuPattern (
8788 parent : undefined | MenuItemPattern < string > | MenuTriggerPattern < string > ,
8889 values : string [ ] ,
90+ opts ?: { textDirection : 'ltr' | 'rtl' } ,
8991) {
9092 const items = signal < TestMenuItem [ ] > ( [ ] ) ;
9193
@@ -99,7 +101,7 @@ function getMenuPattern(
99101 softDisabled : signal ( true ) ,
100102 multi : signal ( false ) ,
101103 focusMode : signal ( 'activedescendant' ) ,
102- textDirection : signal ( 'ltr' ) ,
104+ textDirection : signal ( opts ?. textDirection || 'ltr' ) ,
103105 orientation : signal ( 'vertical' ) ,
104106 selectionMode : signal ( 'explicit' ) ,
105107 element : signal ( document . createElement ( 'div' ) ) ,
@@ -409,6 +411,27 @@ describe('Standalone Menu Pattern', () => {
409411 expect ( submenu . isVisible ( ) ) . toBe ( true ) ;
410412 } ) ;
411413 } ) ;
414+
415+ describe ( 'RTL' , ( ) => {
416+ beforeEach ( ( ) => {
417+ const opts = { textDirection : 'rtl' as const } ;
418+ menu = getMenuPattern ( undefined , [ 'a' , 'b' , 'c' ] , opts ) ;
419+ submenu = getMenuPattern ( menu . inputs . items ( ) [ 0 ] , [ 'd' , 'e' ] , opts ) ;
420+ } ) ;
421+
422+ it ( 'should open submenu on arrow left' , ( ) => {
423+ menu . onKeydown ( left ( ) ) ;
424+ expect ( submenu . isVisible ( ) ) . toBe ( true ) ;
425+ } ) ;
426+
427+ it ( 'should close submenu on arrow right' , ( ) => {
428+ menu . onKeydown ( left ( ) ) ;
429+ expect ( submenu . isVisible ( ) ) . toBe ( true ) ;
430+
431+ submenu . onKeydown ( right ( ) ) ;
432+ expect ( submenu . isVisible ( ) ) . toBe ( false ) ;
433+ } ) ;
434+ } ) ;
412435} ) ;
413436
414437describe ( 'Menu Trigger Pattern' , ( ) => {
@@ -830,5 +853,59 @@ describe('Menu Bar Pattern', () => {
830853 expect ( menubarItems [ 0 ] . expanded ( ) ) . toBe ( true ) ;
831854 expect ( menubar . inputs . activeItem ( ) ) . toBe ( menubarItems [ 0 ] ) ;
832855 } ) ;
856+
857+ describe ( 'RTL' , ( ) => {
858+ beforeEach ( ( ) => {
859+ const opts = { textDirection : 'rtl' as const } ;
860+ menubar = getMenuBarPattern ( [ 'a' , 'b' , 'c' ] , opts ) ;
861+ menuA = getMenuPattern ( menubar . inputs . items ( ) [ 0 ] , [ 'apple' , 'avocado' ] , opts ) ;
862+ menuB = getMenuPattern ( menubar . inputs . items ( ) [ 1 ] , [ 'banana' , 'blueberry' ] , opts ) ;
863+ menuC = getMenuPattern ( menubar . inputs . items ( ) [ 2 ] , [ 'cherry' , 'cranberry' ] , opts ) ;
864+ } ) ;
865+
866+ it ( 'should close on arrow left on a leaf menu item' , ( ) => {
867+ const menubarItems = menubar . inputs . items ( ) ;
868+ menubar . onClick ( clickMenuItem ( menubarItems , 0 ) ) ;
869+ expect ( menuA . isVisible ( ) ) . toBe ( true ) ;
870+
871+ menuA . onKeydown ( left ( ) ) ;
872+
873+ expect ( menuA . isVisible ( ) ) . toBe ( false ) ;
874+ expect ( menubarItems [ 0 ] . expanded ( ) ) . toBe ( false ) ;
875+ } ) ;
876+
877+ it ( 'should close on arrow right on a root menu item' , ( ) => {
878+ const menubarItems = menubar . inputs . items ( ) ;
879+ menubar . onClick ( clickMenuItem ( menubarItems , 1 ) ) ;
880+ expect ( menuB . isVisible ( ) ) . toBe ( true ) ;
881+
882+ menuB . onKeydown ( right ( ) ) ;
883+
884+ expect ( menuB . isVisible ( ) ) . toBe ( false ) ;
885+ expect ( menubarItems [ 1 ] . expanded ( ) ) . toBe ( false ) ;
886+ } ) ;
887+
888+ it ( 'should expand the next menu bar item on arrow left on a leaf menu item' , ( ) => {
889+ const menubarItems = menubar . inputs . items ( ) ;
890+ menubar . onClick ( clickMenuItem ( menubarItems , 0 ) ) ;
891+
892+ menuA . onKeydown ( left ( ) ) ;
893+
894+ expect ( menuB . isVisible ( ) ) . toBe ( true ) ;
895+ expect ( menubarItems [ 1 ] . expanded ( ) ) . toBe ( true ) ;
896+ expect ( menubar . inputs . activeItem ( ) ) . toBe ( menubarItems [ 1 ] ) ;
897+ } ) ;
898+
899+ it ( 'should expand the previous menu bar item on arrow right on a root menu item' , ( ) => {
900+ const menubarItems = menubar . inputs . items ( ) ;
901+ menubar . onClick ( clickMenuItem ( menubarItems , 1 ) ) ;
902+
903+ menuB . onKeydown ( right ( ) ) ;
904+
905+ expect ( menuA . isVisible ( ) ) . toBe ( true ) ;
906+ expect ( menubarItems [ 0 ] . expanded ( ) ) . toBe ( true ) ;
907+ expect ( menubar . inputs . activeItem ( ) ) . toBe ( menubarItems [ 0 ] ) ;
908+ } ) ;
909+ } ) ;
833910 } ) ;
834911} ) ;
0 commit comments