@@ -23,6 +23,7 @@ import {of as observableOf, Observable, Subject, defer} from 'rxjs';
2323import { DialogRef } from './dialog-ref' ;
2424import { DialogConfig } from './dialog-config' ;
2525import { Directionality } from '@angular/cdk/bidi' ;
26+ import { _supportsInert } from '@angular/cdk/platform' ;
2627import {
2728 ComponentType ,
2829 Overlay ,
@@ -44,7 +45,7 @@ export class Dialog implements OnDestroy {
4445 private _openDialogsAtThisLevel : DialogRef < any , any > [ ] = [ ] ;
4546 private readonly _afterAllClosedAtThisLevel = new Subject < void > ( ) ;
4647 private readonly _afterOpenedAtThisLevel = new Subject < DialogRef > ( ) ;
47- private _ariaHiddenElements = new Map < Element , string | null > ( ) ;
48+ private _inertElements = new Map < Element , [ ariaHidden : string | null , inert : string | null ] > ( ) ;
4849 private _scrollStrategy : ( ) => ScrollStrategy ;
4950
5051 /** Keeps track of the currently-open dialogs. */
@@ -162,7 +163,7 @@ export class Dialog implements OnDestroy {
162163 ngOnDestroy ( ) {
163164 // Make one pass over all the dialogs that need to be untracked, but should not be closed. We
164165 // want to stop tracking the open dialog even if it hasn't been closed, because the tracking
165- // determines when `aria-hidden ` is removed from elements outside the dialog.
166+ // determines when `inert ` is removed from elements outside the dialog.
166167 reverseForEach ( this . _openDialogsAtThisLevel , dialog => {
167168 // Check for `false` specifically since we want `undefined` to be interpreted as `true`.
168169 if ( dialog . config . closeOnDestroy === false ) {
@@ -350,18 +351,29 @@ export class Dialog implements OnDestroy {
350351 if ( index > - 1 ) {
351352 ( this . openDialogs as DialogRef < R , C > [ ] ) . splice ( index , 1 ) ;
352353
353- // If all the dialogs were closed, remove/restore the `aria-hidden`
354+ // If all the dialogs were closed, remove/restore the inert attribute
354355 // to a the siblings and emit to the `afterAllClosed` stream.
355356 if ( ! this . openDialogs . length ) {
356- this . _ariaHiddenElements . forEach ( ( previousValue , element ) => {
357- if ( previousValue ) {
358- element . setAttribute ( 'aria-hidden' , previousValue ) ;
357+ this . _inertElements . forEach ( ( previousValue , element ) => {
358+ const [ ariaHidden , inert ] = previousValue ;
359+
360+ // Note: this code is somewhat repetitive, but we want to use static strings inside
361+ // the `setAttribute` calls so that we don't trip up some internal XSS checks.
362+ if ( ariaHidden ) {
363+ element . setAttribute ( 'aria-hidden' , ariaHidden ) ;
359364 } else {
360365 element . removeAttribute ( 'aria-hidden' ) ;
361366 }
362- } ) ;
363367
364- this . _ariaHiddenElements . clear ( ) ;
368+ if ( _supportsInert ( ) ) {
369+ if ( inert ) {
370+ element . setAttribute ( 'inert' , inert ) ;
371+ } else {
372+ element . removeAttribute ( 'inert' ) ;
373+ }
374+ }
375+ } ) ;
376+ this . _inertElements . clear ( ) ;
365377
366378 if ( emitEvent ) {
367379 this . _getAfterAllClosed ( ) . next ( ) ;
@@ -387,8 +399,17 @@ export class Dialog implements OnDestroy {
387399 sibling . nodeName !== 'STYLE' &&
388400 ! sibling . hasAttribute ( 'aria-live' )
389401 ) {
390- this . _ariaHiddenElements . set ( sibling , sibling . getAttribute ( 'aria-hidden' ) ) ;
402+ const ariaHidden = sibling . getAttribute ( 'aria-hidden' ) ;
403+ const inert = _supportsInert ( ) ? sibling . getAttribute ( 'inert' ) : null ;
404+
405+ // TODO(crisbeto): ideally we'd set only either `aria-hidden` or `inert` here, but
406+ // at the moment of writing, some internal checks don't consider `inert` elements as
407+ // removed from the a11y tree which reveals a bunch of pre-existing breakages.
408+ this . _inertElements . set ( sibling , [ ariaHidden , inert ] ) ;
391409 sibling . setAttribute ( 'aria-hidden' , 'true' ) ;
410+ if ( _supportsInert ( ) ) {
411+ sibling . setAttribute ( 'inert' , 'true' ) ;
412+ }
392413 }
393414 }
394415 }
0 commit comments