From ef3e7d0e4582d9294376f23a733cc4dc69f38cb7 Mon Sep 17 00:00:00 2001 From: dev_agent Date: Sat, 3 Oct 2026 18:17:15 -0400 Subject: [PATCH] Leave controls disabled by a fieldset out of the Sheet focus trap Co-Authored-By: Claude Opus 5.5 --- web/src/ui/Sheet.test.tsx | 25 +++++++++++++++++++++++++ web/src/ui/Sheet.tsx | 16 +++++++++------- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/web/src/ui/Sheet.test.tsx b/web/src/ui/Sheet.test.tsx index fb672d2..8e8cc27 100644 --- a/web/src/ui/Sheet.test.tsx +++ b/web/src/ui/Sheet.test.tsx @@ -137,6 +137,31 @@ describe('Sheet', () => { expect(last).toHaveFocus() }) + it('does not stop the Tab trap on a control disabled by its fieldset', () => { + render( + {}}> +
+ +
+ + +
+ +
+
, + ) + const first = screen.getByRole('button', { name: 'Try it' }) + const last = screen.getByRole('button', { name: 'Pass' }) + expect(first).toHaveFocus() + + last.focus() + fireEvent.keyDown(last, { key: 'Tab' }) + expect(first).toHaveFocus() + + fireEvent.keyDown(first, { key: 'Tab', shiftKey: true }) + expect(last).toHaveFocus() + }) + it('wraps Tab and Shift+Tab inside the sheet', () => { render() openSheet() diff --git a/web/src/ui/Sheet.tsx b/web/src/ui/Sheet.tsx index 18303b5..4686f3a 100644 --- a/web/src/ui/Sheet.tsx +++ b/web/src/ui/Sheet.tsx @@ -15,17 +15,19 @@ export type SheetProps = SheetName & { const focusableSelector = [ 'a[href]', - 'button:not([disabled])', - 'input:not([disabled])', - 'select:not([disabled])', - 'textarea:not([disabled])', + 'button', + 'input', + 'select', + 'textarea', '[tabindex]', ].join(',') -// Whether an element can take keyboard focus: a negative tabIndex, a hidden, -// aria-hidden or inert ancestor (or self), display: none on the way up to the -// panel, or visibility: hidden all rule it out. +// Whether an element can take keyboard focus: being disabled (by its own +// attribute or a disabled fieldset), a negative tabIndex, a hidden, aria-hidden +// or inert ancestor (or self), display: none on the way up to the panel, or +// visibility: hidden all rule it out. function isKeyboardFocusable(element: HTMLElement, panel: HTMLElement): boolean { + if (element.matches(':disabled')) return false if (element.tabIndex < 0) return false if (element.closest('[hidden], [aria-hidden="true"], [inert]')) return false if (getComputedStyle(element).visibility === 'hidden') return false