@@ -65,7 +65,6 @@ export const FOCUS_MONITOR_DEFAULT_OPTIONS =
6565 new InjectionToken < FocusMonitorOptions > ( 'cdk-focus-monitor-default-options' ) ;
6666
6767type MonitoredElementInfo = {
68- unlisten : Function ,
6968 checkChildren : boolean ,
7069 subject : Subject < FocusOrigin >
7170} ;
@@ -181,6 +180,19 @@ export class FocusMonitor implements OnDestroy {
181180 this . _document = document ;
182181 this . _detectionMode = options ?. detectionMode || FocusMonitorDetectionMode . IMMEDIATE ;
183182 }
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+ }
184196
185197 /**
186198 * Monitors focus on an element and applies appropriate CSS classes.
@@ -211,34 +223,19 @@ export class FocusMonitor implements OnDestroy {
211223
212224 // Check if we're already monitoring this element.
213225 if ( this . _elementInfo . has ( nativeElement ) ) {
214- let cachedInfo = this . _elementInfo . get ( nativeElement ) ;
226+ const cachedInfo = this . _elementInfo . get ( nativeElement ) ;
215227 cachedInfo ! . checkChildren = checkChildren ;
216228 return cachedInfo ! . subject . asObservable ( ) ;
217229 }
218230
219231 // Create monitored element info.
220- let info : MonitoredElementInfo = {
221- unlisten : ( ) => { } ,
232+ const info : MonitoredElementInfo = {
222233 checkChildren : checkChildren ,
223234 subject : new Subject < FocusOrigin > ( )
224235 } ;
225236 this . _elementInfo . set ( nativeElement , info ) ;
226237 this . _incrementMonitoredElementCount ( ) ;
227238
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-
242239 return info . subject . asObservable ( ) ;
243240 }
244241
@@ -259,7 +256,6 @@ export class FocusMonitor implements OnDestroy {
259256 const elementInfo = this . _elementInfo . get ( nativeElement ) ;
260257
261258 if ( elementInfo ) {
262- elementInfo . unlisten ( ) ;
263259 elementInfo . subject . complete ( ) ;
264260
265261 this . _setClasses ( nativeElement ) ;
@@ -322,21 +318,37 @@ export class FocusMonitor implements OnDestroy {
322318 }
323319 }
324320
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+
325341 /**
326342 * Sets the focus classes on the element based on the given focus origin.
327343 * @param element The element to update the classes on.
328344 * @param origin The focus origin.
329345 */
330346 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' ) ;
340352 }
341353
342354 /**
@@ -403,23 +415,7 @@ export class FocusMonitor implements OnDestroy {
403415 return ;
404416 }
405417
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 ) ;
423419 this . _setClasses ( element , origin ) ;
424420 this . _emitOrigin ( elementInfo . subject , origin ) ;
425421 this . _lastFocusOrigin = origin ;
@@ -457,6 +453,10 @@ export class FocusMonitor implements OnDestroy {
457453 const document = this . _getDocument ( ) ;
458454 const window = this . _getWindow ( ) ;
459455
456+ document . addEventListener ( 'focus' , this . _documentFocusAndBlurListener ,
457+ captureEventListenerOptions ) ;
458+ document . addEventListener ( 'blur' , this . _documentFocusAndBlurListener ,
459+ captureEventListenerOptions ) ;
460460 document . addEventListener ( 'keydown' , this . _documentKeydownListener ,
461461 captureEventListenerOptions ) ;
462462 document . addEventListener ( 'mousedown' , this . _documentMousedownListener ,
@@ -474,6 +474,10 @@ export class FocusMonitor implements OnDestroy {
474474 const document = this . _getDocument ( ) ;
475475 const window = this . _getWindow ( ) ;
476476
477+ document . removeEventListener ( 'focus' , this . _documentFocusAndBlurListener ,
478+ captureEventListenerOptions ) ;
479+ document . removeEventListener ( 'blur' , this . _documentFocusAndBlurListener ,
480+ captureEventListenerOptions ) ;
477481 document . removeEventListener ( 'keydown' , this . _documentKeydownListener ,
478482 captureEventListenerOptions ) ;
479483 document . removeEventListener ( 'mousedown' , this . _documentMousedownListener ,
0 commit comments