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 = (
+
+ );
+ 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 = (
-
- );
- const { container, baseElement } = render(
-
-
- ,
- );
- const trigger = container.querySelector('.my-button');
-
- trigger.focus();
- fireEvent.click(trigger);
- await waitForTime();
+ try {
+ const overlay = (
+
+ );
+ 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 () => {