From 7f0175085e56b33c37bf9fdc274a5115031a5661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=B3=E5=BB=B7=E5=AE=89?= <73953029+nrps9909@users.noreply.github.com> Date: Fri, 28 Aug 2026 05:42:08 +0800 Subject: [PATCH 1/2] fix: preserve reverse tab navigation --- src/hooks/useAccessibility.ts | 5 +++++ tests/basic.test.tsx | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/hooks/useAccessibility.ts b/src/hooks/useAccessibility.ts index a946e39..3835b38 100644 --- a/src/hooks/useAccessibility.ts +++ b/src/hooks/useAccessibility.ts @@ -42,6 +42,11 @@ export default function useAccessibility({ handleCloseMenuAndReturnFocus(); break; case TAB: { + if (event.shiftKey) { + handleCloseMenuAndReturnFocus(); + break; + } + let focusResult: boolean = false; if (!focusMenuRef.current) { focusResult = focusMenu(); diff --git a/tests/basic.test.tsx b/tests/basic.test.tsx index ed7e5cf..bcd4f61 100644 --- a/tests/basic.test.tsx +++ b/tests/basic.test.tsx @@ -407,6 +407,43 @@ describe('dropdown', () => { jest.useRealTimers(); }); + it('Shift+Tab should close the menu without moving focus into it', async () => { + jest.useFakeTimers(); + + const overlay = ( + + one + two + + ); + const { container, baseElement } = render( + + + , + ); + const trigger = container.querySelector('.my-button'); + + trigger.focus(); + fireEvent.click(trigger); + await waitForTime(); + + const event = new KeyboardEvent('keydown', { + keyCode: 9, + shiftKey: true, + cancelable: true, + }); + window.dispatchEvent(event); + await waitForTime(); + + expect(event.defaultPrevented).toBeFalsy(); + expect(document.activeElement).toBe(trigger); + expect(baseElement.querySelector('.rc-dropdown')).toHaveClass( + 'rc-dropdown-hidden', + ); + + jest.useRealTimers(); + }); + it('Tab should close menu if overlay cannot be focused', async () => { jest.useFakeTimers(); From 139540a125e5e6e62020d744bf6da8d3cd8b93d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=B3=E5=BB=B7=E5=AE=89?= <73953029+nrps9909@users.noreply.github.com> Date: Sun, 20 Sep 2026 14:49:28 +0800 Subject: [PATCH 2/2] test: always restore timers in reverse navigation regression --- tests/basic.test.tsx | 60 +++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/tests/basic.test.tsx b/tests/basic.test.tsx index bcd4f61..b892a3a 100644 --- a/tests/basic.test.tsx +++ b/tests/basic.test.tsx @@ -410,38 +410,40 @@ describe('dropdown', () => { it('Shift+Tab should close the menu without moving focus into it', async () => { jest.useFakeTimers(); - const overlay = ( - - one - two - - ); - const { container, baseElement } = render( - - - , - ); - const trigger = container.querySelector('.my-button'); - - trigger.focus(); - fireEvent.click(trigger); - await waitForTime(); + try { + const overlay = ( + + one + two + + ); + const { container, baseElement } = render( + + + , + ); + const trigger = container.querySelector('.my-button'); - const event = new KeyboardEvent('keydown', { - keyCode: 9, - shiftKey: true, - cancelable: true, - }); - window.dispatchEvent(event); - await waitForTime(); + trigger.focus(); + fireEvent.click(trigger); + await waitForTime(); - expect(event.defaultPrevented).toBeFalsy(); - expect(document.activeElement).toBe(trigger); - expect(baseElement.querySelector('.rc-dropdown')).toHaveClass( - 'rc-dropdown-hidden', - ); + const event = new KeyboardEvent('keydown', { + keyCode: 9, + shiftKey: true, + cancelable: true, + }); + window.dispatchEvent(event); + await waitForTime(); - jest.useRealTimers(); + expect(event.defaultPrevented).toBeFalsy(); + expect(document.activeElement).toBe(trigger); + expect(baseElement.querySelector('.rc-dropdown')).toHaveClass( + 'rc-dropdown-hidden', + ); + } finally { + jest.useRealTimers(); + } }); it('Tab should close menu if overlay cannot be focused', async () => {