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