Skip to content

Commit 83a5673

Browse files
committed
fix(autocomplete): unable to click to select items in IE
Fixes being unable to select autocomplete items by clicking in IE. This was due to IE not setting the event.relatedTarget for blur events. Fixes potential issue if the user uses mat-option, instead of md-option. Fixes #3351.
1 parent aa3360a commit 83a5673

File tree

4 files changed

+42
-31
lines changed

4 files changed

+42
-31
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
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+
Renderer,
1112
} from '@angular/core';
1213
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
1314
import {Overlay, OverlayRef, OverlayState, TemplatePortal} from '../core';
@@ -58,8 +59,8 @@ export const MD_AUTOCOMPLETE_VALUE_ACCESSOR: any = {
5859
'[attr.aria-expanded]': 'panelOpen.toString()',
5960
'[attr.aria-owns]': 'autocomplete?.id',
6061
'(focus)': 'openPanel()',
61-
'(blur)': '_handleBlur($event.relatedTarget?.tagName)',
6262
'(input)': '_handleInput($event)',
63+
'(blur)': '_onTouched()',
6364
'(keydown)': '_handleKeydown($event)',
6465
},
6566
providers: [MD_AUTOCOMPLETE_VALUE_ACCESSOR]
@@ -74,12 +75,15 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
7475

7576
private _positionStrategy: ConnectedPositionStrategy;
7677

77-
/** Stream of blur events that should close the panel. */
78-
private _blurStream = new Subject<any>();
78+
/** Stream of click events that should close the panel. */
79+
private _outsideClickStream = new Subject<any>();
7980

8081
/** Whether or not the placeholder state is being overridden. */
8182
private _manuallyFloatingPlaceholder = false;
8283

84+
/** Keeps track of the function that allows us to remove the `document` click listener. */
85+
private _unbindGlobalListener: Function;
86+
8387
/** View -> model callback called when value changes */
8488
_onChange = (value: any) => {};
8589

@@ -100,7 +104,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
100104
}
101105

102106
constructor(private _element: ElementRef, private _overlay: Overlay,
103-
private _viewContainerRef: ViewContainerRef,
107+
private _renderer: Renderer, private _viewContainerRef: ViewContainerRef,
104108
@Optional() private _dir: Dir, private _zone: NgZone,
105109
@Optional() @Host() private _inputContainer: MdInputContainer) {}
106110

@@ -133,6 +137,16 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
133137

134138
this._panelOpen = true;
135139
this._floatPlaceholder();
140+
141+
if (!this._unbindGlobalListener) {
142+
this._unbindGlobalListener = this._renderer.listenGlobal('document', 'click',
143+
(event: MouseEvent) => {
144+
if (!this._inputContainer._elementRef.nativeElement.contains(event.target) &&
145+
!this._overlayRef.overlayElement.contains(event.target as HTMLElement)) {
146+
this._outsideClickStream.next(null);
147+
}
148+
});
149+
}
136150
}
137151

138152
/** Closes the autocomplete suggestion panel. */
@@ -143,6 +157,11 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
143157

144158
this._panelOpen = false;
145159
this._resetPlaceholder();
160+
161+
if (this._unbindGlobalListener) {
162+
this._unbindGlobalListener();
163+
this._unbindGlobalListener = null;
164+
}
146165
}
147166

148167
/**
@@ -151,9 +170,9 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
151170
*/
152171
get panelClosingActions(): Observable<MdOptionSelectionChange> {
153172
return Observable.merge(
154-
this.optionSelections,
155-
this._blurStream.asObservable(),
156-
this.autocomplete._keyManager.tabOut
173+
this.optionSelections,
174+
this.autocomplete._keyManager.tabOut,
175+
this._outsideClickStream
157176
);
158177
}
159178

@@ -223,15 +242,6 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
223242
}
224243
}
225244

226-
_handleBlur(newlyFocusedTag: string): void {
227-
this._onTouched();
228-
229-
// Only emit blur event if the new focus is *not* on an option.
230-
if (newlyFocusedTag !== 'MD-OPTION') {
231-
this._blurStream.next(null);
232-
}
233-
}
234-
235245
/**
236246
* In "auto" mode, the placeholder will animate down as soon as focus is lost.
237247
* This causes the value to jump when selecting an option with the mouse.
@@ -304,8 +314,8 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
304314
* control to that value. It will also mark the control as dirty if this interaction
305315
* stemmed from the user.
306316
*/
307-
private _setValueAndClose(event: MdOptionSelectionChange | null): void {
308-
if (event) {
317+
private _setValueAndClose(event: MdOptionSelectEvent | null): void {
318+
if (event && event.source) {
309319
this._setTriggerValue(event.source.value);
310320
this._onChange(event.source.value);
311321
}

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,12 @@ describe('MdAutocomplete', () => {
101101
});
102102
}));
103103

104-
it('should close the panel when blurred', async(() => {
104+
it('should close the panel when input loses focus', async(() => {
105105
dispatchFakeEvent(input, 'focus');
106106
fixture.detectChanges();
107107

108108
fixture.whenStable().then(() => {
109-
dispatchFakeEvent(input, 'blur');
110-
fixture.detectChanges();
109+
dispatchFakeEvent(document, 'click');
111110

112111
expect(fixture.componentInstance.trigger.panelOpen)
113112
.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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,8 @@ export class MdInputContainer implements AfterContentInit {
289289

290290
@ContentChildren(MdHint) _hintChildren: QueryList<MdHint>;
291291

292+
constructor(public _elementRef: ElementRef) { }
293+
292294
ngAfterContentInit() {
293295
if (!this._mdInputChild) {
294296
throw new MdInputContainerMissingMdInputError();

0 commit comments

Comments
 (0)