Skip to content

Commit 78985b7

Browse files
crisbetokara
authored andcommitted
fix(autocomplete): unable to click to select items in IE (#3188)
Fixes #3351.
1 parent fbfd032 commit 78985b7

File tree

4 files changed

+45
-33
lines changed

4 files changed

+45
-33
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import {
2-
Directive,
3-
ElementRef,
4-
forwardRef,
5-
Host,
6-
Input,
7-
NgZone,
8-
Optional,
9-
OnDestroy,
10-
ViewContainerRef,
2+
Directive,
3+
ElementRef,
4+
forwardRef,
5+
Host,
6+
Input,
7+
NgZone,
8+
Optional,
9+
OnDestroy,
10+
ViewContainerRef,
11+
Inject,
12+
ChangeDetectorRef,
1113
} from '@angular/core';
1214
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
15+
import {DOCUMENT} from '@angular/platform-browser';
1316
import {Overlay, OverlayRef, OverlayState, TemplatePortal} from '../core';
1417
import {MdAutocomplete} from './autocomplete';
1518
import {PositionStrategy} from '../core/overlay/position/position-strategy';
@@ -18,12 +21,13 @@ import {Observable} from 'rxjs/Observable';
1821
import {MdOptionSelectionChange, MdOption} from '../core/option/option';
1922
import {ENTER, UP_ARROW, DOWN_ARROW} from '../core/keyboard/keycodes';
2023
import {Dir} from '../core/rtl/dir';
24+
import {MdInputContainer} from '../input/input-container';
2125
import {Subscription} from 'rxjs/Subscription';
22-
import {Subject} from 'rxjs/Subject';
2326
import 'rxjs/add/observable/merge';
27+
import 'rxjs/add/observable/fromEvent';
28+
import 'rxjs/add/operator/filter';
2429
import 'rxjs/add/operator/startWith';
2530
import 'rxjs/add/operator/switchMap';
26-
import {MdInputContainer} from '../input/input-container';
2731

2832
/**
2933
* The following style constants are necessary to save here in order
@@ -58,8 +62,8 @@ export const MD_AUTOCOMPLETE_VALUE_ACCESSOR: any = {
5862
'[attr.aria-expanded]': 'panelOpen.toString()',
5963
'[attr.aria-owns]': 'autocomplete?.id',
6064
'(focus)': 'openPanel()',
61-
'(blur)': '_handleBlur($event.relatedTarget?.tagName)',
6265
'(input)': '_handleInput($event)',
66+
'(blur)': '_onTouched()',
6367
'(keydown)': '_handleKeydown($event)',
6468
},
6569
providers: [MD_AUTOCOMPLETE_VALUE_ACCESSOR]
@@ -74,9 +78,6 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
7478

7579
private _positionStrategy: ConnectedPositionStrategy;
7680

77-
/** Stream of blur events that should close the panel. */
78-
private _blurStream = new Subject<any>();
79-
8081
/** Whether or not the placeholder state is being overridden. */
8182
private _manuallyFloatingPlaceholder = false;
8283

@@ -101,8 +102,10 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
101102

102103
constructor(private _element: ElementRef, private _overlay: Overlay,
103104
private _viewContainerRef: ViewContainerRef,
105+
private _changeDetectorRef: ChangeDetectorRef,
104106
@Optional() private _dir: Dir, private _zone: NgZone,
105-
@Optional() @Host() private _inputContainer: MdInputContainer) {}
107+
@Optional() @Host() private _inputContainer: MdInputContainer,
108+
@Optional() @Inject(DOCUMENT) private _document: any) {}
106109

107110
ngOnDestroy() {
108111
if (this._panelPositionSubscription) {
@@ -144,6 +147,12 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
144147

145148
this._panelOpen = false;
146149
this._resetPlaceholder();
150+
151+
// We need to trigger change detection manually, because
152+
// `fromEvent` doesn't seem to do it at the proper time.
153+
// This ensures that the placeholder is reset when the
154+
// user clicks outside.
155+
this._changeDetectorRef.detectChanges();
147156
}
148157

149158
/**
@@ -152,9 +161,9 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
152161
*/
153162
get panelClosingActions(): Observable<MdOptionSelectionChange> {
154163
return Observable.merge(
155-
this.optionSelections,
156-
this._blurStream.asObservable(),
157-
this.autocomplete._keyManager.tabOut
164+
this.optionSelections,
165+
this.autocomplete._keyManager.tabOut,
166+
this._outsideClickStream
158167
);
159168
}
160169

@@ -170,6 +179,18 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
170179
}
171180
}
172181

182+
/** Stream of clicks outside of the autocomplete panel. */
183+
private get _outsideClickStream(): Observable<any> {
184+
if (this._document) {
185+
return Observable.fromEvent(this._document, 'click').filter((event: MouseEvent) => {
186+
let clickTarget = event.target as HTMLElement;
187+
return this._panelOpen &&
188+
!this._inputContainer._elementRef.nativeElement.contains(clickTarget) &&
189+
!this._overlayRef.overlayElement.contains(clickTarget);
190+
});
191+
}
192+
}
193+
173194
/**
174195
* Sets the autocomplete's value. Part of the ControlValueAccessor interface
175196
* required to integrate with Angular's core forms API.
@@ -225,15 +246,6 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
225246
}
226247
}
227248

228-
_handleBlur(newlyFocusedTag: string): void {
229-
this._onTouched();
230-
231-
// Only emit blur event if the new focus is *not* on an option.
232-
if (newlyFocusedTag !== 'MD-OPTION') {
233-
this._blurStream.next(null);
234-
}
235-
}
236-
237249
/**
238250
* In "auto" mode, the placeholder will animate down as soon as focus is lost.
239251
* This causes the value to jump when selecting an option with the mouse.
@@ -307,7 +319,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
307319
* stemmed from the user.
308320
*/
309321
private _setValueAndClose(event: MdOptionSelectionChange | null): void {
310-
if (event) {
322+
if (event && event.source) {
311323
this._clearPreviousSelectedOption(event.source);
312324
this._setTriggerValue(event.source.value);
313325
this._onChange(event.source.value);

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,12 @@ describe('MdAutocomplete', () => {
130130
});
131131
}));
132132

133-
it('should close the panel when blurred', async(() => {
133+
it('should close the panel when input loses focus', async(() => {
134134
dispatchFakeEvent(input, 'focus');
135135
fixture.detectChanges();
136136

137137
fixture.whenStable().then(() => {
138-
dispatchFakeEvent(input, 'blur');
139-
fixture.detectChanges();
138+
dispatchFakeEvent(document, 'click');
140139

141140
expect(fixture.componentInstance.trigger.panelOpen)
142141
.toBe(false, `Expected clicking outside the panel to set its state to closed.`);

src/lib/core/testing/event-objects.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function createKeyboardEvent(type: string, keyCode: number) {
4040

4141
/** Creates a fake event object with any desired event type. */
4242
export function createFakeEvent(type: string) {
43-
let event = document.createEvent('Event');
43+
let event = document.createEvent('Event');
4444
event.initEvent(type, true, true);
4545
return event;
4646
}

src/lib/input/input-container.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit {
339339
@ContentChildren(MdSuffix) _suffixChildren: QueryList<MdSuffix>;
340340

341341
constructor(
342+
public _elementRef: ElementRef,
342343
private _changeDetectorRef: ChangeDetectorRef,
343344
@Optional() private _parentForm: NgForm,
344345
@Optional() private _parentFormGroup: FormGroupDirective) { }

0 commit comments

Comments
 (0)