Add option to keep ``MenuAnchor` open after `MenuItem`` tap (#123723)

Add option to keep ```MenuAnchor``` open after ```MenuItem``` tap
This commit is contained in:
Henry Riehl 2023-03-31 18:22:59 +01:00 committed by GitHub
parent 63e30480cd
commit 9a344c2604
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 98 additions and 1 deletions

View file

@ -810,6 +810,7 @@ class MenuItemButton extends StatefulWidget {
this.clipBehavior = Clip.none,
this.leadingIcon,
this.trailingIcon,
this.closeOnActivate = true,
required this.child,
});
@ -871,6 +872,14 @@ class MenuItemButton extends StatefulWidget {
/// An optional icon to display after the [child] label.
final Widget? trailingIcon;
/// {@template flutter.material.menu_anchor.closeOnActivate}
/// Determines if the menu will be closed when a [MenuItemButton]
/// is pressed.
///
/// Defaults to true.
/// {@endtemplate}
final bool closeOnActivate;
/// The widget displayed in the center of this button.
///
/// Typically this is the button's label, using a [Text] widget.
@ -1089,7 +1098,9 @@ class _MenuItemButtonState extends State<MenuItemButton> {
void _handleSelect() {
assert(_debugMenuInfo('Selected ${widget.child} menu'));
widget.onPressed?.call();
_MenuAnchorState._maybeOf(context)?._root._close();
if (widget.closeOnActivate) {
_MenuAnchorState._maybeOf(context)?._root._close();
}
}
void _createInternalFocusNodeIfNeeded() {
@ -1140,6 +1151,7 @@ class CheckboxMenuButton extends StatelessWidget {
this.statesController,
this.clipBehavior = Clip.none,
this.trailingIcon,
this.closeOnActivate = true,
required this.child,
});
@ -1242,6 +1254,9 @@ class CheckboxMenuButton extends StatelessWidget {
/// An optional icon to display after the [child] label.
final Widget? trailingIcon;
/// {@macro flutter.material.menu_anchor.closeOnActivate}
final bool closeOnActivate;
/// The widget displayed in the center of this button.
///
/// Typically this is the button's label, using a [Text] widget.
@ -1292,6 +1307,7 @@ class CheckboxMenuButton extends StatelessWidget {
),
clipBehavior: clipBehavior,
trailingIcon: trailingIcon,
closeOnActivate: closeOnActivate,
child: child,
);
}
@ -1332,6 +1348,7 @@ class RadioMenuButton<T> extends StatelessWidget {
this.statesController,
this.clipBehavior = Clip.none,
this.trailingIcon,
this.closeOnActivate = true,
required this.child,
});
@ -1436,6 +1453,9 @@ class RadioMenuButton<T> extends StatelessWidget {
/// An optional icon to display after the [child] label.
final Widget? trailingIcon;
/// {@macro flutter.material.menu_anchor.closeOnActivate}
final bool closeOnActivate;
/// The widget displayed in the center of this button.
///
/// Typically this is the button's label, using a [Text] widget.
@ -1483,6 +1503,7 @@ class RadioMenuButton<T> extends StatelessWidget {
),
clipBehavior: clipBehavior,
trailingIcon: trailingIcon,
closeOnActivate: closeOnActivate,
child: child,
);
}

View file

@ -2799,6 +2799,82 @@ void main() {
expect(radioValue, 1);
});
});
testWidgets('MenuItemButton respects closeOnActivate property', (WidgetTester tester) async {
final MenuController controller = MenuController();
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: MenuAnchor(
controller: controller,
menuChildren: <Widget> [
MenuItemButton(
onPressed: () {},
child: const Text('Button 1'),
),
],
builder: (BuildContext context, MenuController controller, Widget? child) {
return FilledButton(
onPressed: () {
controller.open();
},
child: const Text('Tap me'),
);
},
),
),
),
)
);
await tester.tap(find.text('Tap me'));
await tester.pump();
expect(find.byType(MenuItemButton), findsNWidgets(1));
// Taps the MenuItemButton which should close the menu
await tester.tap(find.text('Button 1'));
await tester.pump();
expect(find.byType(MenuItemButton), findsNWidgets(0));
await tester.pumpAndSettle();
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: MenuAnchor(
controller: controller,
menuChildren: <Widget> [
MenuItemButton(
closeOnActivate: false,
onPressed: () {},
child: const Text('Button 1'),
),
],
builder: (BuildContext context, MenuController controller, Widget? child) {
return FilledButton(
onPressed: () {
controller.open();
},
child: const Text('Tap me'),
);
},
),
),
),
)
);
await tester.tap(find.text('Tap me'));
await tester.pump();
expect(find.byType(MenuItemButton), findsNWidgets(1));
// Taps the MenuItemButton which shouldn't close the menu
await tester.tap(find.text('Button 1'));
await tester.pump();
expect(find.byType(MenuItemButton), findsNWidgets(1));
});
}
List<Widget> createTestMenus({