Skip to content

Commit 26d63ee

Browse files
crisbetovivian-hu-zz
authored andcommittedNov 10, 2018
fix(drag-drop): avoid interference from native drag&drop (#14054)
Currently if the user starts dragging from an element that has the native drag&drop (e.g. `img` tags have it by default), the CDK dragging won't work correctly. These changes add a `preventDefault` to work around it.
1 parent 9b05c3c commit 26d63ee

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed
 

‎src/cdk/drag-drop/drag.spec.ts

+39
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
dispatchEvent,
1919
dispatchMouseEvent,
2020
dispatchTouchEvent,
21+
createTouchEvent,
2122
} from '@angular/cdk/testing';
2223
import {Directionality} from '@angular/cdk/bidi';
2324
import {CdkDrag, CDK_DRAG_CONFIG, CdkDragConfig} from './drag';
@@ -169,6 +170,25 @@ describe('CdkDrag', () => {
169170
expect(dragElement.style.transform).toBe('translateY(-50%) translate3d(150px, 300px, 0px)');
170171
}));
171172

173+
it('should prevent the `mousedown` action for native draggable elements', fakeAsync(() => {
174+
const fixture = createComponent(StandaloneDraggable);
175+
fixture.detectChanges();
176+
const dragElement = fixture.componentInstance.dragElement.nativeElement;
177+
178+
dragElement.draggable = true;
179+
180+
const mousedownEvent = createMouseEvent('mousedown', 50, 50);
181+
Object.defineProperty(mousedownEvent, 'target', {get: () => dragElement});
182+
spyOn(mousedownEvent, 'preventDefault').and.callThrough();
183+
dispatchEvent(dragElement, mousedownEvent);
184+
fixture.detectChanges();
185+
186+
dispatchMouseEvent(document, 'mousemove', 50, 50);
187+
fixture.detectChanges();
188+
189+
expect(mousedownEvent.preventDefault).toHaveBeenCalled();
190+
}));
191+
172192
});
173193

174194
describe('touch dragging', () => {
@@ -244,6 +264,25 @@ describe('CdkDrag', () => {
244264
dispatchTouchEvent(document, 'touchend');
245265
fixture.detectChanges();
246266
}));
267+
268+
it('should not prevent `touchstart` action for native draggable elements', fakeAsync(() => {
269+
const fixture = createComponent(StandaloneDraggable);
270+
fixture.detectChanges();
271+
const dragElement = fixture.componentInstance.dragElement.nativeElement;
272+
273+
dragElement.draggable = true;
274+
275+
const touchstartEvent = createTouchEvent('touchstart', 50, 50);
276+
Object.defineProperty(touchstartEvent, 'target', {get: () => dragElement});
277+
spyOn(touchstartEvent, 'preventDefault').and.callThrough();
278+
dispatchEvent(dragElement, touchstartEvent);
279+
fixture.detectChanges();
280+
281+
dispatchTouchEvent(document, 'touchmove');
282+
fixture.detectChanges();
283+
284+
expect(touchstartEvent.preventDefault).not.toHaveBeenCalled();
285+
}));
247286
});
248287

249288
it('should dispatch an event when the user has started dragging', fakeAsync(() => {

‎src/cdk/drag-drop/drag.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ export function CDK_DRAG_CONFIG_FACTORY(): CdkDragConfig {
8282
/** Options that can be used to bind a passive event listener. */
8383
const passiveEventListenerOptions = normalizePassiveListenerOptions({passive: true});
8484

85+
/** Options that can be used to bind an active event listener. */
86+
const activeEventListenerOptions = normalizePassiveListenerOptions({passive: false});
87+
8588
/** Element that can be moved inside a CdkDropList container. */
8689
@Directive({
8790
selector: '[cdkDrag]',
@@ -279,7 +282,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
279282
.pipe(take(1))
280283
.subscribe(() => {
281284
const rootElement = this._rootElement = this._getRootElement();
282-
rootElement.addEventListener('mousedown', this._pointerDown, passiveEventListenerOptions);
285+
rootElement.addEventListener('mousedown', this._pointerDown, activeEventListenerOptions);
283286
rootElement.addEventListener('touchstart', this._pointerDown, passiveEventListenerOptions);
284287
this._handles.changes.pipe(startWith(null)).subscribe(() =>
285288
toggleNativeDragInteractions(rootElement, this.getChildHandles().length > 0));
@@ -290,7 +293,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
290293
// The directive might have been destroyed before the root element is initialized.
291294
if (this._rootElement) {
292295
this._rootElement.removeEventListener('mousedown', this._pointerDown,
293-
passiveEventListenerOptions);
296+
activeEventListenerOptions);
294297
this._rootElement.removeEventListener('touchstart', this._pointerDown,
295298
passiveEventListenerOptions);
296299

@@ -354,6 +357,16 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
354357
// starting another sequence for a draggable parent somewhere up the DOM tree.
355358
event.stopPropagation();
356359

360+
// If the event started from an element with the native HTML drag&drop, it'll interfere
361+
// with our own dragging (e.g. `img` tags do it by default). Prevent the default action
362+
// to stop it from happening. Note that preventing on `dragstart` also seems to work, but
363+
// it's flaky and it fails if the user drags it away quickly. Also note that we only want
364+
// to do this for `mousedown` since doing the same for `touchstart` will stop any `click`
365+
// events from firing on touch devices.
366+
if (event.target && (event.target as HTMLElement).draggable && event.type === 'mousedown') {
367+
event.preventDefault();
368+
}
369+
357370
// Abort if the user is already dragging or is using a mouse button other than the primary one.
358371
if (this._isDragging() || (!this._isTouchEvent(event) && event.button !== 0)) {
359372
return;

0 commit comments

Comments
 (0)
Please sign in to comment.