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 ...@@ -599,6 +599,17 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
bool get selectionEnabled => widget.selectionEnabled; bool get selectionEnabled => widget.selectionEnabled;
// End of API for TextSelectionGestureDetectorBuilderDelegate. // 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 @override
void initState() { void initState() {
super.initState(); super.initState();
...@@ -859,48 +870,51 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -859,48 +870,51 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
final Widget paddedEditable = Padding( final Widget paddedEditable = Padding(
padding: widget.padding, padding: widget.padding,
child: RepaintBoundary( child: RepaintBoundary(
child: EditableText( child: Shortcuts(
key: editableTextKey, shortcuts: _disabledNavigationKeys,
controller: controller, child: EditableText(
readOnly: widget.readOnly, key: editableTextKey,
toolbarOptions: widget.toolbarOptions, controller: controller,
showCursor: widget.showCursor, readOnly: widget.readOnly,
showSelectionHandles: _showSelectionHandles, toolbarOptions: widget.toolbarOptions,
focusNode: _effectiveFocusNode, showCursor: widget.showCursor,
keyboardType: widget.keyboardType, showSelectionHandles: _showSelectionHandles,
textInputAction: widget.textInputAction, focusNode: _effectiveFocusNode,
textCapitalization: widget.textCapitalization, keyboardType: widget.keyboardType,
style: textStyle, textInputAction: widget.textInputAction,
strutStyle: widget.strutStyle, textCapitalization: widget.textCapitalization,
textAlign: widget.textAlign, style: textStyle,
autofocus: widget.autofocus, strutStyle: widget.strutStyle,
obscureText: widget.obscureText, textAlign: widget.textAlign,
autocorrect: widget.autocorrect, autofocus: widget.autofocus,
maxLines: widget.maxLines, obscureText: widget.obscureText,
minLines: widget.minLines, autocorrect: widget.autocorrect,
expands: widget.expands, maxLines: widget.maxLines,
selectionColor: CupertinoTheme.of(context).primaryColor.withOpacity(0.2), minLines: widget.minLines,
selectionControls: widget.selectionEnabled expands: widget.expands,
? cupertinoTextSelectionControls : null, selectionColor: CupertinoTheme.of(context).primaryColor.withOpacity(0.2),
onChanged: widget.onChanged, selectionControls: widget.selectionEnabled
onSelectionChanged: _handleSelectionChanged, ? cupertinoTextSelectionControls : null,
onEditingComplete: widget.onEditingComplete, onChanged: widget.onChanged,
onSubmitted: widget.onSubmitted, onSelectionChanged: _handleSelectionChanged,
inputFormatters: formatters, onEditingComplete: widget.onEditingComplete,
rendererIgnoresPointer: true, onSubmitted: widget.onSubmitted,
cursorWidth: widget.cursorWidth, inputFormatters: formatters,
cursorRadius: widget.cursorRadius, rendererIgnoresPointer: true,
cursorColor: cursorColor, cursorWidth: widget.cursorWidth,
cursorOpacityAnimates: true, cursorRadius: widget.cursorRadius,
cursorOffset: cursorOffset, cursorColor: cursorColor,
paintCursorAboveText: true, cursorOpacityAnimates: true,
backgroundCursorColor: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context), cursorOffset: cursorOffset,
scrollPadding: widget.scrollPadding, paintCursorAboveText: true,
keyboardAppearance: keyboardAppearance, backgroundCursorColor: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context),
dragStartBehavior: widget.dragStartBehavior, scrollPadding: widget.scrollPadding,
scrollController: widget.scrollController, keyboardAppearance: keyboardAppearance,
scrollPhysics: widget.scrollPhysics, dragStartBehavior: widget.dragStartBehavior,
enableInteractiveSelection: widget.enableInteractiveSelection, scrollController: widget.scrollController,
scrollPhysics: widget.scrollPhysics,
enableInteractiveSelection: widget.enableInteractiveSelection,
),
), ),
), ),
); );
......
...@@ -705,6 +705,17 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe ...@@ -705,6 +705,17 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
bool _isHovering = false; 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 bool get needsCounter => widget.maxLength != null
&& widget.decoration != null && widget.decoration != null
&& widget.decoration.counterText == null; && widget.decoration.counterText == null;
...@@ -936,49 +947,52 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe ...@@ -936,49 +947,52 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
} }
Widget child = RepaintBoundary( Widget child = RepaintBoundary(
child: EditableText( child: Shortcuts(
key: editableTextKey, shortcuts: _disabledNavigationKeys,
readOnly: widget.readOnly, child: EditableText(
toolbarOptions: widget.toolbarOptions, key: editableTextKey,
showCursor: widget.showCursor, readOnly: widget.readOnly,
showSelectionHandles: _showSelectionHandles, toolbarOptions: widget.toolbarOptions,
controller: controller, showCursor: widget.showCursor,
focusNode: focusNode, showSelectionHandles: _showSelectionHandles,
keyboardType: widget.keyboardType, controller: controller,
textInputAction: widget.textInputAction, focusNode: focusNode,
textCapitalization: widget.textCapitalization, keyboardType: widget.keyboardType,
style: style, textInputAction: widget.textInputAction,
strutStyle: widget.strutStyle, textCapitalization: widget.textCapitalization,
textAlign: widget.textAlign, style: style,
textDirection: widget.textDirection, strutStyle: widget.strutStyle,
autofocus: widget.autofocus, textAlign: widget.textAlign,
obscureText: widget.obscureText, textDirection: widget.textDirection,
autocorrect: widget.autocorrect, autofocus: widget.autofocus,
maxLines: widget.maxLines, obscureText: widget.obscureText,
minLines: widget.minLines, autocorrect: widget.autocorrect,
expands: widget.expands, maxLines: widget.maxLines,
selectionColor: themeData.textSelectionColor, minLines: widget.minLines,
selectionControls: widget.selectionEnabled ? textSelectionControls : null, expands: widget.expands,
onChanged: widget.onChanged, selectionColor: themeData.textSelectionColor,
onSelectionChanged: _handleSelectionChanged, selectionControls: widget.selectionEnabled ? textSelectionControls : null,
onEditingComplete: widget.onEditingComplete, onChanged: widget.onChanged,
onSubmitted: widget.onSubmitted, onSelectionChanged: _handleSelectionChanged,
onSelectionHandleTapped: _handleSelectionHandleTapped, onEditingComplete: widget.onEditingComplete,
inputFormatters: formatters, onSubmitted: widget.onSubmitted,
rendererIgnoresPointer: true, onSelectionHandleTapped: _handleSelectionHandleTapped,
cursorWidth: widget.cursorWidth, inputFormatters: formatters,
cursorRadius: cursorRadius, rendererIgnoresPointer: true,
cursorColor: cursorColor, cursorWidth: widget.cursorWidth,
cursorOpacityAnimates: cursorOpacityAnimates, cursorRadius: cursorRadius,
cursorOffset: cursorOffset, cursorColor: cursorColor,
paintCursorAboveText: paintCursorAboveText, cursorOpacityAnimates: cursorOpacityAnimates,
backgroundCursorColor: CupertinoColors.inactiveGray, cursorOffset: cursorOffset,
scrollPadding: widget.scrollPadding, paintCursorAboveText: paintCursorAboveText,
keyboardAppearance: keyboardAppearance, backgroundCursorColor: CupertinoColors.inactiveGray,
enableInteractiveSelection: widget.enableInteractiveSelection, scrollPadding: widget.scrollPadding,
dragStartBehavior: widget.dragStartBehavior, keyboardAppearance: keyboardAppearance,
scrollController: widget.scrollController, enableInteractiveSelection: widget.enableInteractiveSelection,
scrollPhysics: widget.scrollPhysics, dragStartBehavior: widget.dragStartBehavior,
scrollController: widget.scrollController,
scrollPhysics: widget.scrollPhysics,
),
), ),
); );
......
...@@ -3870,4 +3870,90 @@ void main() { ...@@ -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() { ...@@ -7256,4 +7256,92 @@ void main() {
// visible. // visible.
expect(scrollController.offset, 44.0); 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