From 21a12f6c991f3806d551d70e6af916fbe80f1ee3 Mon Sep 17 00:00:00 2001 From: hangyu Date: Mon, 17 Jun 2024 14:16:14 -0700 Subject: [PATCH] [a11y] Add semantics: button to bottom navigation bar items and dropdown menu items (#149375) issue: https://github.com/flutter/flutter/issues/117997 --- .../src/material/bottom_navigation_bar.dart | 1 + .../flutter/lib/src/material/dropdown.dart | 13 ++++++----- .../material/bottom_navigation_bar_test.dart | 16 +++++++++++++- .../flutter/test/material/dropdown_test.dart | 22 ++++++++++++++----- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/packages/flutter/lib/src/material/bottom_navigation_bar.dart b/packages/flutter/lib/src/material/bottom_navigation_bar.dart index 0f9cf05692e1..6e7877881298 100644 --- a/packages/flutter/lib/src/material/bottom_navigation_bar.dart +++ b/packages/flutter/lib/src/material/bottom_navigation_bar.dart @@ -605,6 +605,7 @@ class _BottomNavigationTile extends StatelessWidget { result = Semantics( selected: selected, + button: true, container: true, child: Stack( children: [ diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index c2abe0d8596f..1f4a5e35fa33 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -785,9 +785,12 @@ class _DropdownMenuItemContainer extends StatelessWidget { @override Widget build(BuildContext context) { - return ConstrainedBox( - constraints: const BoxConstraints(minHeight: _kMenuItemHeight), - child: Align(alignment: alignment, child: child), + return Semantics( + button: true, + child: ConstrainedBox( + constraints: const BoxConstraints(minHeight: _kMenuItemHeight), + child: Align(alignment: alignment, child: child), + ), ); } } @@ -1583,9 +1586,9 @@ class _DropdownButtonState extends State> with WidgetsBindi child: result, ); } - + final bool childHasButtonSemantic = hintIndex != null || (_selectedIndex != null && widget.selectedItemBuilder == null); return Semantics( - button: true, + button: !childHasButtonSemantic, child: Actions( actions: _actionMap, child: InkWell( diff --git a/packages/flutter/test/material/bottom_navigation_bar_test.dart b/packages/flutter/test/material/bottom_navigation_bar_test.dart index f0e65837bb76..ea256354576e 100644 --- a/packages/flutter/test/material/bottom_navigation_bar_test.dart +++ b/packages/flutter/test/material/bottom_navigation_bar_test.dart @@ -2108,6 +2108,7 @@ void main() { matchesSemantics( label: 'AC\nTab 1 of 3', textDirection: TextDirection.ltr, + isButton: true, isFocusable: true, isSelected: true, hasTapAction: true, @@ -2119,6 +2120,7 @@ void main() { matchesSemantics( label: 'Alarm\nTab 2 of 3', textDirection: TextDirection.ltr, + isButton: true, isFocusable: true, hasTapAction: true, hasFocusAction: true, @@ -2129,6 +2131,7 @@ void main() { matchesSemantics( label: 'Hot Tub\nTab 3 of 3', textDirection: TextDirection.ltr, + isButton: true, isFocusable: true, hasTapAction: true, hasFocusAction: true, @@ -2165,6 +2168,7 @@ void main() { matchesSemantics( label: 'AC\nTab 1 of 3', textDirection: TextDirection.ltr, + isButton: true, isFocusable: true, isSelected: true, hasTapAction: true, @@ -2176,6 +2180,7 @@ void main() { matchesSemantics( label: 'Alarm\nTab 2 of 3', textDirection: TextDirection.ltr, + isButton: true, isFocusable: true, hasTapAction: true, hasFocusAction: true, @@ -2186,6 +2191,7 @@ void main() { matchesSemantics( label: 'Hot Tub\nTab 3 of 3', textDirection: TextDirection.ltr, + isButton: true, isFocusable: true, hasTapAction: true, hasFocusAction: true, @@ -2518,6 +2524,7 @@ void main() { matchesSemantics( label: 'Red\nTab 1 of 2', textDirection: TextDirection.ltr, + isButton: true, isFocusable: true, isSelected: true, hasTapAction: true, @@ -2529,6 +2536,7 @@ void main() { matchesSemantics( label: 'Green\nTab 2 of 2', textDirection: TextDirection.ltr, + isButton: true, isFocusable: true, hasTapAction: true, hasFocusAction: true, @@ -2563,6 +2571,7 @@ void main() { matchesSemantics( label: 'Red\nTab 1 of 2', textDirection: TextDirection.ltr, + isButton: true, isFocusable: true, isSelected: true, hasTapAction: true, @@ -2574,6 +2583,7 @@ void main() { matchesSemantics( label: 'Green\nTab 2 of 2', textDirection: TextDirection.ltr, + isButton: true, isFocusable: true, hasTapAction: true, hasFocusAction: true, @@ -2754,6 +2764,7 @@ void main() { children: [ TestSemantics( flags: [ + SemanticsFlag.isButton, SemanticsFlag.isSelected, SemanticsFlag.isFocusable, ], @@ -2762,7 +2773,10 @@ void main() { textDirection: TextDirection.ltr, ), TestSemantics( - flags: [SemanticsFlag.isFocusable], + flags: [ + SemanticsFlag.isButton, + SemanticsFlag.isFocusable, + ], actions: [SemanticsAction.tap, SemanticsAction.focus], label: 'B\nTab 2 of 2', textDirection: TextDirection.ltr, diff --git a/packages/flutter/test/material/dropdown_test.dart b/packages/flutter/test/material/dropdown_test.dart index c04ae2b1aa47..135ef2dda642 100644 --- a/packages/flutter/test/material/dropdown_test.dart +++ b/packages/flutter/test/material/dropdown_test.dart @@ -1296,7 +1296,7 @@ void main() { )); // By default the hint contributes the label. - expect(tester.getSemantics(find.byKey(key)), matchesSemantics( + expect(tester.getSemantics(find.text('test')), matchesSemantics( isButton: true, label: 'test', hasTapAction: true, @@ -1311,8 +1311,8 @@ void main() { hint: const Text('test'), )); - // Displays label of select item and is no longer tappable. - expect(tester.getSemantics(find.byKey(key)), matchesSemantics( + // Displays label of select item. + expect(tester.getSemantics(find.text('three')), matchesSemantics( isButton: true, label: 'three', hasTapAction: true, @@ -1358,6 +1358,7 @@ void main() { label: 'one', textDirection: TextDirection.ltr, flags: [ + SemanticsFlag.isButton, SemanticsFlag.isFocused, SemanticsFlag.isFocusable, ], @@ -1367,21 +1368,30 @@ void main() { TestSemantics( label: 'two', textDirection: TextDirection.ltr, - flags: [SemanticsFlag.isFocusable], + flags: [ + SemanticsFlag.isButton, + SemanticsFlag.isFocusable, + ], tags: [const SemanticsTag('RenderViewport.twoPane')], actions: [SemanticsAction.tap, SemanticsAction.focus], ), TestSemantics( label: 'three', textDirection: TextDirection.ltr, - flags: [SemanticsFlag.isFocusable], + flags: [ + SemanticsFlag.isButton, + SemanticsFlag.isFocusable, + ], tags: [const SemanticsTag('RenderViewport.twoPane')], actions: [SemanticsAction.tap, SemanticsAction.focus], ), TestSemantics( label: 'four', textDirection: TextDirection.ltr, - flags: [SemanticsFlag.isFocusable], + flags: [ + SemanticsFlag.isButton, + SemanticsFlag.isFocusable, + ], tags: [const SemanticsTag('RenderViewport.twoPane')], actions: [SemanticsAction.tap, SemanticsAction.focus], ),