Skip to content

Commit dcfb96b

Browse files
gspencergoogInconnu08
authored andcommitted
Disable arrow key focus navigation for text fields (flutter#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 flutter#42259
1 parent f7381cc commit dcfb96b

File tree

4 files changed

+287
-85
lines changed

4 files changed

+287
-85
lines changed

packages/flutter/lib/src/cupertino/text_field.dart

Lines changed: 56 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,17 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
599599
bool get selectionEnabled => widget.selectionEnabled;
600600
// End of API for TextSelectionGestureDetectorBuilderDelegate.
601601

602+
// Disables all directional focus actions inside of a text field, since up and
603+
// down shouldn't go to another field, even in a single line text field. We
604+
// remap the keys rather than the actions, since someone might want to invoke
605+
// a directional navigation action from another key binding.
606+
final Map<LogicalKeySet, Intent> _disabledNavigationKeys = <LogicalKeySet, Intent>{
607+
LogicalKeySet(LogicalKeyboardKey.arrowUp): const Intent(DoNothingAction.key),
608+
LogicalKeySet(LogicalKeyboardKey.arrowDown): const Intent(DoNothingAction.key),
609+
LogicalKeySet(LogicalKeyboardKey.arrowLeft): const Intent(DoNothingAction.key),
610+
LogicalKeySet(LogicalKeyboardKey.arrowRight): const Intent(DoNothingAction.key),
611+
};
612+
602613
@override
603614
void initState() {
604615
super.initState();
@@ -859,48 +870,51 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
859870
final Widget paddedEditable = Padding(
860871
padding: widget.padding,
861872
child: RepaintBoundary(
862-
child: EditableText(
863-
key: editableTextKey,
864-
controller: controller,
865-
readOnly: widget.readOnly,
866-
toolbarOptions: widget.toolbarOptions,
867-
showCursor: widget.showCursor,
868-
showSelectionHandles: _showSelectionHandles,
869-
focusNode: _effectiveFocusNode,
870-
keyboardType: widget.keyboardType,
871-
textInputAction: widget.textInputAction,
872-
textCapitalization: widget.textCapitalization,
873-
style: textStyle,
874-
strutStyle: widget.strutStyle,
875-
textAlign: widget.textAlign,
876-
autofocus: widget.autofocus,
877-
obscureText: widget.obscureText,
878-
autocorrect: widget.autocorrect,
879-
maxLines: widget.maxLines,
880-
minLines: widget.minLines,
881-
expands: widget.expands,
882-
selectionColor: CupertinoTheme.of(context).primaryColor.withOpacity(0.2),
883-
selectionControls: widget.selectionEnabled
884-
? cupertinoTextSelectionControls : null,
885-
onChanged: widget.onChanged,
886-
onSelectionChanged: _handleSelectionChanged,
887-
onEditingComplete: widget.onEditingComplete,
888-
onSubmitted: widget.onSubmitted,
889-
inputFormatters: formatters,
890-
rendererIgnoresPointer: true,
891-
cursorWidth: widget.cursorWidth,
892-
cursorRadius: widget.cursorRadius,
893-
cursorColor: cursorColor,
894-
cursorOpacityAnimates: true,
895-
cursorOffset: cursorOffset,
896-
paintCursorAboveText: true,
897-
backgroundCursorColor: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context),
898-
scrollPadding: widget.scrollPadding,
899-
keyboardAppearance: keyboardAppearance,
900-
dragStartBehavior: widget.dragStartBehavior,
901-
scrollController: widget.scrollController,
902-
scrollPhysics: widget.scrollPhysics,
903-
enableInteractiveSelection: widget.enableInteractiveSelection,
873+
child: Shortcuts(
874+
shortcuts: _disabledNavigationKeys,
875+
child: EditableText(
876+
key: editableTextKey,
877+
controller: controller,
878+
readOnly: widget.readOnly,
879+
toolbarOptions: widget.toolbarOptions,
880+
showCursor: widget.showCursor,
881+
showSelectionHandles: _showSelectionHandles,
882+
focusNode: _effectiveFocusNode,
883+
keyboardType: widget.keyboardType,
884+
textInputAction: widget.textInputAction,
885+
textCapitalization: widget.textCapitalization,
886+
style: textStyle,
887+
strutStyle: widget.strutStyle,
888+
textAlign: widget.textAlign,
889+
autofocus: widget.autofocus,
890+
obscureText: widget.obscureText,
891+
autocorrect: widget.autocorrect,
892+
maxLines: widget.maxLines,
893+
minLines: widget.minLines,
894+
expands: widget.expands,
895+
selectionColor: CupertinoTheme.of(context).primaryColor.withOpacity(0.2),
896+
selectionControls: widget.selectionEnabled
897+
? cupertinoTextSelectionControls : null,
898+
onChanged: widget.onChanged,
899+
onSelectionChanged: _handleSelectionChanged,
900+
onEditingComplete: widget.onEditingComplete,
901+
onSubmitted: widget.onSubmitted,
902+
inputFormatters: formatters,
903+
rendererIgnoresPointer: true,
904+
cursorWidth: widget.cursorWidth,
905+
cursorRadius: widget.cursorRadius,
906+
cursorColor: cursorColor,
907+
cursorOpacityAnimates: true,
908+
cursorOffset: cursorOffset,
909+
paintCursorAboveText: true,
910+
backgroundCursorColor: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context),
911+
scrollPadding: widget.scrollPadding,
912+
keyboardAppearance: keyboardAppearance,
913+
dragStartBehavior: widget.dragStartBehavior,
914+
scrollController: widget.scrollController,
915+
scrollPhysics: widget.scrollPhysics,
916+
enableInteractiveSelection: widget.enableInteractiveSelection,
917+
),
904918
),
905919
),
906920
);

packages/flutter/lib/src/material/text_field.dart

Lines changed: 57 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,17 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
705705

706706
bool _isHovering = false;
707707

708+
// Disables all directional focus actions inside of a text field, since up and
709+
// down shouldn't go to another field, even in a single line text field. We
710+
// remap the keys rather than the actions, since someone might want to invoke
711+
// a directional navigation action from another key binding.
712+
final Map<LogicalKeySet, Intent> _disabledNavigationKeys = <LogicalKeySet, Intent>{
713+
LogicalKeySet(LogicalKeyboardKey.arrowUp): const Intent(DoNothingAction.key),
714+
LogicalKeySet(LogicalKeyboardKey.arrowDown): const Intent(DoNothingAction.key),
715+
LogicalKeySet(LogicalKeyboardKey.arrowLeft): const Intent(DoNothingAction.key),
716+
LogicalKeySet(LogicalKeyboardKey.arrowRight): const Intent(DoNothingAction.key),
717+
};
718+
708719
bool get needsCounter => widget.maxLength != null
709720
&& widget.decoration != null
710721
&& widget.decoration.counterText == null;
@@ -936,49 +947,52 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
936947
}
937948

938949
Widget child = RepaintBoundary(
939-
child: EditableText(
940-
key: editableTextKey,
941-
readOnly: widget.readOnly,
942-
toolbarOptions: widget.toolbarOptions,
943-
showCursor: widget.showCursor,
944-
showSelectionHandles: _showSelectionHandles,
945-
controller: controller,
946-
focusNode: focusNode,
947-
keyboardType: widget.keyboardType,
948-
textInputAction: widget.textInputAction,
949-
textCapitalization: widget.textCapitalization,
950-
style: style,
951-
strutStyle: widget.strutStyle,
952-
textAlign: widget.textAlign,
953-
textDirection: widget.textDirection,
954-
autofocus: widget.autofocus,
955-
obscureText: widget.obscureText,
956-
autocorrect: widget.autocorrect,
957-
maxLines: widget.maxLines,
958-
minLines: widget.minLines,
959-
expands: widget.expands,
960-
selectionColor: themeData.textSelectionColor,
961-
selectionControls: widget.selectionEnabled ? textSelectionControls : null,
962-
onChanged: widget.onChanged,
963-
onSelectionChanged: _handleSelectionChanged,
964-
onEditingComplete: widget.onEditingComplete,
965-
onSubmitted: widget.onSubmitted,
966-
onSelectionHandleTapped: _handleSelectionHandleTapped,
967-
inputFormatters: formatters,
968-
rendererIgnoresPointer: true,
969-
cursorWidth: widget.cursorWidth,
970-
cursorRadius: cursorRadius,
971-
cursorColor: cursorColor,
972-
cursorOpacityAnimates: cursorOpacityAnimates,
973-
cursorOffset: cursorOffset,
974-
paintCursorAboveText: paintCursorAboveText,
975-
backgroundCursorColor: CupertinoColors.inactiveGray,
976-
scrollPadding: widget.scrollPadding,
977-
keyboardAppearance: keyboardAppearance,
978-
enableInteractiveSelection: widget.enableInteractiveSelection,
979-
dragStartBehavior: widget.dragStartBehavior,
980-
scrollController: widget.scrollController,
981-
scrollPhysics: widget.scrollPhysics,
950+
child: Shortcuts(
951+
shortcuts: _disabledNavigationKeys,
952+
child: EditableText(
953+
key: editableTextKey,
954+
readOnly: widget.readOnly,
955+
toolbarOptions: widget.toolbarOptions,
956+
showCursor: widget.showCursor,
957+
showSelectionHandles: _showSelectionHandles,
958+
controller: controller,
959+
focusNode: focusNode,
960+
keyboardType: widget.keyboardType,
961+
textInputAction: widget.textInputAction,
962+
textCapitalization: widget.textCapitalization,
963+
style: style,
964+
strutStyle: widget.strutStyle,
965+
textAlign: widget.textAlign,
966+
textDirection: widget.textDirection,
967+
autofocus: widget.autofocus,
968+
obscureText: widget.obscureText,
969+
autocorrect: widget.autocorrect,
970+
maxLines: widget.maxLines,
971+
minLines: widget.minLines,
972+
expands: widget.expands,
973+
selectionColor: themeData.textSelectionColor,
974+
selectionControls: widget.selectionEnabled ? textSelectionControls : null,
975+
onChanged: widget.onChanged,
976+
onSelectionChanged: _handleSelectionChanged,
977+
onEditingComplete: widget.onEditingComplete,
978+
onSubmitted: widget.onSubmitted,
979+
onSelectionHandleTapped: _handleSelectionHandleTapped,
980+
inputFormatters: formatters,
981+
rendererIgnoresPointer: true,
982+
cursorWidth: widget.cursorWidth,
983+
cursorRadius: cursorRadius,
984+
cursorColor: cursorColor,
985+
cursorOpacityAnimates: cursorOpacityAnimates,
986+
cursorOffset: cursorOffset,
987+
paintCursorAboveText: paintCursorAboveText,
988+
backgroundCursorColor: CupertinoColors.inactiveGray,
989+
scrollPadding: widget.scrollPadding,
990+
keyboardAppearance: keyboardAppearance,
991+
enableInteractiveSelection: widget.enableInteractiveSelection,
992+
dragStartBehavior: widget.dragStartBehavior,
993+
scrollController: widget.scrollController,
994+
scrollPhysics: widget.scrollPhysics,
995+
),
982996
),
983997
);
984998

packages/flutter/test/cupertino/text_field_test.dart

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3870,4 +3870,90 @@ void main() {
38703870
},
38713871
);
38723872
});
3873+
testWidgets("Arrow keys don't move input focus", (WidgetTester tester) async {
3874+
final TextEditingController controller1 = TextEditingController();
3875+
final TextEditingController controller2 = TextEditingController();
3876+
final TextEditingController controller3 = TextEditingController();
3877+
final TextEditingController controller4 = TextEditingController();
3878+
final TextEditingController controller5 = TextEditingController();
3879+
final FocusNode focusNode1 = FocusNode(debugLabel: 'Field 1');
3880+
final FocusNode focusNode2 = FocusNode(debugLabel: 'Field 2');
3881+
final FocusNode focusNode3 = FocusNode(debugLabel: 'Field 3');
3882+
final FocusNode focusNode4 = FocusNode(debugLabel: 'Field 4');
3883+
final FocusNode focusNode5 = FocusNode(debugLabel: 'Field 5');
3884+
3885+
// Lay out text fields in a "+" formation, and focus the center one.
3886+
await tester.pumpWidget(CupertinoApp(
3887+
home: Center(
3888+
child: Column(
3889+
mainAxisAlignment: MainAxisAlignment.center,
3890+
mainAxisSize: MainAxisSize.min,
3891+
children: <Widget>[
3892+
Container(
3893+
width: 100.0,
3894+
child: CupertinoTextField(
3895+
controller: controller1,
3896+
focusNode: focusNode1,
3897+
),
3898+
),
3899+
Row(
3900+
mainAxisAlignment: MainAxisAlignment.center,
3901+
mainAxisSize: MainAxisSize.min,
3902+
children: <Widget>[
3903+
Container(
3904+
width: 100.0,
3905+
child: CupertinoTextField(
3906+
controller: controller2,
3907+
focusNode: focusNode2,
3908+
),
3909+
),
3910+
Container(
3911+
width: 100.0,
3912+
child: CupertinoTextField(
3913+
controller: controller3,
3914+
focusNode: focusNode3,
3915+
),
3916+
),
3917+
Container(
3918+
width: 100.0,
3919+
child: CupertinoTextField(
3920+
controller: controller4,
3921+
focusNode: focusNode4,
3922+
),
3923+
),
3924+
],
3925+
),
3926+
Container(
3927+
width: 100.0,
3928+
child: CupertinoTextField(
3929+
controller: controller5,
3930+
focusNode: focusNode5,
3931+
),
3932+
),
3933+
],
3934+
),
3935+
),
3936+
),
3937+
);
3938+
3939+
focusNode3.requestFocus();
3940+
await tester.pump();
3941+
expect(focusNode3.hasPrimaryFocus, isTrue);
3942+
3943+
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
3944+
await tester.pump();
3945+
expect(focusNode3.hasPrimaryFocus, isTrue);
3946+
3947+
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
3948+
await tester.pump();
3949+
expect(focusNode3.hasPrimaryFocus, isTrue);
3950+
3951+
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
3952+
await tester.pump();
3953+
expect(focusNode3.hasPrimaryFocus, isTrue);
3954+
3955+
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
3956+
await tester.pump();
3957+
expect(focusNode3.hasPrimaryFocus, isTrue);
3958+
});
38733959
}

0 commit comments

Comments
 (0)