@@ -118,7 +118,7 @@ export class FocusManager {
118
118
}
119
119
}
120
120
121
- private getRightValidPreviousElement ( ) : HTMLElement | undefined {
121
+ private getValidPreviousElement ( ) : HTMLElement | undefined {
122
122
// Find the first connected element in the history
123
123
for ( let i = 0 ; i < this . _previousElements . length ; i ++ ) {
124
124
const element = this . _previousElements [ i ] ;
@@ -386,11 +386,11 @@ export class FocusManager {
386
386
387
387
// Clear current if it matches
388
388
if ( this . _currentElement === element ) {
389
- const rightElement = this . getRightValidPreviousElement ( ) ;
390
- if ( rightElement ) {
391
- this . _currentElement = rightElement ;
389
+ const previousElement = this . getValidPreviousElement ( ) ;
390
+ if ( previousElement ) {
391
+ this . _currentElement = previousElement ;
392
392
// Remove the selected element from history since it's now current
393
- removeFromArray ( this . _previousElements , rightElement ) ;
393
+ removeFromArray ( this . _previousElements , previousElement ) ;
394
394
} else {
395
395
this . _currentElement = undefined ;
396
396
}
@@ -401,7 +401,7 @@ export class FocusManager {
401
401
/**
402
402
* Fires onActive callbacks for an element and all its parent focusables
403
403
*/
404
- private fireOnActiveForHierarchy ( element : HTMLElement , active : boolean ) : void {
404
+ private triggerOnActiveCallbacks ( element : HTMLElement , active : boolean ) : void {
405
405
let metadata = this . getMetadata ( element ) ;
406
406
while ( metadata ) {
407
407
try {
@@ -415,6 +415,23 @@ export class FocusManager {
415
415
}
416
416
}
417
417
418
+ private findFocusableChild ( element : HTMLElement ) : HTMLElement | undefined {
419
+ const metadata = this . getMetadata ( element ) ;
420
+ if ( ! metadata ?. children . length ) return undefined ;
421
+
422
+ // Try preferred child first, then iterate through all children
423
+ const childrenToTry = [ this . getPreferredChild ( element ) , ...metadata . children ] . filter (
424
+ ( child , index , arr ) => child && arr . indexOf ( child ) === index
425
+ ) ; // Remove duplicates
426
+
427
+ for ( const child of childrenToTry ) {
428
+ const activated = this . setActive ( child ! ) ;
429
+ if ( activated ) return activated ;
430
+ }
431
+
432
+ return undefined ;
433
+ }
434
+
418
435
/**
419
436
* Sets the specified element as the currently active (focused) element.
420
437
* If the element has skip=true, it will try to focus the preferred child instead.
@@ -423,44 +440,29 @@ export class FocusManager {
423
440
* for all parent focusables. Updates focus history and caches the active index
424
441
* for list focusables.
425
442
*/
426
- setActive ( element : HTMLElement ) {
427
- if ( ! element || ! element . isConnected || ! this . isElementRegistered ( element ) ) {
428
- return ;
429
- }
443
+ setActive ( element : HTMLElement ) : HTMLElement | undefined {
444
+ if ( ! element ?. isConnected || ! this . isElementRegistered ( element ) ) return undefined ;
430
445
431
446
const metadata = this . getMetadata ( element ) ;
432
-
433
- // If element should be skipped, try to focus preferred child (cached or first) instead
434
- if ( metadata ?. options . skip ) {
435
- const preferredChild = this . getPreferredChildFromContainer ( element ) ;
436
- if ( preferredChild ) {
437
- this . setActive ( preferredChild ) ;
438
- return ;
439
- }
440
- // If no children, don't set this element as active
441
- return ;
447
+ if ( ! metadata ) {
448
+ console . warn ( 'Could not find metadata' , element ) ;
449
+ return undefined ;
442
450
}
443
451
452
+ const targetElement = this . findFocusableChild ( element ) || element ;
444
453
const previousElement = this . _currentElement ;
445
454
446
- // Fire onActive(false) for previous element and all its parents
447
455
if ( previousElement ) {
448
- this . fireOnActiveForHierarchy ( previousElement , false ) ;
456
+ this . triggerOnActiveCallbacks ( previousElement , false ) ;
457
+ this . addToPreviousElements ( previousElement ) ;
449
458
}
450
459
451
- // Cache the active child index for all parent elements
452
- this . cacheActiveIndexForAllParents ( element ) ;
453
-
454
- // Add current element to history before changing
455
- if ( this . _currentElement ) {
456
- this . addToPreviousElements ( this . _currentElement ) ;
457
- }
458
- this . _currentElement = element ;
460
+ this . _currentElement = targetElement ;
461
+ this . cacheActiveIndex ( targetElement ) ;
462
+ this . triggerOnActiveCallbacks ( targetElement , true ) ;
463
+ this . cursor . set ( targetElement ) ;
459
464
460
- // Fire onActive(true) for new element and all its parents
461
- this . fireOnActiveForHierarchy ( element , true ) ;
462
-
463
- this . cursor . set ( element ) ;
465
+ return targetElement ;
464
466
}
465
467
466
468
/**
@@ -473,7 +475,7 @@ export class FocusManager {
473
475
* @param forward - Whether to search forward or backward through siblings
474
476
* @returns The right matching element or undefined if none found
475
477
*/
476
- private findNext (
478
+ private findNextByType (
477
479
searchElement : HTMLElement ,
478
480
searchTypes : DefinedFocusable | DefinedFocusable [ ] ,
479
481
forward : boolean
@@ -495,7 +497,7 @@ export class FocusManager {
495
497
}
496
498
497
499
if ( ! currentMeta ?. parentElement ) return undefined ;
498
- return this . findNext ( currentMeta . parentElement , searchTypes , forward ) ;
500
+ return this . findNextByType ( currentMeta . parentElement , searchTypes , forward ) ;
499
501
}
500
502
501
503
/**
@@ -557,7 +559,7 @@ export class FocusManager {
557
559
* @param containerElement - The container element to get a child from
558
560
* @returns The preferred child element to focus, or undefined if no children
559
561
*/
560
- private getPreferredChildFromContainer ( containerElement : HTMLElement ) : HTMLElement | undefined {
562
+ private getPreferredChild ( containerElement : HTMLElement ) : HTMLElement | undefined {
561
563
const containerMetadata = this . getMetadata ( containerElement ) ;
562
564
if ( ! containerMetadata ?. children . length ) {
563
565
return undefined ;
@@ -595,7 +597,7 @@ export class FocusManager {
595
597
visited . add ( current ) ;
596
598
597
599
// Get the preferred child from current container
598
- const preferredChild = this . getPreferredChildFromContainer ( current ) ;
600
+ const preferredChild = this . getPreferredChild ( current ) ;
599
601
if ( ! preferredChild ) {
600
602
// No children available, return current container
601
603
return current ;
@@ -626,10 +628,7 @@ export class FocusManager {
626
628
* @param forward - Whether to search forward (next) or backward (previous) through siblings
627
629
* @returns The next non-skipped focusable element or undefined if none found
628
630
*/
629
- private findNextNonSkippedInList (
630
- searchElement : HTMLElement ,
631
- forward : boolean
632
- ) : HTMLElement | undefined {
631
+ private findNextInList ( searchElement : HTMLElement , forward : boolean ) : HTMLElement | undefined {
633
632
const currentMeta = this . getMetadata ( searchElement ) ;
634
633
const parentMeta = this . getParentMetadata ( currentMeta ) ;
635
634
if ( ! currentMeta || ! parentMeta || ! this . _currentElement ) return undefined ;
@@ -654,7 +653,7 @@ export class FocusManager {
654
653
655
654
// Only continue traversing up if the parent has list: true
656
655
if ( parentMeta . options . list && currentMeta . parentElement ) {
657
- return this . findNextNonSkippedInList ( currentMeta . parentElement , forward ) ;
656
+ return this . findNextInList ( currentMeta . parentElement , forward ) ;
658
657
}
659
658
660
659
return undefined ;
@@ -702,8 +701,10 @@ export class FocusManager {
702
701
703
702
private activateAndFocus ( element : HTMLElement | undefined ) : boolean {
704
703
if ( ! element ) return false ;
705
- this . setActive ( element ) ;
706
- this . focusElement ( element ) ;
704
+ const activatedElement = this . setActive ( element ) ;
705
+ if ( activatedElement ) {
706
+ this . focusElement ( activatedElement ) ;
707
+ }
707
708
return true ;
708
709
}
709
710
@@ -761,7 +762,7 @@ export class FocusManager {
761
762
const metadata = this . getCurrentMetadata ( ) ;
762
763
if ( ! metadata ?. parentElement || ! this . _currentElement ) return false ;
763
764
764
- const cousinTarget = this . findNext (
765
+ const cousinTarget = this . findNextByType (
765
766
this . _currentElement ,
766
767
metadata . logicalId as DefinedFocusable ,
767
768
forward
@@ -781,7 +782,7 @@ export class FocusManager {
781
782
if ( ! metadata ?. parentElement || ! this . _currentElement ) return false ;
782
783
783
784
// Find the next non-skipped focusable, traversing parents while list: true
784
- const linkedTarget = this . findNextNonSkippedInList ( this . _currentElement , forward ) ;
785
+ const linkedTarget = this . findNextInList ( this . _currentElement , forward ) ;
785
786
if ( ! linkedTarget ) return false ;
786
787
787
788
return this . activateAndFocus ( linkedTarget ) ;
@@ -795,7 +796,7 @@ export class FocusManager {
795
796
if ( ! linkToIds || linkToIds . length === 0 ) return false ;
796
797
797
798
// Find the nearest element of any of the target types
798
- const linkedTarget = this . findNext ( this . _currentElement , linkToIds , forward ) ;
799
+ const linkedTarget = this . findNextByType ( this . _currentElement , linkToIds , forward ) ;
799
800
return this . activateAndFocus ( linkedTarget ) ;
800
801
}
801
802
@@ -875,14 +876,14 @@ export class FocusManager {
875
876
if ( ! metadata || ! this . _currentElement ) return false ;
876
877
877
878
// Get the preferred child (cached or first child)
878
- const preferredChild = this . getPreferredChildFromContainer ( this . _currentElement ) ;
879
+ const preferredChild = this . getPreferredChild ( this . _currentElement ) ;
879
880
if ( ! preferredChild ) return false ;
880
881
881
882
const childMetadata = this . getMetadata ( preferredChild ) ;
882
883
883
884
// If the preferred child should be skipped, try to focus its preferred child (cached or first)
884
885
if ( childMetadata ?. options . skip ) {
885
- const preferredGrandChild = this . getPreferredChildFromContainer ( preferredChild ) ;
886
+ const preferredGrandChild = this . getPreferredChild ( preferredChild ) ;
886
887
if ( preferredGrandChild ) {
887
888
return this . activateAndFocus ( preferredGrandChild ) ;
888
889
}
@@ -1140,7 +1141,7 @@ export class FocusManager {
1140
1141
*
1141
1142
* @param element - The currently active element
1142
1143
*/
1143
- private cacheActiveIndexForAllParents ( element : HTMLElement ) {
1144
+ private cacheActiveIndex ( element : HTMLElement ) {
1144
1145
let currentElement = element ;
1145
1146
const metadata = this . getMetadata ( currentElement ) ;
1146
1147
@@ -1277,7 +1278,7 @@ export class FocusManager {
1277
1278
1278
1279
// Check each child of the non-list parent to see which one contains our current element
1279
1280
for ( const child of parentMetadata . children ) {
1280
- if ( this . elementContains ( child , currentElement ) ) {
1281
+ if ( this . containsElement ( child , currentElement ) ) {
1281
1282
return child ;
1282
1283
}
1283
1284
}
@@ -1292,15 +1293,15 @@ export class FocusManager {
1292
1293
* @param target - The target element to find
1293
1294
* @returns True if container contains target
1294
1295
*/
1295
- private elementContains ( container : HTMLElement , target : HTMLElement ) : boolean {
1296
+ private containsElement ( container : HTMLElement , target : HTMLElement ) : boolean {
1296
1297
if ( container === target ) return true ;
1297
1298
1298
1299
const containerMetadata = this . getMetadata ( container ) ;
1299
1300
if ( ! containerMetadata ) return false ;
1300
1301
1301
1302
// Recursively check children
1302
1303
for ( const child of containerMetadata . children ) {
1303
- if ( this . elementContains ( child , target ) ) {
1304
+ if ( this . containsElement ( child , target ) ) {
1304
1305
return true ;
1305
1306
}
1306
1307
}
0 commit comments