@@ -391,83 +391,98 @@ export class OverlayStack {
391
391
}
392
392
}
393
393
394
+ private manageFocusAfterCloseWhenOverlaysRemain ( ) : void {
395
+ const topOverlay = this . overlays [ this . overlays . length - 1 ] ;
396
+ topOverlay . feature ( ) ;
397
+ // Push focus in the the next remaining overlay as needed when a `type="modal"` overlay exists.
398
+ if ( topOverlay . interaction === 'modal' || topOverlay . hasModalRoot ) {
399
+ topOverlay . focus ( ) ;
400
+ } else {
401
+ this . stopTabTrapping ( ) ;
402
+ }
403
+ }
404
+
405
+ private manageFocusAfterCloseWhenLastOverlay ( overlay : ActiveOverlay ) : void {
406
+ this . stopTabTrapping ( ) ;
407
+ const isModal = overlay . interaction === 'modal' ;
408
+ const isReplace = overlay . interaction === 'replace' ;
409
+ const isInline = overlay . interaction === 'inline' ;
410
+ const isTabbingAwayFromInlineOrReplace =
411
+ ( isReplace || isInline ) && ! overlay . tabbingAway ;
412
+ overlay . tabbingAway = false ;
413
+ if ( ! isModal && ! isTabbingAwayFromInlineOrReplace ) {
414
+ return ;
415
+ }
416
+ // Manage post closure focus when needed.
417
+ const overlayRoot = overlay . overlayContent . getRootNode ( ) as ShadowRoot ;
418
+ const overlayContentActiveElement = overlayRoot . activeElement ;
419
+ let triggerRoot : ShadowRoot ;
420
+ let triggerActiveElement : Element | null ;
421
+ const contentContainsActiveElement = ( ) : boolean =>
422
+ overlay . overlayContent . contains ( overlayContentActiveElement ) ;
423
+ const triggerRootContainsActiveElement = ( ) : boolean => {
424
+ triggerRoot = overlay . trigger . getRootNode ( ) as ShadowRoot ;
425
+ triggerActiveElement = triggerRoot . activeElement ;
426
+ return triggerRoot . contains ( triggerActiveElement ) ;
427
+ } ;
428
+ const triggerHostIsActiveElement = ( ) : boolean =>
429
+ triggerRoot . host && triggerRoot . host === triggerActiveElement ;
430
+ // Return focus to the trigger as long as the user hasn't actively focused
431
+ // something outside of the current overlay interface; trigger, root, host.
432
+ if (
433
+ isModal ||
434
+ contentContainsActiveElement ( ) ||
435
+ triggerRootContainsActiveElement ( ) ||
436
+ triggerHostIsActiveElement ( )
437
+ ) {
438
+ overlay . trigger . focus ( ) ;
439
+ }
440
+ }
441
+
394
442
private async hideAndCloseOverlay (
395
443
overlay ?: ActiveOverlay ,
396
444
animated ?: boolean
397
445
) : Promise < void > {
398
- if ( overlay ) {
399
- await overlay . hide ( animated ) ;
400
- const contentWithLifecycle =
401
- overlay . overlayContent as unknown as ManagedOverlayContent ;
402
- if ( typeof contentWithLifecycle . open !== 'undefined' ) {
403
- contentWithLifecycle . open = false ;
404
- }
405
- if ( contentWithLifecycle . overlayCloseCallback ) {
406
- const { trigger } = overlay ;
407
- contentWithLifecycle . overlayCloseCallback ( { trigger } ) ;
408
- }
409
- if ( overlay . state != 'dispose' ) return ;
446
+ if ( ! overlay ) {
447
+ return ;
448
+ }
449
+ await overlay . hide ( animated ) ;
450
+ const contentWithLifecycle =
451
+ overlay . overlayContent as unknown as ManagedOverlayContent ;
452
+ if ( typeof contentWithLifecycle . open !== 'undefined' ) {
453
+ contentWithLifecycle . open = false ;
454
+ }
455
+ if ( contentWithLifecycle . overlayCloseCallback ) {
456
+ const { trigger } = overlay ;
457
+ contentWithLifecycle . overlayCloseCallback ( { trigger } ) ;
458
+ }
410
459
411
- const index = this . overlays . indexOf ( overlay ) ;
412
- if ( index >= 0 ) {
413
- this . overlays . splice ( index , 1 ) ;
414
- }
415
- if ( this . overlays . length ) {
416
- const topOverlay = this . overlays [ this . overlays . length - 1 ] ;
417
- topOverlay . feature ( ) ;
418
- if (
419
- topOverlay . interaction === 'modal' ||
420
- topOverlay . hasModalRoot
421
- ) {
422
- topOverlay . focus ( ) ;
423
- } else {
424
- this . stopTabTrapping ( ) ;
425
- }
426
- } else {
427
- this . stopTabTrapping ( ) ;
428
- if (
429
- overlay . interaction === 'modal' ||
430
- ( ( overlay . interaction === 'replace' ||
431
- overlay . interaction === 'inline' ) &&
432
- ! overlay . tabbingAway )
433
- ) {
434
- const overlayRoot =
435
- overlay . overlayContent . getRootNode ( ) as ShadowRoot ;
436
- const overlayContentActiveElement =
437
- overlayRoot . activeElement ;
438
- const triggerRoot =
439
- overlay . trigger . getRootNode ( ) as ShadowRoot ;
440
- const triggerActiveElement = triggerRoot . activeElement ;
441
- if (
442
- overlay . overlayContent . contains (
443
- overlayContentActiveElement
444
- ) ||
445
- overlay . trigger
446
- . getRootNode ( )
447
- . contains ( triggerActiveElement ) ||
448
- ( triggerRoot . host &&
449
- triggerRoot . host === triggerActiveElement )
450
- ) {
451
- overlay . trigger . focus ( ) ;
452
- }
453
- }
454
- overlay . tabbingAway = false ;
455
- }
460
+ if ( overlay . state != 'dispose' ) return ;
456
461
457
- overlay . remove ( ) ;
458
- overlay . dispose ( ) ;
459
-
460
- overlay . trigger . dispatchEvent (
461
- new CustomEvent < OverlayOpenCloseDetail > ( 'sp-closed' , {
462
- bubbles : true ,
463
- composed : true ,
464
- cancelable : true ,
465
- detail : {
466
- interaction : overlay . interaction ,
467
- } ,
468
- } )
469
- ) ;
462
+ const index = this . overlays . indexOf ( overlay ) ;
463
+ if ( index >= 0 ) {
464
+ this . overlays . splice ( index , 1 ) ;
470
465
}
466
+
467
+ if ( this . overlays . length ) {
468
+ this . manageFocusAfterCloseWhenOverlaysRemain ( ) ;
469
+ } else {
470
+ this . manageFocusAfterCloseWhenLastOverlay ( overlay ) ;
471
+ }
472
+
473
+ overlay . remove ( ) ;
474
+ overlay . dispose ( ) ;
475
+
476
+ overlay . trigger . dispatchEvent (
477
+ new CustomEvent < OverlayOpenCloseDetail > ( 'sp-closed' , {
478
+ bubbles : true ,
479
+ composed : true ,
480
+ cancelable : true ,
481
+ detail : {
482
+ interaction : overlay . interaction ,
483
+ } ,
484
+ } )
485
+ ) ;
471
486
}
472
487
473
488
private closeTopOverlay ( ) : Promise < void > {
@@ -494,9 +509,7 @@ export class OverlayStack {
494
509
495
510
private handleKeyUp = ( event : KeyboardEvent ) : void => {
496
511
if ( event . code === 'Escape' ) {
497
- const overlay = this . topOverlay as ActiveOverlay ;
498
512
this . closeTopOverlay ( ) ;
499
- overlay && overlay . trigger . focus ( ) ;
500
513
}
501
514
} ;
502
515
0 commit comments