@@ -21,6 +21,7 @@ import {of as observableOf, Observable, Subject, defer} from 'rxjs';
2121import { DialogRef } from './dialog-ref' ;
2222import { DialogConfig } from './dialog-config' ;
2323import { Directionality } from '@angular/cdk/bidi' ;
24+ import { _supportsInert } from '@angular/cdk/platform' ;
2425import {
2526 ComponentType ,
2627 Overlay ,
@@ -47,7 +48,7 @@ export class Dialog implements OnDestroy {
4748 private _openDialogsAtThisLevel : DialogRef < any , any > [ ] = [ ] ;
4849 private readonly _afterAllClosedAtThisLevel = new Subject < void > ( ) ;
4950 private readonly _afterOpenedAtThisLevel = new Subject < DialogRef > ( ) ;
50- private _ariaHiddenElements = new Map < Element , string | null > ( ) ;
51+ private _inertElements = new Map < Element , [ ariaHidden : string | null , inert : string | null ] > ( ) ;
5152 private _scrollStrategy = inject ( DIALOG_SCROLL_STRATEGY ) ;
5253
5354 /** Keeps track of the currently-open dialogs. */
@@ -158,7 +159,7 @@ export class Dialog implements OnDestroy {
158159 ngOnDestroy ( ) {
159160 // Make one pass over all the dialogs that need to be untracked, but should not be closed. We
160161 // want to stop tracking the open dialog even if it hasn't been closed, because the tracking
161- // determines when `aria-hidden ` is removed from elements outside the dialog.
162+ // determines when `inert ` is removed from elements outside the dialog.
162163 reverseForEach ( this . _openDialogsAtThisLevel , dialog => {
163164 // Check for `false` specifically since we want `undefined` to be interpreted as `true`.
164165 if ( dialog . config . closeOnDestroy === false ) {
@@ -347,18 +348,29 @@ export class Dialog implements OnDestroy {
347348 if ( index > - 1 ) {
348349 ( this . openDialogs as DialogRef < R , C > [ ] ) . splice ( index , 1 ) ;
349350
350- // If all the dialogs were closed, remove/restore the `aria-hidden`
351+ // If all the dialogs were closed, remove/restore the inert attribute
351352 // to a the siblings and emit to the `afterAllClosed` stream.
352353 if ( ! this . openDialogs . length ) {
353- this . _ariaHiddenElements . forEach ( ( previousValue , element ) => {
354- if ( previousValue ) {
355- element . setAttribute ( 'aria-hidden' , previousValue ) ;
354+ this . _inertElements . forEach ( ( previousValue , element ) => {
355+ const [ ariaHidden , inert ] = previousValue ;
356+
357+ // Note: this code is somewhat repetitive, but we want to use static strings inside
358+ // the `setAttribute` calls so that we don't trip up some internal XSS checks.
359+ if ( ariaHidden ) {
360+ element . setAttribute ( 'aria-hidden' , ariaHidden ) ;
356361 } else {
357362 element . removeAttribute ( 'aria-hidden' ) ;
358363 }
359- } ) ;
360364
361- this . _ariaHiddenElements . clear ( ) ;
365+ if ( _supportsInert ( ) ) {
366+ if ( inert ) {
367+ element . setAttribute ( 'inert' , inert ) ;
368+ } else {
369+ element . removeAttribute ( 'inert' ) ;
370+ }
371+ }
372+ } ) ;
373+ this . _inertElements . clear ( ) ;
362374
363375 if ( emitEvent ) {
364376 this . _getAfterAllClosed ( ) . next ( ) ;
@@ -384,8 +396,17 @@ export class Dialog implements OnDestroy {
384396 sibling . nodeName !== 'STYLE' &&
385397 ! sibling . hasAttribute ( 'aria-live' )
386398 ) {
387- this . _ariaHiddenElements . set ( sibling , sibling . getAttribute ( 'aria-hidden' ) ) ;
399+ const ariaHidden = sibling . getAttribute ( 'aria-hidden' ) ;
400+ const inert = _supportsInert ( ) ? sibling . getAttribute ( 'inert' ) : null ;
401+
402+ // TODO(crisbeto): ideally we'd set only either `aria-hidden` or `inert` here, but
403+ // at the moment of writing, some internal checks don't consider `inert` elements as
404+ // removed from the a11y tree which reveals a bunch of pre-existing breakages.
405+ this . _inertElements . set ( sibling , [ ariaHidden , inert ] ) ;
388406 sibling . setAttribute ( 'aria-hidden' , 'true' ) ;
407+ if ( _supportsInert ( ) ) {
408+ sibling . setAttribute ( 'inert' , 'true' ) ;
409+ }
389410 }
390411 }
391412 }
0 commit comments