Unverified Commit 6b2cc855 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Disable arrow key focus navigation for text fields (#42533)

This disables the arrow key focus navigation for text fields. This was non-standard behavior for text fields, although it remains useful for other kinds of controls.

Fixes #42259
parent 1d4d63ac
......@@ -599,6 +599,17 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
bool get selectionEnabled => widget.selectionEnabled;
// End of API for TextSelectionGestureDetectorBuilderDelegate.
// Disables all directional focus actions inside of a text field, since up and
// down shouldn't go to another field, even in a single line text field. We
// remap the keys rather than the actions, since someone might want to invoke
// a directional navigation action from another key binding.
final Map<LogicalKeySet, Intent> _disabledNavigationKeys = <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.arrowUp): const Intent(DoNothingAction.key),
LogicalKeySet(LogicalKeyboardKey.arrowDown): const Intent(DoNothingAction.key),
LogicalKeySet(LogicalKeyboardKey.arrowLeft): const Intent(DoNothingAction.key),
LogicalKeySet(LogicalKeyboardKey.arrowRight): const Intent(DoNothingAction.key),
};
@override
void initState() {
super.initState();
......@@ -859,6 +870,8 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
final Widget paddedEditable = Padding(
padding: widget.padding,
child: RepaintBoundary(
child: Shortcuts(
shortcuts: _disabledNavigationKeys,
child: EditableText(
key: editableTextKey,
controller: controller,
......@@ -903,6 +916,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
enableInteractiveSelection: widget.enableInteractiveSelection,
),
),
),
);
return Semantics(
......
......@@ -705,6 +705,17 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
bool _isHovering = false;
// Disables all directional focus actions inside of a text field, since up and
// down shouldn't go to another field, even in a single line text field. We
// remap the keys rather than the actions, since someone might want to invoke
// a directional navigation action from another key binding.
final Map<LogicalKeySet, Intent> _disabledNavigationKeys = <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.arrowUp): const Intent(DoNothingAction.key),
LogicalKeySet(LogicalKeyboardKey.arrowDown): const Intent(DoNothingAction.key),
LogicalKeySet(LogicalKeyboardKey.arrowLeft): const Intent(DoNothingAction.key),
LogicalKeySet(LogicalKeyboardKey.arrowRight): const Intent(DoNothingAction.key),
};
bool get needsCounter => widget.maxLength != null
&& widget.decoration != null
&& widget.decoration.counterText == null;
......@@ -936,6 +947,8 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
}
Widget child = RepaintBoundary(
child: Shortcuts(
shortcuts: _disabledNavigationKeys,
child: EditableText(
key: editableTextKey,
readOnly: widget.readOnly,
......@@ -980,6 +993,7 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
scrollController: widget.scrollController,
scrollPhysics: widget.scrollPhysics,
),
),
);
if (widget.decoration != null) {
......
......@@ -3870,4 +3870,90 @@ void main() {
},
);
});
testWidgets("Arrow keys don't move input focus", (WidgetTester tester) async {
final TextEditingController controller1 = TextEditingController();
final TextEditingController controller2 = TextEditingController();
final TextEditingController controller3 = TextEditingController();
final TextEditingController controller4 = TextEditingController();
final TextEditingController controller5 = TextEditingController();
final FocusNode focusNode1 = FocusNode(debugLabel: 'Field 1');
final FocusNode focusNode2 = FocusNode(debugLabel: 'Field 2');
final FocusNode focusNode3 = FocusNode(debugLabel: 'Field 3');
final FocusNode focusNode4 = FocusNode(debugLabel: 'Field 4');
final FocusNode focusNode5 = FocusNode(debugLabel: 'Field 5');
// Lay out text fields in a "+" formation, and focus the center one.
await tester.pumpWidget(CupertinoApp(
home: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
width: 100.0,
child: CupertinoTextField(
controller: controller1,
focusNode: focusNode1,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
width: 100.0,
child: CupertinoTextField(
controller: controller2,
focusNode: focusNode2,
),
),
Container(
width: 100.0,
child: CupertinoTextField(
controller: controller3,
focusNode: focusNode3,
),
),
Container(
width: 100.0,
child: CupertinoTextField(
controller: controller4,
focusNode: focusNode4,
),
),
],
),
Container(
width: 100.0,
child: CupertinoTextField(
controller: controller5,
focusNode: focusNode5,
),
),
],
),
),
),
);
focusNode3.requestFocus();
await tester.pump();
expect(focusNode3.hasPrimaryFocus, isTrue);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pump();
expect(focusNode3.hasPrimaryFocus, isTrue);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pump();
expect(focusNode3.hasPrimaryFocus, isTrue);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pump();
expect(focusNode3.hasPrimaryFocus, isTrue);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pump();
expect(focusNode3.hasPrimaryFocus, isTrue);
});
}
......@@ -7256,4 +7256,92 @@ void main() {
// visible.
expect(scrollController.offset, 44.0);
});
testWidgets("Arrow keys don't move input focus", (WidgetTester tester) async {
final TextEditingController controller1 = TextEditingController();
final TextEditingController controller2 = TextEditingController();
final TextEditingController controller3 = TextEditingController();
final TextEditingController controller4 = TextEditingController();
final TextEditingController controller5 = TextEditingController();
final FocusNode focusNode1 = FocusNode(debugLabel: 'Field 1');
final FocusNode focusNode2 = FocusNode(debugLabel: 'Field 2');
final FocusNode focusNode3 = FocusNode(debugLabel: 'Field 3');
final FocusNode focusNode4 = FocusNode(debugLabel: 'Field 4');
final FocusNode focusNode5 = FocusNode(debugLabel: 'Field 5');
// Lay out text fields in a "+" formation, and focus the center one.
await tester.pumpWidget(MaterialApp(
theme: ThemeData(),
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
width: 100.0,
child: TextField(
controller: controller1,
focusNode: focusNode1,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
width: 100.0,
child: TextField(
controller: controller2,
focusNode: focusNode2,
),
),
Container(
width: 100.0,
child: TextField(
controller: controller3,
focusNode: focusNode3,
),
),
Container(
width: 100.0,
child: TextField(
controller: controller4,
focusNode: focusNode4,
),
),
],
),
Container(
width: 100.0,
child: TextField(
controller: controller5,
focusNode: focusNode5,
),
),
],
),
),
),
),);
focusNode3.requestFocus();
await tester.pump();
expect(focusNode3.hasPrimaryFocus, isTrue);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pump();
expect(focusNode3.hasPrimaryFocus, isTrue);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pump();
expect(focusNode3.hasPrimaryFocus, isTrue);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pump();
expect(focusNode3.hasPrimaryFocus, isTrue);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pump();
expect(focusNode3.hasPrimaryFocus, isTrue);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment