@@ -65,7 +65,6 @@ export const FOCUS_MONITOR_DEFAULT_OPTIONS =
65
65
new InjectionToken < FocusMonitorOptions > ( 'cdk-focus-monitor-default-options' ) ;
66
66
67
67
type MonitoredElementInfo = {
68
- unlisten : Function ,
69
68
checkChildren : boolean ,
70
69
subject : Subject < FocusOrigin >
71
70
} ;
@@ -181,6 +180,19 @@ export class FocusMonitor implements OnDestroy {
181
180
this . _document = document ;
182
181
this . _detectionMode = options ?. detectionMode || FocusMonitorDetectionMode . IMMEDIATE ;
183
182
}
183
+ /**
184
+ * Event listener for `focus` and 'blur' events on the document.
185
+ * Needs to be an arrow function in order to preserve the context when it gets bound.
186
+ */
187
+ private _documentFocusAndBlurListener = ( event : FocusEvent ) => {
188
+ const target = event . target as HTMLElement | null ;
189
+ const handler = event . type === 'focus' ? this . _onFocus : this . _onBlur ;
190
+
191
+ // We need to walk up the ancestor chain in order to support `checkChildren`.
192
+ for ( let el = target ; el ; el = el . parentElement ) {
193
+ handler . call ( this , event , el ) ;
194
+ }
195
+ }
184
196
185
197
/**
186
198
* Monitors focus on an element and applies appropriate CSS classes.
@@ -211,34 +223,19 @@ export class FocusMonitor implements OnDestroy {
211
223
212
224
// Check if we're already monitoring this element.
213
225
if ( this . _elementInfo . has ( nativeElement ) ) {
214
- let cachedInfo = this . _elementInfo . get ( nativeElement ) ;
226
+ const cachedInfo = this . _elementInfo . get ( nativeElement ) ;
215
227
cachedInfo ! . checkChildren = checkChildren ;
216
228
return cachedInfo ! . subject . asObservable ( ) ;
217
229
}
218
230
219
231
// Create monitored element info.
220
- let info : MonitoredElementInfo = {
221
- unlisten : ( ) => { } ,
232
+ const info : MonitoredElementInfo = {
222
233
checkChildren : checkChildren ,
223
234
subject : new Subject < FocusOrigin > ( )
224
235
} ;
225
236
this . _elementInfo . set ( nativeElement , info ) ;
226
237
this . _incrementMonitoredElementCount ( ) ;
227
238
228
- // Start listening. We need to listen in capture phase since focus events don't bubble.
229
- let focusListener = ( event : FocusEvent ) => this . _onFocus ( event , nativeElement ) ;
230
- let blurListener = ( event : FocusEvent ) => this . _onBlur ( event , nativeElement ) ;
231
- this . _ngZone . runOutsideAngular ( ( ) => {
232
- nativeElement . addEventListener ( 'focus' , focusListener , true ) ;
233
- nativeElement . addEventListener ( 'blur' , blurListener , true ) ;
234
- } ) ;
235
-
236
- // Create an unlisten function for later.
237
- info . unlisten = ( ) => {
238
- nativeElement . removeEventListener ( 'focus' , focusListener , true ) ;
239
- nativeElement . removeEventListener ( 'blur' , blurListener , true ) ;
240
- } ;
241
-
242
239
return info . subject . asObservable ( ) ;
243
240
}
244
241
@@ -259,7 +256,6 @@ export class FocusMonitor implements OnDestroy {
259
256
const elementInfo = this . _elementInfo . get ( nativeElement ) ;
260
257
261
258
if ( elementInfo ) {
262
- elementInfo . unlisten ( ) ;
263
259
elementInfo . subject . complete ( ) ;
264
260
265
261
this . _setClasses ( nativeElement ) ;
@@ -322,21 +318,37 @@ export class FocusMonitor implements OnDestroy {
322
318
}
323
319
}
324
320
321
+ private _getFocusOrigin ( event : FocusEvent ) : FocusOrigin {
322
+ // If we couldn't detect a cause for the focus event, it's due to one of three reasons:
323
+ // 1) The window has just regained focus, in which case we want to restore the focused state of
324
+ // the element from before the window blurred.
325
+ // 2) It was caused by a touch event, in which case we mark the origin as 'touch'.
326
+ // 3) The element was programmatically focused, in which case we should mark the origin as
327
+ // 'program'.
328
+ if ( this . _origin ) {
329
+ return this . _origin ;
330
+ }
331
+
332
+ if ( this . _windowFocused && this . _lastFocusOrigin ) {
333
+ return this . _lastFocusOrigin ;
334
+ } else if ( this . _wasCausedByTouch ( event ) ) {
335
+ return 'touch' ;
336
+ } else {
337
+ return 'program' ;
338
+ }
339
+ }
340
+
325
341
/**
326
342
* Sets the focus classes on the element based on the given focus origin.
327
343
* @param element The element to update the classes on.
328
344
* @param origin The focus origin.
329
345
*/
330
346
private _setClasses ( element : HTMLElement , origin ?: FocusOrigin ) : void {
331
- const elementInfo = this . _elementInfo . get ( element ) ;
332
-
333
- if ( elementInfo ) {
334
- this . _toggleClass ( element , 'cdk-focused' , ! ! origin ) ;
335
- this . _toggleClass ( element , 'cdk-touch-focused' , origin === 'touch' ) ;
336
- this . _toggleClass ( element , 'cdk-keyboard-focused' , origin === 'keyboard' ) ;
337
- this . _toggleClass ( element , 'cdk-mouse-focused' , origin === 'mouse' ) ;
338
- this . _toggleClass ( element , 'cdk-program-focused' , origin === 'program' ) ;
339
- }
347
+ this . _toggleClass ( element , 'cdk-focused' , ! ! origin ) ;
348
+ this . _toggleClass ( element , 'cdk-touch-focused' , origin === 'touch' ) ;
349
+ this . _toggleClass ( element , 'cdk-keyboard-focused' , origin === 'keyboard' ) ;
350
+ this . _toggleClass ( element , 'cdk-mouse-focused' , origin === 'mouse' ) ;
351
+ this . _toggleClass ( element , 'cdk-program-focused' , origin === 'program' ) ;
340
352
}
341
353
342
354
/**
@@ -403,23 +415,7 @@ export class FocusMonitor implements OnDestroy {
403
415
return ;
404
416
}
405
417
406
- // If we couldn't detect a cause for the focus event, it's due to one of three reasons:
407
- // 1) The window has just regained focus, in which case we want to restore the focused state of
408
- // the element from before the window blurred.
409
- // 2) It was caused by a touch event, in which case we mark the origin as 'touch'.
410
- // 3) The element was programmatically focused, in which case we should mark the origin as
411
- // 'program'.
412
- let origin = this . _origin ;
413
- if ( ! origin ) {
414
- if ( this . _windowFocused && this . _lastFocusOrigin ) {
415
- origin = this . _lastFocusOrigin ;
416
- } else if ( this . _wasCausedByTouch ( event ) ) {
417
- origin = 'touch' ;
418
- } else {
419
- origin = 'program' ;
420
- }
421
- }
422
-
418
+ const origin = this . _getFocusOrigin ( event ) ;
423
419
this . _setClasses ( element , origin ) ;
424
420
this . _emitOrigin ( elementInfo . subject , origin ) ;
425
421
this . _lastFocusOrigin = origin ;
@@ -457,6 +453,10 @@ export class FocusMonitor implements OnDestroy {
457
453
const document = this . _getDocument ( ) ;
458
454
const window = this . _getWindow ( ) ;
459
455
456
+ document . addEventListener ( 'focus' , this . _documentFocusAndBlurListener ,
457
+ captureEventListenerOptions ) ;
458
+ document . addEventListener ( 'blur' , this . _documentFocusAndBlurListener ,
459
+ captureEventListenerOptions ) ;
460
460
document . addEventListener ( 'keydown' , this . _documentKeydownListener ,
461
461
captureEventListenerOptions ) ;
462
462
document . addEventListener ( 'mousedown' , this . _documentMousedownListener ,
@@ -474,6 +474,10 @@ export class FocusMonitor implements OnDestroy {
474
474
const document = this . _getDocument ( ) ;
475
475
const window = this . _getWindow ( ) ;
476
476
477
+ document . removeEventListener ( 'focus' , this . _documentFocusAndBlurListener ,
478
+ captureEventListenerOptions ) ;
479
+ document . removeEventListener ( 'blur' , this . _documentFocusAndBlurListener ,
480
+ captureEventListenerOptions ) ;
477
481
document . removeEventListener ( 'keydown' , this . _documentKeydownListener ,
478
482
captureEventListenerOptions ) ;
479
483
document . removeEventListener ( 'mousedown' , this . _documentMousedownListener ,
0 commit comments