From 95ffe371b1d606a942e1285219eb5f79cb3e9983 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sat, 3 Feb 2018 01:02:07 +0100 Subject: [PATCH] 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. --- src/lib/autocomplete/autocomplete-trigger.ts | 30 +++++++++++++------- src/lib/autocomplete/autocomplete.spec.ts | 27 ++++++++++++++++++ 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index 740ec0eef069..8efa9fe4d238 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -121,6 +121,9 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy { private _portal: TemplatePortal; private _componentDestroyed = false; + /** Old value of the native input. Used to work around issues with the `input` event on IE. */ + private _previousValue: string | number | null; + /** Strategy that is used to position the panel. */ private _positionStrategy: ConnectedPositionStrategy; @@ -301,18 +304,22 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy { } _handleInput(event: KeyboardEvent): void { - // We need to ensure that the input is focused, because IE will fire the `input` - // event on focus/blur/load if the input has a placeholder. See: - // https://connect.microsoft.com/IE/feedback/details/885747/ - if (this._canOpen() && document.activeElement === event.target) { - let target = event.target as HTMLInputElement; - let value: number | string | null = target.value; - - // Based on `NumberValueAccessor` from forms. - if (target.type === 'number') { - value = value == '' ? null : parseFloat(value); - } + let target = event.target as HTMLInputElement; + let value: number | string | null = target.value; + + // Based on `NumberValueAccessor` from forms. + if (target.type === 'number') { + value = value == '' ? null : parseFloat(value); + } + // If the input has a placeholder, IE will fire the `input` event on page load, + // focus and blur, in addition to when the user actually changed the value. To + // filter out all of the extra events, we save the value on focus and between + // `input` events, and we check whether it changed. + // See: https://connect.microsoft.com/IE/feedback/details/885747/ + if (this._canOpen() && this._previousValue !== value && + document.activeElement === event.target) { + this._previousValue = value; this._onChange(value); this.openPanel(); } @@ -320,6 +327,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy { _handleFocus(): void { if (this._canOpen()) { + this._previousValue = this._element.nativeElement.value; this._attachOverlay(); this._floatLabel(true); } diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index 0f0df3a7cefa..f54065af3f03 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -234,6 +234,7 @@ describe('MatAutocomplete', () => { // Changing value from 'Alabama' to 'al' to re-populate the option list, // ensuring that 'California' is created new. + dispatchFakeEvent(input, 'focusin'); typeInElement('al', input); fixture.detectChanges(); tick(); @@ -818,6 +819,7 @@ describe('MatAutocomplete', () => { expect(overlayContainerElement.textContent) .toEqual('', `Expected panel to close after ENTER key.`); + dispatchFakeEvent(input, 'focusin'); typeInElement('Alabama', input); fixture.detectChanges(); tick(); @@ -828,6 +830,31 @@ describe('MatAutocomplete', () => { .toContain('Alabama', `Expected panel to display when typing in input.`); })); + it('should not open the panel if the `input` event was dispatched with changing the value', + fakeAsync(() => { + const trigger = fixture.componentInstance.trigger; + + dispatchFakeEvent(input, 'focusin'); + typeInElement('A', input); + fixture.detectChanges(); + tick(); + + expect(trigger.panelOpen).toBe(true, 'Expected panel to be open.'); + + trigger.closePanel(); + fixture.detectChanges(); + + expect(trigger.panelOpen).toBe(false, 'Expected panel to be closed.'); + + // Dispatch the event without actually changing the value + // to simulate what happen in some cases on IE. + dispatchFakeEvent(input, 'input'); + fixture.detectChanges(); + tick(); + + expect(trigger.panelOpen).toBe(false, 'Expected panel to stay closed.'); + })); + it('should scroll to active options below the fold', () => { const trigger = fixture.componentInstance.trigger; const scrollContainer =