Add "onTap" callback to PopupMenuItem (#81686)
This commit is contained in:
parent
a3893aea04
commit
336ae2de79
@ -219,6 +219,7 @@ class PopupMenuItem<T> extends PopupMenuEntry<T> {
|
|||||||
const PopupMenuItem({
|
const PopupMenuItem({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.value,
|
this.value,
|
||||||
|
this.onTap,
|
||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
this.height = kMinInteractiveDimension,
|
this.height = kMinInteractiveDimension,
|
||||||
this.padding,
|
this.padding,
|
||||||
@ -232,6 +233,9 @@ class PopupMenuItem<T> extends PopupMenuEntry<T> {
|
|||||||
/// The value that will be returned by [showMenu] if this entry is selected.
|
/// The value that will be returned by [showMenu] if this entry is selected.
|
||||||
final T? value;
|
final T? value;
|
||||||
|
|
||||||
|
/// Called when the menu item is tapped.
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
/// Whether the user is permitted to select this item.
|
/// Whether the user is permitted to select this item.
|
||||||
///
|
///
|
||||||
/// Defaults to true. If this is false, then the item will not react to
|
/// Defaults to true. If this is false, then the item will not react to
|
||||||
@ -319,6 +323,8 @@ class PopupMenuItemState<T, W extends PopupMenuItem<T>> extends State<W> {
|
|||||||
/// the menu route.
|
/// the menu route.
|
||||||
@protected
|
@protected
|
||||||
void handleTap() {
|
void handleTap() {
|
||||||
|
widget.onTap?.call();
|
||||||
|
|
||||||
Navigator.pop<T>(context, widget.value);
|
Navigator.pop<T>(context, widget.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,6 +289,139 @@ void main() {
|
|||||||
expect(Focus.of(childKey.currentContext!).hasPrimaryFocus, isTrue);
|
expect(Focus.of(childKey.currentContext!).hasPrimaryFocus, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('PopupMenuItem onTap callback is called when defined', (WidgetTester tester) async {
|
||||||
|
final List<int> menuItemTapCounters = <int>[0, 0];
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
TestApp(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: Material(
|
||||||
|
child: RepaintBoundary(
|
||||||
|
child: PopupMenuButton<void>(
|
||||||
|
child: const Text('Actions'),
|
||||||
|
itemBuilder: (BuildContext context) => <PopupMenuItem<void>>[
|
||||||
|
PopupMenuItem<void>(
|
||||||
|
child: const Text('First option'),
|
||||||
|
onTap: () {
|
||||||
|
menuItemTapCounters[0]++;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
PopupMenuItem<void>(
|
||||||
|
child: const Text('Second option'),
|
||||||
|
onTap: () {
|
||||||
|
menuItemTapCounters[1]++;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const PopupMenuItem<void>(
|
||||||
|
child: Text('Option without onTap'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tap the first tiem
|
||||||
|
await tester.tap(find.text('Actions'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text('First option'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(menuItemTapCounters, <int>[1, 0]);
|
||||||
|
|
||||||
|
// Tap the item again
|
||||||
|
await tester.tap(find.text('Actions'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text('First option'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(menuItemTapCounters, <int>[2, 0]);
|
||||||
|
|
||||||
|
// Tap a different item
|
||||||
|
await tester.tap(find.text('Actions'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text('Second option'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(menuItemTapCounters, <int>[2, 1]);
|
||||||
|
|
||||||
|
// Tap an iteem without onTap
|
||||||
|
await tester.tap(find.text('Actions'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text('Option without onTap'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(menuItemTapCounters, <int>[2, 1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('PopupMenuItem can have both onTap and value', (WidgetTester tester) async {
|
||||||
|
final List<int> menuItemTapCounters = <int>[0, 0];
|
||||||
|
String? selected;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
TestApp(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: Material(
|
||||||
|
child: RepaintBoundary(
|
||||||
|
child: PopupMenuButton<String>(
|
||||||
|
child: const Text('Actions'),
|
||||||
|
onSelected: (String value) { selected = value; },
|
||||||
|
itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
child: const Text('First option'),
|
||||||
|
value: 'first',
|
||||||
|
onTap: () {
|
||||||
|
menuItemTapCounters[0]++;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
child: const Text('Second option'),
|
||||||
|
value: 'second',
|
||||||
|
onTap: () {
|
||||||
|
menuItemTapCounters[1]++;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const PopupMenuItem<String>(
|
||||||
|
child: Text('Option without onTap'),
|
||||||
|
value: 'third',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tap the first item
|
||||||
|
await tester.tap(find.text('Actions'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text('First option'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(menuItemTapCounters, <int>[1, 0]);
|
||||||
|
expect(selected, 'first');
|
||||||
|
|
||||||
|
// Tap the item again
|
||||||
|
await tester.tap(find.text('Actions'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text('First option'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(menuItemTapCounters, <int>[2, 0]);
|
||||||
|
expect(selected, 'first');
|
||||||
|
|
||||||
|
// Tap a different item
|
||||||
|
await tester.tap(find.text('Actions'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text('Second option'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(menuItemTapCounters, <int>[2, 1]);
|
||||||
|
expect(selected, 'second');
|
||||||
|
|
||||||
|
// Tap an iteem without onTap
|
||||||
|
await tester.tap(find.text('Actions'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text('Option without onTap'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(menuItemTapCounters, <int>[2, 1]);
|
||||||
|
expect(selected, 'third');
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('PopupMenuItem is only focusable when enabled', (WidgetTester tester) async {
|
testWidgets('PopupMenuItem is only focusable when enabled', (WidgetTester tester) async {
|
||||||
final Key popupButtonKey = UniqueKey();
|
final Key popupButtonKey = UniqueKey();
|
||||||
final GlobalKey childKey = GlobalKey();
|
final GlobalKey childKey = GlobalKey();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user