diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts
index dd2b266b528..308d7a3f0da 100644
--- a/src/lib/datepicker/datepicker.spec.ts
+++ b/src/lib/datepicker/datepicker.spec.ts
@@ -1,14 +1,19 @@
-import {async, ComponentFixture, TestBed} from '@angular/core/testing';
-import {MdDatepickerModule} from './index';
import {Component, ViewChild} from '@angular/core';
-import {MdDatepicker} from './datepicker';
-import {MdDatepickerInput} from './datepicker-input';
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {By} from '@angular/platform-browser';
-import {dispatchFakeEvent, dispatchMouseEvent} from '../core/testing/dispatch-events';
+import {MdDatepickerModule} from './index';
+import {MdDatepicker} from './datepicker';
+import {MdDatepickerInput} from './datepicker-input';
import {MdInputModule} from '../input/index';
-import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {MdNativeDateModule, DateAdapter, NativeDateAdapter} from '../core/datetime/index';
+import {ESCAPE} from '../core';
+import {
+ dispatchFakeEvent,
+ dispatchMouseEvent,
+ dispatchKeyboardEvent,
+} from '../core/testing/dispatch-events';
// When constructing a Date, the month is zero-based. This can be confusing, since people are
@@ -107,6 +112,23 @@ describe('MdDatepicker', () => {
});
}));
+ it('should close the popup when pressing ESCAPE', () => {
+ testComponent.datepicker.open();
+ fixture.detectChanges();
+
+ let content = document.querySelector('.cdk-overlay-pane md-datepicker-content');
+ expect(content).toBeTruthy('Expected datepicker to be open.');
+
+ let keyboadEvent = dispatchKeyboardEvent(content, 'keydown', ESCAPE);
+ fixture.detectChanges();
+
+ content = document.querySelector('.cdk-overlay-pane md-datepicker-content');
+
+ expect(content).toBeFalsy('Expected datepicker to be closed.');
+ expect(keyboadEvent.defaultPrevented)
+ .toBe(true, 'Expected default ESCAPE action to be prevented.');
+ });
+
it('close should close dialog', async(() => {
testComponent.touch = true;
fixture.detectChanges();
@@ -425,6 +447,30 @@ describe('MdDatepicker', () => {
let toggle = fixture.debugElement.query(By.css('button')).nativeElement;
expect(toggle.getAttribute('type')).toBe('button');
});
+
+ it('should restore focus to the toggle after the calendar is closed', () => {
+ let toggle = fixture.debugElement.query(By.css('button')).nativeElement;
+
+ fixture.componentInstance.touchUI = false;
+ fixture.detectChanges();
+
+ toggle.focus();
+ expect(document.activeElement).toBe(toggle, 'Expected toggle to be focused.');
+
+ fixture.componentInstance.datepicker.open();
+ fixture.detectChanges();
+
+ let pane = document.querySelector('.cdk-overlay-pane');
+
+ expect(pane).toBeTruthy('Expected calendar to be open.');
+ expect(pane.contains(document.activeElement))
+ .toBe(true, 'Expected focus to be inside the calendar.');
+
+ fixture.componentInstance.datepicker.close();
+ fixture.detectChanges();
+
+ expect(document.activeElement).toBe(toggle, 'Expected focus to be restored to toggle.');
+ });
});
describe('datepicker inside input-container', () => {
@@ -767,11 +813,12 @@ class DatepickerWithFormControl {
template: `
-
+
`,
})
class DatepickerWithToggle {
@ViewChild('d') datepicker: MdDatepicker;
+ touchUI = true;
}
diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts
index 76f35a73a37..ce0787ab5ad 100644
--- a/src/lib/datepicker/datepicker.ts
+++ b/src/lib/datepicker/datepicker.ts
@@ -20,7 +20,9 @@ import {
ViewContainerRef,
ViewEncapsulation,
NgZone,
+ Inject,
} from '@angular/core';
+import {DOCUMENT} from '@angular/platform-browser';
import {Overlay} from '../core/overlay/overlay';
import {OverlayRef} from '../core/overlay/overlay-ref';
import {ComponentPortal} from '../core/portal/portal';
@@ -77,16 +79,10 @@ export class MdDatepickerContent implements AfterContentInit {
* @param event The event.
*/
_handleKeydown(event: KeyboardEvent): void {
- switch (event.keyCode) {
- case ESCAPE:
- this.datepicker.close();
- break;
- default:
- // Return so that we don't preventDefault on keys that are not explicitly handled.
- return;
+ if (event.keyCode === ESCAPE) {
+ this.datepicker.close();
+ event.preventDefault();
}
-
- event.preventDefault();
}
}
@@ -158,6 +154,9 @@ export class MdDatepicker implements OnDestroy {
/** The input element this datepicker is associated with. */
private _datepickerInput: MdDatepickerInput;
+ /** The element that was focused before the datepicker was opened. */
+ private _focusedElementBeforeOpen: HTMLElement;
+
private _inputSubscription: Subscription;
constructor(private _dialog: MdDialog,
@@ -165,11 +164,12 @@ export class MdDatepicker implements OnDestroy {
private _ngZone: NgZone,
private _viewContainerRef: ViewContainerRef,
@Optional() private _dateAdapter: DateAdapter,
- @Optional() private _dir: Dir) {
+ @Optional() private _dir: Dir,
+ @Optional() @Inject(DOCUMENT) private _document: any) {
+
if (!this._dateAdapter) {
throw createMissingDateImplError('DateAdapter');
}
-
}
ngOnDestroy() {
@@ -213,6 +213,9 @@ export class MdDatepicker implements OnDestroy {
if (!this._datepickerInput) {
throw Error('Attempted to open an MdDatepicker with no associated input.');
}
+ if (this._document) {
+ this._focusedElementBeforeOpen = this._document.activeElement;
+ }
this.touchUi ? this._openAsDialog() : this._openAsPopup();
this.opened = true;
@@ -233,6 +236,11 @@ export class MdDatepicker implements OnDestroy {
if (this._calendarPortal && this._calendarPortal.isAttached) {
this._calendarPortal.detach();
}
+ if (this._focusedElementBeforeOpen && 'focus' in this._focusedElementBeforeOpen) {
+ this._focusedElementBeforeOpen.focus();
+ this._focusedElementBeforeOpen = null;
+ }
+
this.opened = false;
}