diff --git a/packages/flutter/lib/src/material/dropdown_menu.dart b/packages/flutter/lib/src/material/dropdown_menu.dart index 9e64631582..88b291970f 100644 --- a/packages/flutter/lib/src/material/dropdown_menu.dart +++ b/packages/flutter/lib/src/material/dropdown_menu.dart @@ -164,6 +164,7 @@ class DropdownMenu extends StatefulWidget { this.expandedInsets, this.searchCallback, required this.dropdownMenuEntries, + this.inputFormatters, }); /// Determine if the [DropdownMenu] is enabled. @@ -389,6 +390,20 @@ class DropdownMenu extends StatefulWidget { /// which contains the contents of the text input field. final SearchCallback? searchCallback; + /// Optional input validation and formatting overrides. + /// + /// Formatters are run in the provided order when the user changes the text + /// this widget contains. When this parameter changes, the new formatters will + /// not be applied until the next time the user inserts or deletes text. + /// Formatters don't run when the text is changed + /// programmatically via [controller]. + /// + /// See also: + /// + /// * [TextEditingController], which implements the [Listenable] interface + /// and notifies its listeners on [TextEditingValue] changes. + final List? inputFormatters; + @override State> createState() => _DropdownMenuState(); } @@ -755,6 +770,7 @@ class _DropdownMenuState extends State> { _enableFilter = widget.enableFilter; }); }, + inputFormatters: widget.inputFormatters, decoration: InputDecoration( enabled: widget.enabled, label: widget.label, diff --git a/packages/flutter/test/material/dropdown_menu_test.dart b/packages/flutter/test/material/dropdown_menu_test.dart index 9e4ad60100..683ca4f08c 100644 --- a/packages/flutter/test/material/dropdown_menu_test.dart +++ b/packages/flutter/test/material/dropdown_menu_test.dart @@ -1972,6 +1972,54 @@ void main() { // Test input border when focused. expect(box, paints..rrect(color: theme.colorScheme.primary)); }); + + testWidgets('DropdownMenu honors inputFormatters', (WidgetTester tester) async { + int called = 0; + final TextInputFormatter formatter = TextInputFormatter.withFunction( + (TextEditingValue oldValue, TextEditingValue newValue) { + called += 1; + return newValue; + }, + ); + final TextEditingController controller = TextEditingController(); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DropdownMenu( + controller: controller, + dropdownMenuEntries: const >[ + DropdownMenuEntry( + value: 'Blue', + label: 'Blue', + ), + DropdownMenuEntry( + value: 'Green', + label: 'Green', + ), + ], + inputFormatters: [ + formatter, + FilteringTextInputFormatter.deny(RegExp('[0-9]')) + ], + ), + ), + ), + ); + + final EditableTextState state = tester.firstState(find.byType(EditableText)); + state.updateEditingValue(const TextEditingValue(text: 'Blue')); + expect(called, 1); + expect(controller.text, 'Blue'); + + state.updateEditingValue(const TextEditingValue(text: 'Green')); + expect(called, 2); + expect(controller.text, 'Green'); + + state.updateEditingValue(const TextEditingValue(text: 'Green2')); + expect(called, 3); + expect(controller.text, 'Green'); + }); } enum TestMenu {