From 8ef0f3c48205c6312b405029548947486a4e4dee Mon Sep 17 00:00:00 2001 From: crisbeto Date: Wed, 10 Oct 2018 09:41:21 +0300 Subject: [PATCH] fix(drag-drop): prevent mouse wheel scrolling while dragging Prevents users from scrolling using the mouse wheel while an item is being dragged. This is a temporary measure until we have a solution to auto-scrolling while dragging, as well as to avoid having to measure the page for every pixel that the user has dragged. Fixes #13508. --- src/cdk/drag-drop/drag-drop-registry.spec.ts | 10 ++++++ src/cdk/drag-drop/drag-drop-registry.ts | 35 +++++++++++++------- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/cdk/drag-drop/drag-drop-registry.spec.ts b/src/cdk/drag-drop/drag-drop-registry.spec.ts index 4a9d24bf23e5..8426b74a0879 100644 --- a/src/cdk/drag-drop/drag-drop-registry.spec.ts +++ b/src/cdk/drag-drop/drag-drop-registry.spec.ts @@ -5,6 +5,7 @@ import { dispatchMouseEvent, createTouchEvent, dispatchTouchEvent, + dispatchFakeEvent, } from '@angular/cdk/testing'; import {DragDropRegistry} from './drag-drop-registry'; import {DragDropModule} from './drag-drop-module'; @@ -192,6 +193,15 @@ describe('DragDropRegistry', () => { expect(event.defaultPrevented).toBe(true); }); + it('should not prevent the default `wheel` actions when nothing is being dragged', () => { + expect(dispatchFakeEvent(document, 'wheel').defaultPrevented).toBe(false); + }); + + it('should prevent the default `wheel` action when an item is being dragged', () => { + registry.startDragging(testComponent.dragItems.first, createMouseEvent('mousedown')); + expect(dispatchFakeEvent(document, 'wheel').defaultPrevented).toBe(true); + }); + }); @Component({ diff --git a/src/cdk/drag-drop/drag-drop-registry.ts b/src/cdk/drag-drop/drag-drop-registry.ts index 9dedfed0fd37..ed4b5be26dfa 100644 --- a/src/cdk/drag-drop/drag-drop-registry.ts +++ b/src/cdk/drag-drop/drag-drop-registry.ts @@ -43,7 +43,7 @@ export class DragDropRegistry implements OnDestroy { private _activeDragInstances = new Set(); /** Keeps track of the event listeners that we've bound to the `document`. */ - private _globalListeners = new Map<'touchmove' | 'mousemove' | 'touchend' | 'mouseup', { + private _globalListeners = new Map<'touchmove' | 'mousemove' | 'touchend' | 'mouseup' | 'wheel', { handler: PointerEventHandler, options?: AddEventListenerOptions | boolean }>(); @@ -81,10 +81,13 @@ export class DragDropRegistry implements OnDestroy { registerDragItem(drag: I) { this._dragInstances.add(drag); + // The `touchmove` event gets bound once, ahead of time, because WebKit + // won't preventDefault on a dynamically-added `touchmove` listener. + // See https://bugs.webkit.org/show_bug.cgi?id=184250. if (this._dragInstances.size === 1) { this._ngZone.runOutsideAngular(() => { - // The event handler has to be explicitly active, because - // newer browsers make it passive by default. + // The event handler has to be explicitly active, + // because newer browsers make it passive by default. this._document.addEventListener('touchmove', this._preventScrollListener, activeCapturingEventOptions); }); @@ -135,12 +138,22 @@ export class DragDropRegistry implements OnDestroy { .set(upEvent, { handler: e => this.pointerUp.next(e), options: true - }) - .forEach((config, name) => { - this._ngZone.runOutsideAngular(() => { - this._document.addEventListener(name, config.handler, config.options); - }); }); + + // TODO(crisbeto): prevent mouse wheel scrolling while + // dragging until we've set up proper scroll handling. + if (!isTouchEvent) { + this._globalListeners.set('wheel', { + handler: this._preventScrollListener, + options: activeCapturingEventOptions + }); + } + + this._ngZone.runOutsideAngular(() => { + this._globalListeners.forEach((config, name) => { + this._document.addEventListener(name, config.handler, config.options); + }); + }); } } @@ -173,11 +186,9 @@ export class DragDropRegistry implements OnDestroy { } /** - * Listener used to prevent `touchmove` events while the element is being dragged. - * This gets bound once, ahead of time, because WebKit won't preventDefault on a - * dynamically-added `touchmove` listener. See https://bugs.webkit.org/show_bug.cgi?id=184250. + * Listener used to prevent `touchmove` and `wheel` events while the element is being dragged. */ - private _preventScrollListener = (event: TouchEvent) => { + private _preventScrollListener = (event: Event) => { if (this._activeDragInstances.size) { event.preventDefault(); }