Skip to content

Commit 95ffe37

Browse files
crisbetotinayuangao
authored andcommitted
fix(autocomplete): panel not closing on IE when selecting an option with an empty string display value (#9506)
Fixes the autocomplete panel reopening on IE11 when the user selects an option whose display value is an empty string. Fixes #9479.
1 parent e30852a commit 95ffe37

File tree

2 files changed

+46
-11
lines changed

2 files changed

+46
-11
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
121121
private _portal: TemplatePortal;
122122
private _componentDestroyed = false;
123123

124+
/** Old value of the native input. Used to work around issues with the `input` event on IE. */
125+
private _previousValue: string | number | null;
126+
124127
/** Strategy that is used to position the panel. */
125128
private _positionStrategy: ConnectedPositionStrategy;
126129

@@ -301,25 +304,30 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
301304
}
302305

303306
_handleInput(event: KeyboardEvent): void {
304-
// We need to ensure that the input is focused, because IE will fire the `input`
305-
// event on focus/blur/load if the input has a placeholder. See:
306-
// https://connect.microsoft.com/IE/feedback/details/885747/
307-
if (this._canOpen() && document.activeElement === event.target) {
308-
let target = event.target as HTMLInputElement;
309-
let value: number | string | null = target.value;
310-
311-
// Based on `NumberValueAccessor` from forms.
312-
if (target.type === 'number') {
313-
value = value == '' ? null : parseFloat(value);
314-
}
307+
let target = event.target as HTMLInputElement;
308+
let value: number | string | null = target.value;
309+
310+
// Based on `NumberValueAccessor` from forms.
311+
if (target.type === 'number') {
312+
value = value == '' ? null : parseFloat(value);
313+
}
315314

315+
// If the input has a placeholder, IE will fire the `input` event on page load,
316+
// focus and blur, in addition to when the user actually changed the value. To
317+
// filter out all of the extra events, we save the value on focus and between
318+
// `input` events, and we check whether it changed.
319+
// See: https://connect.microsoft.com/IE/feedback/details/885747/
320+
if (this._canOpen() && this._previousValue !== value &&
321+
document.activeElement === event.target) {
322+
this._previousValue = value;
316323
this._onChange(value);
317324
this.openPanel();
318325
}
319326
}
320327

321328
_handleFocus(): void {
322329
if (this._canOpen()) {
330+
this._previousValue = this._element.nativeElement.value;
323331
this._attachOverlay();
324332
this._floatLabel(true);
325333
}

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ describe('MatAutocomplete', () => {
234234

235235
// Changing value from 'Alabama' to 'al' to re-populate the option list,
236236
// ensuring that 'California' is created new.
237+
dispatchFakeEvent(input, 'focusin');
237238
typeInElement('al', input);
238239
fixture.detectChanges();
239240
tick();
@@ -818,6 +819,7 @@ describe('MatAutocomplete', () => {
818819
expect(overlayContainerElement.textContent)
819820
.toEqual('', `Expected panel to close after ENTER key.`);
820821

822+
dispatchFakeEvent(input, 'focusin');
821823
typeInElement('Alabama', input);
822824
fixture.detectChanges();
823825
tick();
@@ -828,6 +830,31 @@ describe('MatAutocomplete', () => {
828830
.toContain('Alabama', `Expected panel to display when typing in input.`);
829831
}));
830832

833+
it('should not open the panel if the `input` event was dispatched with changing the value',
834+
fakeAsync(() => {
835+
const trigger = fixture.componentInstance.trigger;
836+
837+
dispatchFakeEvent(input, 'focusin');
838+
typeInElement('A', input);
839+
fixture.detectChanges();
840+
tick();
841+
842+
expect(trigger.panelOpen).toBe(true, 'Expected panel to be open.');
843+
844+
trigger.closePanel();
845+
fixture.detectChanges();
846+
847+
expect(trigger.panelOpen).toBe(false, 'Expected panel to be closed.');
848+
849+
// Dispatch the event without actually changing the value
850+
// to simulate what happen in some cases on IE.
851+
dispatchFakeEvent(input, 'input');
852+
fixture.detectChanges();
853+
tick();
854+
855+
expect(trigger.panelOpen).toBe(false, 'Expected panel to stay closed.');
856+
}));
857+
831858
it('should scroll to active options below the fold', () => {
832859
const trigger = fixture.componentInstance.trigger;
833860
const scrollContainer =

0 commit comments

Comments
 (0)