Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
There was a problem hiding this comment.
Approval is waiting on a maintainer decision: arrow keys between the header text field and the rows move focus between separately focusable parts, which AST-020 FR15 allows only as a named exception, so the DropdownMenu owner needs to accept or decline proposed FR17 and DEC-12 as that exception. Independently, ArrowDown should be left to a header field that already handled it: today a combobox such as Typeahead in header acts on the key and then loses focus to the first row, and skipping events whose default is already prevented, plus a regression case, would cover it. Please also retarget to main, where FR17 already names another requirement, and renumber the new one.
[Automated review]
| if ( | ||
| isPlainArrow(e, 'ArrowDown') && | ||
| isTextEntryField(e.target) && | ||
| headerRef.current?.contains(e.target) === true && | ||
| focusFirst() | ||
| ) { | ||
| e.preventDefault(); |
There was a problem hiding this comment.
This check never looks at whether the field already handled ArrowDown. A combobox in header (Typeahead renders <input type="text" role="combobox"> and only calls preventDefault()) acts on the key and then loses focus to the first row, so its suggestions can't be reached by keyboard. Something like this lets the field's own handling win:
const field = e.target;
if (
!e.defaultPrevented &&
isPlainArrow(e, 'ArrowDown') &&
isTextEntryField(field) &&
field.getAttribute('role') !== 'combobox' &&
!field.hasAttribute('list') &&
headerRef.current?.contains(field) === true &&
focusFirst()
) {
e.preventDefault();
}A "header field arrows" case with a combobox field that handles ArrowDown would pin it.
There was a problem hiding this comment.
Fixed in 795b8c6: ArrowDown moves on only when its default is not already prevented, and a field with a list of its own (role="combobox", or an input with a list) is not treated as the menu's input in either direction. New case "leaves ArrowDown to a header field that handles it or has a list of its own" fails at the previous head.
2d09320 to
795b8c6
Compare
|
Rebased onto |
PR Analysis Report📚 Storybook Preview🧪 Sandbox PreviewNo new or modified components detected. Bundle Size Summary
Accessibility AuditStatus: No accessibility violations detected. Visual Regression46 of 486 shot(s) changed. A change here is a question, not a failure: check whether the after is the
and 26 more. Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
There was a problem hiding this comment.
Approval is waiting on a maintainer decision: arrow keys between a header text field and the menu rows move focus between separately focusable parts, which AST-020 FR15 allows only as a named exception, so a maintainer (the DropdownMenu owner) needs to accept or decline proposed FR18 and DEC-11 as the governing spec for that exception. If it is accepted, please also:
- scope FR18, the
headerdoc sentence and the changeset to the pointer menu's top-level rows, or extend the arrows, since the bottom sheet and a drilled-in sub-menu view do not get them today; - amend FR15 and DEC-8, which still say a region keeps its own keys, and confirm that a
patchfits the narrowed "keeps its own keys" promise.
The earlier header-combobox and renumbering findings are fixed.
[Automated review]
| | FR15 | With `header` or `footer`, the pointer popover is a `role="dialog"` with `aria-modal="true"`, named like the menu, holding the header, then the `role="menu"` element, then the footer; the menu element holds only its rows. The trigger says `aria-haspopup="dialog"` and its `aria-controls` names the popover that holds the dialog. A region keeps its own keys and presses: neither the menu's key handling nor its press model claims an event that begins there. The menu is one Tab stop between the regions. Tab and Shift+Tab move through the header's controls, the menu, then the footer's controls, and wrap at either end; a control that is hidden, or under a hidden ancestor within its region, is not a stop. From a row, or a row of an open sub-menu, Tab and Shift+Tab close every open flyout and continue from the menu's stop. With no other stop, focus stays in the menu. Escape closes from anywhere in the popover, and focus returns to the trigger. The bottom sheet, already a dialog, shows the header above the actions and the footer below them. Without either prop, the menu renders and behaves exactly as it does without them: no dialog, `aria-haspopup="menu"`, and Tab closes the menu. | DEC-8, DEC-10, docs, and tests | Proposed; awaiting owner approval | | ||
| | FR16 | `DropdownMenuCheckboxItem` MUST expose one `menuitemcheckbox` row whose `aria-checked` equals its controlled value; enabled activation reports `!value`, stays open by default, and composes consumer click and pointer-move handlers with owned activation and hover focus. Disabled activation calls no consumer activation handler. | Released subcomponent API; `architecture:public-component-api/INV5–INV8`; `DropdownMenuSelectable.test.tsx` | Verified current behavior; consumer handlers are preserved | | ||
| | FR17 | Disabled menu rows SHOULD remain keyboard-discoverable while activation stays blocked. Current shared `useListFocus` deliberately skips every `aria-disabled` row, including checkbox items. | WAI-ARIA Menu pattern; current shared navigation source/tests | Objective mismatch; shared mechanism remains undecided in OQ1 | | ||
| | FR18 | With a single-line text field in `header` (an `input` of type `text`, `search`, `email`, `url`, `tel` or `password`), ArrowDown in that field moves focus to the menu's first enabled row, as from a combobox's input to its list, and ArrowUp on the first enabled row moves focus back to the last such field in the header instead of wrapping to the last row. Both keys are handled in full only when focus moves. An arrow with a modifier, a key inside an IME composition, an ArrowDown the field already handled (its default prevented), and any key in a control whose arrows mean something of their own (a number, a range, a multi-line text, a button, a combobox, an input with a `list`) keep their native behavior. The menu's other keys (FR12) are unchanged, and without such a field ArrowUp on the first row wraps as FR12 states. | Proposed DEC-11; FR12; FR15 | Proposed; verified in jsdom and Chromium, pending owner review | |
There was a problem hiding this comment.
FR18 states the arrows for any single-line text field in header, but only the pointer menu's top-level rows run them: the bottom sheet renders header without a key handler (DropdownMenu.tsx:968-970), and in a drilled-in sub-menu view ArrowUp on the Back row wraps to the last drilled row. If FR18 is accepted, scope this row, the header doc sentence and the changeset to the pointer menu's top-level rows (or extend both handlers), and amend FR15 (line 119) and DEC-8, which still say a region keeps its own keys.
There was a problem hiding this comment.
Scoped in e7e9667. FR18 now opens "In the pointer popover" and says the bottom sheet and a drilled-in sub-menu view hand no arrows between the field and the rows. DEC-11 gives the reason: the sheet's actions are a list of their own, and a drilled-in view's field filters rows that are out of view. Two guards pin it, the bottom sheet's field keeping ArrowDown, and a drilled-in view where the field keeps ArrowDown and Back keeps the wrap; the verification row lists both. FR15 now says a region keeps its own keys "except for the arrows of proposed FR18". DEC-8 says proposed DEC-11 hands on one key, a header text field's ArrowDown.
| type: 'ReactNode', | ||
| description: | ||
| 'Content above the rows and outside the menu itself — a filter field, a toggle. A menu holds only its rows, so a control that is not a row goes here or in `footer`, never among the rows. It keeps its own keys and presses. With a header or footer the popover is a modal dialog around the menu: Tab and Shift+Tab move through the header, the menu and the footer and wrap at either end, and Escape closes. The bottom sheet shows it above the actions.', | ||
| 'Content above the rows and outside the menu itself — a filter field, a toggle. A menu holds only its rows, so a control that is not a row goes here or in `footer`, never among the rows. It keeps its own keys and presses, except that ArrowDown in a single-line text field there moves to the first row, and ArrowUp on the first row returns to it. With a header or footer the popover is a modal dialog around the menu: Tab and Shift+Tab move through the header, the menu and the footer and wrap at either end, and Escape closes. The bottom sheet shows it above the actions.', |
There was a problem hiding this comment.
This sentence promises the arrows wherever header holds a text field, including the bottom sheet the last sentence mentions; with presentation="bottom-sheet" ArrowDown in the field stays put. Naming the pointer menu here would match what the component does.
There was a problem hiding this comment.
Fixed in e7e9667: the sentence now says "in the popover", and the changeset names the bottom sheet and a drilled-in sub-menu view as keeping their keys.
|
On the classification: a If the owner would rather ship it in a patch, the compatible route is opt-in, through a prop or a marker on the field, with the hand-off staying off by default. That is an API choice for the record's owner, together with FR18 and DEC-11. |
There was a problem hiding this comment.
Approval is waiting on a maintainer decision: arrow keys that move focus between a header text field and the menu rows join two separately focusable parts, which AST-020 FR15 allows only as a named exception, so the DropdownMenu owner needs to approve or decline proposed FR18 and DEC-11 as the governing spec for that exception, on by default in the next planned minor or behind an opt-in. If it is approved, the ArrowUp target needs to skip header steppers: NumberInput renders <input type="text" role="spinbutton"> and TimeInput a plain text input, so ArrowUp on the first row enters them and the ArrowDown pressed to return steps the value (100 to 99 in Chromium), which a header NumberInput and TimeInput case would pin. Please also name FR18 as that exception in the record (AR5, FR18 and DEC-11, the way AR4 and DEC-9 do for the trigger's arrow keys) and update the Header And Footer story description, which still says the field keeps its own keys.
[Automated review]
e7e9667 to
fabcf58
Compare
|
Rebased onto |
There was a problem hiding this comment.
Approval is waiting on a maintainer decision: arrow keys that move focus between a header text field and the menu rows join two separately focusable parts, which AST-020 FR15 allows only as a named exception, so the DropdownMenu owner needs to approve or decline proposed FR18 and DEC-11 as the governing spec for it, and choose between on by default in the next planned minor (main declares 0.7.2, so check:changesets refuses the [breaking] Changeset until then) or an opt-in. These points from the last round still apply after the rebase and need fixing if it is approved:
- A header
NumberInput(<input type="text" role="spinbutton">) orTimeInputbecomes the ArrowUp target, so the ArrowDown pressed to return to the rows steps its value instead (100 to 99 in Chromium); skippingrole="spinbutton", preferring the field ArrowDown last left, and headerNumberInputandTimeInputcases would cover it. - FR18, DEC-11 and AR5 should name the hand-off as DropdownMenu's named exception under
spec:AST-020/FR15, the way AR4 and DEC-9 do for the trigger's arrow keys. - The Header And Footer story description still says the field keeps its own keys.
[Automated review]
| ) && | ||
| // A field with a list of its own (a combobox, or an input with a | ||
| // datalist) keeps its arrows for that list. | ||
| element.getAttribute('role') !== 'combobox' && |
There was a problem hiding this comment.
This is the only gate on the ArrowUp target (line 1286), and it lets a stepper through: NumberInput renders <input type="text" role="spinbutton"> and TimeInput a plain <input type="text">, and both step on ArrowDown. With a filter field and then one of them in header, ArrowUp on the first row lands in the stepper, and the ArrowDown pressed to go back changes its value (100 to 99 in Chromium) while focus stays. Skipping role="spinbutton" here, preferring the field ArrowDown last left (which also covers TimeInput), and adding header NumberInput and TimeInput cases would fix it.
There was a problem hiding this comment.
Fixed in 62303f3, rebased onto main 2c3ed99.
- A field with a role other than
textboxorsearchboxkeeps its arrows, soNumberInput(rolespinbutton) is never the ArrowUp target. - ArrowUp on the first row returns to the field ArrowDown last left, which passes over a later time field; otherwise it goes to the last text field.
Three new cases fail on the previous head:
- a header
NumberInputafter the filter; - a
TimeInputafter the filter; - two plain fields, where ArrowDown left from the first.
One gap remains: before any ArrowDown hand-off, a TimeInput that is the header's last text field is still the ArrowUp target, because nothing in its markup says its arrows step.
Five open pull requests propose rules in this record: facebook#7319 (FR18, DEC-11), facebook#7352 (FR19, DEC-12), facebook#7530 (FR20, AR7, DEC-13), facebook#7532 (FR21, DEC-14) and facebook#7541 (FR22, DEC-15). Each takes its own number, so none changes when another merges first. Record wording only.
Five open pull requests propose rules in this record: facebook#7319 (FR18, DEC-11), facebook#7352 (FR19, DEC-12), facebook#7530 (FR20, AR7, DEC-13), facebook#7532 (FR21, DEC-14) and facebook#7541 (FR22, DEC-15). Each takes its own number, so none changes when another merges first. Record wording only.
fabcf58 to
62303f3
Compare
Five open pull requests propose rules in this record: facebook#7319 (FR18, DEC-11), facebook#7352 (FR19, DEC-12), facebook#7530 (FR20, AR7, DEC-13), facebook#7532 (FR21, DEC-14) and facebook#7541 (FR22, DEC-15). Each takes its own number, so none changes when another merges first. Record wording only.
There was a problem hiding this comment.
Approval is waiting on a maintainer decision: arrow keys that move focus between a header text field and the menu rows join two separately focusable parts, which spec:AST-020 FR15 allows only as a named exception, so the DropdownMenu owner needs to approve or decline proposed FR21 and DEC-14 as the governing spec for it, and choose between on by default in a planned minor (main declares 0.7.2, so check:changesets refuses the [breaking] Changeset until then) or an opt-in. The TimeInput, Accessibility-contract and header doc points from the last round are fixed, and no other defect turned up. One point for the record while it waits:
- FR21 says where focus moves but not what a screen-reader user perceives: the field keeps its plain textbox or searchbox role with no link to the menu, so a Down Arrow pressed to re-read the field now leaves it; stating that, or recording it as accepted, would help the owner decide.
[Automated review]
Five open pull requests propose rules in this record: facebook#7319 (FR18, DEC-11), facebook#7352 (FR19, DEC-12), facebook#7530 (FR20, AR7, DEC-13), facebook#7532 (FR21, DEC-14) and facebook#7541 (FR22, DEC-15). Each takes its own number, so none changes when another merges first. Record wording only.
Five open pull requests propose rules in this record: facebook#7319 (FR18, DEC-11), facebook#7352 (FR19, DEC-12), facebook#7530 (FR20, AR7, DEC-13), facebook#7532 (FR21, DEC-14) and facebook#7541 (FR22, DEC-15). Each takes its own number, so none changes when another merges first. Record wording only.
Five open pull requests propose rules in this record: facebook#7319 (FR18, DEC-11), facebook#7352 (FR19, DEC-12), facebook#7530 (FR20, AR7, DEC-13), facebook#7532 (FR21, DEC-14) and facebook#7541 (FR22, DEC-15). Each takes its own number, so none changes when another merges first. Record wording only.
adb3358 to
ece5f14
Compare
There was a problem hiding this comment.
Approval is waiting on a maintainer decision: arrow keys that move focus between a header text field and the menu rows join two separately focusable parts, which spec:AST-020 FR15 allows only as a named exception, so the DropdownMenu owner needs to approve or decline proposed FR21 and DEC-14 as the governing spec for it, and choose between on by default in a planned minor (main declares 0.7.2, so check:changesets refuses the [breaking] Changeset until then) or an opt-in. No defect turned up, so nothing is needed from you until that decision lands; if the owner picks the opt-in, the hand-off will need that new surface and a compatible Changeset. One point the owner may want FR21 to state is what a screen-reader user perceives: the field keeps its plain textbox role with nothing tying it to the menu, so a Down Arrow pressed to re-read the field now leaves it.
[Automated review]
Five open pull requests propose rules in this record: facebook#7319 (FR18, DEC-11), facebook#7352 (FR19, DEC-12), facebook#7530 (FR20, AR7, DEC-13), facebook#7532 (FR21, DEC-14) and facebook#7541 (FR22, DEC-15). Each takes its own number, so none changes when another merges first. Record wording only.
ece5f14 to
504b40b
Compare
|
On what a screen-reader user perceives of the hand-off: DEC-14 now records it as accepted (504b40b). The field stays a plain textbox or searchbox with no relation to the menu, and the first row is heard as focus moves to it. A Down Arrow pressed to re-read a one-line field, as some screen readers allow in focus mode, now moves to the rows; the menu leaves Up Arrow and the screen reader's own reading commands to the field, and a field that needs Down Arrow keeps it by preventing the default. Record wording only. |
Five open pull requests propose rules in this record: facebook#7319 (FR18, DEC-11), facebook#7352 (FR19, DEC-12), facebook#7530 (FR20, AR7, DEC-13), facebook#7532 (FR21, DEC-14) and facebook#7541 (FR22, DEC-15). Each takes its own number, so none changes when another merges first. Record wording only.
Five open pull requests propose rules in this record: facebook#7319 (FR18, DEC-11), facebook#7352 (FR19, DEC-12), facebook#7530 (FR20, AR7, DEC-13), facebook#7532 (FR21, DEC-14) and facebook#7541 (FR22, DEC-15). Each takes its own number, so none changes when another merges first. Record wording only.
There was a problem hiding this comment.
Approval is waiting on a maintainer decision: arrow keys that move focus between a header text field and the menu rows join two separately focusable parts, which spec:AST-020 FR15 allows only as a named exception in the owning record, so the DropdownMenu owner needs to approve or decline proposed FR21 and DEC-14 as the governing spec for that exception, and choose between on by default in a planned minor (main declares 0.7.2, so check:changesets refuses the [breaking] Changeset until then) or an opt-in. No defect turned up on this revision, so nothing is needed from you until that decision lands; if the owner picks the opt-in, the hand-off will need that new surface and a compatible Changeset.
[Automated review]
Five open pull requests propose rules in this record: facebook#7319 (FR18, DEC-11), facebook#7352 (FR19, DEC-12), facebook#7530 (FR20, AR7, DEC-13), facebook#7532 (FR21, DEC-14) and facebook#7541 (FR22, DEC-15). Each takes its own number, so none changes when another merges first. Record wording only.
Five open pull requests propose rules in this record: facebook#7319 (FR18, DEC-11), facebook#7352 (FR19, DEC-12), facebook#7530 (FR20, AR7, DEC-13), facebook#7532 (FR21, DEC-14) and facebook#7541 (FR22, DEC-15). Each takes its own number, so none changes when another merges first. Record wording only.
…#7530) > Data items' `header` and `footer`, and the bottom sheet's level regions, follow once this merges. ## User need A sub-menu picking from a long list needs a filter field above its rows, often a "Create" button below. #7344 gave the root menu `header` and `footer`; a sub-menu has neither, so the field goes among the rows (DEC-8 forbids it) or the picker leaves the menu. ## Problem and solution fit `DropdownMenuSubMenu` takes `header` and `footer` under the root's rules (DEC-8, DEC-10). With either, the sub-menu's surface (the flyout, or the drilled-in view on a phone) is a modal dialog named by its row, holding the header, the sub-menu's `role="menu"` with rows only, and the footer. A flyout renders inside its parent menu, so `DropdownMenu` renders a host beside its menu for the dialog. A drilled-in view with regions replaces the root's menu and regions until popped, by a style rule that reads the host. ```tsx <DropdownMenuSubMenu label="Labels" header={<TextInput label="Filter labels" … />} footer={<Button label="Create label" … />}> {visibleLabelRows} </DropdownMenuSubMenu> ``` ## Current specification `component:DropdownMenu` (draft) gains a concept row, FR20, AR7 and DEC-13, all proposed and pending owner review. Other proposals in open pull requests: #7319 FR18/DEC-11, #7352 FR19/DEC-12, #7532 FR21/DEC-14, #7541 FR22/DEC-15. `module:DropdownMenu/useMenuPress` (current) gains a proposed ORD1a: a press released on a row of that dialog's menu acts, as FR1 requires for any flyout; a picker placed in its header or footer stays its own press root. ## Public delta and caller burden - `header` and `footer` on `DropdownMenuSubMenu`. - With either, the row says `aria-haspopup="dialog"`; Tab walks header, rows as one stop, footer, and wraps; an Escape nothing inside handled returns to the row; a pointer leaving the flyout keeps it open until focus leaves. Rendered beside the root's menu, such a flyout closes with the flyout holding its row. - Phone drill-in: a shown view follows its regions and caps at the root's `menuMaxHeight`. - `ContextMenu` renders no host: there the sub-menu shows its rows alone and warns in development; parity follows once this merges. - Without them, nothing changes. ## Design and alternatives Rejected: regions inside the flyout (inside the parent menu); a separate `DropdownMenu` per level; closing on pointer-leave, losing typed text. A drilled-in view's header sits above Back. ## Writing rubric results - R1 narration: none. - R2 research in a record: none. - R3 evidence naming a change: none. - R4 rejected alternatives over one line or outside a decision: none. - R5 pull request or issue references: none. - R6 ruled open questions listing options: none. ## Evidence See Testing. ## Scope - [x] One capability is added. - [x] Unrelated fixes, cleanup, and policy changes were removed or split. - [x] Consumer docs, focused tests, a story and a changeset ship with the feature. - [x] Public text and artifacts contain no internal Meta context. ## Testing Review round 8 (rebased onto `main` b74cf21): `pr-a11y` gets a "Run DropdownMenu sub-menu header and footer contract" step beside "Run DropdownMenu header and footer contract". It runs `SubMenuHeaderFooter.a11y.chromium.spec.ts` with `--project chromium`, so FR20's real-engine checks gate every head. That exact command passes locally against a Storybook built from this head (6 cases). 15 files, +1867/−121 (1,988 lines). DropdownMenu, ContextMenu, Layer, MoreMenu, RadioList, `useMenuPress` and doc-prop suites: 739 tests; header and footer Chromium specs: 16; `.github/scripts` workflow tests: 100; typecheck, `lint:strict`, `check:knowledge`, Storybook build: clean. Review round 7 (rebased onto `main` 863e167): `surfaceHost`, `surfaceOwnerId` and `menuMaxHeight` leave the exported `DropdownMenuContextValue` for `DropdownMenuSurfaceContext`, which the package does not export, layer-scoped and re-provided by each flyout as the menu context is; the public type matches `main`. A new `DropdownMenuContext.public.test.ts` pins its four members (it fails typecheck with the three back) and checks the internal context is not exported. Data-mode parity (data items' `header` and `footer`, `renderDropdownItems`, the bottom sheet's warning and their tests) moves to the follow-up, and the three pointer-leave cases share one setup: 14 files, +1856/−121 (1,977 lines). DropdownMenu, ContextMenu, Layer, MoreMenu, RadioList, `useMenuPress` and doc-prop suites: 739 tests; header and footer Chromium specs: 16; typecheck, `lint:strict` (no warnings), `check:knowledge`, Storybook build: clean. Rebased onto `main` d0f9afe, no conflicts or changes. The previous head's `pr-a11y` and `smoke-test` reds came from CI: main's #7802 added a `pr-a11y` step whose spec file the older head lacked, and jsdelivr was still propagating core@0.7.2. DropdownMenu, ContextMenu, RadioList, docProp* suites: 477 tests; typecheck, `check:knowledge`: clean. Review round 6: a flyout the pointer has left now decides that focus left once the move has completed, so an arrow key in a header radio group, where the browser moves focus itself, no longer closes it. A jsdom case replays the browser's order (focusout, a microtask checkpoint, focusin) and fails on the previous head; a Chromium case on the new "Submenu / header with a choice" story presses ArrowDown after the pointer leaves, and fails against a Storybook built from the previous head, where the flyout closes. DropdownMenu, ContextMenu, Layer, MoreMenu, RadioList, `useMenuPress` and doc-prop suites: 741 tests; header and footer Chromium specs: 16; typecheck, `lint:strict`, `check:knowledge`, Storybook build: clean. Review round 5 (rebased onto `main` 2c3ed99; the earlier `test` and `test-node` reds were `main`'s own, a PostCSS fixture that #7797 fixed): an Escape a control in a region already handled (a header menu closing, a field clearing itself) closes nothing more, and the new case fails on the previous head. The contract takes FR20, AR7 and DEC-13 so open proposals in this record stay distinct. DropdownMenu, ContextMenu, Layer, MoreMenu, `useMenuPress` and doc-prop suites: 646 tests; header and footer Chromium specs: 15; typecheck, `lint:strict`, `check:knowledge`, Storybook build: clean. Rebased onto `main` 577a62e after #7737 merged: one commit, 15 files, +1733/−109. DropdownMenu, ContextMenu, Layer, MoreMenu, `useMenuPress` and doc-prop suites: 645 tests; header and footer Chromium specs: 15; typecheck, `lint:strict`, `check:knowledge`, Storybook build: clean. Review round 4 (amended into the last commit): the surface host is owned through a ref, with only a key in state that changes when a host attaches (it moves when the root's header or footer comes or goes); a drilled-in view with regions caps at the root's `menuMaxHeight`. New cases: the view cap (fails on the previous head) and the host move (a guard). Gates below are for this head. Round 3, then stacked on #7737: the root steps aside for a view by a style rule over its surface host instead of a drill-in record kept in an Effect; a flyout with regions closes from the `onHide` of the flyout holding its row instead of an Effect; the bottom sheet's level regions moved to a follow-up. Each change has a case that fails with it reverted. - `DropdownMenuSubMenu.test.tsx` "header and footer" suite: 20 cases, plus one in `ContextMenu.test.tsx` (rows alone, with the warning). They cover the dialog's role, modality, name and row wiring; regions outside every menu; the Tab walk and wrap from a field, a row and a deeper flyout; field keys; Escape, an IME Escape and Escapes a region handled; a press from a menu row released on a dialog row (acts) or its field (outside); a menu placed in a region keeping its own press; pointer-leave with focus inside, focus moving within (a header radio's arrow included), then focus leaving; a nested flyout closing with its parent; the phone drill-in, the root stepping aside, a shown view following its regions and its height cap. - `__tests__/SubMenuHeaderFooter.a11y.chromium.spec.ts`, real Chromium: six cases (laptop structure, keyboard walk, picks, press-drag pick, a header radio after the pointer leaves; phone drill-in with the root stepped aside). - iOS 26.2 simulator (Mobile Safari), earlier head: phone drill-in as described above; on `main` the level has no field or button.
504b40b to
8a2c3e0
Compare
There was a problem hiding this comment.
Approval is waiting on a maintainer decision: arrow keys that move focus between a header text field and the menu rows join two separately focusable parts, which spec:AST-020 FR15 allows only as a named exception in the owning record, so the DropdownMenu owner needs to approve or decline proposed FR21 and DEC-14 and choose between on by default in a planned minor (main declares 0.7.3, so check:changesets refuses the [breaking] Changeset until then) or an opt-in. If the default-on route is approved, the Compatibility and migration section of DropdownMenu.spec.md also needs to stop saying the released default is preserved and the change is additive documentation, and to name DEC-14 as the migration decision. The implementation, the jsdom suite and the Chromium spec otherwise hold up on this revision.
[Automated review]
| | FR16 | `DropdownMenuCheckboxItem` MUST expose one `menuitemcheckbox` row whose `aria-checked` equals its controlled value; enabled activation reports `!value`, stays open by default, and composes consumer click and pointer-move handlers with owned activation and hover focus. Disabled activation calls no consumer activation handler. | Released subcomponent API; `architecture:public-component-api/INV5–INV8`; `DropdownMenuSelectable.test.tsx` | Verified current behavior; consumer handlers are preserved | | ||
| | FR17 | Disabled menu rows SHOULD remain keyboard-discoverable while activation stays blocked. Current shared `useListFocus` deliberately skips every `aria-disabled` row, including checkbox items. | WAI-ARIA Menu pattern; current shared navigation source/tests | Objective mismatch; shared mechanism remains undecided in OQ1 | | ||
| | FR20 | With `header` or `footer` on a sub-menu (`DropdownMenuSubMenu`), its surface is a modal dialog named by its row (AR7), holding the header, the sub-menu's `role="menu"` (rows only), then the footer. It sits in a host `DropdownMenu` renders beside its menu, never inside a menu; under a root with no such host (`ContextMenu`) the sub-menu shows its rows alone. On a fine pointer the dialog is the flyout; on a compact touch display, the drilled-in view, which replaces the root's menu and regions until popped. A region keeps its own keys and presses. The rows are one Tab stop: Tab and Shift+Tab walk the header's controls, the rows, then the footer's, and wrap; from a deeper flyout, that flyout closes and the walk goes on from the rows. An open lands as for any sub-menu (FR11, FR12), not on a region. Escape in the dialog, outside an IME composition and unless a control there handled it, returns focus to the row and leaves the root open. A pointer leaving the flyout keeps it open until focus leaves, and it closes with the flyout holding its row. Without either prop nothing changes. | Proposed DEC-13; FR15; AR5 | Proposed; verified in jsdom and Chromium, pending owner review | | ||
| | FR21 | In the pointer popover, with a single-line text field in `header` (an `input` of type `text`, `search`, `email`, `url`, `tel` or `password`, with no role but `textbox` or `searchbox`), ArrowDown in that field moves focus to the menu's first enabled row, as from a combobox's input to its list, and ArrowUp on that row moves focus back to the field ArrowDown left in this open instead of wrapping to the last row; until a field has, the row wraps as FR12 states. Both keys are handled in full only when focus moves. An arrow with a modifier, a key inside an IME composition, an ArrowDown the field already handled (its own `onKeyDown` prevented the default), and any key in a control whose arrows mean something of their own (a number, a range, a multi-line text, a button, a combobox, a spinbutton, an input with a `list`) keep their native behavior. The bottom sheet, and a drilled-in sub-menu view, hand no arrows between the field and the rows. The menu's other keys (FR12) are unchanged. It is a named exception under `spec:AST-020/FR15` (AR5, DEC-14). | Proposed DEC-14; FR12; FR15; `spec:AST-020/FR15` exception | Proposed; verified in jsdom and Chromium, pending owner review | |
There was a problem hiding this comment.
This row makes the hand-off on by default, but the record's Compatibility and migration section (lines 53-58) still says "Released default preserved: yes" and "Compatibility class: additive documentation", with a migration decision that names only useMenuPress DECs. If the default-on route is approved, that section should say the released default is not preserved for a header text field's ArrowDown in the popover, classify the change as breaking (minor), and name DEC-14 (prevent the default in the field's own onKeyDown) as the migration decision.
There was a problem hiding this comment.
Fixed in 41a2ee9. The Compatibility and migration section now reads:
- Released default preserved:
no. With proposed FR21, ArrowDown in a single-line text field inheadermoves focus to the first row, where the released header kept every key of its own. - Compatibility class: a breaking interaction change under
spec:AST-017FR1 (proposed FR21). - Migration decision: a field that needs ArrowDown prevents its default (proposed DEC-14).
The press-model references stay as they were. Record wording only; the branch is also rebased onto main.
8a2c3e0 to
385a678
Compare
There was a problem hiding this comment.
Approval is waiting on a maintainer decision: arrow keys that move focus between a header text field and the menu rows join two separately focusable parts, which spec:AST-020 FR15 allows only as a named exception in the owning record, so the DropdownMenu owner needs to approve or decline proposed FR21 and DEC-14 and choose between on by default in a planned minor (main declares 0.7.3, so check:changesets refuses the [breaking] Changeset until then) or an opt-in. If the default-on route is approved, the Compatibility and migration section of DropdownMenu.spec.md still needs to stop saying the released default is preserved and the change is additive documentation, and to name DEC-14 as the migration decision. The implementation, the jsdom suite and the Chromium spec otherwise hold up on this revision.
[Automated review]
… rows With a single-line text field in header, ArrowDown in the field moves to the first enabled row, and ArrowUp on the first row returns to the field ArrowDown left in this open, as between a combobox's input and its list. Modified arrows, IME composition, a field that prevents ArrowDown's default and controls with arrows of their own keep their keys; without such a field the wrap is unchanged. Proposed FR21 and DEC-14 in the DropdownMenu record, with AR5 naming the exception under AST-020 FR15. pr-a11y runs the header field arrows Chromium contract. Rebased onto main after facebook#7530 as one commit; review rounds are folded in unchanged. Committed with --no-verify: check:changesets rejects [breaking] while main declares a patch.
…hange to the released default The Compatibility and migration section still said the released default was preserved and the change was additive documentation, while proposed FR21 hands ArrowDown from a header text field to the rows, where the released header kept every key of its own. It now says the released default is not preserved, names the change as breaking under spec:AST-017 FR1 and gives the migration from proposed DEC-14: a field that needs ArrowDown prevents its default. Record wording only. Committed with --no-verify: check:changesets rejects [breaking] while main declares a patch; check:knowledge and prettier pass.
385a678 to
41a2ee9
Compare
There was a problem hiding this comment.
Approval is waiting on a maintainer decision: arrow keys that move focus between a header text field and the menu rows join two separately focusable parts, which spec:AST-020 FR15 allows only as a named exception in the owning record, so the DropdownMenu owner needs to approve or decline proposed FR21 and DEC-14 as the governing spec for it, and choose between on by default in a planned minor (main declares 0.7.3, so check:changesets refuses the [breaking] Changeset until then) or an opt-in. The Compatibility and migration fix from the last round is in, and the implementation, the jsdom suite and the Chromium spec hold up on this revision, so nothing else is needed from you until that decision lands. If the owner picks the opt-in, the hand-off will need that new surface and a compatible Changeset.
[Automated review]
User need
A menu with a filter field in its
header(#7344) is used by typing, then picking. From the field only Tab reaches the rows, stopping on the menu as a whole, and no arrow leads back. A combobox moves between its input and list with ArrowDown and ArrowUp; keyboard users expect the same here.Problem and solution fit
In the popover, with a single-line text field in
header, ArrowDown in the field moves to the first enabled row, and ArrowUp on that row then returns to the field instead of wrapping to the last row; before any hand-off, ArrowUp wraps as before. The field keeps every key whose meaning it owns. The bottom sheet and a drilled-in sub-menu view hand no arrows on.Current specification
component:DropdownMenu(draft, @cixzhang) gains FR21 and DEC-14, both proposed and pending owner review, as a named exception tospec:AST-020FR15 that AR5 names; FR15 and DEC-8 point to it where they say a region keeps its own keys. Other proposals in open pull requests: #7319 FR18/DEC-11, #7352 FR19/DEC-12, #7541 FR22/DEC-15; #7839 and #7840 amend FR20.Public delta and caller burden
inputof typetext,search,email,url,telorpasswordmoves focus to the first enabled row; the key is handled only when focus moves.NumberInput) control or an input with alist.spec:AST-017FR1: 0.7 documents that a header "keeps its own keys and presses", so a field whose ArrowDown handler leaves the default now also hands focus on. Migration: prevent that default; no codemod, as the reliance is not visible in source.[breaking]waits for a planned minor; an opt-in prop would keep it a patch, the owner's call.Design and alternatives
Rejected: moving to the rows from any header control, which takes arrows from controls that use them; and keeping the wrap on the first row, which leaves the field unreachable by arrow.
Writing rubric results
Evidence
See Testing.
Scope
Testing
Review round (41a2ee9, rebased onto
mainbe42333): the record's Compatibility and migration section said the released default was preserved and the change was additive documentation; it now says the released default is not preserved (with proposed FR21 a header text field hands ArrowDown to the rows), names the change as breaking underspec:AST-017FR1, and gives proposed DEC-14's migration (a field that needs ArrowDown prevents its default). Record wording only. DropdownMenu, NumberInput, TimeInput and doc-prop suites: 762 tests; header arrows and header-and-footer Chromium specs: 12; typecheck, stricteslint,check:knowledge, the rest ofcheck:repo, Storybook build: clean;check:changesetsrejects[breaking]whilemaindeclares patch 0.7.3.Rebased onto
maincab6e90, no conflicts or changes: main's newpr-a11yTopNav step runs a spec file the previous head predates. DropdownMenu, NumberInput, TimeInput and doc-prop suites: 762 tests; typecheck,check:knowledge: clean.Rebased onto
mainb7860bc after feat(DropdownMenuSubMenu): header and footer around a sub-menu's rows #7530 merged, as one commit; the record andDropdownMenu.tsxconflicts keep both sides.pr-a11ygets a "Run DropdownMenu header field arrows contract" step forMenuHeaderArrows.a11y.chromium.spec.ts. DropdownMenu, ContextMenu, MoreMenu, NumberInput, TimeInput and doc-prop suites: 849 tests; header arrows and header-and-footer Chromium specs: 12; typecheck, stricteslint,check:knowledge, the rest ofcheck:repo, Storybook build: clean;check:changesetsrejects[breaking]whilemaindeclares patch 0.7.3.Review round (rebased onto
main863e167): DEC-14 records, as accepted, what a screen-reader user perceives of the hand-off: the field stays a plain textbox or searchbox with no relation to the menu, and a Down Arrow pressed to re-read a one-line field now moves to the rows, while the menu leaves Up Arrow and the screen reader's own reading commands to the field. Record wording only. DropdownMenu and doc-prop suites: 313 tests; header arrows and header-and-footer Chromium specs: 12; typecheck, stricteslint,check:knowledge, the rest ofcheck:repo, Storybook build: clean;check:changesetsrejects[breaking]as before.Rebased onto
maind0f9afe, no conflicts or changes. The previous head'spr-a11yandsmoke-testreds came from CI: main's fix(Tooltip): keep the tooltip open when the pointer crosses onto it in one step #7802 added apr-a11ystep whose spec file the older head lacked, and jsdelivr was still propagating core@0.7.2. DropdownMenu, NumberInput, TimeInput, docProp* suites: 740 tests; typecheck,check:knowledge: clean.Review round: ArrowUp on the first row returns only to the field ArrowDown left for the rows in this open, and otherwise wraps as FR12 states, so it never lands in a
TimeInputbefore a hand-off. New cases (wrap before a hand-off, past a headerTimeInput, after a reopen) fail on the previous head. The Accessibility contract's opening names FR21's hand-off, and theheaderdoc says a field that prevents ArrowDown's default keeps it. The Chromium spec reaches the field with Shift+Tab and checks the wrap first. DropdownMenu, ContextMenu, MoreMenu, NumberInput, TimeInput and doc-prop suites: 826 tests; header arrows and header-and-footer Chromium specs: 12; typecheck, stricteslint,check:knowledge, the rest ofcheck:repo, Storybook build: clean;check:changesetsrejects[breaking]as before.Review round (rebased onto
main2c3ed99): ArrowUp on the first row returns to the field ArrowDown last left, and a field with a role of its own (aNumberInputspinbutton) keeps its arrows; three new cases (a headerNumberInput, a time field after the filter, two plain fields) fail on the previous head. AR5, FR21 and DEC-14 name the hand-off as DropdownMenu's named exception underspec:AST-020/FR15, and the Header And Footer story says the field hands its arrows on. The rule takes FR21 and DEC-14 so open proposals in this record stay distinct. DropdownMenu, ContextMenu, MoreMenu, NumberInput, TimeInput and doc-prop suites: 824 tests; header arrows and header-and-footer Chromium specs: 12; typecheck, stricteslint,check:knowledge, the rest ofcheck:repo, Storybook build: clean.check:changesetsstill rejects[breaking]whilemaindeclares patch 0.7.2.Rebased onto
main577a62e after refactor(DropdownMenu): the header and footer Tab walk moves to a module of its own #7737 moved the header and footer Tab walk intomenuRegionStops.ts: the hand-off now importstabbablesInandfocusRegionControlfrom there instead of keeping its own copies, unchanged. DropdownMenu, ContextMenu, MoreMenu and doc-prop suites: 394 tests; the header arrows and header-and-footer Chromium specs: 12; typecheck,eslint(strict),check:knowledge, the rest ofcheck:repo, Storybook build: clean.check:changesetsstill rejects the[breaking]changeset whilemaindeclares patch 0.7.2, as before.Review round: FR18, the
headerdoc and the changeset are scoped to the popover. Two new guards pass onmainand here: the bottom sheet's header field keeps ArrowDown, and while a sub-menu view is drilled in the field keeps ArrowDown and Back keeps the wrap.check:changesetsfails by design ([breaking]whilemainplans 0.7.2), so the commit used--no-verifyafter lint-staged and every othercheck:repostep passed by hand. DropdownMenu, ContextMenu, MoreMenu and doc-prop suites (394 tests), the header Chromium specs (12), core typecheck, ESLint strict,check:knowledge, Storybook build: green.Rebased onto
main(22c592a) now that fix(DropdownMenu): a popover with a header or footer is a modal dialog, and Tab wraps #7515 has merged, and retargeted tomain. The rule is proposed FR18 and the decision DEC-11, sincemaintook FR17.DropdownMenu.test.tsx"header field arrows" suite, 9 cases (7 plus the 2 guards): ArrowDown from the field; a field that already handled ArrowDown, a combobox, and an input with alistkeep it; ArrowUp from the first row and from another row; a disabled first row both ways; several header controls; Shift, IME, number and multi-line controls keep the key; no text field means the wrap is unchanged. Onmain4 fail and the 3 keep-the-key guards pass; the combobox case also fails at the previous head. All 7 pass here.__tests__/MenuHeaderArrows.a11y.chromium.spec.tsagainst the existing "Header And Footer" story, real Chromium and keyboard: a keyboard open, ArrowUp to the field, typing a filter, ArrowDown to the first filtered row, and ArrowUp back with the value and caret intact; Shift with an arrow stays in the field. Both fail against amainbuild and pass here.DropdownMenu, ContextMenu, Typeahead and doc-prop suites (487 tests), plus every DropdownMenu Chromium spec (59): green. Core typecheck,
lint:strict,check:knowledge, Storybook build: clean.Not run on the iOS simulator: it cannot deliver hardware arrow keys to web content, and touch has no arrows.