Skip to content

Commit afaa2dc

Browse files
crisbetoandrewseguin
authored andcommitted
fix(dialog): restoring focus too early (#4329)
Fixes the dialog restoring focus before the close animation is done. Fixes #4287.
1 parent c5680c0 commit afaa2dc

File tree

3 files changed

+27
-42
lines changed

3 files changed

+27
-42
lines changed

src/lib/dialog/dialog-container.ts

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,7 @@ export class MdDialogContainer extends BasePortalHost {
109109
return this._portalHost.attachTemplatePortal(portal);
110110
}
111111

112-
/**
113-
* Moves the focus inside the focus trap.
114-
*/
112+
/** Moves the focus inside the focus trap. */
115113
private _trapFocus() {
116114
if (!this._focusTrap) {
117115
this._focusTrap = this._focusTrapFactory.create(this._elementRef.nativeElement);
@@ -123,45 +121,36 @@ export class MdDialogContainer extends BasePortalHost {
123121
this._focusTrap.focusFirstTabbableElementWhenReady();
124122
}
125123

126-
/**
127-
* Saves a reference to the element that was focused before the dialog was opened.
128-
*/
124+
/** Restores focus to the element that was focused before the dialog opened. */
125+
private _restoreFocus() {
126+
const toFocus = this._elementFocusedBeforeDialogWasOpened;
127+
128+
// We need the extra check, because IE can set the `activeElement` to null in some cases.
129+
if (toFocus && 'focus' in toFocus) {
130+
toFocus.focus();
131+
}
132+
133+
if (this._focusTrap) {
134+
this._focusTrap.destroy();
135+
}
136+
}
137+
138+
/** Saves a reference to the element that was focused before the dialog was opened. */
129139
private _savePreviouslyFocusedElement() {
130140
if (this._document) {
131141
this._elementFocusedBeforeDialogWasOpened = this._document.activeElement as HTMLElement;
132142
}
133143
}
134144

135-
/**
136-
* Callback, invoked whenever an animation on the host completes.
137-
* @docs-private
138-
*/
145+
/** Callback, invoked whenever an animation on the host completes. */
139146
_onAnimationDone(event: AnimationEvent) {
140147
this._onAnimationStateChange.emit(event);
141148

142149
if (event.toState === 'enter') {
143150
this._trapFocus();
144151
} else if (event.toState === 'exit') {
152+
this._restoreFocus();
145153
this._onAnimationStateChange.complete();
146154
}
147155
}
148-
149-
/**
150-
* Kicks off the leave animation and restores focus to the previously-focused element.
151-
* @docs-private
152-
*/
153-
_exit(): void {
154-
// We need the extra check, because IE can set the `activeElement` to null in some cases.
155-
let toFocus = this._elementFocusedBeforeDialogWasOpened;
156-
157-
if (toFocus && 'focus' in toFocus) {
158-
toFocus.focus();
159-
}
160-
161-
if (this._focusTrap) {
162-
this._focusTrap.destroy();
163-
}
164-
165-
this._state = 'exit';
166-
}
167156
}

src/lib/dialog/dialog-ref.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class MdDialogRef<T> {
4242
*/
4343
close(dialogResult?: any): void {
4444
this._result = dialogResult;
45-
this._containerInstance._exit();
45+
this._containerInstance._state = 'exit';
4646
this._overlayRef.detachBackdrop(); // Transition the backdrop in parallel with the dialog.
4747
}
4848

src/lib/dialog/dialog.spec.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -480,15 +480,9 @@ describe('MdDialog', () => {
480480
});
481481

482482
describe('focus management', () => {
483-
484483
// When testing focus, all of the elements must be in the DOM.
485-
beforeEach(() => {
486-
document.body.appendChild(overlayContainerElement);
487-
});
488-
489-
afterEach(() => {
490-
document.body.removeChild(overlayContainerElement);
491-
});
484+
beforeEach(() => document.body.appendChild(overlayContainerElement));
485+
afterEach(() => document.body.removeChild(overlayContainerElement));
492486

493487
it('should focus the first tabbable element of the dialog on open', fakeAsync(() => {
494488
dialog.open(PizzaMsg, {
@@ -515,17 +509,19 @@ describe('MdDialog', () => {
515509

516510
viewContainerFixture.detectChanges();
517511
flushMicrotasks();
518-
519512
expect(document.activeElement.id)
520513
.not.toBe('dialog-trigger', 'Expected the focus to change when dialog was opened.');
521514

522515
dialogRef.close();
516+
expect(document.activeElement.id).not.toBe('dialog-trigger',
517+
'Expcted the focus not to have changed before the animation finishes.');
518+
523519
tick(500);
524520
viewContainerFixture.detectChanges();
525521
flushMicrotasks();
526522

527-
expect(document.activeElement.id)
528-
.toBe('dialog-trigger', 'Expected that the trigger was refocused after dialog close');
523+
expect(document.activeElement.id).toBe('dialog-trigger',
524+
'Expected that the trigger was refocused after the dialog is closed.');
529525

530526
document.body.removeChild(button);
531527
}));
@@ -552,7 +548,7 @@ describe('MdDialog', () => {
552548
flushMicrotasks();
553549

554550
expect(document.activeElement.id).toBe('input-to-be-focused',
555-
'Expected that the trigger was refocused after dialog close');
551+
'Expected that the trigger was refocused after the dialog is closed.');
556552

557553
document.body.removeChild(button);
558554
document.body.removeChild(input);

0 commit comments

Comments
 (0)