From 402ed6c503ff20dbd88a9647de045eb0163b9534 Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Fri, 23 Aug 2024 09:57:08 +0200 Subject: [PATCH] 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. --- .../lib/src/material/dropdown_menu.dart | 4 +- .../test/material/dropdown_menu_test.dart | 40 ++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/packages/flutter/lib/src/material/dropdown_menu.dart b/packages/flutter/lib/src/material/dropdown_menu.dart index a6cb62cff4f..d6861bea370 100644 --- a/packages/flutter/lib/src/material/dropdown_menu.dart +++ b/packages/flutter/lib/src/material/dropdown_menu.dart @@ -670,7 +670,7 @@ class _DropdownMenuState extends State> { ) : effectiveStyle; - final Widget menuItemButton = MenuItemButton( + final Widget menuItemButton = MenuItemButton( key: enableScrollToHighlight ? buttonItemKeys[i] : null, style: effectiveStyle, leadingIcon: entry.leadingIcon, @@ -890,7 +890,7 @@ class _DropdownMenuState extends State> { width: widget.width, children: [ textField, - ..._initialMenu!, + ..._initialMenu!.map((Widget item) => ExcludeFocus(excluding: !controller.isOpen, child: item)), trailingButton, leadingButton, ], diff --git a/packages/flutter/test/material/dropdown_menu_test.dart b/packages/flutter/test/material/dropdown_menu_test.dart index b1527427a2f..6642f8881ac 100644 --- a/packages/flutter/test/material/dropdown_menu_test.dart +++ b/packages/flutter/test/material/dropdown_menu_test.dart @@ -2427,6 +2427,44 @@ void main() { 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: [ + DropdownMenu(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 { int called = 0; final TextInputFormatter formatter = TextInputFormatter.withFunction( @@ -2562,7 +2600,7 @@ void main() { DropdownMenu( dropdownMenuEntries: >[], ), - DropdownMenu( + DropdownMenu( textAlign: TextAlign.center, dropdownMenuEntries: >[], ),