@@ -10,26 +10,33 @@ import {
10
10
ChangeDetectionStrategy ,
11
11
Component ,
12
12
Injectable ,
13
+ ListenerOptions ,
13
14
NgZone ,
14
15
OnDestroy ,
16
+ RendererFactory2 ,
15
17
ViewEncapsulation ,
16
18
WritableSignal ,
17
19
inject ,
18
20
signal ,
19
21
} from '@angular/core' ;
20
22
import { DOCUMENT } from '@angular/common' ;
21
- import { normalizePassiveListenerOptions } from '@angular/cdk/platform' ;
23
+ import { _bindEventWithOptions } from '@angular/cdk/platform' ;
22
24
import { _CdkPrivateStyleLoader } from '@angular/cdk/private' ;
23
25
import { Observable , Observer , Subject , merge } from 'rxjs' ;
24
26
import type { DropListRef } from './drop-list-ref' ;
25
27
import type { DragRef } from './drag-ref' ;
26
28
import type { CdkDrag } from './directives/drag' ;
27
29
30
+ /** Event options that can be used to bind a capturing event. */
31
+ const capturingEventOptions = {
32
+ capture : true ,
33
+ } ;
34
+
28
35
/** Event options that can be used to bind an active, capturing event. */
29
- const activeCapturingEventOptions = normalizePassiveListenerOptions ( {
36
+ const activeCapturingEventOptions = {
30
37
passive : false ,
31
38
capture : true ,
32
- } ) ;
39
+ } ;
33
40
34
41
/**
35
42
* Component used to load the drag&drop reset styles.
@@ -55,6 +62,8 @@ export class DragDropRegistry<_ = unknown, __ = unknown> implements OnDestroy {
55
62
private _ngZone = inject ( NgZone ) ;
56
63
private _document = inject ( DOCUMENT ) ;
57
64
private _styleLoader = inject ( _CdkPrivateStyleLoader ) ;
65
+ private _renderer = inject ( RendererFactory2 ) . createRenderer ( null , null ) ;
66
+ private _cleanupDocumentTouchmove : ( ( ) => void ) | undefined ;
58
67
59
68
/** Registered drop container instances. */
60
69
private _dropInstances = new Set < DropListRef > ( ) ;
@@ -66,13 +75,7 @@ export class DragDropRegistry<_ = unknown, __ = unknown> implements OnDestroy {
66
75
private _activeDragInstances : WritableSignal < DragRef [ ] > = signal ( [ ] ) ;
67
76
68
77
/** Keeps track of the event listeners that we've bound to the `document`. */
69
- private _globalListeners = new Map <
70
- string ,
71
- {
72
- handler : ( event : Event ) => void ;
73
- options ?: AddEventListenerOptions | boolean ;
74
- }
75
- > ( ) ;
78
+ private _globalListeners : ( ( ) => void ) [ ] | undefined ;
76
79
77
80
/**
78
81
* Predicate function to check if an item is being dragged. Moved out into a property,
@@ -127,7 +130,10 @@ export class DragDropRegistry<_ = unknown, __ = unknown> implements OnDestroy {
127
130
this . _ngZone . runOutsideAngular ( ( ) => {
128
131
// The event handler has to be explicitly active,
129
132
// because newer browsers make it passive by default.
130
- this . _document . addEventListener (
133
+ this . _cleanupDocumentTouchmove ?.( ) ;
134
+ this . _cleanupDocumentTouchmove = _bindEventWithOptions (
135
+ this . _renderer ,
136
+ this . _document ,
131
137
'touchmove' ,
132
138
this . _persistentTouchmoveListener ,
133
139
activeCapturingEventOptions ,
@@ -147,11 +153,7 @@ export class DragDropRegistry<_ = unknown, __ = unknown> implements OnDestroy {
147
153
this . stopDragging ( drag ) ;
148
154
149
155
if ( this . _dragInstances . size === 0 ) {
150
- this . _document . removeEventListener (
151
- 'touchmove' ,
152
- this . _persistentTouchmoveListener ,
153
- activeCapturingEventOptions ,
154
- ) ;
156
+ this . _cleanupDocumentTouchmove ?.( ) ;
155
157
}
156
158
}
157
159
@@ -174,47 +176,43 @@ export class DragDropRegistry<_ = unknown, __ = unknown> implements OnDestroy {
174
176
// passive ones for `mousemove` and `touchmove`. The events need to be active, because we
175
177
// use `preventDefault` to prevent the page from scrolling while the user is dragging.
176
178
const isTouchEvent = event . type . startsWith ( 'touch' ) ;
177
- const endEventHandler = {
178
- handler : ( e : Event ) => this . pointerUp . next ( e as TouchEvent | MouseEvent ) ,
179
- options : true ,
180
- } ;
179
+ const endEventHandler = ( e : Event ) => this . pointerUp . next ( e as TouchEvent | MouseEvent ) ;
181
180
182
- if ( isTouchEvent ) {
183
- this . _globalListeners . set ( 'touchend' , endEventHandler ) ;
184
- this . _globalListeners . set ( 'touchcancel' , endEventHandler ) ;
185
- } else {
186
- this . _globalListeners . set ( 'mouseup' , endEventHandler ) ;
187
- }
181
+ const toBind : [ name : string , handler : ( event : Event ) => void , options : ListenerOptions ] [ ] = [
182
+ // Use capturing so that we pick up scroll changes in any scrollable nodes that aren't
183
+ // the document. See https://github.com/angular/components/issues/17144.
184
+ [ 'scroll' , ( e : Event ) => this . scroll . next ( e ) , capturingEventOptions ] ,
188
185
189
- this . _globalListeners
190
- . set ( 'scroll' , {
191
- handler : ( e : Event ) => this . scroll . next ( e ) ,
192
- // Use capturing so that we pick up scroll changes in any scrollable nodes that aren't
193
- // the document. See https://github.com/angular/components/issues/17144.
194
- options : true ,
195
- } )
196
186
// Preventing the default action on `mousemove` isn't enough to disable text selection
197
187
// on Safari so we need to prevent the selection event as well. Alternatively this can
198
188
// be done by setting `user-select: none` on the `body`, however it has causes a style
199
189
// recalculation which can be expensive on pages with a lot of elements.
200
- . set ( 'selectstart' , {
201
- handler : this . _preventDefaultWhileDragging ,
202
- options : activeCapturingEventOptions ,
203
- } ) ;
190
+ [ 'selectstart' , this . _preventDefaultWhileDragging , activeCapturingEventOptions ] ,
191
+ ] ;
192
+
193
+ if ( isTouchEvent ) {
194
+ toBind . push (
195
+ [ 'touchend' , endEventHandler , capturingEventOptions ] ,
196
+ [ 'touchcancel' , endEventHandler , capturingEventOptions ] ,
197
+ ) ;
198
+ } else {
199
+ toBind . push ( [ 'mouseup' , endEventHandler , capturingEventOptions ] ) ;
200
+ }
204
201
205
202
// We don't have to bind a move event for touch drag sequences, because
206
203
// we already have a persistent global one bound from `registerDragItem`.
207
204
if ( ! isTouchEvent ) {
208
- this . _globalListeners . set ( 'mousemove' , {
209
- handler : ( e : Event ) => this . pointerMove . next ( e as MouseEvent ) ,
210
- options : activeCapturingEventOptions ,
211
- } ) ;
205
+ toBind . push ( [
206
+ 'mousemove' ,
207
+ ( e : Event ) => this . pointerMove . next ( e as MouseEvent ) ,
208
+ activeCapturingEventOptions ,
209
+ ] ) ;
212
210
}
213
211
214
212
this . _ngZone . runOutsideAngular ( ( ) => {
215
- this . _globalListeners . forEach ( ( config , name ) => {
216
- this . _document . addEventListener ( name , config . handler , config . options ) ;
217
- } ) ;
213
+ this . _globalListeners = toBind . map ( ( [ name , handler , options ] ) =>
214
+ _bindEventWithOptions ( this . _renderer , this . _document , name , handler , options ) ,
215
+ ) ;
218
216
} ) ;
219
217
}
220
218
}
@@ -257,17 +255,20 @@ export class DragDropRegistry<_ = unknown, __ = unknown> implements OnDestroy {
257
255
streams . push (
258
256
new Observable ( ( observer : Observer < Event > ) => {
259
257
return this . _ngZone . runOutsideAngular ( ( ) => {
260
- const eventOptions = true ;
261
- const callback = ( event : Event ) => {
262
- if ( this . _activeDragInstances ( ) . length ) {
263
- observer . next ( event ) ;
264
- }
265
- } ;
266
-
267
- ( shadowRoot as ShadowRoot ) . addEventListener ( 'scroll' , callback , eventOptions ) ;
258
+ const cleanup = _bindEventWithOptions (
259
+ this . _renderer ,
260
+ shadowRoot as ShadowRoot ,
261
+ 'scroll' ,
262
+ ( event : Event ) => {
263
+ if ( this . _activeDragInstances ( ) . length ) {
264
+ observer . next ( event ) ;
265
+ }
266
+ } ,
267
+ capturingEventOptions ,
268
+ ) ;
268
269
269
270
return ( ) => {
270
- ( shadowRoot as ShadowRoot ) . removeEventListener ( 'scroll' , callback , eventOptions ) ;
271
+ cleanup ( ) ;
271
272
} ;
272
273
} ) ;
273
274
} ) ,
@@ -338,10 +339,7 @@ export class DragDropRegistry<_ = unknown, __ = unknown> implements OnDestroy {
338
339
339
340
/** Clears out the global event listeners from the `document`. */
340
341
private _clearGlobalListeners ( ) {
341
- this . _globalListeners . forEach ( ( config , name ) => {
342
- this . _document . removeEventListener ( name , config . handler , config . options ) ;
343
- } ) ;
344
-
345
- this . _globalListeners . clear ( ) ;
342
+ this . _globalListeners ?. forEach ( cleanup => cleanup ( ) ) ;
343
+ this . _globalListeners = undefined ;
346
344
}
347
345
}
0 commit comments