8
8
9
9
import {
10
10
Platform ,
11
- normalizePassiveListenerOptions ,
12
11
_getShadowRoot ,
13
12
_getEventTarget ,
13
+ _bindEventWithOptions ,
14
14
} from '@angular/cdk/platform' ;
15
15
import {
16
16
Directive ,
@@ -23,6 +23,7 @@ import {
23
23
Output ,
24
24
AfterViewInit ,
25
25
inject ,
26
+ RendererFactory2 ,
26
27
} from '@angular/core' ;
27
28
import { Observable , of as observableOf , Subject , Subscription } from 'rxjs' ;
28
29
import { takeUntil } from 'rxjs/operators' ;
@@ -76,16 +77,18 @@ type MonitoredElementInfo = {
76
77
* Event listener options that enable capturing and also
77
78
* mark the listener as passive if the browser supports it.
78
79
*/
79
- const captureEventListenerOptions = normalizePassiveListenerOptions ( {
80
+ const captureEventListenerOptions = {
80
81
passive : true ,
81
82
capture : true ,
82
- } ) ;
83
+ } ;
83
84
84
85
/** Monitors mouse and keyboard events to determine the cause of focus events. */
85
86
@Injectable ( { providedIn : 'root' } )
86
87
export class FocusMonitor implements OnDestroy {
87
88
private _ngZone = inject ( NgZone ) ;
88
89
private _platform = inject ( Platform ) ;
90
+ private _renderer = inject ( RendererFactory2 ) . createRenderer ( null , null ) ;
91
+ private _cleanupWindowFocus : ( ( ) => void ) | undefined ;
89
92
private readonly _inputModalityDetector = inject ( InputModalityDetector ) ;
90
93
91
94
/** The focus origin that the next focus event is a result of. */
@@ -121,7 +124,13 @@ export class FocusMonitor implements OnDestroy {
121
124
* handlers differently from the rest of the events, because the browser won't emit events
122
125
* to the document when focus moves inside of a shadow root.
123
126
*/
124
- private _rootNodeFocusListenerCount = new Map < HTMLElement | Document | ShadowRoot , number > ( ) ;
127
+ private _rootNodeFocusListeners = new Map <
128
+ HTMLElement | Document | ShadowRoot ,
129
+ {
130
+ count : number ;
131
+ cleanups : ( ( ) => void ) [ ] ;
132
+ }
133
+ > ( ) ;
125
134
126
135
/**
127
136
* The specified detection mode, used for attributing the origin of a focus
@@ -307,12 +316,6 @@ export class FocusMonitor implements OnDestroy {
307
316
return this . _document || document ;
308
317
}
309
318
310
- /** Use defaultView of injected document if available or fallback to global window reference */
311
- private _getWindow ( ) : Window {
312
- const doc = this . _getDocument ( ) ;
313
- return doc . defaultView || window ;
314
- }
315
-
316
319
private _getFocusOrigin ( focusEventTarget : HTMLElement | null ) : FocusOrigin {
317
320
if ( this . _origin ) {
318
321
// If the origin was realized via a touch interaction, we need to perform additional checks
@@ -468,32 +471,45 @@ export class FocusMonitor implements OnDestroy {
468
471
}
469
472
470
473
const rootNode = elementInfo . rootNode ;
471
- const rootNodeFocusListeners = this . _rootNodeFocusListenerCount . get ( rootNode ) || 0 ;
474
+ const listeners = this . _rootNodeFocusListeners . get ( rootNode ) ;
472
475
473
- if ( ! rootNodeFocusListeners ) {
476
+ if ( listeners ) {
477
+ listeners . count ++ ;
478
+ } else {
474
479
this . _ngZone . runOutsideAngular ( ( ) => {
475
- rootNode . addEventListener (
476
- 'focus' ,
477
- this . _rootNodeFocusAndBlurListener ,
478
- captureEventListenerOptions ,
479
- ) ;
480
- rootNode . addEventListener (
481
- 'blur' ,
482
- this . _rootNodeFocusAndBlurListener ,
483
- captureEventListenerOptions ,
484
- ) ;
480
+ this . _rootNodeFocusListeners . set ( rootNode , {
481
+ count : 1 ,
482
+ cleanups : [
483
+ _bindEventWithOptions (
484
+ this . _renderer ,
485
+ rootNode ,
486
+ 'focus' ,
487
+ this . _rootNodeFocusAndBlurListener ,
488
+ captureEventListenerOptions ,
489
+ ) ,
490
+ _bindEventWithOptions (
491
+ this . _renderer ,
492
+ rootNode ,
493
+ 'blur' ,
494
+ this . _rootNodeFocusAndBlurListener ,
495
+ captureEventListenerOptions ,
496
+ ) ,
497
+ ] ,
498
+ } ) ;
485
499
} ) ;
486
500
}
487
501
488
- this . _rootNodeFocusListenerCount . set ( rootNode , rootNodeFocusListeners + 1 ) ;
489
-
490
502
// Register global listeners when first element is monitored.
491
503
if ( ++ this . _monitoredElementCount === 1 ) {
492
504
// Note: we listen to events in the capture phase so we
493
505
// can detect them even if the user stops propagation.
494
506
this . _ngZone . runOutsideAngular ( ( ) => {
495
- const window = this . _getWindow ( ) ;
496
- window . addEventListener ( 'focus' , this . _windowFocusListener ) ;
507
+ this . _cleanupWindowFocus ?.( ) ;
508
+ this . _cleanupWindowFocus = this . _renderer . listen (
509
+ 'window' ,
510
+ 'focus' ,
511
+ this . _windowFocusListener ,
512
+ ) ;
497
513
} ) ;
498
514
499
515
// The InputModalityDetector is also just a collection of global listeners.
@@ -506,32 +522,20 @@ export class FocusMonitor implements OnDestroy {
506
522
}
507
523
508
524
private _removeGlobalListeners ( elementInfo : MonitoredElementInfo ) {
509
- const rootNode = elementInfo . rootNode ;
525
+ const listeners = this . _rootNodeFocusListeners . get ( elementInfo . rootNode ) ;
510
526
511
- if ( this . _rootNodeFocusListenerCount . has ( rootNode ) ) {
512
- const rootNodeFocusListeners = this . _rootNodeFocusListenerCount . get ( rootNode ) ! ;
513
-
514
- if ( rootNodeFocusListeners > 1 ) {
515
- this . _rootNodeFocusListenerCount . set ( rootNode , rootNodeFocusListeners - 1 ) ;
527
+ if ( listeners ) {
528
+ if ( listeners . count > 1 ) {
529
+ listeners . count -- ;
516
530
} else {
517
- rootNode . removeEventListener (
518
- 'focus' ,
519
- this . _rootNodeFocusAndBlurListener ,
520
- captureEventListenerOptions ,
521
- ) ;
522
- rootNode . removeEventListener (
523
- 'blur' ,
524
- this . _rootNodeFocusAndBlurListener ,
525
- captureEventListenerOptions ,
526
- ) ;
527
- this . _rootNodeFocusListenerCount . delete ( rootNode ) ;
531
+ listeners . cleanups . forEach ( cleanup => cleanup ( ) ) ;
532
+ this . _rootNodeFocusListeners . delete ( elementInfo . rootNode ) ;
528
533
}
529
534
}
530
535
531
536
// Unregister global listeners when last element is unmonitored.
532
537
if ( ! -- this . _monitoredElementCount ) {
533
- const window = this . _getWindow ( ) ;
534
- window . removeEventListener ( 'focus' , this . _windowFocusListener ) ;
538
+ this . _cleanupWindowFocus ?.( ) ;
535
539
536
540
// Equivalently, stop our InputModalityDetector subscription.
537
541
this . _stopInputModalityDetector . next ( ) ;
0 commit comments