Skip to content

Commit

Permalink
fix(drawer): unable to close using keyboard if there are no focusable…
Browse files Browse the repository at this point in the history
… elements (#8783)

Fixes the drawer not trapping focus if it doesn't have any focusable elements, making it impossible to close using the keyboard.
  • Loading branch information
crisbeto authored and andrewseguin committed Dec 13, 2017
1 parent bd50fa6 commit fba3728
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 6 deletions.
35 changes: 30 additions & 5 deletions src/lib/sidenav/drawer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ describe('MatDrawer', () => {
DrawerSetToOpenedFalse,
DrawerSetToOpenedTrue,
DrawerDynamicPosition,
DrawerWitFocusableElements,
DrawerWithFocusableElements,
DrawerOpenBinding,
DrawerWithoutFocusableElements,
],
});

Expand Down Expand Up @@ -370,14 +371,14 @@ describe('MatDrawer', () => {
});

describe('focus trapping behavior', () => {
let fixture: ComponentFixture<DrawerWitFocusableElements>;
let testComponent: DrawerWitFocusableElements;
let fixture: ComponentFixture<DrawerWithFocusableElements>;
let testComponent: DrawerWithFocusableElements;
let drawer: MatDrawer;
let firstFocusableElement: HTMLElement;
let lastFocusableElement: HTMLElement;

beforeEach(() => {
fixture = TestBed.createComponent(DrawerWitFocusableElements);
fixture = TestBed.createComponent(DrawerWithFocusableElements);
testComponent = fixture.debugElement.componentInstance;
drawer = fixture.debugElement.query(By.directive(MatDrawer)).componentInstance;
firstFocusableElement = fixture.debugElement.query(By.css('.link1')).nativeElement;
Expand Down Expand Up @@ -417,6 +418,21 @@ describe('MatDrawer', () => {

expect(document.activeElement).toBe(lastFocusableElement);
}));

it('should focus the drawer if there are no focusable elements', fakeAsync(() => {
fixture.destroy();

const nonFocusableFixture = TestBed.createComponent(DrawerWithoutFocusableElements);
const drawerEl = nonFocusableFixture.debugElement.query(By.directive(MatDrawer));
nonFocusableFixture.detectChanges();

drawerEl.componentInstance.open();
nonFocusableFixture.detectChanges();
tick();

expect(document.activeElement).toBe(drawerEl.nativeElement);
}));

});
});

Expand Down Expand Up @@ -676,10 +692,19 @@ class DrawerDynamicPosition {
<a class="link2" href="#">link2</a>
</mat-drawer-container>`,
})
class DrawerWitFocusableElements {
class DrawerWithFocusableElements {
mode: string = 'over';
}

@Component({
template: `
<mat-drawer-container>
<mat-drawer position="start" mode="over">
<button disabled>Not focusable</button>
</mat-drawer>
</mat-drawer-container>`,
})
class DrawerWithoutFocusableElements {}

@Component({
template: `
Expand Down
14 changes: 13 additions & 1 deletion src/lib/sidenav/drawer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,21 +261,33 @@ export class MatDrawer implements AfterContentInit, AfterContentChecked, OnDestr
private _focusMonitor: FocusMonitor,
private _platform: Platform,
@Optional() @Inject(DOCUMENT) private _doc: any) {

this.openedChange.subscribe((opened: boolean) => {
if (opened) {
if (this._doc) {
this._elementFocusedBeforeDrawerWasOpened = this._doc.activeElement as HTMLElement;
}

if (this._isFocusTrapEnabled && this._focusTrap) {
this._focusTrap.focusInitialElementWhenReady();
this._trapFocus();
}
} else {
this._restoreFocus();
}
});
}

/** Traps focus inside the drawer. */
private _trapFocus() {
this._focusTrap.focusInitialElementWhenReady().then(hasMovedFocus => {
// If there were no focusable elements, focus the sidenav itself so the keyboard navigation
// still works. We need to check that `focus` is a function due to Universal.
if (!hasMovedFocus && typeof this._elementRef.nativeElement.focus === 'function') {
this._elementRef.nativeElement.focus();
}
});
}

/**
* If focus is currently inside the drawer, restores it to where it was before the drawer
* opened.
Expand Down

0 comments on commit fba3728

Please # to comment.