Fix DropdownMenu focus traversal (#153931)

## Description

This PR fixes `DropdownMenu` focus traversal.

Before this PR, if a `DropdownMenu` contains several items, the 'tab' key had to be pressed many times before the focus move to the inner text field.

This fix is based on https://github.com/flutter/flutter/issues/131120#issuecomment-1654233358.

## Related Issue

Fixes https://github.com/flutter/flutter/issues/131120.

## Tests

Adds 1 test.
This commit is contained in:
Bruno Leroux 2024-08-23 09:57:08 +02:00 committed by GitHub
parent b6c14d783a
commit 402ed6c503
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 41 additions and 3 deletions

View File

@ -670,7 +670,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
) )
: effectiveStyle; : effectiveStyle;
final Widget menuItemButton = MenuItemButton( final Widget menuItemButton = MenuItemButton(
key: enableScrollToHighlight ? buttonItemKeys[i] : null, key: enableScrollToHighlight ? buttonItemKeys[i] : null,
style: effectiveStyle, style: effectiveStyle,
leadingIcon: entry.leadingIcon, leadingIcon: entry.leadingIcon,
@ -890,7 +890,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
width: widget.width, width: widget.width,
children: <Widget>[ children: <Widget>[
textField, textField,
..._initialMenu!, ..._initialMenu!.map((Widget item) => ExcludeFocus(excluding: !controller.isOpen, child: item)),
trailingButton, trailingButton,
leadingButton, leadingButton,
], ],

View File

@ -2427,6 +2427,44 @@ void main() {
expect(box, paints..rrect(color: theme.colorScheme.primary)); expect(box, paints..rrect(color: theme.colorScheme.primary));
}); });
// Regression test for https://github.com/flutter/flutter/issues/131120.
testWidgets('Focus traversal ignores non visible entries', (WidgetTester tester) async {
final FocusNode buttonFocusNode = FocusNode();
addTearDown(buttonFocusNode.dispose);
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Column(
children: <Widget>[
DropdownMenu<TestMenu>(dropdownMenuEntries: menuChildren),
ElevatedButton(
focusNode: buttonFocusNode,
onPressed: () {},
child: const Text('Button'),
)
],
),
),
));
// Move the focus to the text field.
primaryFocus!.nextFocus();
await tester.pump();
final Element textField = tester.element(find.byType(TextField));
expect(Focus.of(textField).hasFocus, isTrue);
// Move the focus to the dropdown trailing icon.
primaryFocus!.nextFocus();
await tester.pump();
final Element iconButton = tester.firstElement(find.byIcon(Icons.arrow_drop_down));
expect(Focus.of(iconButton).hasFocus, isTrue);
// Move the focus to the elevated button.
primaryFocus!.nextFocus();
await tester.pump();
expect(buttonFocusNode.hasFocus, isTrue);
});
testWidgets('DropdownMenu honors inputFormatters', (WidgetTester tester) async { testWidgets('DropdownMenu honors inputFormatters', (WidgetTester tester) async {
int called = 0; int called = 0;
final TextInputFormatter formatter = TextInputFormatter.withFunction( final TextInputFormatter formatter = TextInputFormatter.withFunction(
@ -2562,7 +2600,7 @@ void main() {
DropdownMenu<int>( DropdownMenu<int>(
dropdownMenuEntries: <DropdownMenuEntry<int>>[], dropdownMenuEntries: <DropdownMenuEntry<int>>[],
), ),
DropdownMenu<int>( DropdownMenu<int>(
textAlign: TextAlign.center, textAlign: TextAlign.center,
dropdownMenuEntries: <DropdownMenuEntry<int>>[], dropdownMenuEntries: <DropdownMenuEntry<int>>[],
), ),