From 85f04e0e5f086c20c98e0eb4f2571fc544e49e3e Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 6 Dec 2018 14:18:58 +0100 Subject: [PATCH] fix(drag-drop): prevent text selection while dragging on Safari Fixes being able to select text while dragging an item on Safari. Fixes #14403. --- src/cdk/drag-drop/drag-drop-registry.spec.ts | 10 +++++++ src/cdk/drag-drop/drag-drop-registry.ts | 30 ++++++++++++-------- src/cdk/drag-drop/drag-ref.ts | 2 ++ 3 files changed, 30 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 65fa16aa7939..6d7397a730ce 100644 --- a/src/cdk/drag-drop/drag-drop-registry.spec.ts +++ b/src/cdk/drag-drop/drag-drop-registry.spec.ts @@ -190,6 +190,16 @@ describe('DragDropRegistry', () => { expect(dispatchFakeEvent(document, 'wheel').defaultPrevented).toBe(true); }); + it('should not prevent the default `selectstart` actions when nothing is being dragged', () => { + expect(dispatchFakeEvent(document, 'selectstart').defaultPrevented).toBe(false); + }); + + it('should prevent the default `selectstart` action when an item is being dragged', () => { + registry.startDragging(testComponent.dragItems.first, createMouseEvent('mousedown')); + expect(dispatchFakeEvent(document, 'selectstart').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 54380328b3a6..b40a405bac03 100644 --- a/src/cdk/drag-drop/drag-drop-registry.ts +++ b/src/cdk/drag-drop/drag-drop-registry.ts @@ -17,9 +17,6 @@ const activeCapturingEventOptions = normalizePassiveListenerOptions({ capture: true }); -/** Handler for a pointer event callback. */ -type PointerEventHandler = (event: TouchEvent | MouseEvent) => void; - /** * Service that keeps track of all the drag item and drop container * instances, and manages global event listeners on the `document`. @@ -42,8 +39,8 @@ 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' | 'wheel', { - handler: PointerEventHandler, + private _globalListeners = new Map void, options?: AddEventListenerOptions | boolean }>(); @@ -87,7 +84,7 @@ export class DragDropRegistry implements OnDestroy { this._ngZone.runOutsideAngular(() => { // The event handler has to be explicitly active, // because newer browsers make it passive by default. - this._document.addEventListener('touchmove', this._preventScrollListener, + this._document.addEventListener('touchmove', this._preventDefaultWhileDragging, activeCapturingEventOptions); }); } @@ -104,7 +101,7 @@ export class DragDropRegistry implements OnDestroy { this.stopDragging(drag); if (this._dragInstances.size === 0) { - this._document.removeEventListener('touchmove', this._preventScrollListener, + this._document.removeEventListener('touchmove', this._preventDefaultWhileDragging, activeCapturingEventOptions); } } @@ -127,19 +124,27 @@ export class DragDropRegistry implements OnDestroy { // use `preventDefault` to prevent the page from scrolling while the user is dragging. this._globalListeners .set(moveEvent, { - handler: e => this.pointerMove.next(e), + handler: (e: Event) => this.pointerMove.next(e as TouchEvent | MouseEvent), options: activeCapturingEventOptions }) .set(upEvent, { - handler: e => this.pointerUp.next(e), + handler: (e: Event) => this.pointerUp.next(e as TouchEvent | MouseEvent), options: true + }) + // Preventing the default action on `mousemove` isn't enough to disable text selection + // on Safari so we need to prevent the selection event as well. Alternatively this can + // be done by setting `user-select: none` on the `body`, however it has causes a style + // recalculation which can be expensive on pages with a lot of elements. + .set('selectstart', { + handler: this._preventDefaultWhileDragging, + options: activeCapturingEventOptions }); // 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, + handler: this._preventDefaultWhileDragging, options: activeCapturingEventOptions }); } @@ -184,9 +189,10 @@ export class DragDropRegistry implements OnDestroy { } /** - * Listener used to prevent `touchmove` and `wheel` events while the element is being dragged. + * Event listener that will prevent the default browser action while the user is dragging. + * @param event Event whose default action should be prevented. */ - private _preventScrollListener = (event: Event) => { + private _preventDefaultWhileDragging = (event: Event) => { if (this._activeDragInstances.size) { event.preventDefault(); } diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts index fd7e97a41af0..f9efde97eafb 100644 --- a/src/cdk/drag-drop/drag-ref.ts +++ b/src/cdk/drag-drop/drag-ref.ts @@ -720,6 +720,8 @@ export class DragRef { zIndex: '1000' }); + toggleNativeDragInteractions(preview, false); + preview.classList.add('cdk-drag-preview'); preview.setAttribute('dir', this._dir ? this._dir.value : 'ltr');