@@ -124,13 +124,15 @@ class Focus extends StatefulWidget {
124124 bool ? canRequestFocus,
125125 bool ? skipTraversal,
126126 bool ? descendantsAreFocusable,
127+ bool ? descendantsAreTraversable,
127128 this .includeSemantics = true ,
128129 String ? debugLabel,
129130 }) : _onKeyEvent = onKeyEvent,
130131 _onKey = onKey,
131132 _canRequestFocus = canRequestFocus,
132133 _skipTraversal = skipTraversal,
133134 _descendantsAreFocusable = descendantsAreFocusable,
135+ _descendantsAreTraversable = descendantsAreTraversable,
134136 _debugLabel = debugLabel,
135137 assert (child != null ),
136138 assert (autofocus != null ),
@@ -279,10 +281,17 @@ class Focus extends StatefulWidget {
279281 /// Does not affect the value of [FocusNode.canRequestFocus] on the
280282 /// descendants.
281283 ///
284+ /// If a descendant node loses focus when this value is changed, the focus
285+ /// will move to the scope enclosing this node.
286+ ///
282287 /// See also:
283288 ///
284289 /// * [ExcludeFocus] , a widget that uses this property to conditionally
285290 /// exclude focus for a subtree.
291+ /// * [descendantsAreTraversable] , which makes this widget's descendants
292+ /// untraversable.
293+ /// * [ExcludeFocusTraversal] , a widget that conditionally excludes focus
294+ /// traversal for a subtree.
286295 /// * [FocusTraversalGroup] , a widget used to group together and configure the
287296 /// focus traversal policy for a widget subtree that has a
288297 /// `descendantsAreFocusable` parameter to conditionally block focus for a
@@ -291,6 +300,30 @@ class Focus extends StatefulWidget {
291300 bool get descendantsAreFocusable => _descendantsAreFocusable ?? focusNode? .descendantsAreFocusable ?? true ;
292301 final bool ? _descendantsAreFocusable;
293302
303+ /// {@template flutter.widgets.Focus.descendantsAreTraversable}
304+ /// If false, will make this widget's descendants untraversable.
305+ ///
306+ /// Defaults to true. Does not affect traversablility of this node (just its
307+ /// descendants): for that, use [FocusNode.skipTraversal] .
308+ ///
309+ /// Does not affect the value of [FocusNode.skipTraversal] on the
310+ /// descendants. Does not affect focusability of the descendants.
311+ ///
312+ /// See also:
313+ ///
314+ /// * [ExcludeFocusTraversal] , a widget that uses this property to
315+ /// conditionally exclude focus traversal for a subtree.
316+ /// * [descendantsAreFocusable] , which makes this widget's descendants
317+ /// unfocusable.
318+ /// * [ExcludeFocus] , a widget that conditionally excludes focus for a subtree.
319+ /// * [FocusTraversalGroup] , a widget used to group together and configure the
320+ /// focus traversal policy for a widget subtree that has a
321+ /// `descendantsAreFocusable` parameter to conditionally block focus for a
322+ /// subtree.
323+ /// {@endtemplate}
324+ bool get descendantsAreTraversable => _descendantsAreTraversable ?? focusNode? .descendantsAreTraversable ?? true ;
325+ final bool ? _descendantsAreTraversable;
326+
294327 /// {@template flutter.widgets.Focus.includeSemantics}
295328 /// Include semantics information in this widget.
296329 ///
@@ -420,6 +453,7 @@ class Focus extends StatefulWidget {
420453 properties.add (FlagProperty ('autofocus' , value: autofocus, ifTrue: 'AUTOFOCUS' , defaultValue: false ));
421454 properties.add (FlagProperty ('canRequestFocus' , value: canRequestFocus, ifFalse: 'NOT FOCUSABLE' , defaultValue: false ));
422455 properties.add (FlagProperty ('descendantsAreFocusable' , value: descendantsAreFocusable, ifFalse: 'DESCENDANTS UNFOCUSABLE' , defaultValue: true ));
456+ properties.add (FlagProperty ('descendantsAreTraversable' , value: descendantsAreTraversable, ifFalse: 'DESCENDANTS UNTRAVERSABLE' , defaultValue: true ));
423457 properties.add (DiagnosticsProperty <FocusNode >('focusNode' , focusNode, defaultValue: null ));
424458 }
425459
@@ -459,6 +493,8 @@ class _FocusWithExternalFocusNode extends Focus {
459493 @override
460494 bool get descendantsAreFocusable => focusNode! .descendantsAreFocusable;
461495 @override
496+ bool ? get _descendantsAreTraversable => focusNode! .descendantsAreTraversable;
497+ @override
462498 String ? get debugLabel => focusNode! .debugLabel;
463499}
464500
@@ -468,6 +504,7 @@ class _FocusState extends State<Focus> {
468504 late bool _hadPrimaryFocus;
469505 late bool _couldRequestFocus;
470506 late bool _descendantsWereFocusable;
507+ late bool _descendantsWereTraversable;
471508 bool _didAutofocus = false ;
472509 FocusAttachment ? _focusAttachment;
473510
@@ -485,6 +522,7 @@ class _FocusState extends State<Focus> {
485522 _internalNode ?? = _createNode ();
486523 }
487524 focusNode.descendantsAreFocusable = widget.descendantsAreFocusable;
525+ focusNode.descendantsAreTraversable = widget.descendantsAreTraversable;
488526 if (widget.skipTraversal != null ) {
489527 focusNode.skipTraversal = widget.skipTraversal;
490528 }
@@ -493,6 +531,7 @@ class _FocusState extends State<Focus> {
493531 }
494532 _couldRequestFocus = focusNode.canRequestFocus;
495533 _descendantsWereFocusable = focusNode.descendantsAreFocusable;
534+ _descendantsWereTraversable = focusNode.descendantsAreTraversable;
496535 _hadPrimaryFocus = focusNode.hasPrimaryFocus;
497536 _focusAttachment = focusNode.attach (context, onKeyEvent: widget.onKeyEvent, onKey: widget.onKey);
498537
@@ -507,6 +546,7 @@ class _FocusState extends State<Focus> {
507546 debugLabel: widget.debugLabel,
508547 canRequestFocus: widget.canRequestFocus,
509548 descendantsAreFocusable: widget.descendantsAreFocusable,
549+ descendantsAreTraversable: widget.descendantsAreTraversable,
510550 skipTraversal: widget.skipTraversal,
511551 );
512552 }
@@ -579,6 +619,7 @@ class _FocusState extends State<Focus> {
579619 focusNode.canRequestFocus = widget._canRequestFocus! ;
580620 }
581621 focusNode.descendantsAreFocusable = widget.descendantsAreFocusable;
622+ focusNode.descendantsAreTraversable = widget.descendantsAreTraversable;
582623 }
583624 } else {
584625 _focusAttachment! .detach ();
@@ -595,6 +636,7 @@ class _FocusState extends State<Focus> {
595636 final bool hasPrimaryFocus = focusNode.hasPrimaryFocus;
596637 final bool canRequestFocus = focusNode.canRequestFocus;
597638 final bool descendantsAreFocusable = focusNode.descendantsAreFocusable;
639+ final bool descendantsAreTraversable = focusNode.descendantsAreTraversable;
598640 widget.onFocusChange? .call (focusNode.hasFocus);
599641 // Check the cached states that matter here, and call setState if they have
600642 // changed.
@@ -613,6 +655,11 @@ class _FocusState extends State<Focus> {
613655 _descendantsWereFocusable = descendantsAreFocusable;
614656 });
615657 }
658+ if (_descendantsWereTraversable != descendantsAreTraversable) {
659+ setState (() {
660+ _descendantsWereTraversable = descendantsAreTraversable;
661+ });
662+ }
616663 }
617664
618665 @override
@@ -784,6 +831,8 @@ class _FocusScopeWithExternalFocusNode extends FocusScope {
784831 @override
785832 bool get descendantsAreFocusable => focusNode! .descendantsAreFocusable;
786833 @override
834+ bool get descendantsAreTraversable => focusNode! .descendantsAreTraversable;
835+ @override
787836 String ? get debugLabel => focusNode! .debugLabel;
788837}
789838
0 commit comments