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(); +}