From 6b2cc8554607802f2377253bdfbaff7832657bbb Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Mon, 14 Oct 2019 18:50:05 -0700 Subject: [PATCH] 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 --- .../flutter/lib/src/cupertino/text_field.dart | 98 +++++++++-------- .../flutter/lib/src/material/text_field.dart | 100 ++++++++++-------- .../test/cupertino/text_field_test.dart | 86 +++++++++++++++ .../test/material/text_field_test.dart | 88 +++++++++++++++ 4 files changed, 287 insertions(+), 85 deletions(-) diff --git a/packages/flutter/lib/src/cupertino/text_field.dart b/packages/flutter/lib/src/cupertino/text_field.dart index 98426c2f45..68411be0cc 100644 --- a/packages/flutter/lib/src/cupertino/text_field.dart +++ b/packages/flutter/lib/src/cupertino/text_field.dart @@ -599,6 +599,17 @@ class _CupertinoTextFieldState extends State 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 _disabledNavigationKeys = { + 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 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, + ), ), ), ); diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index be03b5b484..b523ca4978 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -705,6 +705,17 @@ class _TextFieldState extends State 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 _disabledNavigationKeys = { + 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 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, + ), ), ); diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 88b243a839..17f40cf6c9 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -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: [ + Container( + width: 100.0, + child: CupertinoTextField( + controller: controller1, + focusNode: focusNode1, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + 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); + }); } diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 930f394e9e..123dcf3159 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -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: [ + Container( + width: 100.0, + child: TextField( + controller: controller1, + focusNode: focusNode1, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + 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); + }); }