From e0c72d9761ea41bec2dba4b03e6b4a562303eab2 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Sat, 4 Jan 2020 16:18:53 +0200 Subject: [PATCH] fix(drag-drop): prevent dragging selected text with the mouse When a drag item has some text inside it and the user starts dragging after a small delay, they'll pause our dragging sequence mid-way and start the native browser text dragging. Once they've stopped dragging the text, our dragging takes over which ends up looking glitchy. The problem comes from the fact that for mouse events we were calling `preventDefault` only after the delay and the drag threshold had been reached, whereas for touch events we were doing it all the time. These changes move the `preventDefault` call up so we call it for all mouse events as well. --- src/cdk/drag-drop/directives/drag.spec.ts | 28 +++++++++++++++++++++++ src/cdk/drag-drop/drag-ref.ts | 5 +++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/cdk/drag-drop/directives/drag.spec.ts b/src/cdk/drag-drop/directives/drag.spec.ts index dcac4f49d05a..c1b8cf041c54 100644 --- a/src/cdk/drag-drop/directives/drag.spec.ts +++ b/src/cdk/drag-drop/directives/drag.spec.ts @@ -1132,6 +1132,34 @@ describe('CdkDrag', () => { subscription.unsubscribe(); })); + it('should prevent the default `mousemove` action even before the drag threshold has ' + + 'been reached', fakeAsync(() => { + const fixture = createComponent(StandaloneDraggable, [], 5); + fixture.detectChanges(); + const dragElement = fixture.componentInstance.dragElement.nativeElement; + + dispatchMouseEvent(dragElement, 'mousedown', 2, 2); + fixture.detectChanges(); + const mousemoveEvent = dispatchMouseEvent(document, 'mousemove', 2, 2); + fixture.detectChanges(); + + expect(mousemoveEvent.defaultPrevented).toBe(true); + })); + + it('should prevent the default `touchmove` action even before the drag threshold has ' + + 'been reached', fakeAsync(() => { + const fixture = createComponent(StandaloneDraggable, [], 5); + fixture.detectChanges(); + const dragElement = fixture.componentInstance.dragElement.nativeElement; + + dispatchTouchEvent(dragElement, 'touchstart', 2, 2); + fixture.detectChanges(); + const touchmoveEvent = dispatchTouchEvent(document, 'touchmove', 2, 2); + fixture.detectChanges(); + + expect(touchmoveEvent.defaultPrevented).toBe(true); + })); + }); describe('draggable with a handle', () => { diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts index fac3ab655dc0..56f4ebade056 100644 --- a/src/cdk/drag-drop/drag-ref.ts +++ b/src/cdk/drag-drop/drag-ref.ts @@ -525,6 +525,10 @@ export class DragRef { /** Handler that is invoked when the user moves their pointer after they've initiated a drag. */ private _pointerMove = (event: MouseEvent | TouchEvent) => { + // Prevent the default action as early as possible in order to block + // native actions like dragging the selected text or images with the mouse. + event.preventDefault(); + if (!this._hasStartedDragging) { const pointerPosition = this._getPointerPositionOnPage(event); const distanceX = Math.abs(pointerPosition.x - this._pickupPositionOnPage.x); @@ -565,7 +569,6 @@ export class DragRef { const constrainedPointerPosition = this._getConstrainedPointerPosition(event); this._hasMoved = true; - event.preventDefault(); this._updatePointerDirectionDelta(constrainedPointerPosition); if (this._dropContainer) {