Adds semanticsLabel to MenuItemButton (#145846)

This PR adds a `semanticsLabel` field to the MenuItemButton, letting users pass in a Semantics Label if they have issues with the way a ScreenReader reads the generated text.

fixes: [142939: Screen Readers won't read punctuation characters in Shortcuts](https://github.com/flutter/flutter/issues/142939)
This commit is contained in:
philipfranchi 2024-04-04 17:19:45 -04:00 committed by GitHub
parent 23ecc37ed2
commit 77201fdf71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 48 additions and 2 deletions

View File

@ -848,6 +848,7 @@ class MenuItemButton extends StatefulWidget {
this.onFocusChange,
this.focusNode,
this.shortcut,
this.semanticsLabel,
this.style,
this.statesController,
this.clipBehavior = Clip.none,
@ -892,6 +893,21 @@ class MenuItemButton extends StatefulWidget {
/// {@macro flutter.material.MenuBar.shortcuts_note}
final MenuSerializableShortcut? shortcut;
/// An optional Semantics label, applied to the entire [MenuItemButton].
///
/// A screen reader will default to reading the derived text on the
/// [MenuItemButton] itself, which is not guaranteed to be readable.
/// (For some shortcuts, such as comma, semicolon, and other
/// punctuation, screen readers read silence)
///
/// Setting this label overwrites the semantics properties of the entire
/// Widget, including its children. Consider wrapping this widget in
/// [Semantics] if you want to customize other properties besides just
/// the label.
///
/// Null by default.
final String? semanticsLabel;
/// Customizes this button's appearance.
///
/// Non-null properties of this style override the corresponding properties in
@ -1119,6 +1135,7 @@ class _MenuItemButtonState extends State<MenuItemButton> {
child: _MenuItemLabel(
leadingIcon: widget.leadingIcon,
shortcut: widget.shortcut,
semanticsLabel: widget.semanticsLabel,
trailingIcon: widget.trailingIcon,
hasSubmenu: false,
overflowAxis: _anchor?._orientation ?? widget.overflowAxis,
@ -2973,6 +2990,7 @@ class _MenuItemLabel extends StatelessWidget {
this.leadingIcon,
this.trailingIcon,
this.shortcut,
this.semanticsLabel,
this.overflowAxis = Axis.vertical,
required this.child,
});
@ -2997,6 +3015,10 @@ class _MenuItemLabel extends StatelessWidget {
/// the shortcut.
final MenuSerializableShortcut? shortcut;
/// An optional Semantics label, which replaces the generated string when
/// read by a screen reader.
final String? semanticsLabel;
/// The direction in which the menu item expands.
final Axis overflowAxis;
@ -3010,7 +3032,6 @@ class _MenuItemLabel extends StatelessWidget {
_kLabelItemMinSpacing,
_kLabelItemDefaultSpacing + density.horizontal * 2,
);
Widget leadings;
if (overflowAxis == Axis.vertical) {
leadings = Expanded(
@ -3044,7 +3065,7 @@ class _MenuItemLabel extends StatelessWidget {
);
}
return Row(
Widget menuItemLabel = Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
leadings,
@ -3073,6 +3094,10 @@ class _MenuItemLabel extends StatelessWidget {
),
],
);
if (semanticsLabel != null) {
menuItemLabel = Semantics(label: semanticsLabel, excludeSemantics: true, child: menuItemLabel);
}
return menuItemLabel;
}
@override

View File

@ -3387,6 +3387,27 @@ void main() {
semantics.dispose();
});
testWidgets('MenuItemButton semantics respects label', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
MaterialApp(
home: Center(
child: MenuItemButton(
semanticsLabel: 'TestWidget',
shortcut: const SingleActivator(LogicalKeyboardKey.comma),
style: MenuItemButton.styleFrom(fixedSize: const Size(88.0, 36.0)),
onPressed: () {},
child: const Text('ABC'),
),
),
),
);
expect(find.bySemanticsLabel('TestWidget'), findsOneWidget);
semantics.dispose();
}, variant: TargetPlatformVariant.desktop());
testWidgets('SubMenuButton is not a semantic button', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(