From 6702a744d92fc7e1382f9ad9fd797da6d37b96f1 Mon Sep 17 00:00:00 2001 From: Adam Samec Date: Mon, 30 Mar 2026 17:56:23 +0200 Subject: [PATCH 1/3] Fix: Left arrow behavior in MenuGrid submenus --- .../library/src/components/MenuGrid/MenuGrid.cy.tsx | 8 ++++++++ .../library/src/components/MenuGrid/useMenuGrid.ts | 2 +- .../src/components/MenuGrid/useMenuGridContextValues.ts | 1 + .../library/src/components/MenuPopover/useMenuPopover.ts | 5 ++++- .../react-menu/library/src/contexts/menuListContext.tsx | 8 ++++++++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/MenuGrid.cy.tsx b/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/MenuGrid.cy.tsx index 097503f772d7cb..685959ad1f79d6 100644 --- a/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/MenuGrid.cy.tsx +++ b/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/MenuGrid.cy.tsx @@ -308,4 +308,12 @@ describe('With MenuList submenus', () => { cy.get(menuSelector).should('have.length', 1); cy.focused().should('have.attr', 'role', 'menuitem'); }); + + it('should not close submenu when pressing ArrowLeft inside it', () => { + mount(); + cy.get(menuGridItemSelector).first().focus().realPress('ArrowRight').realPress('Enter'); + cy.get(menuSelector).should('have.length', 1); + cy.focused().should('have.attr', 'role', 'menuitem').realPress('ArrowLeft'); + cy.get(menuSelector).should('have.length', 1); + }); }); diff --git a/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGrid.ts b/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGrid.ts index dda14d5a5e0bd1..866b97facfef2b 100644 --- a/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGrid.ts +++ b/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGrid.ts @@ -56,7 +56,7 @@ export const useMenuGrid_unstable = (props: MenuGridProps, ref: React.Ref context.triggerRef); const isSubmenu = useIsSubmenu(); + const shouldCloseOnArrowLeft = useMenuListContext_unstable(ctx => ctx.shouldCloseOnArrowLeft ?? true); + const canDispatchCustomEventRef = React.useRef(true); const restoreFocusSourceAttributes = useRestoreFocusSource(); const [setThrottleTimeout, clearThrottleTimeout] = useTimeout(); @@ -93,7 +96,7 @@ export const useMenuPopover_unstable = (props: MenuPopoverProps, ref: React.Ref< }); rootProps.onKeyDown = useEventCallback((event: React.KeyboardEvent) => { const key = event.key; - if (key === Escape || (isSubmenu && key === CloseArrowKey)) { + if (key === Escape || (isSubmenu && shouldCloseOnArrowLeft && key === CloseArrowKey)) { if (open && popoverRef.current?.contains(event.target as HTMLElement) && !event.isDefaultPrevented()) { setOpen(event, { open: false, keyboard: true, type: 'menuPopoverKeyDown', event }); // stop propagation to avoid conflicting with other elements that listen for `Escape` diff --git a/packages/react-components/react-menu/library/src/contexts/menuListContext.tsx b/packages/react-components/react-menu/library/src/contexts/menuListContext.tsx index 4274fc8d9e61a6..8d1e6020fccba2 100644 --- a/packages/react-components/react-menu/library/src/contexts/menuListContext.tsx +++ b/packages/react-components/react-menu/library/src/contexts/menuListContext.tsx @@ -44,6 +44,14 @@ export type MenuListContextValue = Pick Date: Mon, 30 Mar 2026 18:43:41 +0200 Subject: [PATCH 2/3] Fix: Add change file and API md file --- ...ui-react-menu-11ee527c-b6cc-48eb-b8dd-b85a8d84ca09.json | 7 +++++++ ...-grid-preview-785678f1-ead6-4795-bb56-a75f0d153668.json | 7 +++++++ .../react-menu/library/etc/react-menu.api.md | 1 + 3 files changed, 15 insertions(+) create mode 100644 change/@fluentui-react-menu-11ee527c-b6cc-48eb-b8dd-b85a8d84ca09.json create mode 100644 change/@fluentui-react-menu-grid-preview-785678f1-ead6-4795-bb56-a75f0d153668.json diff --git a/change/@fluentui-react-menu-11ee527c-b6cc-48eb-b8dd-b85a8d84ca09.json b/change/@fluentui-react-menu-11ee527c-b6cc-48eb-b8dd-b85a8d84ca09.json new file mode 100644 index 00000000000000..8c0eda457cce6f --- /dev/null +++ b/change/@fluentui-react-menu-11ee527c-b6cc-48eb-b8dd-b85a8d84ca09.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Fix: Left arrow behavior in MenuGrid submenus", + "packageName": "@fluentui/react-menu", + "email": "email not defined", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-menu-grid-preview-785678f1-ead6-4795-bb56-a75f0d153668.json b/change/@fluentui-react-menu-grid-preview-785678f1-ead6-4795-bb56-a75f0d153668.json new file mode 100644 index 00000000000000..6d4c8208da6c4e --- /dev/null +++ b/change/@fluentui-react-menu-grid-preview-785678f1-ead6-4795-bb56-a75f0d153668.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Fix: Left arrow behavior in MenuGrid submenus", + "packageName": "@fluentui/react-menu-grid-preview", + "email": "email not defined", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-menu/library/etc/react-menu.api.md b/packages/react-components/react-menu/library/etc/react-menu.api.md index fb505122c31a32..3cfd08cfe69df0 100644 --- a/packages/react-components/react-menu/library/etc/react-menu.api.md +++ b/packages/react-components/react-menu/library/etc/react-menu.api.md @@ -231,6 +231,7 @@ export type MenuListContextValue = Pick void; shouldOpenOnArrowRight?: boolean; + shouldCloseOnArrowLeft?: boolean; }; // @public (undocumented) From df43def2aad33ad214c5f91061b0e0056935a945 Mon Sep 17 00:00:00 2001 From: Adam Samec Date: Tue, 31 Mar 2026 12:34:22 +0200 Subject: [PATCH 3/3] Fix: Refactor and related unit tests changes --- .../src/components/MenuGrid/useMenuGrid.test.ts | 14 +++++++------- .../library/src/components/MenuGrid/useMenuGrid.ts | 5 +---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGrid.test.ts b/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGrid.test.ts index 2100a830325856..711f41d78441aa 100644 --- a/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGrid.test.ts +++ b/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGrid.test.ts @@ -22,11 +22,11 @@ describe('useMenuGrid_unstable', () => { beforeEach(() => { rows = [ - { textContent: 'Apple', focus: jest.fn(), hasAttribute: () => true, getAttribute: () => 'row' }, - { textContent: 'Banana', focus: jest.fn(), hasAttribute: () => true, getAttribute: () => 'row' }, - { textContent: 'Cherry', focus: jest.fn(), hasAttribute: () => true, getAttribute: () => 'row' }, - { textContent: 'Apricot', focus: jest.fn(), hasAttribute: () => true, getAttribute: () => 'row' }, - { textContent: 'Date', focus: jest.fn(), hasAttribute: () => true, getAttribute: () => 'row' }, + { textContent: 'Apple', focus: jest.fn(), role: 'row' }, + { textContent: 'Banana', focus: jest.fn(), role: 'row' }, + { textContent: 'Cherry', focus: jest.fn(), role: 'row' }, + { textContent: 'Apricot', focus: jest.fn(), role: 'row' }, + { textContent: 'Date', focus: jest.fn(), role: 'row' }, ]; (useFocusFinders as jest.Mock).mockReturnValue({ @@ -37,7 +37,7 @@ describe('useMenuGrid_unstable', () => { const createEvent = (key: string, target?: Record): React.KeyboardEvent => ({ key, - target: target ?? { hasAttribute: () => true, getAttribute: () => 'row' }, + target: target ?? { role: 'row' }, } as unknown as React.KeyboardEvent); it('should focus the next row matching the pressed character', () => { @@ -97,7 +97,7 @@ describe('useMenuGrid_unstable', () => { it('should not apply first-letter navigation when event target is not a row', () => { const current = rows[0]; // Apple - const nonRowTarget = { hasAttribute: () => true, getAttribute: () => 'gridcell' }; + const nonRowTarget = { role: 'gridcell' }; const { result } = renderHook(() => useMenuGrid_unstable({}, React.createRef())); (result.current.root.ref as React.RefCallback)?.(document.createElement('div')); diff --git a/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGrid.ts b/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGrid.ts index 866b97facfef2b..97fd7056fca840 100644 --- a/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGrid.ts +++ b/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGrid.ts @@ -60,10 +60,7 @@ export const useMenuGrid_unstable = (props: MenuGridProps, ref: React.Ref el.hasAttribute('role') && el.getAttribute('role') === 'row', - ); + const rows = findAllFocusable(innerRef.current, (el: HTMLElement) => el.role === 'row'); let startIndex = rows.indexOf(itemEl) + 1; if (startIndex === rows.length) {