Skip to content

Commit 8c3806f

Browse files
authored
Add parentNode to FocusScope widget (#114034)
1 parent 3ce88d3 commit 8c3806f

File tree

2 files changed

+70
-2
lines changed

2 files changed

+70
-2
lines changed

packages/flutter/lib/src/widgets/focus_scope.dart

+4-2
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,7 @@ class FocusScope extends Focus {
760760
const FocusScope({
761761
super.key,
762762
FocusScopeNode? node,
763+
super.parentNode,
763764
required super.child,
764765
super.autofocus,
765766
super.onFocusChange,
@@ -781,6 +782,7 @@ class FocusScope extends Focus {
781782
Key? key,
782783
required Widget child,
783784
required FocusScopeNode focusScopeNode,
785+
FocusNode? parentNode,
784786
bool autofocus,
785787
ValueChanged<bool>? onFocusChange,
786788
}) = _FocusScopeWithExternalFocusNode;
@@ -809,13 +811,13 @@ class _FocusScopeWithExternalFocusNode extends FocusScope {
809811
super.key,
810812
required super.child,
811813
required FocusScopeNode focusScopeNode,
814+
super.parentNode,
812815
super.autofocus,
813816
super.onFocusChange,
814817
}) : super(
815818
node: focusScopeNode,
816819
);
817820

818-
819821
@override
820822
bool get _usingExternalFocus => true;
821823
@override
@@ -846,7 +848,7 @@ class _FocusScopeState extends _FocusState {
846848

847849
@override
848850
Widget build(BuildContext context) {
849-
_focusAttachment!.reparent();
851+
_focusAttachment!.reparent(parent: widget.parentNode);
850852
return Semantics(
851853
explicitChildNodes: true,
852854
child: _FocusMarker(

packages/flutter/test/widgets/focus_scope_test.dart

+66
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,72 @@ void main() {
532532
expect(insertedNode.hasFocus, isFalse);
533533
});
534534

535+
testWidgets('Setting parentNode determines focus scope tree hierarchy.', (WidgetTester tester) async {
536+
final FocusScopeNode topNode = FocusScopeNode(debugLabel: 'Top');
537+
final FocusScopeNode parentNode = FocusScopeNode(debugLabel: 'Parent');
538+
final FocusScopeNode childNode = FocusScopeNode(debugLabel: 'Child');
539+
final FocusScopeNode insertedNode = FocusScopeNode(debugLabel: 'Inserted');
540+
541+
await tester.pumpWidget(
542+
FocusScope.withExternalFocusNode(
543+
focusScopeNode: topNode,
544+
child: Column(
545+
children: <Widget>[
546+
FocusScope.withExternalFocusNode(
547+
focusScopeNode: parentNode,
548+
child: const SizedBox(),
549+
),
550+
FocusScope.withExternalFocusNode(
551+
focusScopeNode: childNode,
552+
parentNode: parentNode,
553+
child: const Focus(
554+
autofocus: true,
555+
child: SizedBox(),
556+
),
557+
)
558+
],
559+
),
560+
),
561+
);
562+
await tester.pump();
563+
564+
expect(childNode.hasFocus, isTrue);
565+
expect(parentNode.hasFocus, isTrue);
566+
expect(topNode.hasFocus, isTrue);
567+
568+
// Check that inserting a Focus in between doesn't reparent the child.
569+
await tester.pumpWidget(
570+
FocusScope.withExternalFocusNode(
571+
focusScopeNode: topNode,
572+
child: Column(
573+
children: <Widget>[
574+
FocusScope.withExternalFocusNode(
575+
focusScopeNode: parentNode,
576+
child: const SizedBox(),
577+
),
578+
FocusScope.withExternalFocusNode(
579+
focusScopeNode: insertedNode,
580+
child: FocusScope.withExternalFocusNode(
581+
focusScopeNode: childNode,
582+
parentNode: parentNode,
583+
child: const Focus(
584+
autofocus: true,
585+
child: SizedBox(),
586+
),
587+
),
588+
)
589+
],
590+
),
591+
),
592+
);
593+
await tester.pump();
594+
595+
expect(childNode.hasFocus, isTrue);
596+
expect(parentNode.hasFocus, isTrue);
597+
expect(topNode.hasFocus, isTrue);
598+
expect(insertedNode.hasFocus, isFalse);
599+
});
600+
535601
// Arguably, this isn't correct behavior, but it is what happens now.
536602
testWidgets("Removing focused widget doesn't move focus to next widget within FocusScope", (WidgetTester tester) async {
537603
final GlobalKey<TestFocusState> keyA = GlobalKey();

0 commit comments

Comments
 (0)