diff --git a/packages/@react-aria/menu/src/useMenu.ts b/packages/@react-aria/menu/src/useMenu.ts index 795a8caf061..251b2138f32 100644 --- a/packages/@react-aria/menu/src/useMenu.ts +++ b/packages/@react-aria/menu/src/useMenu.ts @@ -74,7 +74,13 @@ export function useMenu(props: AriaMenuOptions, state: TreeState, ref: return { menuProps: mergeProps(domProps, { role: 'menu', - ...listProps + ...listProps, + onKeyDown: (e) => { + // don't clear the menu selected keys if the user is presses escape since escape closes the menu + if (e.key !== 'Escape') { + listProps.onKeyDown(e); + } + } }) }; } diff --git a/packages/@react-spectrum/menu/test/MenuTrigger.test.js b/packages/@react-spectrum/menu/test/MenuTrigger.test.js index c3fcedd219f..76f21849c3d 100644 --- a/packages/@react-spectrum/menu/test/MenuTrigger.test.js +++ b/packages/@react-spectrum/menu/test/MenuTrigger.test.js @@ -370,7 +370,6 @@ describe('MenuTrigger', function () { act(() => {jest.runAllTimers();}); // FocusScope raf } - // Can't figure out why this isn't working for the v2 component it.each` Name | Component | props ${'MenuTrigger'} | ${MenuTrigger} | ${{onOpenChange}} @@ -389,7 +388,39 @@ describe('MenuTrigger', function () { expect(document.activeElement).toBe(button); }); - // Can't figure out why this isn't working for the v2 component + it.each` + Name | Component | props + ${'MenuTrigger'} | ${MenuTrigger} | ${{onOpenChange}} + `('$Name does not clear selection with escape', function ({Component, props}) { + let onSelectionChange = jest.fn(); + tree = renderComponent(Component, props, {selectionMode: 'multiple', defaultSelectedKeys: ['Foo'], onSelectionChange}); + let button = tree.getByRole('button'); + triggerPress(button); + act(() => {jest.runAllTimers();}); + expect(onSelectionChange).not.toHaveBeenCalled(); + + let menu = tree.getByRole('menu'); + expect(menu).toBeTruthy(); + expect(within(menu).getAllByRole('menuitemcheckbox')[0]).toHaveAttribute('aria-checked', 'true'); + fireEvent.keyDown(menu, {key: 'Escape', code: 27, charCode: 27}); + act(() => {jest.runAllTimers();}); // FocusScope useLayoutEffect cleanup + act(() => {jest.runAllTimers();}); // FocusScope raf + expect(menu).not.toBeInTheDocument(); + expect(document.activeElement).toBe(button); + expect(onSelectionChange).not.toHaveBeenCalled(); + + // reopen and make sure we still have the selection + triggerPress(button); + act(() => {jest.runAllTimers();}); + expect(onSelectionChange).not.toHaveBeenCalled(); + + menu = tree.getByRole('menu'); + expect(within(menu).getAllByRole('menuitemcheckbox')[0]).toHaveAttribute('aria-checked', 'true'); + expect(menu).toBeTruthy(); + fireEvent.keyDown(menu, {key: 'Escape', code: 27, charCode: 27}); + expect(onSelectionChange).not.toHaveBeenCalled(); + }); + it.each` Name | Component | props ${'MenuTrigger'} | ${MenuTrigger} | ${{onOpenChange}}