Skip to content

Commit 3bc82f6

Browse files
mmalerbaandrewseguin
authored andcommitted
fix(sidenav): don't restore focus if focus isn't inside sidenav (#4578)
* fix(sidenav): don't restore focus if focus isn't inside sidenav * inject document
1 parent b489fdd commit 3bc82f6

File tree

2 files changed

+58
-16
lines changed

2 files changed

+58
-16
lines changed

src/lib/sidenav/sidenav.spec.ts

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {fakeAsync, async, tick, ComponentFixture, TestBed} from '@angular/core/testing';
2-
import {Component, ViewChild} from '@angular/core';
2+
import {Component, ElementRef, ViewChild} from '@angular/core';
33
import {By} from '@angular/platform-browser';
44
import {MdSidenav, MdSidenavModule, MdSidenavToggleResult, MdSidenavContainer} from './index';
55
import {A11yModule} from '../core/a11y/index';
@@ -310,19 +310,20 @@ describe('MdSidenav', () => {
310310
expect(testComponent.closeCount).toBe(0);
311311
}));
312312

313-
it('should restore focus to the trigger element on close', fakeAsync(() => {
313+
it('should restore focus on close if focus is inside sidenav', fakeAsync(() => {
314314
let fixture = TestBed.createComponent(BasicTestApp);
315315
let sidenav: MdSidenav = fixture.debugElement
316316
.query(By.directive(MdSidenav)).componentInstance;
317-
let trigger = document.createElement('button');
317+
let openButton = fixture.componentInstance.openButton.nativeElement;
318+
let sidenavButton = fixture.componentInstance.sidenavButton.nativeElement;
318319

319-
document.body.appendChild(trigger);
320-
trigger.focus();
320+
openButton.focus();
321321
sidenav.open();
322322

323323
fixture.detectChanges();
324324
endSidenavTransition(fixture);
325325
tick();
326+
sidenavButton.focus();
326327

327328
sidenav.close();
328329

@@ -331,9 +332,32 @@ describe('MdSidenav', () => {
331332
tick();
332333

333334
expect(document.activeElement)
334-
.toBe(trigger, 'Expected focus to be restored to the trigger on close.');
335+
.toBe(openButton, 'Expected focus to be restored to the open button on close.');
336+
}));
337+
338+
it('should not restore focus on close if focus is outside sidenav', fakeAsync(() => {
339+
let fixture = TestBed.createComponent(BasicTestApp);
340+
let sidenav: MdSidenav = fixture.debugElement
341+
.query(By.directive(MdSidenav)).componentInstance;
342+
let openButton = fixture.componentInstance.openButton.nativeElement;
343+
let closeButton = fixture.componentInstance.closeButton.nativeElement;
344+
345+
openButton.focus();
346+
sidenav.open();
347+
348+
fixture.detectChanges();
349+
endSidenavTransition(fixture);
350+
tick();
351+
closeButton.focus();
352+
353+
sidenav.close();
335354

336-
trigger.parentNode.removeChild(trigger);
355+
fixture.detectChanges();
356+
endSidenavTransition(fixture);
357+
tick();
358+
359+
expect(document.activeElement)
360+
.toBe(closeButton, 'Expected focus not to be restored to the open button on close.');
337361
}));
338362
});
339363

@@ -508,10 +532,10 @@ class SidenavContainerTwoSidenavTestApp {
508532
(open)="open()"
509533
(close-start)="closeStart()"
510534
(close)="close()">
511-
Content.
535+
<button #sidenavButton>Content.</button>
512536
</md-sidenav>
513-
<button (click)="sidenav.open()" class="open"></button>
514-
<button (click)="sidenav.close()" class="close"></button>
537+
<button (click)="sidenav.open()" class="open" #openButton></button>
538+
<button (click)="sidenav.close()" class="close" #closeButton></button>
515539
</md-sidenav-container>`,
516540
})
517541
class BasicTestApp {
@@ -521,6 +545,10 @@ class BasicTestApp {
521545
closeCount: number = 0;
522546
backdropClickedCount: number = 0;
523547

548+
@ViewChild('sidenavButton') sidenavButton: ElementRef;
549+
@ViewChild('openButton') openButton: ElementRef;
550+
@ViewChild('closeButton') closeButton: ElementRef;
551+
524552
openStart() {
525553
this.openStartCount++;
526554
}

src/lib/sidenav/sidenav.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ import {
1212
Renderer2,
1313
ViewEncapsulation,
1414
NgZone,
15-
OnDestroy,
15+
OnDestroy, Inject,
1616
} from '@angular/core';
1717
import {Dir, coerceBooleanProperty} from '../core';
1818
import {FocusTrapFactory, FocusTrap} from '../core/a11y/focus-trap';
1919
import {ESCAPE} from '../core/keyboard/keycodes';
2020
import 'rxjs/add/operator/first';
21+
import {DOCUMENT} from '@angular/platform-browser';
2122

2223

2324
/** Throws an exception when two MdSidenav are matching the same side. */
@@ -126,24 +127,37 @@ export class MdSidenav implements AfterContentInit, OnDestroy {
126127
* @param _elementRef The DOM element reference. Used for transition and width calculation.
127128
* If not available we do not hook on transitions.
128129
*/
129-
constructor(private _elementRef: ElementRef, private _focusTrapFactory: FocusTrapFactory) {
130+
constructor(private _elementRef: ElementRef,
131+
private _focusTrapFactory: FocusTrapFactory,
132+
@Optional() @Inject(DOCUMENT) private _doc: any) {
130133
this.onOpen.subscribe(() => {
131-
this._elementFocusedBeforeSidenavWasOpened = document.activeElement as HTMLElement;
134+
if (this._doc) {
135+
this._elementFocusedBeforeSidenavWasOpened = this._doc.activeElement as HTMLElement;
136+
}
132137

133138
if (this.isFocusTrapEnabled && this._focusTrap) {
134139
this._focusTrap.focusInitialElementWhenReady();
135140
}
136141
});
137142

138-
this.onClose.subscribe(() => {
143+
this.onClose.subscribe(() => this._restoreFocus());
144+
}
145+
146+
/**
147+
* If focus is currently inside the sidenav, restores it to where it was before the sidenav
148+
* opened.
149+
*/
150+
private _restoreFocus() {
151+
let activeEl = this._doc && this._doc.activeElement;
152+
if (activeEl && this._elementRef.nativeElement.contains(activeEl)) {
139153
if (this._elementFocusedBeforeSidenavWasOpened instanceof HTMLElement) {
140154
this._elementFocusedBeforeSidenavWasOpened.focus();
141155
} else {
142156
this._elementRef.nativeElement.blur();
143157
}
158+
}
144159

145-
this._elementFocusedBeforeSidenavWasOpened = null;
146-
});
160+
this._elementFocusedBeforeSidenavWasOpened = null;
147161
}
148162

149163
ngAfterContentInit() {

0 commit comments

Comments
 (0)