@@ -28,6 +28,7 @@ const activeCapturingEventOptions = normalizePassiveListenerOptions({
2828@Injectable ( { providedIn : 'root' } )
2929export class DragDropRegistry < I extends { isDragging ( ) : boolean } , C > implements OnDestroy {
3030 private _document : Document ;
31+ private _window : Window | null ;
3132
3233 /** Registered drop container instances. */
3334 private _dropInstances = new Set < C > ( ) ;
@@ -41,6 +42,10 @@ export class DragDropRegistry<I extends {isDragging(): boolean}, C> implements O
4142 /** Keeps track of the event listeners that we've bound to the `document`. */
4243 private _globalListeners = new Map < string , {
4344 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 ,
4449 options ?: AddEventListenerOptions | boolean
4550 } > ( ) ;
4651
@@ -54,13 +59,13 @@ export class DragDropRegistry<I extends {isDragging(): boolean}, C> implements O
5459 * Emits the `touchmove` or `mousemove` events that are dispatched
5560 * while the user is dragging a drag item instance.
5661 */
57- readonly pointerMove : Subject < TouchEvent | MouseEvent > = new Subject < TouchEvent | MouseEvent > ( ) ;
62+ pointerMove : Subject < TouchEvent | MouseEvent > = new Subject < TouchEvent | MouseEvent > ( ) ;
5863
5964 /**
6065 * Emits the `touchend` or `mouseup` events that are dispatched
6166 * while the user is dragging a drag item instance.
6267 */
63- readonly pointerUp : Subject < TouchEvent | MouseEvent > = new Subject < TouchEvent | MouseEvent > ( ) ;
68+ pointerUp : Subject < TouchEvent | MouseEvent > = new Subject < TouchEvent | MouseEvent > ( ) ;
6469
6570 /**
6671 * 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
6974 */
7075 readonly scroll : Subject < Event > = new Subject < Event > ( ) ;
7176
77+ /** Emits when the page has been blurred while the user is dragging an item. */
78+ pageBlurred : Subject < void > = new Subject < void > ( ) ;
79+
7280 constructor (
7381 private _ngZone : NgZone ,
7482 @Inject ( DOCUMENT ) _document : any ) {
7583 this . _document = _document ;
84+ this . _window = ( typeof window !== 'undefined' && window . addEventListener ) ? window : null ;
7685 }
7786
7887 /** Adds a drop container to the registry. */
@@ -137,35 +146,45 @@ export class DragDropRegistry<I extends {isDragging(): boolean}, C> implements O
137146 this . _globalListeners
138147 . set ( isTouchEvent ? 'touchend' : 'mouseup' , {
139148 handler : ( e : Event ) => this . pointerUp . next ( e as TouchEvent | MouseEvent ) ,
140- options : true
149+ options : true ,
150+ target : this . _document
141151 } )
142152 . set ( 'scroll' , {
143153 handler : ( e : Event ) => this . scroll . next ( e ) ,
144154 // Use capturing so that we pick up scroll changes in any scrollable nodes that aren't
145155 // the document. See https://github.com/angular/components/issues/17144.
146- options : true
156+ options : true ,
157+ target : this . _document
147158 } )
148159 // Preventing the default action on `mousemove` isn't enough to disable text selection
149160 // on Safari so we need to prevent the selection event as well. Alternatively this can
150161 // be done by setting `user-select: none` on the `body`, however it has causes a style
151162 // recalculation which can be expensive on pages with a lot of elements.
152163 . set ( 'selectstart' , {
153164 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
155171 } ) ;
156172
157173 // We don't have to bind a move event for touch drag sequences, because
158174 // we already have a persistent global one bound from `registerDragItem`.
159175 if ( ! isTouchEvent ) {
160176 this . _globalListeners . set ( 'mousemove' , {
161177 handler : ( e : Event ) => this . pointerMove . next ( e as MouseEvent ) ,
162- options : activeCapturingEventOptions
178+ options : activeCapturingEventOptions ,
179+ target : this . _document
163180 } ) ;
164181 }
165182
166183 this . _ngZone . runOutsideAngular ( ( ) => {
167184 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+ }
169188 } ) ;
170189 } ) ;
171190 }
@@ -230,6 +249,7 @@ export class DragDropRegistry<I extends {isDragging(): boolean}, C> implements O
230249 this . _clearGlobalListeners ( ) ;
231250 this . pointerMove . complete ( ) ;
232251 this . pointerUp . complete ( ) ;
252+ this . pageBlurred . complete ( ) ;
233253 }
234254
235255 /**
@@ -259,7 +279,9 @@ export class DragDropRegistry<I extends {isDragging(): boolean}, C> implements O
259279 /** Clears out the global event listeners from the `document`. */
260280 private _clearGlobalListeners ( ) {
261281 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+ }
263285 } ) ;
264286
265287 this . _globalListeners . clear ( ) ;
0 commit comments