1
1
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 ,
11
13
} from '@angular/core' ;
12
14
import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ;
15
+ import { DOCUMENT } from '@angular/platform-browser' ;
13
16
import { Overlay , OverlayRef , OverlayState , TemplatePortal } from '../core' ;
14
17
import { MdAutocomplete } from './autocomplete' ;
15
18
import { PositionStrategy } from '../core/overlay/position/position-strategy' ;
@@ -18,12 +21,13 @@ import {Observable} from 'rxjs/Observable';
18
21
import { MdOptionSelectionChange , MdOption } from '../core/option/option' ;
19
22
import { ENTER , UP_ARROW , DOWN_ARROW } from '../core/keyboard/keycodes' ;
20
23
import { Dir } from '../core/rtl/dir' ;
24
+ import { MdInputContainer } from '../input/input-container' ;
21
25
import { Subscription } from 'rxjs/Subscription' ;
22
- import { Subject } from 'rxjs/Subject' ;
23
26
import 'rxjs/add/observable/merge' ;
27
+ import 'rxjs/add/observable/fromEvent' ;
28
+ import 'rxjs/add/operator/filter' ;
24
29
import 'rxjs/add/operator/startWith' ;
25
30
import 'rxjs/add/operator/switchMap' ;
26
- import { MdInputContainer } from '../input/input-container' ;
27
31
28
32
/**
29
33
* The following style constants are necessary to save here in order
@@ -58,8 +62,8 @@ export const MD_AUTOCOMPLETE_VALUE_ACCESSOR: any = {
58
62
'[attr.aria-expanded]' : 'panelOpen.toString()' ,
59
63
'[attr.aria-owns]' : 'autocomplete?.id' ,
60
64
'(focus)' : 'openPanel()' ,
61
- '(blur)' : '_handleBlur($event.relatedTarget?.tagName)' ,
62
65
'(input)' : '_handleInput($event)' ,
66
+ '(blur)' : '_onTouched()' ,
63
67
'(keydown)' : '_handleKeydown($event)' ,
64
68
} ,
65
69
providers : [ MD_AUTOCOMPLETE_VALUE_ACCESSOR ]
@@ -74,9 +78,6 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
74
78
75
79
private _positionStrategy : ConnectedPositionStrategy ;
76
80
77
- /** Stream of blur events that should close the panel. */
78
- private _blurStream = new Subject < any > ( ) ;
79
-
80
81
/** Whether or not the placeholder state is being overridden. */
81
82
private _manuallyFloatingPlaceholder = false ;
82
83
@@ -101,8 +102,10 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
101
102
102
103
constructor ( private _element : ElementRef , private _overlay : Overlay ,
103
104
private _viewContainerRef : ViewContainerRef ,
105
+ private _changeDetectorRef : ChangeDetectorRef ,
104
106
@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 ) { }
106
109
107
110
ngOnDestroy ( ) {
108
111
if ( this . _panelPositionSubscription ) {
@@ -144,6 +147,12 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
144
147
145
148
this . _panelOpen = false ;
146
149
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 ( ) ;
147
156
}
148
157
149
158
/**
@@ -152,9 +161,9 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
152
161
*/
153
162
get panelClosingActions ( ) : Observable < MdOptionSelectionChange > {
154
163
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
158
167
) ;
159
168
}
160
169
@@ -170,6 +179,18 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
170
179
}
171
180
}
172
181
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
+
173
194
/**
174
195
* Sets the autocomplete's value. Part of the ControlValueAccessor interface
175
196
* required to integrate with Angular's core forms API.
@@ -225,15 +246,6 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
225
246
}
226
247
}
227
248
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
-
237
249
/**
238
250
* In "auto" mode, the placeholder will animate down as soon as focus is lost.
239
251
* This causes the value to jump when selecting an option with the mouse.
@@ -307,7 +319,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
307
319
* stemmed from the user.
308
320
*/
309
321
private _setValueAndClose ( event : MdOptionSelectionChange | null ) : void {
310
- if ( event ) {
322
+ if ( event && event . source ) {
311
323
this . _clearPreviousSelectedOption ( event . source ) ;
312
324
this . _setTriggerValue ( event . source . value ) ;
313
325
this . _onChange ( event . source . value ) ;
0 commit comments