@@ -28,6 +28,7 @@ const activeCapturingEventOptions = normalizePassiveListenerOptions({
28
28
@Injectable ( { providedIn : 'root' } )
29
29
export class DragDropRegistry < I extends { isDragging ( ) : boolean } , C > implements OnDestroy {
30
30
private _document : Document ;
31
+ private _window : Window | null ;
31
32
32
33
/** Registered drop container instances. */
33
34
private _dropInstances = new Set < C > ( ) ;
@@ -41,6 +42,10 @@ export class DragDropRegistry<I extends {isDragging(): boolean}, C> implements O
41
42
/** Keeps track of the event listeners that we've bound to the `document`. */
42
43
private _globalListeners = new Map < string , {
43
44
handler : ( event : Event ) => void ,
45
+ // The target needs to be `| null` because we bind either to `window` or `document` which
46
+ // aren't available during SSR. There's an injection token for the document, but not one for
47
+ // window so we fall back to not binding events to it.
48
+ target : EventTarget | null ,
44
49
options ?: AddEventListenerOptions | boolean
45
50
} > ( ) ;
46
51
@@ -54,13 +59,13 @@ export class DragDropRegistry<I extends {isDragging(): boolean}, C> implements O
54
59
* Emits the `touchmove` or `mousemove` events that are dispatched
55
60
* while the user is dragging a drag item instance.
56
61
*/
57
- readonly pointerMove : Subject < TouchEvent | MouseEvent > = new Subject < TouchEvent | MouseEvent > ( ) ;
62
+ pointerMove : Subject < TouchEvent | MouseEvent > = new Subject < TouchEvent | MouseEvent > ( ) ;
58
63
59
64
/**
60
65
* Emits the `touchend` or `mouseup` events that are dispatched
61
66
* while the user is dragging a drag item instance.
62
67
*/
63
- readonly pointerUp : Subject < TouchEvent | MouseEvent > = new Subject < TouchEvent | MouseEvent > ( ) ;
68
+ pointerUp : Subject < TouchEvent | MouseEvent > = new Subject < TouchEvent | MouseEvent > ( ) ;
64
69
65
70
/**
66
71
* Emits when the viewport has been scrolled while the user is dragging an item.
@@ -69,10 +74,14 @@ export class DragDropRegistry<I extends {isDragging(): boolean}, C> implements O
69
74
*/
70
75
readonly scroll : Subject < Event > = new Subject < Event > ( ) ;
71
76
77
+ /** Emits when the page has been blurred while the user is dragging an item. */
78
+ pageBlurred : Subject < void > = new Subject < void > ( ) ;
79
+
72
80
constructor (
73
81
private _ngZone : NgZone ,
74
82
@Inject ( DOCUMENT ) _document : any ) {
75
83
this . _document = _document ;
84
+ this . _window = ( typeof window !== 'undefined' && window . addEventListener ) ? window : null ;
76
85
}
77
86
78
87
/** Adds a drop container to the registry. */
@@ -137,35 +146,45 @@ export class DragDropRegistry<I extends {isDragging(): boolean}, C> implements O
137
146
this . _globalListeners
138
147
. set ( isTouchEvent ? 'touchend' : 'mouseup' , {
139
148
handler : ( e : Event ) => this . pointerUp . next ( e as TouchEvent | MouseEvent ) ,
140
- options : true
149
+ options : true ,
150
+ target : this . _document
141
151
} )
142
152
. set ( 'scroll' , {
143
153
handler : ( e : Event ) => this . scroll . next ( e ) ,
144
154
// Use capturing so that we pick up scroll changes in any scrollable nodes that aren't
145
155
// the document. See https://github.com/angular/components/issues/17144.
146
- options : true
156
+ options : true ,
157
+ target : this . _document
147
158
} )
148
159
// Preventing the default action on `mousemove` isn't enough to disable text selection
149
160
// on Safari so we need to prevent the selection event as well. Alternatively this can
150
161
// be done by setting `user-select: none` on the `body`, however it has causes a style
151
162
// recalculation which can be expensive on pages with a lot of elements.
152
163
. set ( 'selectstart' , {
153
164
handler : this . _preventDefaultWhileDragging ,
154
- options : activeCapturingEventOptions
165
+ options : activeCapturingEventOptions ,
166
+ target : this . _document
167
+ } )
168
+ . set ( 'blur' , {
169
+ handler : ( ) => this . pageBlurred . next ( ) ,
170
+ target : this . _window // Note that this event can only be bound on the window, not document
155
171
} ) ;
156
172
157
173
// We don't have to bind a move event for touch drag sequences, because
158
174
// we already have a persistent global one bound from `registerDragItem`.
159
175
if ( ! isTouchEvent ) {
160
176
this . _globalListeners . set ( 'mousemove' , {
161
177
handler : ( e : Event ) => this . pointerMove . next ( e as MouseEvent ) ,
162
- options : activeCapturingEventOptions
178
+ options : activeCapturingEventOptions ,
179
+ target : this . _document
163
180
} ) ;
164
181
}
165
182
166
183
this . _ngZone . runOutsideAngular ( ( ) => {
167
184
this . _globalListeners . forEach ( ( config , name ) => {
168
- this . _document . addEventListener ( name , config . handler , config . options ) ;
185
+ if ( config . target ) {
186
+ config . target . addEventListener ( name , config . handler , config . options ) ;
187
+ }
169
188
} ) ;
170
189
} ) ;
171
190
}
@@ -230,6 +249,7 @@ export class DragDropRegistry<I extends {isDragging(): boolean}, C> implements O
230
249
this . _clearGlobalListeners ( ) ;
231
250
this . pointerMove . complete ( ) ;
232
251
this . pointerUp . complete ( ) ;
252
+ this . pageBlurred . complete ( ) ;
233
253
}
234
254
235
255
/**
@@ -259,7 +279,9 @@ export class DragDropRegistry<I extends {isDragging(): boolean}, C> implements O
259
279
/** Clears out the global event listeners from the `document`. */
260
280
private _clearGlobalListeners ( ) {
261
281
this . _globalListeners . forEach ( ( config , name ) => {
262
- this . _document . removeEventListener ( name , config . handler , config . options ) ;
282
+ if ( config . target ) {
283
+ config . target . removeEventListener ( name , config . handler , config . options ) ;
284
+ }
263
285
} ) ;
264
286
265
287
this . _globalListeners . clear ( ) ;
0 commit comments