@@ -120,16 +120,6 @@ enum TraversalEdgeBehavior {
120
120
/// address bar, escape an `iframe` , or focus on HTML elements other than
121
121
/// those managed by Flutter.
122
122
leaveFlutterView,
123
-
124
- /// Allows focus to traverse up to parent scope.
125
- ///
126
- /// When reaching the edge of the current scope, requesting the next focus
127
- /// will look up to the parent scope of the current scope and focus the focus
128
- /// node next to the current scope.
129
- ///
130
- /// If there is no parent scope above the current scope, fallback to
131
- /// [closedLoop] behavior.
132
- parentScope,
133
123
}
134
124
135
125
/// Determines how focusable widgets are traversed within a [FocusTraversalGroup] .
@@ -196,60 +186,6 @@ abstract class FocusTraversalPolicy with Diagnosticable {
196
186
);
197
187
}
198
188
199
- /// Request focus on a focus node as a result of a tab traversal.
200
- ///
201
- /// If the `node` is a [FocusScopeNode] , this method will recursively find
202
- /// the next focus from its descendants until it find a regular [FocusNode] .
203
- ///
204
- /// Returns true if this method focused a new focus node.
205
- bool _requestTabTraversalFocus (
206
- FocusNode node, {
207
- ScrollPositionAlignmentPolicy ? alignmentPolicy,
208
- double ? alignment,
209
- Duration ? duration,
210
- Curve ? curve,
211
- required bool forward,
212
- }) {
213
- if (node is FocusScopeNode ) {
214
- if (node.focusedChild != null ) {
215
- // Can't stop here as the `focusedChild` may be a focus scope node
216
- // without a first focus. The first focus will be picked in the
217
- // next iteration.
218
- return _requestTabTraversalFocus (
219
- node.focusedChild! ,
220
- alignmentPolicy: alignmentPolicy,
221
- alignment: alignment,
222
- duration: duration,
223
- curve: curve,
224
- forward: forward,
225
- );
226
- }
227
- final List <FocusNode > sortedChildren = _sortAllDescendants (node, node);
228
- if (sortedChildren.isNotEmpty) {
229
- _requestTabTraversalFocus (
230
- forward ? sortedChildren.first : sortedChildren.last,
231
- alignmentPolicy: alignmentPolicy,
232
- alignment: alignment,
233
- duration: duration,
234
- curve: curve,
235
- forward: forward,
236
- );
237
- // Regardless if _requestTabTraversalFocus return true or false, a first
238
- // focus has been picked.
239
- return true ;
240
- }
241
- }
242
- final bool nodeHadPrimaryFocus = node.hasPrimaryFocus;
243
- requestFocusCallback (
244
- node,
245
- alignmentPolicy: alignmentPolicy,
246
- alignment: alignment,
247
- duration: duration,
248
- curve: curve,
249
- );
250
- return ! nodeHadPrimaryFocus;
251
- }
252
-
253
189
/// Returns the node that should receive focus if focus is traversing
254
190
/// forwards, and there is no current focus.
255
191
///
@@ -416,21 +352,10 @@ abstract class FocusTraversalPolicy with Diagnosticable {
416
352
@protected
417
353
Iterable <FocusNode > sortDescendants (Iterable <FocusNode > descendants, FocusNode currentNode);
418
354
419
- static Iterable <FocusNode > _getDescendantsWithoutExpandingScope (FocusNode node) {
420
- final List <FocusNode > result = < FocusNode > [];
421
- for (final FocusNode child in node.children) {
422
- if (child is ! FocusScopeNode ) {
423
- result.addAll (_getDescendantsWithoutExpandingScope (child));
424
- }
425
- result.add (child);
426
- }
427
- return result;
428
- }
429
-
430
- static Map <FocusNode ?, _FocusTraversalGroupInfo > _findGroups (FocusScopeNode scope, _FocusTraversalGroupNode ? scopeGroupNode, FocusNode currentNode) {
355
+ Map <FocusNode ?, _FocusTraversalGroupInfo > _findGroups (FocusScopeNode scope, _FocusTraversalGroupNode ? scopeGroupNode, FocusNode currentNode) {
431
356
final FocusTraversalPolicy defaultPolicy = scopeGroupNode? .policy ?? ReadingOrderTraversalPolicy ();
432
357
final Map <FocusNode ?, _FocusTraversalGroupInfo > groups = < FocusNode ? , _FocusTraversalGroupInfo > {};
433
- for (final FocusNode node in _getDescendantsWithoutExpandingScope ( scope) ) {
358
+ for (final FocusNode node in scope.descendants ) {
434
359
final _FocusTraversalGroupNode ? groupNode = FocusTraversalGroup ._getGroupNode (node);
435
360
// Group nodes need to be added to their parent's node, or to the "null"
436
361
// node if no parent is found. This creates the hierarchy of group nodes
@@ -463,7 +388,7 @@ abstract class FocusTraversalPolicy with Diagnosticable {
463
388
464
389
// Sort all descendants, taking into account the FocusTraversalGroup
465
390
// that they are each in, and filtering out non-traversable/focusable nodes.
466
- static List <FocusNode > _sortAllDescendants (FocusScopeNode scope, FocusNode currentNode) {
391
+ List <FocusNode > _sortAllDescendants (FocusScopeNode scope, FocusNode currentNode) {
467
392
final _FocusTraversalGroupNode ? scopeGroupNode = FocusTraversalGroup ._getGroupNode (scope);
468
393
// Build the sorting data structure, separating descendants into groups.
469
394
final Map <FocusNode ?, _FocusTraversalGroupInfo > groups = _findGroups (scope, scopeGroupNode, currentNode);
@@ -548,81 +473,52 @@ abstract class FocusTraversalPolicy with Diagnosticable {
548
473
if (focusedChild == null ) {
549
474
final FocusNode ? firstFocus = forward ? findFirstFocus (currentNode) : findLastFocus (currentNode);
550
475
if (firstFocus != null ) {
551
- return _requestTabTraversalFocus (
476
+ requestFocusCallback (
552
477
firstFocus,
553
478
alignmentPolicy: forward ? ScrollPositionAlignmentPolicy .keepVisibleAtEnd : ScrollPositionAlignmentPolicy .keepVisibleAtStart,
554
- forward: forward,
555
479
);
480
+ return true ;
556
481
}
557
482
}
558
483
focusedChild ?? = nearestScope;
559
484
final List <FocusNode > sortedNodes = _sortAllDescendants (nearestScope, focusedChild);
560
- assert (sortedNodes.contains (focusedChild));
561
485
486
+ assert (sortedNodes.contains (focusedChild));
487
+ if (sortedNodes.length < 2 ) {
488
+ // If there are no nodes to traverse to, like when descendantsAreTraversable
489
+ // is false or skipTraversal for all the nodes is true.
490
+ return false ;
491
+ }
562
492
if (forward && focusedChild == sortedNodes.last) {
563
493
switch (nearestScope.traversalEdgeBehavior) {
564
494
case TraversalEdgeBehavior .leaveFlutterView:
565
495
focusedChild.unfocus ();
566
496
return false ;
567
- case TraversalEdgeBehavior .parentScope:
568
- final FocusScopeNode ? parentScope = nearestScope.enclosingScope;
569
- if (parentScope != null && parentScope != FocusManager .instance.rootScope) {
570
- focusedChild.unfocus ();
571
- parentScope.nextFocus ();
572
- // Verify the focus really has changed.
573
- return focusedChild.enclosingScope? .focusedChild != focusedChild;
574
- }
575
- // No valid parent scope. Fallback to closed loop behavior.
576
- return _requestTabTraversalFocus (
577
- sortedNodes.first,
578
- alignmentPolicy: ScrollPositionAlignmentPolicy .keepVisibleAtEnd,
579
- forward: forward,
580
- );
581
497
case TraversalEdgeBehavior .closedLoop:
582
- return _requestTabTraversalFocus (
583
- sortedNodes.first,
584
- alignmentPolicy: ScrollPositionAlignmentPolicy .keepVisibleAtEnd,
585
- forward: forward,
586
- );
498
+ requestFocusCallback (sortedNodes.first, alignmentPolicy: ScrollPositionAlignmentPolicy .keepVisibleAtEnd);
499
+ return true ;
587
500
}
588
501
}
589
502
if (! forward && focusedChild == sortedNodes.first) {
590
503
switch (nearestScope.traversalEdgeBehavior) {
591
504
case TraversalEdgeBehavior .leaveFlutterView:
592
505
focusedChild.unfocus ();
593
506
return false ;
594
- case TraversalEdgeBehavior .parentScope:
595
- final FocusScopeNode ? parentScope = nearestScope.enclosingScope;
596
- if (parentScope != null && parentScope != FocusManager .instance.rootScope) {
597
- focusedChild.unfocus ();
598
- parentScope.previousFocus ();
599
- // Verify the focus really has changed.
600
- return focusedChild.enclosingScope? .focusedChild != focusedChild;
601
- }
602
- // No valid parent scope. Fallback to closed loop behavior.
603
- return _requestTabTraversalFocus (
604
- sortedNodes.last,
605
- alignmentPolicy: ScrollPositionAlignmentPolicy .keepVisibleAtStart,
606
- forward: forward,
607
- );
608
507
case TraversalEdgeBehavior .closedLoop:
609
- return _requestTabTraversalFocus (
610
- sortedNodes.last,
611
- alignmentPolicy: ScrollPositionAlignmentPolicy .keepVisibleAtStart,
612
- forward: forward,
613
- );
508
+ requestFocusCallback (sortedNodes.last, alignmentPolicy: ScrollPositionAlignmentPolicy .keepVisibleAtStart);
509
+ return true ;
614
510
}
615
511
}
616
512
617
513
final Iterable <FocusNode > maybeFlipped = forward ? sortedNodes : sortedNodes.reversed;
618
514
FocusNode ? previousNode;
619
515
for (final FocusNode node in maybeFlipped) {
620
516
if (previousNode == focusedChild) {
621
- return _requestTabTraversalFocus (
517
+ requestFocusCallback (
622
518
node,
623
519
alignmentPolicy: forward ? ScrollPositionAlignmentPolicy .keepVisibleAtEnd : ScrollPositionAlignmentPolicy .keepVisibleAtStart,
624
- forward: forward,
625
520
);
521
+ return true ;
626
522
}
627
523
previousNode = node;
628
524
}
0 commit comments