diff --git a/src/material/sidenav/drawer.spec.ts b/src/material/sidenav/drawer.spec.ts index d65226cf1194..8d6e83ecb5a4 100644 --- a/src/material/sidenav/drawer.spec.ts +++ b/src/material/sidenav/drawer.spec.ts @@ -518,7 +518,7 @@ describe('MatDrawer', () => { expect(document.activeElement).toBe(firstFocusableElement); })); - it('should not trap focus when opened in "side" mode', fakeAsync(() => { + it('should not auto-focus by default when opened in "side" mode', fakeAsync(() => { testComponent.mode = 'side'; fixture.detectChanges(); lastFocusableElement.focus(); @@ -530,6 +530,19 @@ describe('MatDrawer', () => { expect(document.activeElement).toBe(lastFocusableElement); })); + it('should auto-focus when opened in "side" mode when enabled explicitly', fakeAsync(() => { + drawer.autoFocus = true; + testComponent.mode = 'side'; + fixture.detectChanges(); + lastFocusableElement.focus(); + + drawer.open(); + fixture.detectChanges(); + tick(); + + expect(document.activeElement).toBe(firstFocusableElement); + })); + it('should focus the drawer if there are no focusable elements', fakeAsync(() => { fixture.destroy(); @@ -545,7 +558,7 @@ describe('MatDrawer', () => { })); it('should be able to disable auto focus', fakeAsync(() => { - testComponent.autoFocus = false; + drawer.autoFocus = false; testComponent.mode = 'push'; fixture.detectChanges(); lastFocusableElement.focus(); @@ -981,7 +994,7 @@ class DrawerDynamicPosition { // to be focusable across all platforms. template: ` - + @@ -989,7 +1002,6 @@ class DrawerDynamicPosition { }) class DrawerWithFocusableElements { mode: string = 'over'; - autoFocus = true; } @Component({ diff --git a/src/material/sidenav/drawer.ts b/src/material/sidenav/drawer.ts index b5f113d893c9..934483a101ab 100644 --- a/src/material/sidenav/drawer.ts +++ b/src/material/sidenav/drawer.ts @@ -173,11 +173,22 @@ export class MatDrawer implements AfterContentInit, AfterContentChecked, OnDestr set disableClose(value: boolean) { this._disableClose = coerceBooleanProperty(value); } private _disableClose: boolean = false; - /** Whether the drawer should focus the first focusable element automatically when opened. */ + /** + * Whether the drawer should focus the first focusable element automatically when opened. + * Defaults to false in when `mode` is set to `side`, otherwise defaults to `true`. If explicitly + * enabled, focus will be moved into the sidenav in `side` mode as well. + */ @Input() - get autoFocus(): boolean { return this._autoFocus; } + get autoFocus(): boolean { + const value = this._autoFocus; + + // Note that usually we disable auto focusing in `side` mode, because we don't know how the + // sidenav is being used, but in some cases it still makes sense to do it. If the consumer + // explicitly enabled `autoFocus`, we take it as them always wanting to enable it. + return value == null ? this.mode !== 'side' : value; + } set autoFocus(value: boolean) { this._autoFocus = coerceBooleanProperty(value); } - private _autoFocus: boolean = true; + private _autoFocus: boolean | undefined; /** * Whether the drawer is opened. We overload this because we trigger an event when it @@ -253,11 +264,6 @@ export class MatDrawer implements AfterContentInit, AfterContentChecked, OnDestr */ readonly _modeChanged = new Subject(); - get _isFocusTrapEnabled(): boolean { - // The focus trap is only enabled when the drawer is open in any mode other than side. - return this.opened && this.mode !== 'side'; - } - constructor(private _elementRef: ElementRef, private _focusTrapFactory: FocusTrapFactory, private _focusMonitor: FocusMonitor, @@ -276,9 +282,7 @@ export class MatDrawer implements AfterContentInit, AfterContentChecked, OnDestr this._elementFocusedBeforeDrawerWasOpened = this._doc.activeElement as HTMLElement; } - if (this._isFocusTrapEnabled && this._focusTrap) { - this._trapFocus(); - } + this._takeFocus(); } else { this._restoreFocus(); } @@ -316,9 +320,12 @@ export class MatDrawer implements AfterContentInit, AfterContentChecked, OnDestr }); } - /** Traps focus inside the drawer. */ - private _trapFocus() { - if (!this.autoFocus) { + /** + * Moves focus into the drawer. Note that this works even if + * the focus trap is disabled in `side` mode. + */ + private _takeFocus() { + if (!this.autoFocus || !this._focusTrap) { return; } @@ -428,7 +435,8 @@ export class MatDrawer implements AfterContentInit, AfterContentChecked, OnDestr /** Updates the enabled state of the focus trap. */ private _updateFocusTrapState() { if (this._focusTrap) { - this._focusTrap.enabled = this._isFocusTrapEnabled; + // The focus trap is only enabled when the drawer is open in any mode other than side. + this._focusTrap.enabled = this.opened && this.mode !== 'side'; } } diff --git a/tools/public_api_guard/material/sidenav.d.ts b/tools/public_api_guard/material/sidenav.d.ts index bb81a8907070..2b351345768c 100644 --- a/tools/public_api_guard/material/sidenav.d.ts +++ b/tools/public_api_guard/material/sidenav.d.ts @@ -8,7 +8,6 @@ export declare class MatDrawer implements AfterContentInit, AfterContentChecked, _animationState: 'open-instant' | 'open' | 'void'; readonly _closedStream: Observable; _container?: MatDrawerContainer | undefined; - readonly _isFocusTrapEnabled: boolean; readonly _modeChanged: Subject; readonly _openedStream: Observable; readonly _width: number;