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
This commit is contained in:
Greg Spencer 2019-10-14 18:50:05 -07:00 committed by GitHub
parent 1d4d63ace1
commit 6b2cc85546
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 287 additions and 85 deletions

View file

@ -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,48 +870,51 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
final Widget paddedEditable = Padding(
padding: widget.padding,
child: RepaintBoundary(
child: EditableText(
key: editableTextKey,
controller: controller,
readOnly: widget.readOnly,
toolbarOptions: widget.toolbarOptions,
showCursor: widget.showCursor,
showSelectionHandles: _showSelectionHandles,
focusNode: _effectiveFocusNode,
keyboardType: widget.keyboardType,
textInputAction: widget.textInputAction,
textCapitalization: widget.textCapitalization,
style: textStyle,
strutStyle: widget.strutStyle,
textAlign: widget.textAlign,
autofocus: widget.autofocus,
obscureText: widget.obscureText,
autocorrect: widget.autocorrect,
maxLines: widget.maxLines,
minLines: widget.minLines,
expands: widget.expands,
selectionColor: CupertinoTheme.of(context).primaryColor.withOpacity(0.2),
selectionControls: widget.selectionEnabled
? cupertinoTextSelectionControls : null,
onChanged: widget.onChanged,
onSelectionChanged: _handleSelectionChanged,
onEditingComplete: widget.onEditingComplete,
onSubmitted: widget.onSubmitted,
inputFormatters: formatters,
rendererIgnoresPointer: true,
cursorWidth: widget.cursorWidth,
cursorRadius: widget.cursorRadius,
cursorColor: cursorColor,
cursorOpacityAnimates: true,
cursorOffset: cursorOffset,
paintCursorAboveText: true,
backgroundCursorColor: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context),
scrollPadding: widget.scrollPadding,
keyboardAppearance: keyboardAppearance,
dragStartBehavior: widget.dragStartBehavior,
scrollController: widget.scrollController,
scrollPhysics: widget.scrollPhysics,
enableInteractiveSelection: widget.enableInteractiveSelection,
child: Shortcuts(
shortcuts: _disabledNavigationKeys,
child: EditableText(
key: editableTextKey,
controller: controller,
readOnly: widget.readOnly,
toolbarOptions: widget.toolbarOptions,
showCursor: widget.showCursor,
showSelectionHandles: _showSelectionHandles,
focusNode: _effectiveFocusNode,
keyboardType: widget.keyboardType,
textInputAction: widget.textInputAction,
textCapitalization: widget.textCapitalization,
style: textStyle,
strutStyle: widget.strutStyle,
textAlign: widget.textAlign,
autofocus: widget.autofocus,
obscureText: widget.obscureText,
autocorrect: widget.autocorrect,
maxLines: widget.maxLines,
minLines: widget.minLines,
expands: widget.expands,
selectionColor: CupertinoTheme.of(context).primaryColor.withOpacity(0.2),
selectionControls: widget.selectionEnabled
? cupertinoTextSelectionControls : null,
onChanged: widget.onChanged,
onSelectionChanged: _handleSelectionChanged,
onEditingComplete: widget.onEditingComplete,
onSubmitted: widget.onSubmitted,
inputFormatters: formatters,
rendererIgnoresPointer: true,
cursorWidth: widget.cursorWidth,
cursorRadius: widget.cursorRadius,
cursorColor: cursorColor,
cursorOpacityAnimates: true,
cursorOffset: cursorOffset,
paintCursorAboveText: true,
backgroundCursorColor: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context),
scrollPadding: widget.scrollPadding,
keyboardAppearance: keyboardAppearance,
dragStartBehavior: widget.dragStartBehavior,
scrollController: widget.scrollController,
scrollPhysics: widget.scrollPhysics,
enableInteractiveSelection: widget.enableInteractiveSelection,
),
),
),
);

View file

@ -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,49 +947,52 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
}
Widget child = RepaintBoundary(
child: EditableText(
key: editableTextKey,
readOnly: widget.readOnly,
toolbarOptions: widget.toolbarOptions,
showCursor: widget.showCursor,
showSelectionHandles: _showSelectionHandles,
controller: controller,
focusNode: focusNode,
keyboardType: widget.keyboardType,
textInputAction: widget.textInputAction,
textCapitalization: widget.textCapitalization,
style: style,
strutStyle: widget.strutStyle,
textAlign: widget.textAlign,
textDirection: widget.textDirection,
autofocus: widget.autofocus,
obscureText: widget.obscureText,
autocorrect: widget.autocorrect,
maxLines: widget.maxLines,
minLines: widget.minLines,
expands: widget.expands,
selectionColor: themeData.textSelectionColor,
selectionControls: widget.selectionEnabled ? textSelectionControls : null,
onChanged: widget.onChanged,
onSelectionChanged: _handleSelectionChanged,
onEditingComplete: widget.onEditingComplete,
onSubmitted: widget.onSubmitted,
onSelectionHandleTapped: _handleSelectionHandleTapped,
inputFormatters: formatters,
rendererIgnoresPointer: true,
cursorWidth: widget.cursorWidth,
cursorRadius: cursorRadius,
cursorColor: cursorColor,
cursorOpacityAnimates: cursorOpacityAnimates,
cursorOffset: cursorOffset,
paintCursorAboveText: paintCursorAboveText,
backgroundCursorColor: CupertinoColors.inactiveGray,
scrollPadding: widget.scrollPadding,
keyboardAppearance: keyboardAppearance,
enableInteractiveSelection: widget.enableInteractiveSelection,
dragStartBehavior: widget.dragStartBehavior,
scrollController: widget.scrollController,
scrollPhysics: widget.scrollPhysics,
child: Shortcuts(
shortcuts: _disabledNavigationKeys,
child: EditableText(
key: editableTextKey,
readOnly: widget.readOnly,
toolbarOptions: widget.toolbarOptions,
showCursor: widget.showCursor,
showSelectionHandles: _showSelectionHandles,
controller: controller,
focusNode: focusNode,
keyboardType: widget.keyboardType,
textInputAction: widget.textInputAction,
textCapitalization: widget.textCapitalization,
style: style,
strutStyle: widget.strutStyle,
textAlign: widget.textAlign,
textDirection: widget.textDirection,
autofocus: widget.autofocus,
obscureText: widget.obscureText,
autocorrect: widget.autocorrect,
maxLines: widget.maxLines,
minLines: widget.minLines,
expands: widget.expands,
selectionColor: themeData.textSelectionColor,
selectionControls: widget.selectionEnabled ? textSelectionControls : null,
onChanged: widget.onChanged,
onSelectionChanged: _handleSelectionChanged,
onEditingComplete: widget.onEditingComplete,
onSubmitted: widget.onSubmitted,
onSelectionHandleTapped: _handleSelectionHandleTapped,
inputFormatters: formatters,
rendererIgnoresPointer: true,
cursorWidth: widget.cursorWidth,
cursorRadius: cursorRadius,
cursorColor: cursorColor,
cursorOpacityAnimates: cursorOpacityAnimates,
cursorOffset: cursorOffset,
paintCursorAboveText: paintCursorAboveText,
backgroundCursorColor: CupertinoColors.inactiveGray,
scrollPadding: widget.scrollPadding,
keyboardAppearance: keyboardAppearance,
enableInteractiveSelection: widget.enableInteractiveSelection,
dragStartBehavior: widget.dragStartBehavior,
scrollController: widget.scrollController,
scrollPhysics: widget.scrollPhysics,
),
),
);

View file

@ -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);
});
}

View file

@ -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);
});
}