From fad9fc20bdeb96242d0066b134593ac5687b1d40 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sat, 12 Mar 2022 10:09:05 +0100 Subject: [PATCH] fix(cdk/drag-drop): only block dragstart event on event targets Currently we block the `dragstart` event on the entire drag element which doesn't account for its disabled state and for any existing handles. Fixes #24533. --- src/cdk/drag-drop/directives/drag.spec.ts | 34 +++++++++++++++++++++ src/cdk/drag-drop/drag-ref.ts | 37 +++++++++++++++-------- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/cdk/drag-drop/directives/drag.spec.ts b/src/cdk/drag-drop/directives/drag.spec.ts index bdb320187086..c756aec243d8 100644 --- a/src/cdk/drag-drop/directives/drag.spec.ts +++ b/src/cdk/drag-drop/directives/drag.spec.ts @@ -271,6 +271,19 @@ describe('CdkDrag', () => { expect(event.defaultPrevented).toBe(true); })); + + it('should not prevent the default dragstart action when dragging is disabled', fakeAsync(() => { + const fixture = createComponent(StandaloneDraggable); + fixture.detectChanges(); + fixture.componentInstance.dragInstance.disabled = true; + const event = dispatchFakeEvent( + fixture.componentInstance.dragElement.nativeElement, + 'dragstart', + ); + fixture.detectChanges(); + + expect(event.defaultPrevented).toBe(false); + })); }); describe('touch dragging', () => { @@ -1619,6 +1632,27 @@ describe('CdkDrag', () => { dragElementViaMouse(fixture, handleChild, 50, 100); expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); })); + + it('should prevent default dragStart on handle, not on entire draggable', fakeAsync(() => { + const fixture = createComponent(StandaloneDraggableWithHandle); + fixture.detectChanges(); + + const draggableEvent = dispatchFakeEvent( + fixture.componentInstance.dragElement.nativeElement, + 'dragstart', + ); + fixture.detectChanges(); + + const handleEvent = dispatchFakeEvent( + fixture.componentInstance.handleElement.nativeElement, + 'dragstart', + true, + ); + fixture.detectChanges(); + + expect(draggableEvent.defaultPrevented).toBe(false); + expect(handleEvent.defaultPrevented).toBe(true); + })); }); describe('in a drop container', () => { diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts index bf9cf9e8b6cc..9c296274886c 100644 --- a/src/cdk/drag-drop/drag-ref.ts +++ b/src/cdk/drag-drop/drag-ref.ts @@ -448,9 +448,7 @@ 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); + element.addEventListener('dragstart', this._nativeDragStart, activeEventListenerOptions); }); this._initialTransform = undefined; this._rootElement = element; @@ -637,9 +635,7 @@ export class DragRef { // Delegate the event based on whether it started from a handle or the element itself. if (this._handles.length) { - const targetHandle = this._handles.find(handle => { - return event.target && (event.target === handle || handle.contains(event.target as Node)); - }); + const targetHandle = this._getTargetHandle(event); if (targetHandle && !this._disabledHandles.has(targetHandle) && !this.disabled) { this._initializeDragSequence(targetHandle, event); @@ -1295,7 +1291,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); + element.removeEventListener('dragstart', this._nativeDragStart, activeEventListenerOptions); } /** @@ -1520,6 +1516,28 @@ export class DragRef { return this._previewRect; } + + /** Handles a native `dragstart` event. */ + private _nativeDragStart = (event: DragEvent) => { + if (this._handles.length) { + const targetHandle = this._getTargetHandle(event); + + if (targetHandle && !this._disabledHandles.has(targetHandle) && !this.disabled) { + event.preventDefault(); + } + } else if (!this.disabled) { + // 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). + event.preventDefault(); + } + }; + + /** Gets a handle that is the target of an event. */ + private _getTargetHandle(event: Event): HTMLElement | undefined { + return this._handles.find(handle => { + return event.target && (event.target === handle || handle.contains(event.target as Node)); + }); + } } /** @@ -1572,8 +1590,3 @@ 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(); -}