From 14b914ab926355bf51859b69b829746a9dd4c47d Mon Sep 17 00:00:00 2001 From: hangyu Date: Thu, 29 Feb 2024 15:16:53 -0800 Subject: [PATCH] Reland [a11y] Fix date picker cannot focus on the edit field (#144198) reland https://github.com/flutter/flutter/pull/143117 fixes: https://github.com/flutter/flutter/issues/143116 fixes: https://github.com/flutter/flutter/issues/141992 ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [Features we expect every widget to implement]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat [Data Driven Fixes]: https://github.com/flutter/flutter/wiki/Data-driven-Fixes --- .../src/material/calendar_date_picker.dart | 1 + .../flutter/lib/src/material/date_picker.dart | 108 ++++++++++-------- .../input_date_picker_form_field.dart | 31 ++--- .../test/material/date_picker_test.dart | 25 +++- 4 files changed, 102 insertions(+), 63 deletions(-) diff --git a/packages/flutter/lib/src/material/calendar_date_picker.dart b/packages/flutter/lib/src/material/calendar_date_picker.dart index e39fba6af50..d203a96cadd 100644 --- a/packages/flutter/lib/src/material/calendar_date_picker.dart +++ b/packages/flutter/lib/src/material/calendar_date_picker.dart @@ -409,6 +409,7 @@ class _DatePickerModeToggleButtonState extends State<_DatePickerModeToggleButton label: MaterialLocalizations.of(context).selectYearSemanticsLabel, excludeSemantics: true, button: true, + container: true, child: SizedBox( height: _subHeaderHeight, child: InkWell( diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index 8b5aadbe911..cf75c91d10f 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -861,64 +861,76 @@ class _DatePickerHeader extends StatelessWidget { switch (orientation) { case Orientation.portrait: - return SizedBox( - height: _datePickerHeaderPortraitHeight, - child: Material( - color: backgroundColor, - child: Padding( - padding: const EdgeInsetsDirectional.only( - start: 24, - end: 12, - bottom: 12, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 16), - help, - const Flexible(child: SizedBox(height: 38)), - Row( - children: [ - Expanded(child: title), - if (entryModeButton != null) - entryModeButton!, - ], - ), - ], + return Semantics( + container: true, + child: SizedBox( + height: _datePickerHeaderPortraitHeight, + child: Material( + color: backgroundColor, + child: Padding( + padding: const EdgeInsetsDirectional.only( + start: 24, + end: 12, + bottom: 12, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + help, + const Flexible(child: SizedBox(height: 38)), + Row( + children: [ + Expanded(child: title), + if (entryModeButton != null) + Semantics( + container: true, + child: entryModeButton, + ), + ], + ), + ], + ), ), ), ), ); case Orientation.landscape: - return SizedBox( - width: _datePickerHeaderLandscapeWidth, - child: Material( - color: backgroundColor, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: _headerPaddingLandscape, - ), - child: help, - ), - SizedBox(height: isShort ? 16 : 56), - Expanded( - child: Padding( + return Semantics( + container: true, + child:SizedBox( + width: _datePickerHeaderLandscapeWidth, + child: Material( + color: backgroundColor, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + Padding( padding: const EdgeInsets.symmetric( horizontal: _headerPaddingLandscape, ), - child: title, + child: help, ), - ), - if (entryModeButton != null) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: entryModeButton, + SizedBox(height: isShort ? 16 : 56), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: _headerPaddingLandscape, + ), + child: title, + ), ), - ], + if (entryModeButton != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Semantics( + container: true, + child: entryModeButton, + ), + ), + ], + ), ), ), ); diff --git a/packages/flutter/lib/src/material/input_date_picker_form_field.dart b/packages/flutter/lib/src/material/input_date_picker_form_field.dart index 6341360da68..62210d6d49b 100644 --- a/packages/flutter/lib/src/material/input_date_picker_form_field.dart +++ b/packages/flutter/lib/src/material/input_date_picker_form_field.dart @@ -256,21 +256,24 @@ class _InputDatePickerFormFieldState extends State { ?? theme.inputDecorationTheme.border ?? (useMaterial3 ? const OutlineInputBorder() : const UnderlineInputBorder()); - return TextFormField( - decoration: InputDecoration( - hintText: widget.fieldHintText ?? localizations.dateHelpText, - labelText: widget.fieldLabelText ?? localizations.dateInputLabel, - ).applyDefaults(inputTheme - .merge(datePickerTheme.inputDecorationTheme) - .copyWith(border: effectiveInputBorder), + return Semantics( + container: true, + child: TextFormField( + decoration: InputDecoration( + hintText: widget.fieldHintText ?? localizations.dateHelpText, + labelText: widget.fieldLabelText ?? localizations.dateInputLabel, + ).applyDefaults(inputTheme + .merge(datePickerTheme.inputDecorationTheme) + .copyWith(border: effectiveInputBorder), + ), + validator: _validateDate, + keyboardType: widget.keyboardType ?? TextInputType.datetime, + onSaved: _handleSaved, + onFieldSubmitted: _handleSubmitted, + autofocus: widget.autofocus, + controller: _controller, + focusNode: widget.focusNode, ), - validator: _validateDate, - keyboardType: widget.keyboardType ?? TextInputType.datetime, - onSaved: _handleSaved, - onFieldSubmitted: _handleSubmitted, - autofocus: widget.autofocus, - controller: _controller, - focusNode: widget.focusNode, ); } } diff --git a/packages/flutter/test/material/date_picker_test.dart b/packages/flutter/test/material/date_picker_test.dart index 177a79800e9..926e4598600 100644 --- a/packages/flutter/test/material/date_picker_test.dart +++ b/packages/flutter/test/material/date_picker_test.dart @@ -13,6 +13,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../widgets/clipboard_utils.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -1577,6 +1578,13 @@ void main() { }); testWidgets('input mode', (WidgetTester tester) async { + // Fill the clipboard so that the Paste option is available in the text + // selection menu. + final MockClipboard mockClipboard = MockClipboard(); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); + await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); + addTearDown(() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null)); + final SemanticsHandle semantics = tester.ensureSemantics(); initialEntryMode = DatePickerEntryMode.input; @@ -1596,7 +1604,22 @@ void main() { isFocusable: true, )); - // The semantics of the InputDatePickerFormField are tested in its tests. + expect(tester.getSemantics(find.byType(EditableText)), matchesSemantics( + label: 'Enter Date', + isEnabled: true, + hasEnabledState: true, + isTextField: true, + isFocused: true, + value: '01/15/2016', + hasTapAction: true, + hasSetTextAction: true, + hasSetSelectionAction: true, + hasCopyAction: true, + hasCutAction: true, + hasPasteAction: true, + hasMoveCursorBackwardByCharacterAction: true, + hasMoveCursorBackwardByWordAction: true, + )); // Ok/Cancel buttons expect(tester.getSemantics(find.text('OK')), matchesSemantics(