diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index 7a1723b9c5..7ac303a179 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -2469,14 +2469,19 @@ class EditableTextState extends State /// Read-only input fields do not need a connection with the platform since /// there's no need for text editing capabilities (e.g. virtual keyboard). /// + /// On macOS, most of the selection and focus related shortcuts require a + /// connection with the platform because appropriate platform selectors are + /// sent from the engine and translated into intents. For read-only fields + /// those shortcuts should be available (for instance to allow tab traversal). + /// /// On the web, we always need a connection because we want some browser /// functionalities to continue to work on read-only input fields like: - /// /// - Relevant context menu. /// - cmd/ctrl+c shortcut to copy. /// - cmd/ctrl+a to select all. /// - Changing the selection using a physical keyboard. - bool get _shouldCreateInputConnection => kIsWeb || !widget.readOnly; + bool get _shouldCreateInputConnection => + kIsWeb || defaultTargetPlatform == TargetPlatform.macOS || !widget.readOnly; // The time it takes for the floating cursor to snap to the text aligned // cursor position after the user has finished placing it. diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index f812269dcd..190e398a51 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -3013,6 +3013,62 @@ void main() { } }); + testWidgets( + 'Read-only fields can be traversed on all platforms', + (WidgetTester tester) async { + final TextEditingController controller1 = TextEditingController(); + addTearDown(controller1.dispose); + final TextEditingController controller2 = TextEditingController(); + addTearDown(controller2.dispose); + final FocusNode focusNode1 = FocusNode(); + addTearDown(focusNode1.dispose); + final FocusNode focusNode2 = FocusNode(); + addTearDown(focusNode2.dispose); + + await tester.pumpWidget( + MaterialApp( + home: Column( + children: [ + EditableText( + focusNode: focusNode1, + autofocus: true, + controller: controller1, + backgroundCursorColor: Colors.grey, + style: textStyle, + cursorColor: cursorColor, + ), + EditableText( + readOnly: true, + focusNode: focusNode2, + controller: controller2, + backgroundCursorColor: Colors.grey, + style: textStyle, + cursorColor: cursorColor, + ), + ], + ), + ), + ); + + expect(focusNode1.hasPrimaryFocus, true); + expect(focusNode2.hasPrimaryFocus, false); + + // Change focus to the readonly EditableText. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + + expect(focusNode1.hasPrimaryFocus, false); + expect(focusNode2.hasPrimaryFocus, true); + + // Change focus back to the first EditableText. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + + expect(focusNode1.hasPrimaryFocus, true); + expect(focusNode2.hasPrimaryFocus, false); + }, + variant: TargetPlatformVariant.all(), + skip: kIsWeb, // [intended] + ); + testWidgets('Sends "updateConfig" when read-only flag is flipped', (WidgetTester tester) async { bool readOnly = true; late StateSetter setState;