Skip to content

Commit 417f47f

Browse files
authored
Wire up MenuAnchor, MenuBar, MenuItem-related widgets to aria roles (flutter#165596)
This PR is to wire up `MenuAnchor` related widgets to SemanticsRole. * When use `MenuAnchor` and a menu is opened, the menu has `SemanticsRole.menu`. * `MenuBar` has `SemanticsRole.menuBar` * `MenuItemButton` has `SemanticsRole.menuItem` * `SubmenuButton` has `SemanticsRole.menuItem` with `aria-haspopup` attribute setup. * `CheckboxMenuButton` has `SemanticsRole.menuItemCheckbox` * `RadioMenuButton` has `SemanticsRole.menuItemRadio` This PR also includes some changes related to `OverlayPortal` and `RawMenuAnchor` so the "button" and the "menu" that the button opens has a "parent-children" relationship. Previously, they are siblings relationship which will cause some navigation issue in a11y. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing.
1 parent def2376 commit 417f47f

File tree

8 files changed

+448
-334
lines changed

8 files changed

+448
-334
lines changed

dev/customer_testing/tests.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
55deebbfa3f3b85e63b839fa3aa5a3ad0cf51607
1+
8be72094d9b33dae8ff8a7085a3d922e7b3cad47

examples/api/lib/widgets/raw_menu_anchor/raw_menu_anchor.0.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:ui';
6+
57
import 'package:flutter/material.dart';
68
import 'package:flutter/services.dart';
79

@@ -126,6 +128,7 @@ class CustomMenu extends StatelessWidget {
126128
child: Semantics(
127129
scopesRoute: true,
128130
explicitChildNodes: true,
131+
role: SemanticsRole.menu,
129132
child: TapRegion(
130133
groupId: info.tapRegionGroupId,
131134
onTapOutside: (PointerDownEvent event) {

examples/api/lib/widgets/raw_menu_anchor/raw_menu_anchor.1.dart

Lines changed: 61 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:ui';
6+
57
import 'package:flutter/material.dart';
68
import 'package:flutter/services.dart';
79

@@ -91,66 +93,70 @@ class _RawMenuAnchorGroupExampleState extends State<RawMenuAnchorGroupExample> {
9193
clipBehavior: Clip.hardEdge,
9294
child: RawMenuAnchorGroup(
9395
controller: controller,
94-
child: Row(
95-
children: <Widget>[
96-
for (int i = 0; i < menuItems.length; i++)
97-
CustomSubmenu(
98-
focusNode: focusNodes[i],
99-
anchor: Builder(
100-
builder: (BuildContext context) {
101-
final MenuController submenuController = MenuController.maybeOf(context)!;
102-
final MenuItem item = menuItems[i];
103-
final ButtonStyle openBackground = MenuItemButton.styleFrom(
104-
backgroundColor: const Color(0x0D1A1A1A),
105-
);
106-
return MergeSemantics(
107-
child: Semantics(
108-
expanded: controller.isOpen,
109-
child: MenuItemButton(
110-
style: submenuController.isOpen ? openBackground : null,
111-
onHover: (bool value) {
112-
// If any submenu in the menu bar is already open, other
113-
// submenus should open on hover. Otherwise, blur the menu item
114-
// button if the menu button is no longer hovered.
115-
if (controller.isOpen) {
116-
if (value) {
96+
child: Semantics(
97+
role: SemanticsRole.menu,
98+
child: Row(
99+
children: <Widget>[
100+
for (int i = 0; i < menuItems.length; i++)
101+
CustomSubmenu(
102+
focusNode: focusNodes[i],
103+
anchor: Builder(
104+
builder: (BuildContext context) {
105+
final MenuController submenuController =
106+
MenuController.maybeOf(context)!;
107+
final MenuItem item = menuItems[i];
108+
final ButtonStyle openBackground = MenuItemButton.styleFrom(
109+
backgroundColor: const Color(0x0D1A1A1A),
110+
);
111+
return MergeSemantics(
112+
child: Semantics(
113+
expanded: controller.isOpen,
114+
child: MenuItemButton(
115+
style: submenuController.isOpen ? openBackground : null,
116+
onHover: (bool value) {
117+
// If any submenu in the menu bar is already open, other
118+
// submenus should open on hover. Otherwise, blur the menu item
119+
// button if the menu button is no longer hovered.
120+
if (controller.isOpen) {
121+
if (value) {
122+
submenuController.open();
123+
}
124+
} else if (!value) {
125+
Focus.of(context).unfocus();
126+
}
127+
},
128+
onPressed: () {
129+
if (submenuController.isOpen) {
130+
submenuController.close();
131+
} else {
117132
submenuController.open();
118133
}
119-
} else if (!value) {
120-
Focus.of(context).unfocus();
121-
}
122-
},
123-
onPressed: () {
124-
if (submenuController.isOpen) {
125-
submenuController.close();
126-
} else {
127-
submenuController.open();
128-
}
129-
},
130-
leadingIcon: item.leading,
131-
child: Text(item.label),
134+
},
135+
leadingIcon: item.leading,
136+
child: Text(item.label),
137+
),
132138
),
139+
);
140+
},
141+
),
142+
children: <Widget>[
143+
for (final MenuItem child in menuItems[i].children ?? <MenuItem>[])
144+
MenuItemButton(
145+
onPressed: () {
146+
setState(() {
147+
_selected = child;
148+
});
149+
150+
// Close the menu bar after a selection.
151+
controller.close();
152+
},
153+
leadingIcon: child.leading,
154+
child: Text(child.label),
133155
),
134-
);
135-
},
156+
],
136157
),
137-
children: <Widget>[
138-
for (final MenuItem child in menuItems[i].children ?? <MenuItem>[])
139-
MenuItemButton(
140-
onPressed: () {
141-
setState(() {
142-
_selected = child;
143-
});
144-
145-
// Close the menu bar after a selection.
146-
controller.close();
147-
},
148-
leadingIcon: child.leading,
149-
child: Text(child.label),
150-
),
151-
],
152-
),
153-
],
158+
],
159+
),
154160
),
155161
),
156162
),

0 commit comments

Comments
 (0)