@@ -28,6 +28,7 @@ const activeCapturingEventOptions = normalizePassiveListenerOptions({
28
28
@Injectable ( { providedIn : 'root' } )
29
29
export class DragDropRegistry < I , 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,28 +42,36 @@ export class DragDropRegistry<I, C> implements OnDestroy {
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
47
52
/**
48
53
* Emits the `touchmove` or `mousemove` events that are dispatched
49
54
* while the user is dragging a drag item instance.
50
55
*/
51
- readonly pointerMove : Subject < TouchEvent | MouseEvent > = new Subject < TouchEvent | MouseEvent > ( ) ;
56
+ pointerMove : Subject < TouchEvent | MouseEvent > = new Subject < TouchEvent | MouseEvent > ( ) ;
52
57
53
58
/**
54
59
* Emits the `touchend` or `mouseup` events that are dispatched
55
60
* while the user is dragging a drag item instance.
56
61
*/
57
- readonly pointerUp : Subject < TouchEvent | MouseEvent > = new Subject < TouchEvent | MouseEvent > ( ) ;
62
+ pointerUp : Subject < TouchEvent | MouseEvent > = new Subject < TouchEvent | MouseEvent > ( ) ;
58
63
59
64
/** Emits when the viewport has been scrolled while the user is dragging an item. */
60
- readonly scroll : Subject < Event > = new Subject < Event > ( ) ;
65
+ scroll : Subject < Event > = new Subject < Event > ( ) ;
66
+
67
+ /** Emits when the page has been blurred while the user is dragging an item. */
68
+ pageBlurred : Subject < void > = new Subject < void > ( ) ;
61
69
62
70
constructor (
63
71
private _ngZone : NgZone ,
64
72
@Inject ( DOCUMENT ) _document : any ) {
65
73
this . _document = _document ;
74
+ this . _window = ( typeof window !== 'undefined' && window . addEventListener ) ? window : null ;
66
75
}
67
76
68
77
/** Adds a drop container to the registry. */
@@ -127,35 +136,45 @@ export class DragDropRegistry<I, C> implements OnDestroy {
127
136
this . _globalListeners
128
137
. set ( isTouchEvent ? 'touchend' : 'mouseup' , {
129
138
handler : ( e : Event ) => this . pointerUp . next ( e as TouchEvent | MouseEvent ) ,
130
- options : true
139
+ options : true ,
140
+ target : this . _document
131
141
} )
132
142
. set ( 'scroll' , {
133
143
handler : ( e : Event ) => this . scroll . next ( e ) ,
134
144
// Use capturing so that we pick up scroll changes in any scrollable nodes that aren't
135
145
// the document. See https://github.com/angular/components/issues/17144.
136
- options : true
146
+ options : true ,
147
+ target : this . _document
137
148
} )
138
149
// Preventing the default action on `mousemove` isn't enough to disable text selection
139
150
// on Safari so we need to prevent the selection event as well. Alternatively this can
140
151
// be done by setting `user-select: none` on the `body`, however it has causes a style
141
152
// recalculation which can be expensive on pages with a lot of elements.
142
153
. set ( 'selectstart' , {
143
154
handler : this . _preventDefaultWhileDragging ,
144
- options : activeCapturingEventOptions
155
+ options : activeCapturingEventOptions ,
156
+ target : this . _document
157
+ } )
158
+ . set ( 'blur' , {
159
+ handler : ( ) => this . pageBlurred . next ( ) ,
160
+ target : this . _window // Note that this event can only be bound on the window, not document
145
161
} ) ;
146
162
147
163
// We don't have to bind a move event for touch drag sequences, because
148
164
// we already have a persistent global one bound from `registerDragItem`.
149
165
if ( ! isTouchEvent ) {
150
166
this . _globalListeners . set ( 'mousemove' , {
151
167
handler : ( e : Event ) => this . pointerMove . next ( e as MouseEvent ) ,
152
- options : activeCapturingEventOptions
168
+ options : activeCapturingEventOptions ,
169
+ target : this . _document
153
170
} ) ;
154
171
}
155
172
156
173
this . _ngZone . runOutsideAngular ( ( ) => {
157
174
this . _globalListeners . forEach ( ( config , name ) => {
158
- this . _document . addEventListener ( name , config . handler , config . options ) ;
175
+ if ( config . target ) {
176
+ config . target . addEventListener ( name , config . handler , config . options ) ;
177
+ }
159
178
} ) ;
160
179
} ) ;
161
180
}
@@ -181,6 +200,7 @@ export class DragDropRegistry<I, C> implements OnDestroy {
181
200
this . _clearGlobalListeners ( ) ;
182
201
this . pointerMove . complete ( ) ;
183
202
this . pointerUp . complete ( ) ;
203
+ this . pageBlurred . complete ( ) ;
184
204
}
185
205
186
206
/**
@@ -204,7 +224,9 @@ export class DragDropRegistry<I, C> implements OnDestroy {
204
224
/** Clears out the global event listeners from the `document`. */
205
225
private _clearGlobalListeners ( ) {
206
226
this . _globalListeners . forEach ( ( config , name ) => {
207
- this . _document . removeEventListener ( name , config . handler , config . options ) ;
227
+ if ( config . target ) {
228
+ config . target . removeEventListener ( name , config . handler , config . options ) ;
229
+ }
208
230
} ) ;
209
231
210
232
this . _globalListeners . clear ( ) ;
0 commit comments