Add requestFocusOnTap
to DropdownMenu
(#117504)
* Add canRequestFocus to TextField and requestFocusOnTap to DropdownMenu * Address comments * Address comments --------- Co-authored-by: Qun Cheng <quncheng@google.com>
This commit is contained in:
parent
fc3e8243ca
commit
ad1a44d0a7
@ -67,7 +67,9 @@ class _DropdownMenuExampleState extends State<DropdownMenuExample> {
|
||||
leadingIcon: const Icon(Icons.search),
|
||||
label: const Text('Icon'),
|
||||
dropdownMenuEntries: iconEntries,
|
||||
inputDecorationTheme: const InputDecorationTheme(filled: true),
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
filled: true,
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 5.0)),
|
||||
onSelected: (IconLabel? icon) {
|
||||
setState(() {
|
||||
selectedIcon = icon;
|
||||
|
@ -135,6 +135,7 @@ class DropdownMenu<T> extends StatefulWidget {
|
||||
this.controller,
|
||||
this.initialSelection,
|
||||
this.onSelected,
|
||||
this.requestFocusOnTap,
|
||||
required this.dropdownMenuEntries,
|
||||
});
|
||||
|
||||
@ -228,6 +229,19 @@ class DropdownMenu<T> extends StatefulWidget {
|
||||
/// Defaults to null. If null, only the text field is updated.
|
||||
final ValueChanged<T?>? onSelected;
|
||||
|
||||
/// Determine if the dropdown button requests focus and the on-screen virtual
|
||||
/// keyboard is shown in response to a touch event.
|
||||
///
|
||||
/// By default, on mobile platforms, tapping on the text field and opening
|
||||
/// the menu will not cause a focus request and the virtual keyboard will not
|
||||
/// appear. The default behavior for desktop platforms is for the dropdown to
|
||||
/// take the focus.
|
||||
///
|
||||
/// Defaults to null. Setting this field to true or false, rather than allowing
|
||||
/// the implementation to choose based on the platform, can be useful for
|
||||
/// applications that want to override the default behavior.
|
||||
final bool? requestFocusOnTap;
|
||||
|
||||
/// Descriptions of the menu items in the [DropdownMenu].
|
||||
///
|
||||
/// This is a required parameter. It is recommended that at least one [DropdownMenuEntry]
|
||||
@ -242,7 +256,6 @@ class DropdownMenu<T> extends StatefulWidget {
|
||||
class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
final GlobalKey _anchorKey = GlobalKey();
|
||||
final GlobalKey _leadingKey = GlobalKey();
|
||||
final FocusNode _textFocusNode = FocusNode();
|
||||
final MenuController _controller = MenuController();
|
||||
late final TextEditingController _textEditingController;
|
||||
late bool _enableFilter;
|
||||
@ -288,6 +301,23 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
}
|
||||
}
|
||||
|
||||
bool canRequestFocus() {
|
||||
if (widget.requestFocusOnTap != null) {
|
||||
return widget.requestFocusOnTap!;
|
||||
}
|
||||
|
||||
switch (Theme.of(context).platform) {
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
return false;
|
||||
case TargetPlatform.macOS:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void refreshLeadingPadding() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
@ -428,7 +458,6 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_textEditingController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -489,13 +518,12 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
builder: (BuildContext context, MenuController controller, Widget? child) {
|
||||
assert(_initialMenu != null);
|
||||
final Widget trailingButton = Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: IconButton(
|
||||
isSelected: controller.isOpen,
|
||||
icon: widget.trailingIcon ?? const Icon(Icons.arrow_drop_down),
|
||||
selectedIcon: widget.selectedTrailingIcon ?? const Icon(Icons.arrow_drop_up),
|
||||
onPressed: () {
|
||||
_textFocusNode.requestFocus();
|
||||
handlePressed(controller);
|
||||
},
|
||||
),
|
||||
@ -511,7 +539,9 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
width: widget.width,
|
||||
children: <Widget>[
|
||||
TextField(
|
||||
focusNode: _textFocusNode,
|
||||
canRequestFocus: canRequestFocus(),
|
||||
enableInteractiveSelection: canRequestFocus(),
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
style: effectiveTextStyle,
|
||||
controller: _textEditingController,
|
||||
onEditingComplete: () {
|
||||
|
@ -312,6 +312,7 @@ class TextField extends StatefulWidget {
|
||||
this.scribbleEnabled = true,
|
||||
this.enableIMEPersonalizedLearning = true,
|
||||
this.contextMenuBuilder = _defaultContextMenuBuilder,
|
||||
this.canRequestFocus = true,
|
||||
this.spellCheckConfiguration,
|
||||
this.magnifierConfiguration,
|
||||
}) : assert(obscuringCharacter.length == 1),
|
||||
@ -762,6 +763,13 @@ class TextField extends StatefulWidget {
|
||||
/// * [AdaptiveTextSelectionToolbar], which is built by default.
|
||||
final EditableTextContextMenuBuilder? contextMenuBuilder;
|
||||
|
||||
/// Determine whether this text field can request the primary focus.
|
||||
///
|
||||
/// Defaults to true. If false, the text field will not request focus
|
||||
/// when tapped, or when its context menu is displayed. If false it will not
|
||||
/// be possible to move the focus to the text field with tab key.
|
||||
final bool canRequestFocus;
|
||||
|
||||
static Widget _defaultContextMenuBuilder(BuildContext context, EditableTextState editableTextState) {
|
||||
return AdaptiveTextSelectionToolbar.editableText(
|
||||
editableTextState: editableTextState,
|
||||
@ -976,7 +984,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
|
||||
if (widget.controller == null) {
|
||||
_createLocalController();
|
||||
}
|
||||
_effectiveFocusNode.canRequestFocus = _isEnabled;
|
||||
_effectiveFocusNode.canRequestFocus = widget.canRequestFocus && _isEnabled;
|
||||
_effectiveFocusNode.addListener(_handleFocusChanged);
|
||||
}
|
||||
|
||||
@ -984,7 +992,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
|
||||
final NavigationMode mode = MediaQuery.maybeNavigationModeOf(context) ?? NavigationMode.traditional;
|
||||
switch (mode) {
|
||||
case NavigationMode.traditional:
|
||||
return _isEnabled;
|
||||
return widget.canRequestFocus && _isEnabled;
|
||||
case NavigationMode.directional:
|
||||
return true;
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ void main() {
|
||||
|
||||
final Finder textField = find.byType(TextField);
|
||||
final Size anchorSize = tester.getSize(textField);
|
||||
expect(anchorSize, const Size(180.0, 54.0));
|
||||
expect(anchorSize, const Size(180.0, 56.0));
|
||||
|
||||
await tester.tap(find.byType(DropdownMenu<TestMenu>));
|
||||
await tester.pumpAndSettle();
|
||||
@ -143,7 +143,7 @@ void main() {
|
||||
|
||||
final Finder anchor = find.byType(TextField);
|
||||
final Size size = tester.getSize(anchor);
|
||||
expect(size, const Size(200.0, 54.0));
|
||||
expect(size, const Size(200.0, 56.0));
|
||||
|
||||
await tester.tap(anchor);
|
||||
await tester.pumpAndSettle();
|
||||
@ -428,7 +428,7 @@ void main() {
|
||||
expect(menuMaterial, findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Down key can highlight the menu item', (WidgetTester tester) async {
|
||||
testWidgets('Down key can highlight the menu item on desktop platforms', (WidgetTester tester) async {
|
||||
final ThemeData themeData = ThemeData();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: themeData,
|
||||
@ -468,9 +468,9 @@ void main() {
|
||||
);
|
||||
item0material = tester.widget<Material>(button0Material);
|
||||
expect(item0material.color, Colors.transparent); // the previous item should not be highlighted.
|
||||
});
|
||||
}, variant: TargetPlatformVariant.desktop());
|
||||
|
||||
testWidgets('Up key can highlight the menu item', (WidgetTester tester) async {
|
||||
testWidgets('Up key can highlight the menu item on desktop platforms', (WidgetTester tester) async {
|
||||
final ThemeData themeData = ThemeData();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: themeData,
|
||||
@ -510,9 +510,10 @@ void main() {
|
||||
|
||||
item5material = tester.widget<Material>(button5Material);
|
||||
expect(item5material.color, Colors.transparent); // the previous item should not be highlighted.
|
||||
});
|
||||
}, variant: TargetPlatformVariant.desktop());
|
||||
|
||||
testWidgets('The text input should match the label of the menu item while pressing down key', (WidgetTester tester) async {
|
||||
testWidgets('The text input should match the label of the menu item '
|
||||
'while pressing down key on desktop platforms', (WidgetTester tester) async {
|
||||
final ThemeData themeData = ThemeData();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: themeData,
|
||||
@ -540,9 +541,10 @@ void main() {
|
||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowDown);
|
||||
await tester.pump();
|
||||
expect(find.widgetWithText(TextField, 'Item 2'), findsOneWidget);
|
||||
});
|
||||
}, variant: TargetPlatformVariant.desktop());
|
||||
|
||||
testWidgets('The text input should match the label of the menu item while pressing up key', (WidgetTester tester) async {
|
||||
testWidgets('The text input should match the label of the menu item '
|
||||
'while pressing up key on desktop platforms', (WidgetTester tester) async {
|
||||
final ThemeData themeData = ThemeData();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: themeData,
|
||||
@ -570,9 +572,9 @@ void main() {
|
||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowUp);
|
||||
await tester.pump();
|
||||
expect(find.widgetWithText(TextField, 'Item 3'), findsOneWidget);
|
||||
});
|
||||
}, variant: TargetPlatformVariant.desktop());
|
||||
|
||||
testWidgets('Disabled button will be skipped while pressing up/down key', (WidgetTester tester) async {
|
||||
testWidgets('Disabled button will be skipped while pressing up/down key on desktop platforms', (WidgetTester tester) async {
|
||||
final ThemeData themeData = ThemeData();
|
||||
final List<DropdownMenuEntry<TestMenu>> menuWithDisabledItems = <DropdownMenuEntry<TestMenu>>[
|
||||
const DropdownMenuEntry<TestMenu>(value: TestMenu.mainMenu0, label: 'Item 0'),
|
||||
@ -614,9 +616,32 @@ void main() {
|
||||
);
|
||||
final Material item3Material = tester.widget<Material>(button3Material);
|
||||
expect(item3Material.color, themeData.colorScheme.onSurface.withOpacity(0.12));
|
||||
});
|
||||
}, variant: TargetPlatformVariant.desktop());
|
||||
|
||||
testWidgets('Searching is enabled by default', (WidgetTester tester) async {
|
||||
testWidgets('Searching is enabled by default on mobile platforms if initialSelection is non null', (WidgetTester tester) async {
|
||||
final ThemeData themeData = ThemeData();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: themeData,
|
||||
home: Scaffold(
|
||||
body: DropdownMenu<TestMenu>(
|
||||
initialSelection: TestMenu.mainMenu1,
|
||||
dropdownMenuEntries: menuChildren,
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
// Open the menu
|
||||
await tester.tap(find.byType(DropdownMenu<TestMenu>));
|
||||
await tester.pump();
|
||||
final Finder buttonMaterial = find.descendant(
|
||||
of: find.widgetWithText(MenuItemButton, 'Menu 1').last,
|
||||
matching: find.byType(Material),
|
||||
);
|
||||
final Material itemMaterial = tester.widget<Material>(buttonMaterial);
|
||||
expect(itemMaterial.color, themeData.colorScheme.onSurface.withOpacity(0.12)); // Menu 1 button is highlighted.
|
||||
}, variant: TargetPlatformVariant.mobile());
|
||||
|
||||
testWidgets('Searching is enabled by default on desktop platform', (WidgetTester tester) async {
|
||||
final ThemeData themeData = ThemeData();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: themeData,
|
||||
@ -638,9 +663,9 @@ void main() {
|
||||
);
|
||||
final Material itemMaterial = tester.widget<Material>(buttonMaterial);
|
||||
expect(itemMaterial.color, themeData.colorScheme.onSurface.withOpacity(0.12)); // Menu 1 button is highlighted.
|
||||
});
|
||||
}, variant: TargetPlatformVariant.desktop());
|
||||
|
||||
testWidgets('Highlight can move up/down from the searching result', (WidgetTester tester) async {
|
||||
testWidgets('Highlight can move up/down starting from the searching result on desktop platforms', (WidgetTester tester) async {
|
||||
final ThemeData themeData = ThemeData();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: themeData,
|
||||
@ -684,7 +709,7 @@ void main() {
|
||||
);
|
||||
final Material item5Material = tester.widget<Material>(button5Material);
|
||||
expect(item5Material.color, themeData.colorScheme.onSurface.withOpacity(0.12));
|
||||
});
|
||||
}, variant: TargetPlatformVariant.desktop());
|
||||
|
||||
testWidgets('Filtering is disabled by default', (WidgetTester tester) async {
|
||||
final ThemeData themeData = ThemeData();
|
||||
@ -692,6 +717,7 @@ void main() {
|
||||
theme: themeData,
|
||||
home: Scaffold(
|
||||
body: DropdownMenu<TestMenu>(
|
||||
requestFocusOnTap: true,
|
||||
dropdownMenuEntries: menuChildren,
|
||||
),
|
||||
),
|
||||
@ -715,6 +741,7 @@ void main() {
|
||||
theme: themeData,
|
||||
home: Scaffold(
|
||||
body: DropdownMenu<TestMenu>(
|
||||
requestFocusOnTap: true,
|
||||
enableFilter: true,
|
||||
dropdownMenuEntries: menuChildren,
|
||||
),
|
||||
@ -748,6 +775,7 @@ void main() {
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return Scaffold(
|
||||
body: DropdownMenu<TestMenu>(
|
||||
requestFocusOnTap: true,
|
||||
enableFilter: true,
|
||||
dropdownMenuEntries: menuChildren,
|
||||
controller: controller,
|
||||
@ -804,29 +832,47 @@ void main() {
|
||||
await tester.tap(find.byType(DropdownMenu<TestMenu>));
|
||||
await tester.pump();
|
||||
|
||||
late final bool isMobile;
|
||||
switch (themeData.platform) {
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.fuchsia:
|
||||
isMobile = true;
|
||||
break;
|
||||
case TargetPlatform.macOS:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
isMobile = false;
|
||||
break;
|
||||
}
|
||||
int expectedCount = isMobile ? 0 : 1;
|
||||
|
||||
// Test onSelected on key press
|
||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowDown);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pumpAndSettle();
|
||||
expect(selectionCount, 1);
|
||||
expect(selectionCount, expectedCount);
|
||||
// The desktop platform closed the menu when a completion action is pressed. So we need to reopen it.
|
||||
if (!isMobile) {
|
||||
await tester.tap(find.byType(DropdownMenu<TestMenu>));
|
||||
await tester.pump();
|
||||
}
|
||||
|
||||
// Disabled item doesn't trigger onSelected callback.
|
||||
await tester.tap(find.byType(DropdownMenu<TestMenu>));
|
||||
await tester.pump();
|
||||
final Finder item1 = find.widgetWithText(MenuItemButton, 'Item 1').last;
|
||||
await tester.tap(item1);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(controller.text, 'Item 0');
|
||||
expect(selectionCount, 1);
|
||||
expect(controller.text, isMobile ? '' : 'Item 0');
|
||||
expect(selectionCount, expectedCount);
|
||||
|
||||
final Finder item2 = find.widgetWithText(MenuItemButton, 'Item 2').last;
|
||||
await tester.tap(item2);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(controller.text, 'Item 2');
|
||||
expect(selectionCount, 2);
|
||||
expect(selectionCount, ++expectedCount);
|
||||
|
||||
await tester.tap(find.byType(DropdownMenu<TestMenu>));
|
||||
await tester.pump();
|
||||
@ -835,18 +881,20 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(controller.text, 'Item 3');
|
||||
expect(selectionCount, 3);
|
||||
expect(selectionCount, ++expectedCount);
|
||||
|
||||
// When typing something in the text field without selecting any of the options,
|
||||
// On desktop platforms, when typing something in the text field without selecting any of the options,
|
||||
// the onSelected should not be called.
|
||||
await tester.enterText(find.byType(TextField).first, 'New Item');
|
||||
expect(controller.text, 'New Item');
|
||||
expect(selectionCount, 3);
|
||||
expect(find.widgetWithText(TextField, 'New Item'), findsOneWidget);
|
||||
await tester.enterText(find.byType(TextField).first, '');
|
||||
expect(selectionCount, 3);
|
||||
expect(controller.text.isEmpty, true);
|
||||
});
|
||||
if (!isMobile) {
|
||||
await tester.enterText(find.byType(TextField).first, 'New Item');
|
||||
expect(controller.text, 'New Item');
|
||||
expect(selectionCount, expectedCount);
|
||||
expect(find.widgetWithText(TextField, 'New Item'), findsOneWidget);
|
||||
await tester.enterText(find.byType(TextField).first, '');
|
||||
expect(selectionCount, expectedCount);
|
||||
expect(controller.text.isEmpty, true);
|
||||
}
|
||||
}, variant: TargetPlatformVariant.all());
|
||||
|
||||
|
||||
testWidgets('The selectedValue gives an initial text and highlights the according item', (WidgetTester tester) async {
|
||||
@ -882,6 +930,107 @@ void main() {
|
||||
final Material itemMaterial = tester.widget<Material>(buttonMaterial);
|
||||
expect(itemMaterial.color, themeData.colorScheme.onSurface.withOpacity(0.12));
|
||||
});
|
||||
|
||||
testWidgets('The default text input field should not be focused on mobile platforms '
|
||||
'when it is tapped', (WidgetTester tester) async {
|
||||
final ThemeData themeData = ThemeData();
|
||||
|
||||
Widget buildDropdownMenu() => MaterialApp(
|
||||
theme: themeData,
|
||||
home: Scaffold(
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
DropdownMenu<TestMenu>(
|
||||
dropdownMenuEntries: menuChildren,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Test default condition.
|
||||
await tester.pumpWidget(buildDropdownMenu());
|
||||
await tester.pump();
|
||||
|
||||
final Finder textFieldFinder = find.byType(TextField);
|
||||
final TextField result = tester.widget<TextField>(textFieldFinder);
|
||||
expect(result.canRequestFocus, false);
|
||||
}, variant: TargetPlatformVariant.mobile());
|
||||
|
||||
testWidgets('The text input field should be focused on desktop platforms '
|
||||
'when it is tapped', (WidgetTester tester) async {
|
||||
final ThemeData themeData = ThemeData();
|
||||
|
||||
Widget buildDropdownMenu() => MaterialApp(
|
||||
theme: themeData,
|
||||
home: Scaffold(
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
DropdownMenu<TestMenu>(
|
||||
dropdownMenuEntries: menuChildren,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(buildDropdownMenu());
|
||||
await tester.pump();
|
||||
|
||||
final Finder textFieldFinder = find.byType(TextField);
|
||||
final TextField result = tester.widget<TextField>(textFieldFinder);
|
||||
expect(result.canRequestFocus, true);
|
||||
}, variant: TargetPlatformVariant.desktop());
|
||||
|
||||
testWidgets('If requestFocusOnTap is true, the text input field can request focus, '
|
||||
'otherwise it cannot request focus', (WidgetTester tester) async {
|
||||
final ThemeData themeData = ThemeData();
|
||||
|
||||
Widget buildDropdownMenu({required bool requestFocusOnTap}) => MaterialApp(
|
||||
theme: themeData,
|
||||
home: Scaffold(
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
DropdownMenu<TestMenu>(
|
||||
requestFocusOnTap: requestFocusOnTap,
|
||||
dropdownMenuEntries: menuChildren,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Set requestFocusOnTap to true.
|
||||
await tester.pumpWidget(buildDropdownMenu(requestFocusOnTap: true));
|
||||
await tester.pump();
|
||||
|
||||
final Finder textFieldFinder = find.byType(TextField);
|
||||
final TextField textField = tester.widget<TextField>(textFieldFinder);
|
||||
expect(textField.canRequestFocus, true);
|
||||
// Open the dropdown menu.
|
||||
await tester.tap(textFieldFinder);
|
||||
await tester.pump();
|
||||
// Make a selection.
|
||||
await tester.tap(find.widgetWithText(MenuItemButton, 'Item 0').last);
|
||||
await tester.pump();
|
||||
expect(find.widgetWithText(TextField, 'Item 0'), findsOneWidget);
|
||||
|
||||
// Set requestFocusOnTap to false.
|
||||
await tester.pumpWidget(Container());
|
||||
await tester.pumpWidget(buildDropdownMenu(requestFocusOnTap: false));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Finder textFieldFinder1 = find.byType(TextField);
|
||||
final TextField textField1 = tester.widget<TextField>(textFieldFinder1);
|
||||
expect(textField1.canRequestFocus, false);
|
||||
// Open the dropdown menu.
|
||||
await tester.tap(textFieldFinder1);
|
||||
await tester.pump();
|
||||
// Make a selection.
|
||||
await tester.tap(find.widgetWithText(MenuItemButton, 'Item 0').last);
|
||||
await tester.pump();
|
||||
expect(find.widgetWithText(TextField, 'Item 0'), findsOneWidget);
|
||||
}, variant: TargetPlatformVariant.all());
|
||||
}
|
||||
|
||||
enum TestMenu {
|
||||
|
@ -13364,6 +13364,48 @@ void main() {
|
||||
skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu.
|
||||
);
|
||||
|
||||
testWidgets('Cannot request focus when canRequestFocus is false', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
// Default test. The canRequestFocus is true by default and the text field can be focused
|
||||
await tester.pumpWidget(
|
||||
boilerplate(
|
||||
child: TextField(
|
||||
focusNode: focusNode,
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(focusNode.hasFocus, isFalse);
|
||||
focusNode.requestFocus();
|
||||
await tester.pump();
|
||||
expect(focusNode.hasFocus, isTrue);
|
||||
|
||||
// Set canRequestFocus to false: the text field cannot be focused when it is tapped/long pressed.
|
||||
await tester.pumpWidget(
|
||||
boilerplate(
|
||||
child: TextField(
|
||||
focusNode: focusNode,
|
||||
canRequestFocus: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(focusNode.hasFocus, isFalse);
|
||||
focusNode.requestFocus();
|
||||
await tester.pump();
|
||||
expect(focusNode.hasFocus, isFalse);
|
||||
|
||||
// The text field cannot be focused if it is tapped.
|
||||
await tester.tap(find.byType(TextField));
|
||||
await tester.pump();
|
||||
expect(focusNode.hasFocus, isFalse);
|
||||
|
||||
// The text field cannot be focused if it is long pressed.
|
||||
await tester.longPress(find.byType(TextField));
|
||||
await tester.pump();
|
||||
expect(focusNode.hasFocus, isFalse);
|
||||
});
|
||||
|
||||
group('Right click focus', () {
|
||||
testWidgets('Can right click to focus multiple times', (WidgetTester tester) async {
|
||||
// Regression test for https://github.com/flutter/flutter/pull/103228
|
||||
@ -13518,6 +13560,34 @@ void main() {
|
||||
expect(controller.selection.baseOffset, 0);
|
||||
expect(controller.selection.extentOffset, 5);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||
|
||||
testWidgets('Right clicking cannot request focus if canRequestFocus is false', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
final UniqueKey key = UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Material(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
TextField(
|
||||
key: key,
|
||||
focusNode: focusNode,
|
||||
canRequestFocus: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tapAt(
|
||||
tester.getCenter(find.byKey(key)),
|
||||
buttons: kSecondaryButton,
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(focusNode.hasFocus, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('context menu', () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user