Repository navigation
fix: treat items disabled by a fieldset as disabled - #42
Merged
Merged
Conversation
keyrove read "disabled" only from an element's own `disabled` attribute, so a button inside `<fieldset disabled>` stayed in the item sequence. Every arrow move onto it was refused by focus() and became a consumed no-op, so the items after it could not be reached, typeahead matched it, a focus key declared on it swallowed the key, and initRovingTabindex could give it the stop. An element is now disabled when it carries `disabled` or matches `:disabled`. The item sequence, the navigable test, the stop holder and the focus-key filter all use the same check. `aria-disabled` still does not count. A focusable non-control inside a disabled fieldset stays an item, since `:disabled` matches only form controls.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A control inside
<fieldset disabled>matches:disabledbut doesn't carry thedisabledattribute itself. keyrove checked only the attribute, so the control stayed in the item sequence. Each arrow move onto it was refused byfocus()and returned a consumed no-op, so the items after it couldn't be reached by arrows. Typeahead also matched it, a focus key on it swallowed the key, andinitRovingTabindexcould give it the stop.The fix updates one predicate, which both readers of a disabled item now share:
disabledattribute still counts on any element, such as<li disabled>, which:disableddoesn't match.:disabledmatches only form controls, so a focusable non-control such as<div tabindex="-1">inside a disabled fieldset is still an item.aria-disabledstill doesn't count. A control with it can still take focus.disabled, or disabled by an ancestor<fieldset disabled>":GLOSSARY.md,docs/glossary.md,docs/api.md,examples/skipped-items.md,examples/typeahead.mdand thetypes.tsdoc comments. Theapi.mdhunk is bigger than the wording change because Prettier re-padded its attribute table.Evidence
Before: the new jsdom tests, run against
main'sgroup.tsandconfig.ts. Six fail. The seventh is a guard that passes either way.After:
The bench tests (19), docs tests (19) and browser suite (21) also pass.
Benchmark:
pnpm bench:headless, two runs each before and after. M1 Pro, Chrome 156 headless, medians.focus():disabledcheck per press)The cost is one
matches(':disabled')per item, about 30 to 50 ns each. We judged it not meaningful in absolute terms. We found no cheaper check that is still correct:'disabled' in elmisses form-associated custom elements, andclosest('fieldset[disabled]')mishandles the fieldset's first<legend>. To get these numbers, we had to patch the bench runner locally (uncommitted) around anENOTEMPTYerror when it removes Chrome's profile. That bug is tracked separately.Merge Danger
Door: two-way
The change is one predicate and has no stored state, so a revert undoes it. Merging into
maintriggers release-it, which tags a patch release from thisfix:commit. A revert would need a follow-up release.Blast Radius: behavioural
<fieldset disabled>see those items leave the item sequence. Arrows, Home/End, typeahead and focus keys now skip them. This matches what the README already promised.disabledattribute already does.🤖 Generated with Claude Code