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