From 81898ca5f6966640273d3bafc828bd29ed400208 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Thu, 17 Feb 2022 23:29:48 +0100 Subject: [PATCH] fix(cdk/drag-drop): stop pointer events on placeholder (#24404) Removes the pointer events from the drag placeholder so that the user can't interact with it while the preview is animating. Fixes #24403. (cherry picked from commit 761f5fde28ffc547e1d700906f3f82f5187ed526) --- src/cdk/drag-drop/directives/drag.spec.ts | 15 +++++++++++++++ src/cdk/drag-drop/drag-ref.ts | 12 ++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/cdk/drag-drop/directives/drag.spec.ts b/src/cdk/drag-drop/directives/drag.spec.ts index 06b5299f4250..3a640a227a1c 100644 --- a/src/cdk/drag-drop/directives/drag.spec.ts +++ b/src/cdk/drag-drop/directives/drag.spec.ts @@ -259,6 +259,18 @@ describe('CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); })); + + it('should prevent the default dragstart action', fakeAsync(() => { + const fixture = createComponent(StandaloneDraggable); + fixture.detectChanges(); + const event = dispatchFakeEvent( + fixture.componentInstance.dragElement.nativeElement, + 'dragstart', + ); + fixture.detectChanges(); + + expect(event.defaultPrevented).toBe(true); + })); }); describe('touch dragging', () => { @@ -2952,6 +2964,9 @@ describe('CdkDrag', () => { expect(placeholder.textContent!.trim()) .withContext('Expected placeholder content to match element') .toContain('One'); + expect(placeholder.style.pointerEvents) + .withContext('Expected pointer events to be disabled on placeholder') + .toBe('none'); dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts index ac23a0ad4838..72283b531b5f 100644 --- a/src/cdk/drag-drop/drag-ref.ts +++ b/src/cdk/drag-drop/drag-ref.ts @@ -442,6 +442,9 @@ export class DragRef { this._ngZone.runOutsideAngular(() => { element.addEventListener('mousedown', this._pointerDown, activeEventListenerOptions); element.addEventListener('touchstart', this._pointerDown, passiveEventListenerOptions); + // Usually this isn't necessary since the we prevent the default action in `pointerDown`, + // but some cases like dragging of links can slip through (see #24403). + element.addEventListener('dragstart', preventDefault, activeEventListenerOptions); }); this._initialTransform = undefined; this._rootElement = element; @@ -1159,6 +1162,9 @@ export class DragRef { placeholder = deepCloneNode(this._rootElement); } + // Stop pointer events on the preview so the user can't + // interact with it while the preview is animating. + placeholder.style.pointerEvents = 'none'; placeholder.classList.add('cdk-drag-placeholder'); return placeholder; } @@ -1290,6 +1296,7 @@ export class DragRef { private _removeRootElementListeners(element: HTMLElement) { element.removeEventListener('mousedown', this._pointerDown, activeEventListenerOptions); element.removeEventListener('touchstart', this._pointerDown, passiveEventListenerOptions); + element.removeEventListener('dragstart', preventDefault, activeEventListenerOptions); } /** @@ -1555,3 +1562,8 @@ function matchElementSize(target: HTMLElement, sourceRect: ClientRect): void { target.style.height = `${sourceRect.height}px`; target.style.transform = getTransform(sourceRect.left, sourceRect.top); } + +/** Utility to prevent the default action of an event. */ +function preventDefault(event: Event): void { + event.preventDefault(); +}