Skip to content

Commit 9a344c2

Browse files
authored
Add option to keep MenuAnchor open after MenuItem tap (#123723)
Add option to keep ```MenuAnchor``` open after ```MenuItem``` tap
1 parent 63e3048 commit 9a344c2

File tree

2 files changed

+98
-1
lines changed

2 files changed

+98
-1
lines changed

packages/flutter/lib/src/material/menu_anchor.dart

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,7 @@ class MenuItemButton extends StatefulWidget {
810810
this.clipBehavior = Clip.none,
811811
this.leadingIcon,
812812
this.trailingIcon,
813+
this.closeOnActivate = true,
813814
required this.child,
814815
});
815816

@@ -871,6 +872,14 @@ class MenuItemButton extends StatefulWidget {
871872
/// An optional icon to display after the [child] label.
872873
final Widget? trailingIcon;
873874

875+
/// {@template flutter.material.menu_anchor.closeOnActivate}
876+
/// Determines if the menu will be closed when a [MenuItemButton]
877+
/// is pressed.
878+
///
879+
/// Defaults to true.
880+
/// {@endtemplate}
881+
final bool closeOnActivate;
882+
874883
/// The widget displayed in the center of this button.
875884
///
876885
/// Typically this is the button's label, using a [Text] widget.
@@ -1089,7 +1098,9 @@ class _MenuItemButtonState extends State<MenuItemButton> {
10891098
void _handleSelect() {
10901099
assert(_debugMenuInfo('Selected ${widget.child} menu'));
10911100
widget.onPressed?.call();
1092-
_MenuAnchorState._maybeOf(context)?._root._close();
1101+
if (widget.closeOnActivate) {
1102+
_MenuAnchorState._maybeOf(context)?._root._close();
1103+
}
10931104
}
10941105

10951106
void _createInternalFocusNodeIfNeeded() {
@@ -1140,6 +1151,7 @@ class CheckboxMenuButton extends StatelessWidget {
11401151
this.statesController,
11411152
this.clipBehavior = Clip.none,
11421153
this.trailingIcon,
1154+
this.closeOnActivate = true,
11431155
required this.child,
11441156
});
11451157

@@ -1242,6 +1254,9 @@ class CheckboxMenuButton extends StatelessWidget {
12421254
/// An optional icon to display after the [child] label.
12431255
final Widget? trailingIcon;
12441256

1257+
/// {@macro flutter.material.menu_anchor.closeOnActivate}
1258+
final bool closeOnActivate;
1259+
12451260
/// The widget displayed in the center of this button.
12461261
///
12471262
/// Typically this is the button's label, using a [Text] widget.
@@ -1292,6 +1307,7 @@ class CheckboxMenuButton extends StatelessWidget {
12921307
),
12931308
clipBehavior: clipBehavior,
12941309
trailingIcon: trailingIcon,
1310+
closeOnActivate: closeOnActivate,
12951311
child: child,
12961312
);
12971313
}
@@ -1332,6 +1348,7 @@ class RadioMenuButton<T> extends StatelessWidget {
13321348
this.statesController,
13331349
this.clipBehavior = Clip.none,
13341350
this.trailingIcon,
1351+
this.closeOnActivate = true,
13351352
required this.child,
13361353
});
13371354

@@ -1436,6 +1453,9 @@ class RadioMenuButton<T> extends StatelessWidget {
14361453
/// An optional icon to display after the [child] label.
14371454
final Widget? trailingIcon;
14381455

1456+
/// {@macro flutter.material.menu_anchor.closeOnActivate}
1457+
final bool closeOnActivate;
1458+
14391459
/// The widget displayed in the center of this button.
14401460
///
14411461
/// Typically this is the button's label, using a [Text] widget.
@@ -1483,6 +1503,7 @@ class RadioMenuButton<T> extends StatelessWidget {
14831503
),
14841504
clipBehavior: clipBehavior,
14851505
trailingIcon: trailingIcon,
1506+
closeOnActivate: closeOnActivate,
14861507
child: child,
14871508
);
14881509
}

packages/flutter/test/material/menu_anchor_test.dart

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2799,6 +2799,82 @@ void main() {
27992799
expect(radioValue, 1);
28002800
});
28012801
});
2802+
2803+
testWidgets('MenuItemButton respects closeOnActivate property', (WidgetTester tester) async {
2804+
final MenuController controller = MenuController();
2805+
await tester.pumpWidget(
2806+
MaterialApp(
2807+
home: Material(
2808+
child: Center(
2809+
child: MenuAnchor(
2810+
controller: controller,
2811+
menuChildren: <Widget> [
2812+
MenuItemButton(
2813+
onPressed: () {},
2814+
child: const Text('Button 1'),
2815+
),
2816+
],
2817+
builder: (BuildContext context, MenuController controller, Widget? child) {
2818+
return FilledButton(
2819+
onPressed: () {
2820+
controller.open();
2821+
},
2822+
child: const Text('Tap me'),
2823+
);
2824+
},
2825+
),
2826+
),
2827+
),
2828+
)
2829+
);
2830+
2831+
await tester.tap(find.text('Tap me'));
2832+
await tester.pump();
2833+
expect(find.byType(MenuItemButton), findsNWidgets(1));
2834+
2835+
// Taps the MenuItemButton which should close the menu
2836+
await tester.tap(find.text('Button 1'));
2837+
await tester.pump();
2838+
expect(find.byType(MenuItemButton), findsNWidgets(0));
2839+
2840+
await tester.pumpAndSettle();
2841+
2842+
await tester.pumpWidget(
2843+
MaterialApp(
2844+
home: Material(
2845+
child: Center(
2846+
child: MenuAnchor(
2847+
controller: controller,
2848+
menuChildren: <Widget> [
2849+
MenuItemButton(
2850+
closeOnActivate: false,
2851+
onPressed: () {},
2852+
child: const Text('Button 1'),
2853+
),
2854+
],
2855+
builder: (BuildContext context, MenuController controller, Widget? child) {
2856+
return FilledButton(
2857+
onPressed: () {
2858+
controller.open();
2859+
},
2860+
child: const Text('Tap me'),
2861+
);
2862+
},
2863+
),
2864+
),
2865+
),
2866+
)
2867+
);
2868+
2869+
await tester.tap(find.text('Tap me'));
2870+
await tester.pump();
2871+
expect(find.byType(MenuItemButton), findsNWidgets(1));
2872+
2873+
// Taps the MenuItemButton which shouldn't close the menu
2874+
await tester.tap(find.text('Button 1'));
2875+
await tester.pump();
2876+
expect(find.byType(MenuItemButton), findsNWidgets(1));
2877+
});
28022878
}
28032879

28042880
List<Widget> createTestMenus({

0 commit comments

Comments
 (0)