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 0000000000000..8c0eda457cce6
--- /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 0000000000000..6d4c8208da6c4
--- /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-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 097503f772d7c..685959ad1f79d 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.test.ts b/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGrid.test.ts
index 2100a83032585..711f41d78441a 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 dda14d5a5e0bd..97fd7056fca84 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,14 +56,11 @@ 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) {
diff --git a/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGridContextValues.ts b/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGridContextValues.ts
index 19d7efea7b0c0..0bcccedfce442 100644
--- a/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGridContextValues.ts
+++ b/packages/react-components/react-menu-grid-preview/library/src/components/MenuGrid/useMenuGridContextValues.ts
@@ -8,6 +8,7 @@ const menuList = {
hasIcons: false,
hasCheckmarks: false,
shouldOpenOnArrowRight: false,
+ shouldCloseOnArrowLeft: false,
};
export function useMenuGridContextValues_unstable(state: MenuGridState): MenuGridContextValues {
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 fb505122c31a3..3cfd08cfe69df 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)
diff --git a/packages/react-components/react-menu/library/src/components/MenuPopover/useMenuPopover.ts b/packages/react-components/react-menu/library/src/components/MenuPopover/useMenuPopover.ts
index 70819a0ce1772..bd1a12031efc5 100644
--- a/packages/react-components/react-menu/library/src/components/MenuPopover/useMenuPopover.ts
+++ b/packages/react-components/react-menu/library/src/components/MenuPopover/useMenuPopover.ts
@@ -8,6 +8,7 @@ import { getIntrinsicElementProps, useEventCallback, useMergedRefs, slot, useTim
import * as React from 'react';
import { useMenuContext_unstable } from '../../contexts/menuContext';
+import { useMenuListContext_unstable } from '../../contexts/menuListContext';
import { dispatchMenuEnterEvent, useIsSubmenu } from '../../utils/index';
import { MenuPopoverProps, MenuPopoverState } from './MenuPopover.types';
@@ -31,6 +32,8 @@ export const useMenuPopover_unstable = (props: MenuPopoverProps, ref: React.Ref<
const triggerRef = useMenuContext_unstable(context => 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 4274fc8d9e61a..8d1e6020fccba 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