mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(datepicker): restore focus to trigger element (#4804)
This commit is contained in:
committed by
Kara
parent
223b237741
commit
8860090cea
@@ -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: `
|
||||
<input [mdDatepicker]="d">
|
||||
<button [mdDatepickerToggle]="d"></button>
|
||||
<md-datepicker #d [touchUi]="true"></md-datepicker>
|
||||
<md-datepicker #d [touchUi]="touchUI"></md-datepicker>
|
||||
`,
|
||||
})
|
||||
class DatepickerWithToggle {
|
||||
@ViewChild('d') datepicker: MdDatepicker<Date>;
|
||||
touchUI = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<D> 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<D> implements OnDestroy {
|
||||
/** The input element this datepicker is associated with. */
|
||||
private _datepickerInput: MdDatepickerInput<D>;
|
||||
|
||||
/** 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<D> implements OnDestroy {
|
||||
private _ngZone: NgZone,
|
||||
private _viewContainerRef: ViewContainerRef,
|
||||
@Optional() private _dateAdapter: DateAdapter<D>,
|
||||
@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<D> 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<D> 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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user