Skip to content

Commit a4e2b32

Browse files
authored
fix(material/sidenav): more robust reset logic for inert attribute (#33257)
In #33247 I added some logic to mark the drawer content as `inert` while it's closed. That logic is too brittle and it seems to fail to reset the attribute if the user calls `close` from the `backdropClick` callback. These changes switch to a more robust approach where we have have the reset logic in a single place and we always execute it, rather than trying to do it right when focus is moved in/out of the sidenav.
1 parent 53034eb commit a4e2b32

2 files changed

Lines changed: 21 additions & 18 deletions

File tree

goldens/material/sidenav/index.api.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,11 @@ export class MatDrawerContent extends CdkScrollable implements AfterContentInit
122122
// (undocumented)
123123
_container: MatDrawerContainer;
124124
// (undocumented)
125+
_drawerToggled(drawer: MatDrawer): void;
126+
// (undocumented)
125127
ngAfterContentInit(): void;
126128
protected _shouldBeHidden(): boolean;
127129
// (undocumented)
128-
_updateInert(): void;
129-
// (undocumented)
130130
static ɵcmp: i0.ɵɵComponentDeclaration<MatDrawerContent, "mat-drawer-content", never, {}, {}, never, ["*"], true, never>;
131131
// (undocumented)
132132
static ɵfac: i0.ɵɵFactoryDeclaration<MatDrawerContent, never>;

src/material/sidenav/drawer.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import {
4444
signal,
4545
} from '@angular/core';
4646
import {merge, Observable, Subject} from 'rxjs';
47-
import {debounceTime, filter, map, mapTo, startWith, take, takeUntil} from 'rxjs/operators';
47+
import {debounceTime, delay, filter, map, mapTo, startWith, take, takeUntil} from 'rxjs/operators';
4848
import {_animationsDisabled} from '../core';
4949

5050
/**
@@ -100,14 +100,30 @@ export class MatDrawerContent extends CdkScrollable implements AfterContentInit
100100
private _platform = inject(Platform);
101101
private _changeDetectorRef = inject(ChangeDetectorRef);
102102
private _element = inject<ElementRef<HTMLElement>>(ElementRef);
103+
private _ngZone = inject(NgZone);
103104
private _isInert = false;
104105
_container = inject(MatDrawerContainer);
105106

106107
ngAfterContentInit() {
107108
this._container._contentMarginChanges.subscribe(() => this._changeDetectorRef.markForCheck());
108109
}
109110

110-
_updateInert() {
111+
_drawerToggled(drawer: MatDrawer) {
112+
if (drawer.opened) {
113+
// If the drawer is being opened, we need to wait until the animation is done before marking
114+
// the content is inert, because the drawer moves focus during the animation. We add a delay
115+
// to be safe.
116+
this._ngZone.runOutsideAngular(() => {
117+
drawer._animationEnd.pipe(delay(50), take(1)).subscribe(() => this._updateInert());
118+
});
119+
} else {
120+
// When the drawer is closing, we need to remove `inert` immediately so
121+
// the elements that focus is being restored to can become focusable.
122+
this._updateInert();
123+
}
124+
}
125+
126+
private _updateInert() {
111127
const newValue = this._container._isShowingBackdrop();
112128

113129
if (newValue !== this._isInert) {
@@ -433,38 +449,24 @@ export class MatDrawer implements AfterViewInit, OnDestroy {
433449
if (!hasMovedFocus && typeof element.focus === 'function') {
434450
element.focus();
435451
}
436-
437-
// When capturing focus, we need to delay making the
438-
// container inert until focus has actually been moved.
439-
this._notifyContentFocus();
440452
},
441453
{injector: this._injector},
442454
);
443455
break;
444456
case 'first-heading':
445457
this._focusByCssSelector('h1, h2, h3, h4, h5, h6, [role="heading"]');
446-
this._notifyContentFocus();
447458
break;
448459
default:
449460
this._focusByCssSelector(this.autoFocus!);
450-
this._notifyContentFocus();
451461
break;
452462
}
453463
}
454464

455-
private _notifyContentFocus() {
456-
(this._container?._content || this._container?._userContent)?._updateInert();
457-
}
458-
459465
/**
460466
* Restores focus to the element that was originally focused when the drawer opened.
461467
* If no element was focused at that time, the focus will be restored to the drawer.
462468
*/
463469
private _restoreFocus(focusOrigin: Exclude<FocusOrigin, null>) {
464-
// When restoring focus, we need remove `inert` as early as possible,
465-
// because the element needs to become focusable before we can focus it.
466-
this._notifyContentFocus();
467-
468470
if (this.autoFocus === 'dialog') {
469471
return;
470472
}
@@ -577,6 +579,7 @@ export class MatDrawer implements AfterViewInit, OnDestroy {
577579
}
578580

579581
this._opened.set(isOpen);
582+
(this._container?._content || this._container?._userContent)?._drawerToggled(this);
580583

581584
if (this._container?._transitionsEnabled) {
582585
// Note: it's important to set this as early as possible,

0 commit comments

Comments
 (0)