Skip to content

Commit a617151

Browse files
committed
fix(sidenav): move focus into sidenav in side mode autoFocus enabled explicitly
This PR is the result of the discussions in #17967. Currently we don't move focus into the sidenav if it's in `side` mode, because we don't know the context that the component is used in, however in some cases it makes sense to move focus anyway. These changes make it so that if the consumer explicitly opted into `autoFocus`, we always move focus into the sidenav, no matter what mode it's in.
1 parent 093d007 commit a617151

File tree

3 files changed

+34
-18
lines changed

3 files changed

+34
-18
lines changed

src/material/sidenav/drawer.spec.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ describe('MatDrawer', () => {
518518
expect(document.activeElement).toBe(firstFocusableElement);
519519
}));
520520

521-
it('should not trap focus when opened in "side" mode', fakeAsync(() => {
521+
it('should not trap focus by default when opened in "side" mode', fakeAsync(() => {
522522
testComponent.mode = 'side';
523523
fixture.detectChanges();
524524
lastFocusableElement.focus();
@@ -530,6 +530,19 @@ describe('MatDrawer', () => {
530530
expect(document.activeElement).toBe(lastFocusableElement);
531531
}));
532532

533+
it('should trap focus when opened in "side" mode when enabled explicitly', fakeAsync(() => {
534+
drawer.autoFocus = true;
535+
testComponent.mode = 'side';
536+
fixture.detectChanges();
537+
lastFocusableElement.focus();
538+
539+
drawer.open();
540+
fixture.detectChanges();
541+
tick();
542+
543+
expect(document.activeElement).toBe(firstFocusableElement);
544+
}));
545+
533546
it('should focus the drawer if there are no focusable elements', fakeAsync(() => {
534547
fixture.destroy();
535548

@@ -545,7 +558,7 @@ describe('MatDrawer', () => {
545558
}));
546559

547560
it('should be able to disable auto focus', fakeAsync(() => {
548-
testComponent.autoFocus = false;
561+
drawer.autoFocus = false;
549562
testComponent.mode = 'push';
550563
fixture.detectChanges();
551564
lastFocusableElement.focus();
@@ -981,15 +994,14 @@ class DrawerDynamicPosition {
981994
// to be focusable across all platforms.
982995
template: `
983996
<mat-drawer-container>
984-
<mat-drawer position="start" [mode]="mode" [autoFocus]="autoFocus">
997+
<mat-drawer position="start" [mode]="mode">
985998
<input type="text" class="input1"/>
986999
</mat-drawer>
9871000
<input type="text" class="input2"/>
9881001
</mat-drawer-container>`,
9891002
})
9901003
class DrawerWithFocusableElements {
9911004
mode: string = 'over';
992-
autoFocus = true;
9931005
}
9941006

9951007
@Component({

src/material/sidenav/drawer.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,22 @@ export class MatDrawer implements AfterContentInit, AfterContentChecked, OnDestr
173173
set disableClose(value: boolean) { this._disableClose = coerceBooleanProperty(value); }
174174
private _disableClose: boolean = false;
175175

176-
/** Whether the drawer should focus the first focusable element automatically when opened. */
176+
/**
177+
* Whether the drawer should focus the first focusable element automatically when opened.
178+
* Defaults to false in when `mode` is set to `side`, otherwise defaults to `true`. If explicitly
179+
* enabled, focus will be moved into the sidenav in `side` mode as well.
180+
*/
177181
@Input()
178-
get autoFocus(): boolean { return this._autoFocus; }
182+
get autoFocus(): boolean {
183+
const value = this._autoFocus;
184+
185+
// Note that usually we disable auto focusing in `side` mode, because we don't know how the
186+
// sidenav is being used, but in some cases it still makes sense to do it. If the consumer
187+
// explicitly enabled `autoFocus`, we take it as them always wanting to enable it.
188+
return value == null ? this.mode !== 'side' : value;
189+
}
179190
set autoFocus(value: boolean) { this._autoFocus = coerceBooleanProperty(value); }
180-
private _autoFocus: boolean = true;
191+
private _autoFocus: boolean | undefined;
181192

182193
/**
183194
* 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
253264
*/
254265
readonly _modeChanged = new Subject<void>();
255266

256-
get _isFocusTrapEnabled(): boolean {
257-
// The focus trap is only enabled when the drawer is open in any mode other than side.
258-
return this.opened && this.mode !== 'side';
259-
}
260-
261267
constructor(private _elementRef: ElementRef<HTMLElement>,
262268
private _focusTrapFactory: FocusTrapFactory,
263269
private _focusMonitor: FocusMonitor,
@@ -276,9 +282,7 @@ export class MatDrawer implements AfterContentInit, AfterContentChecked, OnDestr
276282
this._elementFocusedBeforeDrawerWasOpened = this._doc.activeElement as HTMLElement;
277283
}
278284

279-
if (this._isFocusTrapEnabled && this._focusTrap) {
280-
this._trapFocus();
281-
}
285+
this._trapFocus();
282286
} else {
283287
this._restoreFocus();
284288
}
@@ -318,7 +322,7 @@ export class MatDrawer implements AfterContentInit, AfterContentChecked, OnDestr
318322

319323
/** Traps focus inside the drawer. */
320324
private _trapFocus() {
321-
if (!this.autoFocus) {
325+
if (!this.autoFocus || !this._focusTrap) {
322326
return;
323327
}
324328

@@ -428,7 +432,8 @@ export class MatDrawer implements AfterContentInit, AfterContentChecked, OnDestr
428432
/** Updates the enabled state of the focus trap. */
429433
private _updateFocusTrapState() {
430434
if (this._focusTrap) {
431-
this._focusTrap.enabled = this._isFocusTrapEnabled;
435+
// The focus trap is only enabled when the drawer is open in any mode other than side.
436+
this._focusTrap.enabled = this.opened && this.mode !== 'side';
432437
}
433438
}
434439

tools/public_api_guard/material/sidenav.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ export declare class MatDrawer implements AfterContentInit, AfterContentChecked,
88
_animationState: 'open-instant' | 'open' | 'void';
99
readonly _closedStream: Observable<void>;
1010
_container?: MatDrawerContainer | undefined;
11-
readonly _isFocusTrapEnabled: boolean;
1211
readonly _modeChanged: Subject<void>;
1312
readonly _openedStream: Observable<void>;
1413
readonly _width: number;

0 commit comments

Comments
 (0)