11import {
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' ;
1214import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ;
15+ import { DOCUMENT } from '@angular/platform-browser' ;
1316import { Overlay , OverlayRef , OverlayState , TemplatePortal } from '../core' ;
1417import { MdAutocomplete } from './autocomplete' ;
1518import { PositionStrategy } from '../core/overlay/position/position-strategy' ;
@@ -18,12 +21,13 @@ import {Observable} from 'rxjs/Observable';
1821import { MdOptionSelectionChange , MdOption } from '../core/option/option' ;
1922import { ENTER , UP_ARROW , DOWN_ARROW } from '../core/keyboard/keycodes' ;
2023import { Dir } from '../core/rtl/dir' ;
24+ import { MdInputContainer } from '../input/input-container' ;
2125import { Subscription } from 'rxjs/Subscription' ;
22- import { Subject } from 'rxjs/Subject' ;
2326import 'rxjs/add/observable/merge' ;
27+ import 'rxjs/add/observable/fromEvent' ;
28+ import 'rxjs/add/operator/filter' ;
2429import 'rxjs/add/operator/startWith' ;
2530import '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 ) ;
0 commit comments