Skip to content

Commit a4da08b

Browse files
crisbetommalerba
authored andcommitted
fix(autocomplete): prevent opening on load in IE (#3190)
Prevents the autocomplete being opened on load in IE. This was due to a bug where IE fires the `input` event on `load`, `focus` and `blur`, if the input element has a placeholder. Fixes #3183.
1 parent d744a5f commit a4da08b

File tree

2 files changed

+50
-33
lines changed

2 files changed

+50
-33
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export const MD_AUTOCOMPLETE_VALUE_ACCESSOR: any = {
6161
'[attr.aria-owns]': 'autocomplete?.id',
6262
'(focus)': 'openPanel()',
6363
'(blur)': '_handleBlur($event.relatedTarget?.tagName)',
64-
'(input)': '_handleInput($event.target.value)',
64+
'(input)': '_handleInput($event)',
6565
'(keydown)': '_handleKeydown($event)',
6666
},
6767
providers: [MD_AUTOCOMPLETE_VALUE_ACCESSOR]
@@ -213,9 +213,14 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
213213
}
214214
}
215215

216-
_handleInput(value: string): void {
217-
this._onChange(value);
218-
this.openPanel();
216+
_handleInput(event: KeyboardEvent): void {
217+
// We need to ensure that the input is focused, because IE will fire the `input`
218+
// event on focus/blur/load if the input has a placeholder. See:
219+
// https://connect.microsoft.com/IE/feedback/details/885747/
220+
if (document.activeElement === event.target) {
221+
this._onChange((event.target as HTMLInputElement).value);
222+
this.openPanel();
223+
}
219224
}
220225

221226
_handleBlur(newlyFocusedTag: string): void {

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,7 @@ describe('MdAutocomplete', () => {
124124

125125
fixture.whenStable().then(() => {
126126
// Filter down the option list to a subset of original options ('Alabama', 'California')
127-
input.value = 'al';
128-
dispatchEvent('input', input);
127+
typeInElement('al', input);
129128
fixture.detectChanges();
130129

131130
let options =
@@ -134,8 +133,7 @@ describe('MdAutocomplete', () => {
134133

135134
// Changing value from 'Alabama' to 'al' to re-populate the option list,
136135
// ensuring that 'California' is created new.
137-
input.value = 'al';
138-
dispatchEvent('input', input);
136+
typeInElement('al', input);
139137
fixture.detectChanges();
140138

141139
fixture.whenStable().then(() => {
@@ -177,8 +175,7 @@ describe('MdAutocomplete', () => {
177175
.toContain('mat-autocomplete-visible', `Expected panel to start out visible.`);
178176

179177
// Filter down the option list such that no options match the value
180-
input.value = 'af';
181-
dispatchEvent('input', input);
178+
typeInElement('af', input);
182179
fixture.detectChanges();
183180

184181
fixture.whenStable().then(() => {
@@ -210,6 +207,18 @@ describe('MdAutocomplete', () => {
210207
});
211208
}));
212209

210+
it('should not open the panel when the `input` event is invoked on a non-focused input', () => {
211+
expect(fixture.componentInstance.trigger.panelOpen)
212+
.toBe(false, `Expected panel state to start out closed.`);
213+
214+
input.value = 'Alabama';
215+
dispatchEvent('input', input);
216+
fixture.detectChanges();
217+
218+
expect(fixture.componentInstance.trigger.panelOpen)
219+
.toBe(false, `Expected panel state to stay closed.`);
220+
});
221+
213222
});
214223

215224
it('should have the correct text direction in RTL', () => {
@@ -241,15 +250,13 @@ describe('MdAutocomplete', () => {
241250
fixture.componentInstance.trigger.openPanel();
242251
fixture.detectChanges();
243252

244-
input.value = 'a';
245-
dispatchEvent('input', input);
253+
typeInElement('a', input);
246254
fixture.detectChanges();
247255

248256
expect(fixture.componentInstance.stateCtrl.value)
249257
.toEqual('a', 'Expected control value to be updated as user types.');
250258

251-
input.value = 'al';
252-
dispatchEvent('input', input);
259+
typeInElement('al', input);
253260
fixture.detectChanges();
254261

255262
expect(fixture.componentInstance.stateCtrl.value)
@@ -282,8 +289,7 @@ describe('MdAutocomplete', () => {
282289
options[1].click();
283290
fixture.detectChanges();
284291

285-
input.value = 'Californi';
286-
dispatchEvent('input', input);
292+
typeInElement('Californi', input);
287293
fixture.detectChanges();
288294

289295
expect(fixture.componentInstance.stateCtrl.value)
@@ -339,8 +345,7 @@ describe('MdAutocomplete', () => {
339345
}));
340346

341347
it('should clear the text field if value is reset programmatically', async(() => {
342-
input.value = 'Alabama';
343-
dispatchEvent('input', input);
348+
typeInElement('Alabama', input);
344349
fixture.detectChanges();
345350

346351
fixture.whenStable().then(() => {
@@ -376,8 +381,7 @@ describe('MdAutocomplete', () => {
376381
expect(fixture.componentInstance.stateCtrl.dirty)
377382
.toBe(false, `Expected control to start out pristine.`);
378383

379-
input.value = 'a';
380-
dispatchEvent('input', input);
384+
typeInElement('a', input);
381385
fixture.detectChanges();
382386

383387
expect(fixture.componentInstance.stateCtrl.dirty)
@@ -531,8 +535,7 @@ describe('MdAutocomplete', () => {
531535
fixture.detectChanges();
532536

533537
fixture.whenStable().then(() => {
534-
input.value = 'o';
535-
dispatchEvent('input', input);
538+
typeInElement('o', input);
536539
fixture.detectChanges();
537540

538541
fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);
@@ -565,8 +568,7 @@ describe('MdAutocomplete', () => {
565568

566569
it('should fill the text field, not select an option, when SPACE is entered', async(() => {
567570
fixture.whenStable().then(() => {
568-
input.value = 'New';
569-
dispatchEvent('input', input);
571+
typeInElement('New', input);
570572
fixture.detectChanges();
571573

572574
const SPACE_EVENT = new FakeKeyboardEvent(SPACE) as KeyboardEvent;
@@ -604,8 +606,7 @@ describe('MdAutocomplete', () => {
604606
expect(overlayContainerElement.textContent)
605607
.toEqual('', `Expected panel to close after ENTER key.`);
606608

607-
input.value = 'Alabam';
608-
dispatchEvent('input', input);
609+
typeInElement('Alabama', input);
609610
fixture.detectChanges();
610611

611612
expect(fixture.componentInstance.trigger.panelOpen)
@@ -782,8 +783,7 @@ describe('MdAutocomplete', () => {
782783
fixture.detectChanges();
783784

784785
fixture.whenStable().then(() => {
785-
input.value = 'f';
786-
dispatchEvent('input', input);
786+
typeInElement('f', input);
787787
fixture.detectChanges();
788788

789789
const inputTop = input.getBoundingClientRect().top;
@@ -808,8 +808,7 @@ describe('MdAutocomplete', () => {
808808
fixture.detectChanges();
809809

810810
const input = fixture.debugElement.query(By.css('input')).nativeElement;
811-
input.value = 'd';
812-
dispatchEvent('input', input);
811+
typeInElement('d', input);
813812
fixture.detectChanges();
814813

815814
const options =
@@ -826,7 +825,7 @@ describe('MdAutocomplete', () => {
826825
<md-input-container>
827826
<input mdInput placeholder="State" [mdAutocomplete]="auto" [formControl]="stateCtrl">
828827
</md-input-container>
829-
828+
830829
<md-autocomplete #auto="mdAutocomplete" [displayWith]="displayFn">
831830
<md-option *ngFor="let state of filteredStates" [value]="state">
832831
<span> {{ state.code }}: {{ state.name }} </span>
@@ -881,10 +880,10 @@ class SimpleAutocomplete implements OnDestroy {
881880
@Component({
882881
template: `
883882
<md-input-container>
884-
<input mdInput placeholder="State" [mdAutocomplete]="auto"
883+
<input mdInput placeholder="State" [mdAutocomplete]="auto"
885884
(input)="onInput($event.target?.value)">
886885
</md-input-container>
887-
886+
888887
<md-autocomplete #auto="mdAutocomplete">
889888
<md-option *ngFor="let state of filteredStates" [value]="state">
890889
<span> {{ state }} </span>
@@ -920,6 +919,19 @@ function dispatchEvent(eventName: string, element: HTMLElement): void {
920919
element.dispatchEvent(event);
921920
}
922921

922+
923+
/**
924+
* Focuses an input, sets its value and dispatches
925+
* the `input` event, simulating the user typing.
926+
* @param value Value to be set on the input.
927+
* @param element Element onto which to set the value.
928+
*/
929+
function typeInElement(value: string, element: HTMLInputElement) {
930+
element.focus();
931+
element.value = value;
932+
dispatchEvent('input', element);
933+
}
934+
923935
/** This is a mock keyboard event to test keyboard events in the autocomplete. */
924936
class FakeKeyboardEvent {
925937
constructor(public keyCode: number) {}

0 commit comments

Comments
 (0)