fix(autocomplete): unable to click to select items in IE (#3188)

Fixes #3351.
This commit is contained in:
Kristiyan Kostadinov
2017-04-19 01:43:29 +02:00
committed by Kara
parent fbfd032c6e
commit 78985b711e
4 changed files with 45 additions and 33 deletions
+41 -29
View File
@@ -1,15 +1,18 @@
import {
Directive,
ElementRef,
forwardRef,
Host,
Input,
NgZone,
Optional,
OnDestroy,
ViewContainerRef,
Directive,
ElementRef,
forwardRef,
Host,
Input,
NgZone,
Optional,
OnDestroy,
ViewContainerRef,
Inject,
ChangeDetectorRef,
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {DOCUMENT} from '@angular/platform-browser';
import {Overlay, OverlayRef, OverlayState, TemplatePortal} from '../core';
import {MdAutocomplete} from './autocomplete';
import {PositionStrategy} from '../core/overlay/position/position-strategy';
@@ -18,12 +21,13 @@ import {Observable} from 'rxjs/Observable';
import {MdOptionSelectionChange, MdOption} from '../core/option/option';
import {ENTER, UP_ARROW, DOWN_ARROW} from '../core/keyboard/keycodes';
import {Dir} from '../core/rtl/dir';
import {MdInputContainer} from '../input/input-container';
import {Subscription} from 'rxjs/Subscription';
import {Subject} from 'rxjs/Subject';
import 'rxjs/add/observable/merge';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/switchMap';
import {MdInputContainer} from '../input/input-container';
/**
* The following style constants are necessary to save here in order
@@ -58,8 +62,8 @@ export const MD_AUTOCOMPLETE_VALUE_ACCESSOR: any = {
'[attr.aria-expanded]': 'panelOpen.toString()',
'[attr.aria-owns]': 'autocomplete?.id',
'(focus)': 'openPanel()',
'(blur)': '_handleBlur($event.relatedTarget?.tagName)',
'(input)': '_handleInput($event)',
'(blur)': '_onTouched()',
'(keydown)': '_handleKeydown($event)',
},
providers: [MD_AUTOCOMPLETE_VALUE_ACCESSOR]
@@ -74,9 +78,6 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
private _positionStrategy: ConnectedPositionStrategy;
/** Stream of blur events that should close the panel. */
private _blurStream = new Subject<any>();
/** Whether or not the placeholder state is being overridden. */
private _manuallyFloatingPlaceholder = false;
@@ -101,8 +102,10 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
constructor(private _element: ElementRef, private _overlay: Overlay,
private _viewContainerRef: ViewContainerRef,
private _changeDetectorRef: ChangeDetectorRef,
@Optional() private _dir: Dir, private _zone: NgZone,
@Optional() @Host() private _inputContainer: MdInputContainer) {}
@Optional() @Host() private _inputContainer: MdInputContainer,
@Optional() @Inject(DOCUMENT) private _document: any) {}
ngOnDestroy() {
if (this._panelPositionSubscription) {
@@ -144,6 +147,12 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
this._panelOpen = false;
this._resetPlaceholder();
// We need to trigger change detection manually, because
// `fromEvent` doesn't seem to do it at the proper time.
// This ensures that the placeholder is reset when the
// user clicks outside.
this._changeDetectorRef.detectChanges();
}
/**
@@ -152,9 +161,9 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
*/
get panelClosingActions(): Observable<MdOptionSelectionChange> {
return Observable.merge(
this.optionSelections,
this._blurStream.asObservable(),
this.autocomplete._keyManager.tabOut
this.optionSelections,
this.autocomplete._keyManager.tabOut,
this._outsideClickStream
);
}
@@ -170,6 +179,18 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
}
}
/** Stream of clicks outside of the autocomplete panel. */
private get _outsideClickStream(): Observable<any> {
if (this._document) {
return Observable.fromEvent(this._document, 'click').filter((event: MouseEvent) => {
let clickTarget = event.target as HTMLElement;
return this._panelOpen &&
!this._inputContainer._elementRef.nativeElement.contains(clickTarget) &&
!this._overlayRef.overlayElement.contains(clickTarget);
});
}
}
/**
* Sets the autocomplete's value. Part of the ControlValueAccessor interface
* required to integrate with Angular's core forms API.
@@ -225,15 +246,6 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
}
}
_handleBlur(newlyFocusedTag: string): void {
this._onTouched();
// Only emit blur event if the new focus is *not* on an option.
if (newlyFocusedTag !== 'MD-OPTION') {
this._blurStream.next(null);
}
}
/**
* In "auto" mode, the placeholder will animate down as soon as focus is lost.
* This causes the value to jump when selecting an option with the mouse.
@@ -307,7 +319,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
* stemmed from the user.
*/
private _setValueAndClose(event: MdOptionSelectionChange | null): void {
if (event) {
if (event && event.source) {
this._clearPreviousSelectedOption(event.source);
this._setTriggerValue(event.source.value);
this._onChange(event.source.value);
+2 -3
View File
@@ -130,13 +130,12 @@ describe('MdAutocomplete', () => {
});
}));
it('should close the panel when blurred', async(() => {
it('should close the panel when input loses focus', async(() => {
dispatchFakeEvent(input, 'focus');
fixture.detectChanges();
fixture.whenStable().then(() => {
dispatchFakeEvent(input, 'blur');
fixture.detectChanges();
dispatchFakeEvent(document, 'click');
expect(fixture.componentInstance.trigger.panelOpen)
.toBe(false, `Expected clicking outside the panel to set its state to closed.`);
+1 -1
View File
@@ -40,7 +40,7 @@ export function createKeyboardEvent(type: string, keyCode: number) {
/** Creates a fake event object with any desired event type. */
export function createFakeEvent(type: string) {
let event = document.createEvent('Event');
let event = document.createEvent('Event');
event.initEvent(type, true, true);
return event;
}
+1
View File
@@ -339,6 +339,7 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit {
@ContentChildren(MdSuffix) _suffixChildren: QueryList<MdSuffix>;
constructor(
public _elementRef: ElementRef,
private _changeDetectorRef: ChangeDetectorRef,
@Optional() private _parentForm: NgForm,
@Optional() private _parentFormGroup: FormGroupDirective) { }