mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(dialog): escape key not working once element loses focus (#3082)
Fixes issue in the dialog that prevented the user from being able to close via the escape key, if any of the elements inside the dialog loses focus. Fixes #3009.
This commit is contained in:
committed by
Andrew Seguin
parent
1547440a04
commit
a08dc55802
@@ -35,6 +35,16 @@ describe('dialog', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should close by pressing escape when the first tabbable element has lost focus', () => {
|
||||
element(by.id('default')).click();
|
||||
|
||||
waitForDialog().then(() => {
|
||||
clickElementAtPoint('md-dialog-container', { x: 0, y: 0 });
|
||||
pressKeys(Key.ESCAPE);
|
||||
expectToExist('md-dialog-container', false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should close by clicking on the "close" button', () => {
|
||||
element(by.id('default')).click();
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import 'rxjs/add/operator/first';
|
||||
host: {
|
||||
'[class.mat-dialog-container]': 'true',
|
||||
'[attr.role]': 'dialogConfig?.role',
|
||||
'(keydown.escape)': 'handleEscapeKey()',
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
@@ -93,16 +92,6 @@ export class MdDialogContainer extends BasePortalHost implements OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the user pressing the Escape key.
|
||||
* @docs-private
|
||||
*/
|
||||
handleEscapeKey() {
|
||||
if (!this.dialogConfig.disableClose) {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
// When the dialog is destroyed, return focus to the element that originally had it before
|
||||
// the dialog was opened. Wait for the DOM to finish settling before changing the focus so
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {OverlayRef} from '../core';
|
||||
import {MdDialogConfig} from './dialog-config';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
|
||||
@@ -17,7 +18,7 @@ export class MdDialogRef<T> {
|
||||
/** Subject for notifying the user that the dialog has finished closing. */
|
||||
private _afterClosed: Subject<any> = new Subject();
|
||||
|
||||
constructor(private _overlayRef: OverlayRef) { }
|
||||
constructor(private _overlayRef: OverlayRef, public config: MdDialogConfig) { }
|
||||
|
||||
/**
|
||||
* Close the dialog.
|
||||
|
||||
@@ -15,13 +15,12 @@ import {NgModule,
|
||||
Injector,
|
||||
Inject,
|
||||
} from '@angular/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {MdDialogModule} from './index';
|
||||
import {MdDialog} from './dialog';
|
||||
import {OverlayContainer} from '../core';
|
||||
import {MdDialogRef} from './dialog-ref';
|
||||
import {MdDialogContainer} from './dialog-container';
|
||||
import {MD_DIALOG_DATA} from './dialog-injector';
|
||||
import {ESCAPE} from '../core/keyboard/keycodes';
|
||||
|
||||
|
||||
describe('MdDialog', () => {
|
||||
@@ -136,11 +135,7 @@ describe('MdDialog', () => {
|
||||
|
||||
viewContainerFixture.detectChanges();
|
||||
|
||||
let dialogContainer: MdDialogContainer =
|
||||
viewContainerFixture.debugElement.query(By.directive(MdDialogContainer)).componentInstance;
|
||||
|
||||
// Fake the user pressing the escape key by calling the handler directly.
|
||||
dialogContainer.handleEscapeKey();
|
||||
dispatchKeydownEvent(document, ESCAPE);
|
||||
|
||||
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
|
||||
});
|
||||
@@ -324,11 +319,7 @@ describe('MdDialog', () => {
|
||||
|
||||
viewContainerFixture.detectChanges();
|
||||
|
||||
let dialogContainer: MdDialogContainer = viewContainerFixture.debugElement.query(
|
||||
By.directive(MdDialogContainer)).componentInstance;
|
||||
|
||||
// Fake the user pressing the escape key by calling the handler directly.
|
||||
dialogContainer.handleEscapeKey();
|
||||
dispatchKeydownEvent(document, ESCAPE);
|
||||
|
||||
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeTruthy();
|
||||
});
|
||||
@@ -565,3 +556,15 @@ const TEST_DIRECTIVES = [
|
||||
],
|
||||
})
|
||||
class DialogTestModule { }
|
||||
|
||||
|
||||
// TODO(crisbeto): switch to using function from common testing utils once #2943 is merged.
|
||||
function dispatchKeydownEvent(element: Node, keyCode: number) {
|
||||
let event: any = document.createEvent('KeyboardEvent');
|
||||
(event.initKeyEvent || event.initKeyboardEvent).bind(event)(
|
||||
'keydown', true, true, window, 0, 0, 0, 0, 0, keyCode);
|
||||
Object.defineProperty(event, 'keyCode', {
|
||||
get: function() { return keyCode; }
|
||||
});
|
||||
element.dispatchEvent(event);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {Observable} from 'rxjs/Observable';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
import {Overlay, OverlayRef, ComponentType, OverlayState, ComponentPortal} from '../core';
|
||||
import {extendObject} from '../core/util/object-extend';
|
||||
import {ESCAPE} from '../core/keyboard/keycodes';
|
||||
import {DialogInjector} from './dialog-injector';
|
||||
import {MdDialogConfig} from './dialog-config';
|
||||
import {MdDialogRef} from './dialog-ref';
|
||||
@@ -22,6 +23,7 @@ export class MdDialog {
|
||||
private _openDialogsAtThisLevel: MdDialogRef<any>[] = [];
|
||||
private _afterAllClosedAtThisLevel = new Subject<void>();
|
||||
private _afterOpenAtThisLevel = new Subject<MdDialogRef<any>>();
|
||||
private _boundKeydown = this._handleKeydown.bind(this);
|
||||
|
||||
/** Keeps track of the currently-open dialogs. */
|
||||
get _openDialogs(): MdDialogRef<any>[] {
|
||||
@@ -65,6 +67,10 @@ export class MdDialog {
|
||||
let dialogRef =
|
||||
this._attachDialogContent(componentOrTemplateRef, dialogContainer, overlayRef, config);
|
||||
|
||||
if (!this._openDialogs.length && !this._parentDialog) {
|
||||
document.addEventListener('keydown', this._boundKeydown);
|
||||
}
|
||||
|
||||
this._openDialogs.push(dialogRef);
|
||||
dialogRef.afterClosed().subscribe(() => this._removeOpenDialog(dialogRef));
|
||||
this._afterOpen.next(dialogRef);
|
||||
@@ -129,7 +135,7 @@ export class MdDialog {
|
||||
config?: MdDialogConfig): MdDialogRef<T> {
|
||||
// Create a reference to the dialog we're creating in order to give the user a handle
|
||||
// to modify and close it.
|
||||
let dialogRef = <MdDialogRef<T>> new MdDialogRef(overlayRef);
|
||||
let dialogRef = <MdDialogRef<T>> new MdDialogRef(overlayRef, config);
|
||||
|
||||
if (!config.disableClose) {
|
||||
// When the dialog backdrop is clicked, we want to close it.
|
||||
@@ -199,9 +205,22 @@ export class MdDialog {
|
||||
// no open dialogs are left, call next on afterAllClosed Subject
|
||||
if (!this._openDialogs.length) {
|
||||
this._afterAllClosed.next();
|
||||
document.removeEventListener('keydown', this._boundKeydown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles global key presses while there are open dialogs. Closes the
|
||||
* top dialog when the user presses escape.
|
||||
*/
|
||||
private _handleKeydown(event: KeyboardEvent): void {
|
||||
let topDialog = this._openDialogs[this._openDialogs.length - 1];
|
||||
|
||||
if (event.keyCode === ESCAPE && topDialog && !topDialog.config.disableClose) {
|
||||
topDialog.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user