@@ -46,7 +46,7 @@ export class Dialog implements OnDestroy {
46
46
private _openDialogsAtThisLevel : DialogRef < any , any > [ ] = [ ] ;
47
47
private readonly _afterAllClosedAtThisLevel = new Subject < void > ( ) ;
48
48
private readonly _afterOpenedAtThisLevel = new Subject < DialogRef > ( ) ;
49
- private _ariaHiddenElements = new Map < Element , string | null > ( ) ;
49
+ private _inertElements = new Map < Element , [ ariaHidden : string | null , inert : string | null ] > ( ) ;
50
50
private _scrollStrategy = inject ( DIALOG_SCROLL_STRATEGY ) ;
51
51
52
52
/** Keeps track of the currently-open dialogs. */
@@ -157,7 +157,7 @@ export class Dialog implements OnDestroy {
157
157
ngOnDestroy ( ) {
158
158
// Make one pass over all the dialogs that need to be untracked, but should not be closed. We
159
159
// want to stop tracking the open dialog even if it hasn't been closed, because the tracking
160
- // determines when `aria-hidden ` is removed from elements outside the dialog.
160
+ // determines when `inert ` is removed from elements outside the dialog.
161
161
reverseForEach ( this . _openDialogsAtThisLevel , dialog => {
162
162
// Check for `false` specifically since we want `undefined` to be interpreted as `true`.
163
163
if ( dialog . config . closeOnDestroy === false ) {
@@ -340,18 +340,27 @@ export class Dialog implements OnDestroy {
340
340
if ( index > - 1 ) {
341
341
( this . openDialogs as DialogRef < R , C > [ ] ) . splice ( index , 1 ) ;
342
342
343
- // If all the dialogs were closed, remove/restore the `aria-hidden`
343
+ // If all the dialogs were closed, remove/restore the inert attribute
344
344
// to a the siblings and emit to the `afterAllClosed` stream.
345
345
if ( ! this . openDialogs . length ) {
346
- this . _ariaHiddenElements . forEach ( ( previousValue , element ) => {
347
- if ( previousValue ) {
348
- element . setAttribute ( 'aria-hidden' , previousValue ) ;
346
+ this . _inertElements . forEach ( ( previousValue , element ) => {
347
+ const [ ariaHidden , inert ] = previousValue ;
348
+
349
+ // Note: this code is somewhat repetitive, but we want to use static strings inside
350
+ // the `setAttribute` calls so that we don't trip up some internal XSS checks.
351
+ if ( ariaHidden ) {
352
+ element . setAttribute ( 'aria-hidden' , ariaHidden ) ;
349
353
} else {
350
354
element . removeAttribute ( 'aria-hidden' ) ;
351
355
}
352
- } ) ;
353
356
354
- this . _ariaHiddenElements . clear ( ) ;
357
+ if ( inert ) {
358
+ element . setAttribute ( 'inert' , inert ) ;
359
+ } else {
360
+ element . removeAttribute ( 'inert' ) ;
361
+ }
362
+ } ) ;
363
+ this . _inertElements . clear ( ) ;
355
364
356
365
if ( emitEvent ) {
357
366
this . _getAfterAllClosed ( ) . next ( ) ;
@@ -377,8 +386,15 @@ export class Dialog implements OnDestroy {
377
386
sibling . nodeName !== 'STYLE' &&
378
387
! sibling . hasAttribute ( 'aria-live' )
379
388
) {
380
- this . _ariaHiddenElements . set ( sibling , sibling . getAttribute ( 'aria-hidden' ) ) ;
389
+ const ariaHidden = sibling . getAttribute ( 'aria-hidden' ) ;
390
+ const inert = sibling . getAttribute ( 'inert' ) ;
391
+
392
+ // TODO(crisbeto): ideally we'd set only either `aria-hidden` or `inert` here, but
393
+ // at the moment of writing, some internal checks don't consider `inert` elements as
394
+ // removed from the a11y tree which reveals a bunch of pre-existing breakages.
395
+ this . _inertElements . set ( sibling , [ ariaHidden , inert ] ) ;
381
396
sibling . setAttribute ( 'aria-hidden' , 'true' ) ;
397
+ sibling . setAttribute ( 'inert' , 'true' ) ;
382
398
}
383
399
}
384
400
}
0 commit comments