@@ -36,7 +36,6 @@ export type FocusOrigin = 'touch' | 'mouse' | 'keyboard' | 'program' | null;
36
36
type MonitoredElementInfo = {
37
37
unlisten : Function ,
38
38
checkChildren : boolean ,
39
- renderer : Renderer2 ,
40
39
subject : Subject < FocusOrigin >
41
40
} ;
42
41
@@ -62,22 +61,38 @@ export class FocusMonitor {
62
61
/** Weak map of elements being monitored to their info. */
63
62
private _elementInfo = new WeakMap < Element , MonitoredElementInfo > ( ) ;
64
63
65
- constructor ( private _ngZone : NgZone , private _platform : Platform ) {
66
- this . _ngZone . runOutsideAngular ( ( ) => this . _registerDocumentEvents ( ) ) ;
67
- }
64
+ /** A map of global objects to lists of current listeners. */
65
+ private _unregisterGlobalListeners = ( ) => { } ;
66
+
67
+ /** The number of elements currently being monitored. */
68
+ private _monitoredElementCount = 0 ;
68
69
70
+ constructor ( private _ngZone : NgZone , private _platform : Platform ) { }
71
+
72
+ /**
73
+ * @docs -private
74
+ * @deprecated renderer param no longer needed.
75
+ */
76
+ monitor ( element : HTMLElement , renderer : Renderer2 , checkChildren : boolean ) :
77
+ Observable < FocusOrigin > ;
69
78
/**
70
79
* Monitors focus on an element and applies appropriate CSS classes.
71
80
* @param element The element to monitor
72
- * @param renderer The renderer to use to apply CSS classes to the element.
73
81
* @param checkChildren Whether to count the element as focused when its children are focused.
74
82
* @returns An observable that emits when the focus state of the element changes.
75
83
* When the element is blurred, null will be emitted.
76
84
*/
85
+ monitor ( element : HTMLElement , checkChildren : boolean ) : Observable < FocusOrigin > ;
77
86
monitor (
78
87
element : HTMLElement ,
79
- renderer : Renderer2 ,
80
- checkChildren : boolean ) : Observable < FocusOrigin > {
88
+ renderer : Renderer2 | boolean ,
89
+ checkChildren ?: boolean ) : Observable < FocusOrigin > {
90
+ // TODO(mmalerba): clean up after deprecated signature is removed.
91
+ if ( ! ( renderer instanceof Renderer2 ) ) {
92
+ checkChildren = renderer ;
93
+ }
94
+ checkChildren = ! ! checkChildren ;
95
+
81
96
// Do nothing if we're not on the browser platform.
82
97
if ( ! this . _platform . isBrowser ) {
83
98
return observableOf ( null ) ;
@@ -93,10 +108,10 @@ export class FocusMonitor {
93
108
let info : MonitoredElementInfo = {
94
109
unlisten : ( ) => { } ,
95
110
checkChildren : checkChildren ,
96
- renderer : renderer ,
97
111
subject : new Subject < FocusOrigin > ( )
98
112
} ;
99
113
this . _elementInfo . set ( element , info ) ;
114
+ this . _incrementMonitoredElementCount ( ) ;
100
115
101
116
// Start listening. We need to listen in capture phase since focus events don't bubble.
102
117
let focusListener = ( event : FocusEvent ) => this . _onFocus ( event , element ) ;
@@ -128,6 +143,7 @@ export class FocusMonitor {
128
143
129
144
this . _setClasses ( element ) ;
130
145
this . _elementInfo . delete ( element ) ;
146
+ this . _decrementMonitoredElementCount ( ) ;
131
147
}
132
148
}
133
149
@@ -142,49 +158,69 @@ export class FocusMonitor {
142
158
}
143
159
144
160
/** Register necessary event listeners on the document and window. */
145
- private _registerDocumentEvents ( ) {
161
+ private _registerGlobalListeners ( ) {
146
162
// Do nothing if we're not on the browser platform.
147
163
if ( ! this . _platform . isBrowser ) {
148
164
return ;
149
165
}
150
166
151
- // Note: we listen to events in the capture phase so we can detect them even if the user stops
152
- // propagation.
153
-
154
167
// On keydown record the origin and clear any touch event that may be in progress.
155
- document . addEventListener ( 'keydown' , ( ) => {
168
+ let documentKeydownListener = ( ) => {
156
169
this . _lastTouchTarget = null ;
157
170
this . _setOriginForCurrentEventQueue ( 'keyboard' ) ;
158
- } , true ) ;
171
+ } ;
159
172
160
173
// On mousedown record the origin only if there is not touch target, since a mousedown can
161
174
// happen as a result of a touch event.
162
- document . addEventListener ( 'mousedown' , ( ) => {
175
+ let documentMousedownListener = ( ) => {
163
176
if ( ! this . _lastTouchTarget ) {
164
177
this . _setOriginForCurrentEventQueue ( 'mouse' ) ;
165
178
}
166
- } , true ) ;
179
+ } ;
167
180
168
181
// When the touchstart event fires the focus event is not yet in the event queue. This means
169
182
// we can't rely on the trick used above (setting timeout of 0ms). Instead we wait 650ms to
170
183
// see if a focus happens.
171
- document . addEventListener ( 'touchstart' , ( event : TouchEvent ) => {
184
+ let documentTouchstartListener = ( event : TouchEvent ) => {
172
185
if ( this . _touchTimeout != null ) {
173
186
clearTimeout ( this . _touchTimeout ) ;
174
187
}
175
188
this . _lastTouchTarget = event . target ;
176
189
this . _touchTimeout = setTimeout ( ( ) => this . _lastTouchTarget = null , TOUCH_BUFFER_MS ) ;
177
-
178
- // Note that we need to cast the event options to `any`, because at the time of writing
179
- // (TypeScript 2.5), the built-in types don't support the `addEventListener` options param.
180
- } , supportsPassiveEventListeners ( ) ? ( { passive : true , capture : true } as any ) : true ) ;
190
+ } ;
181
191
182
192
// Make a note of when the window regains focus, so we can restore the origin info for the
183
193
// focused element.
184
- window . addEventListener ( 'focus' , ( ) => {
194
+ let windowFocusListener = ( ) => {
185
195
this . _windowFocused = true ;
186
196
setTimeout ( ( ) => this . _windowFocused = false , 0 ) ;
197
+ } ;
198
+
199
+ // Note: we listen to events in the capture phase so we can detect them even if the user stops
200
+ // propagation.
201
+ this . _ngZone . runOutsideAngular ( ( ) => {
202
+ document . addEventListener ( 'keydown' , documentKeydownListener , true ) ;
203
+ document . addEventListener ( 'mousedown' , documentMousedownListener , true ) ;
204
+ document . addEventListener ( 'touchstart' , documentTouchstartListener ,
205
+ supportsPassiveEventListeners ( ) ? ( { passive : true , capture : true } as any ) : true ) ;
206
+ window . addEventListener ( 'focus' , windowFocusListener ) ;
187
207
} ) ;
208
+
209
+ this . _unregisterGlobalListeners = ( ) => {
210
+ document . removeEventListener ( 'keydown' , documentKeydownListener , true ) ;
211
+ document . removeEventListener ( 'mousedown' , documentMousedownListener , true ) ;
212
+ document . removeEventListener ( 'touchstart' , documentTouchstartListener ,
213
+ supportsPassiveEventListeners ( ) ? ( { passive : true , capture : true } as any ) : true ) ;
214
+ window . removeEventListener ( 'focus' , windowFocusListener ) ;
215
+ } ;
216
+ }
217
+
218
+ private _toggleClass ( element : Element , className : string , shouldSet : boolean ) {
219
+ if ( shouldSet ) {
220
+ element . classList . add ( className ) ;
221
+ } else {
222
+ element . classList . remove ( className ) ;
223
+ }
188
224
}
189
225
190
226
/**
@@ -196,16 +232,11 @@ export class FocusMonitor {
196
232
const elementInfo = this . _elementInfo . get ( element ) ;
197
233
198
234
if ( elementInfo ) {
199
- const toggleClass = ( className : string , shouldSet : boolean ) => {
200
- shouldSet ? elementInfo . renderer . addClass ( element , className ) :
201
- elementInfo . renderer . removeClass ( element , className ) ;
202
- } ;
203
-
204
- toggleClass ( 'cdk-focused' , ! ! origin ) ;
205
- toggleClass ( 'cdk-touch-focused' , origin === 'touch' ) ;
206
- toggleClass ( 'cdk-keyboard-focused' , origin === 'keyboard' ) ;
207
- toggleClass ( 'cdk-mouse-focused' , origin === 'mouse' ) ;
208
- toggleClass ( 'cdk-program-focused' , origin === 'program' ) ;
235
+ this . _toggleClass ( element , 'cdk-focused' , ! ! origin ) ;
236
+ this . _toggleClass ( element , 'cdk-touch-focused' , origin === 'touch' ) ;
237
+ this . _toggleClass ( element , 'cdk-keyboard-focused' , origin === 'keyboard' ) ;
238
+ this . _toggleClass ( element , 'cdk-mouse-focused' , origin === 'mouse' ) ;
239
+ this . _toggleClass ( element , 'cdk-program-focused' , origin === 'program' ) ;
209
240
}
210
241
}
211
242
@@ -235,7 +266,7 @@ export class FocusMonitor {
235
266
// result, this code will still consider it to have been caused by the touch event and will
236
267
// apply the cdk-touch-focused class rather than the cdk-program-focused class. This is a
237
268
// relatively small edge-case that can be worked around by using
238
- // focusVia(parentEl, renderer, 'program') to focus the parent element.
269
+ // focusVia(parentEl, 'program') to focus the parent element.
239
270
//
240
271
// If we decide that we absolutely must handle this case correctly, we can do so by listening
241
272
// for the first focus event after the touchstart, and then the first blur event after that
@@ -304,6 +335,22 @@ export class FocusMonitor {
304
335
this . _setClasses ( element ) ;
305
336
elementInfo . subject . next ( null ) ;
306
337
}
338
+
339
+ private _incrementMonitoredElementCount ( ) {
340
+ // Register global listeners when first element is monitored.
341
+ if ( ++ this . _monitoredElementCount == 1 ) {
342
+ this . _registerGlobalListeners ( ) ;
343
+ }
344
+ }
345
+
346
+ private _decrementMonitoredElementCount ( ) {
347
+ // Unregister global listeners when last element is unmonitored.
348
+ if ( ! -- this . _monitoredElementCount ) {
349
+ this . _unregisterGlobalListeners ( ) ;
350
+ this . _unregisterGlobalListeners = ( ) => { } ;
351
+ }
352
+ }
353
+
307
354
}
308
355
309
356
@@ -323,10 +370,9 @@ export class CdkMonitorFocus implements OnDestroy {
323
370
private _monitorSubscription : Subscription ;
324
371
@Output ( ) cdkFocusChange = new EventEmitter < FocusOrigin > ( ) ;
325
372
326
- constructor ( private _elementRef : ElementRef , private _focusMonitor : FocusMonitor ,
327
- renderer : Renderer2 ) {
373
+ constructor ( private _elementRef : ElementRef , private _focusMonitor : FocusMonitor ) {
328
374
this . _monitorSubscription = this . _focusMonitor . monitor (
329
- this . _elementRef . nativeElement , renderer ,
375
+ this . _elementRef . nativeElement ,
330
376
this . _elementRef . nativeElement . hasAttribute ( 'cdkMonitorSubtreeFocus' ) )
331
377
. subscribe ( origin => this . cdkFocusChange . emit ( origin ) ) ;
332
378
}
0 commit comments