Added 'enabled' property to the PopupMenuButton (#32527)
Added 'enabled' property to the PopupMenuButton to allow the button to be disabled in the case where the menu would be empty.
This commit is contained in:
parent
41b9abdc32
commit
752147e2b1
@ -663,6 +663,8 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
|||||||
|
|
||||||
/// Show a popup menu that contains the `items` at `position`.
|
/// Show a popup menu that contains the `items` at `position`.
|
||||||
///
|
///
|
||||||
|
/// `items` should be non-null and not empty.
|
||||||
|
///
|
||||||
/// If `initialValue` is specified then the first item with a matching value
|
/// If `initialValue` is specified then the first item with a matching value
|
||||||
/// will be highlighted and the value of `position` gives the rectangle whose
|
/// will be highlighted and the value of `position` gives the rectangle whose
|
||||||
/// vertical center will be aligned with the vertical center of the highlighted
|
/// vertical center will be aligned with the vertical center of the highlighted
|
||||||
@ -829,8 +831,10 @@ class PopupMenuButton<T> extends StatefulWidget {
|
|||||||
this.child,
|
this.child,
|
||||||
this.icon,
|
this.icon,
|
||||||
this.offset = Offset.zero,
|
this.offset = Offset.zero,
|
||||||
|
this.enabled = true,
|
||||||
}) : assert(itemBuilder != null),
|
}) : assert(itemBuilder != null),
|
||||||
assert(offset != null),
|
assert(offset != null),
|
||||||
|
assert(enabled != null),
|
||||||
assert(!(child != null && icon != null)), // fails if passed both parameters
|
assert(!(child != null && icon != null)), // fails if passed both parameters
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@ -880,6 +884,20 @@ class PopupMenuButton<T> extends StatefulWidget {
|
|||||||
/// the button that was used to create it.
|
/// the button that was used to create it.
|
||||||
final Offset offset;
|
final Offset offset;
|
||||||
|
|
||||||
|
/// Whether this popup menu button is interactive.
|
||||||
|
///
|
||||||
|
/// Must be non-null, defaults to `true`
|
||||||
|
///
|
||||||
|
/// If `true` the button will respond to presses by displaying the menu.
|
||||||
|
///
|
||||||
|
/// If `false`, the button is styled with the disabled color from the
|
||||||
|
/// current [Theme] and will not respond to presses or show the popup
|
||||||
|
/// menu and [onSelected], [onCanceled] and [itemBuilder] will not be called.
|
||||||
|
///
|
||||||
|
/// This can be useful in situations where the app needs to show the button,
|
||||||
|
/// but doesn't currently have anything to show in the menu.
|
||||||
|
final bool enabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_PopupMenuButtonState<T> createState() => _PopupMenuButtonState<T>();
|
_PopupMenuButtonState<T> createState() => _PopupMenuButtonState<T>();
|
||||||
}
|
}
|
||||||
@ -895,24 +913,28 @@ class _PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
|||||||
),
|
),
|
||||||
Offset.zero & overlay.size,
|
Offset.zero & overlay.size,
|
||||||
);
|
);
|
||||||
showMenu<T>(
|
final List<PopupMenuEntry<T>> items = widget.itemBuilder(context);
|
||||||
context: context,
|
// Only show the menu if there is something to show
|
||||||
elevation: widget.elevation,
|
if (items.isNotEmpty) {
|
||||||
items: widget.itemBuilder(context),
|
showMenu<T>(
|
||||||
initialValue: widget.initialValue,
|
context: context,
|
||||||
position: position,
|
elevation: widget.elevation,
|
||||||
)
|
items: items,
|
||||||
.then<void>((T newValue) {
|
initialValue: widget.initialValue,
|
||||||
if (!mounted)
|
position: position,
|
||||||
return null;
|
)
|
||||||
if (newValue == null) {
|
.then<void>((T newValue) {
|
||||||
if (widget.onCanceled != null)
|
if (!mounted)
|
||||||
widget.onCanceled();
|
return null;
|
||||||
return null;
|
if (newValue == null) {
|
||||||
}
|
if (widget.onCanceled != null)
|
||||||
if (widget.onSelected != null)
|
widget.onCanceled();
|
||||||
widget.onSelected(newValue);
|
return null;
|
||||||
});
|
}
|
||||||
|
if (widget.onSelected != null)
|
||||||
|
widget.onSelected(newValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Icon _getIcon(TargetPlatform platform) {
|
Icon _getIcon(TargetPlatform platform) {
|
||||||
@ -932,14 +954,14 @@ class _PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
|||||||
assert(debugCheckHasMaterialLocalizations(context));
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
return widget.child != null
|
return widget.child != null
|
||||||
? InkWell(
|
? InkWell(
|
||||||
onTap: showButtonMenu,
|
onTap: widget.enabled ? showButtonMenu : null,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
)
|
)
|
||||||
: IconButton(
|
: IconButton(
|
||||||
icon: widget.icon ?? _getIcon(Theme.of(context).platform),
|
icon: widget.icon ?? _getIcon(Theme.of(context).platform),
|
||||||
padding: widget.padding,
|
padding: widget.padding,
|
||||||
tooltip: widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip,
|
tooltip: widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip,
|
||||||
onPressed: showButtonMenu,
|
onPressed: widget.enabled ? showButtonMenu : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,6 +126,55 @@ void main() {
|
|||||||
expect(cancels, equals(2));
|
expect(cancels, equals(2));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('disabled PopupMenuButton will not call itemBuilder, onSelected or onCanceled', (WidgetTester tester) async {
|
||||||
|
final Key popupButtonKey = UniqueKey();
|
||||||
|
bool itemBuilderCalled = false;
|
||||||
|
bool onSelectedCalled = false;
|
||||||
|
bool onCanceledCalled = false;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
PopupMenuButton<int>(
|
||||||
|
key: popupButtonKey,
|
||||||
|
enabled: false,
|
||||||
|
itemBuilder: (BuildContext context) {
|
||||||
|
itemBuilderCalled = true;
|
||||||
|
return <PopupMenuEntry<int>>[
|
||||||
|
const PopupMenuItem<int>(
|
||||||
|
value: 1,
|
||||||
|
child: Text('Tap me please!'),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
onSelected: (int selected) => onSelectedCalled = true,
|
||||||
|
onCanceled: () => onCanceledCalled = true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to bring up the popup menu and select the first item from it
|
||||||
|
await tester.tap(find.byKey(popupButtonKey));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.byKey(popupButtonKey));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(itemBuilderCalled, isFalse);
|
||||||
|
expect(onSelectedCalled, isFalse);
|
||||||
|
|
||||||
|
// Try to bring up the popup menu and tap outside it to cancel the menu
|
||||||
|
await tester.tap(find.byKey(popupButtonKey));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tapAt(const Offset(0.0, 0.0));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(itemBuilderCalled, isFalse);
|
||||||
|
expect(onCanceledCalled, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('PopupMenuButton is horizontal on iOS', (WidgetTester tester) async {
|
testWidgets('PopupMenuButton is horizontal on iOS', (WidgetTester tester) async {
|
||||||
Widget build(TargetPlatform platform) {
|
Widget build(TargetPlatform platform) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user