From 6d8c850ee7df358115d349ec039efd5f4568a648 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Fri, 9 Jul 2021 12:06:05 -0700 Subject: [PATCH] [flutter] tab navigation does not notify the focus scope node (#86141) --- packages/flutter/lib/src/widgets/routes.dart | 15 +---- .../test/material/text_field_focus_test.dart | 59 +++++++++++++++++++ 2 files changed, 62 insertions(+), 12 deletions(-) diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart index f7393ff36f..e7ac262e0f 100644 --- a/packages/flutter/lib/src/widgets/routes.dart +++ b/packages/flutter/lib/src/widgets/routes.dart @@ -2011,11 +2011,8 @@ class _FocusTrap extends SingleChildRenderObjectWidget { } class _RenderFocusTrap extends RenderProxyBoxWithHitTestBehavior { - _RenderFocusTrap(this._focusScopeNode) { - focusScopeNode.addListener(_currentFocusListener); - } + _RenderFocusTrap(this._focusScopeNode); - FocusNode? currentFocus; Rect? currentFocusRect; Expando cachedResults = Expando(); @@ -2024,13 +2021,7 @@ class _RenderFocusTrap extends RenderProxyBoxWithHitTestBehavior { set focusScopeNode(FocusScopeNode value) { if (focusScopeNode == value) return; - focusScopeNode.removeListener(_currentFocusListener); _focusScopeNode = value; - focusScopeNode.addListener(_currentFocusListener); - } - - void _currentFocusListener() { - currentFocus = focusScopeNode.focusedChild; } @override @@ -2069,11 +2060,11 @@ class _RenderFocusTrap extends RenderProxyBoxWithHitTestBehavior { || event.buttons != kPrimaryButton || event.kind != PointerDeviceKind.mouse || _shouldIgnoreEvents - || currentFocus == null) { + || _focusScopeNode.focusedChild == null) { return; } final BoxHitTestResult? result = cachedResults[entry]; - final FocusNode? focusNode = currentFocus; + final FocusNode? focusNode = _focusScopeNode.focusedChild; if (focusNode == null || result == null) return; diff --git a/packages/flutter/test/material/text_field_focus_test.dart b/packages/flutter/test/material/text_field_focus_test.dart index 5baf32304a..f641aab65a 100644 --- a/packages/flutter/test/material/text_field_focus_test.dart +++ b/packages/flutter/test/material/text_field_focus_test.dart @@ -5,6 +5,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -429,4 +430,62 @@ void main() { expect(focusNodeA.hasFocus, false); expect(focusNodeB.hasFocus, true); }, variant: TargetPlatformVariant.desktop()); + + testWidgets('A Focused text-field will lose focus when clicking outside of its hitbox with a mouse on desktop after tab navigation', (WidgetTester tester) async { + final FocusNode focusNodeA = FocusNode(); + final FocusNode focusNodeB = FocusNode(); + final Key key = UniqueKey(); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: ListView( + children: [ + const TextField(), + const TextField(), + TextField( + focusNode: focusNodeA, + ), + Container( + key: key, + height: 200, + ), + TextField( + focusNode: focusNodeB, + ), + ], + ), + ), + ), + ); + // Tab over to the 3rd text field. + for (int i = 0; i < 3; i += 1) { + await tester.sendKeyDownEvent(LogicalKeyboardKey.tab); + await tester.sendKeyUpEvent(LogicalKeyboardKey.tab); + } + + expect(focusNodeA.hasFocus, true); + expect(focusNodeB.hasFocus, false); + + // Click on the container to not hit either text field. + final TestGesture down2 = await tester.startGesture(tester.getCenter(find.byKey(key)), kind: PointerDeviceKind.mouse); + await tester.pump(); + await tester.pumpAndSettle(); + await down2.up(); + await down2.removePointer(); + + expect(focusNodeA.hasFocus, false); + expect(focusNodeB.hasFocus, false); + + // Second text field can still gain focus. + + final TestGesture down3 = await tester.startGesture(tester.getCenter(find.byType(TextField).last), kind: PointerDeviceKind.mouse); + await tester.pump(); + await tester.pumpAndSettle(); + await down3.up(); + await down3.removePointer(); + + expect(focusNodeA.hasFocus, false); + expect(focusNodeB.hasFocus, true); + }, variant: TargetPlatformVariant.desktop()); }