mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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.
This commit is contained in:
committed by
vivian-hu
parent
9b05c3cfba
commit
26d63ee10b
@@ -18,6 +18,7 @@ import {
|
||||
dispatchEvent,
|
||||
dispatchMouseEvent,
|
||||
dispatchTouchEvent,
|
||||
createTouchEvent,
|
||||
} from '@angular/cdk/testing';
|
||||
import {Directionality} from '@angular/cdk/bidi';
|
||||
import {CdkDrag, CDK_DRAG_CONFIG, CdkDragConfig} from './drag';
|
||||
@@ -169,6 +170,25 @@ describe('CdkDrag', () => {
|
||||
expect(dragElement.style.transform).toBe('translateY(-50%) translate3d(150px, 300px, 0px)');
|
||||
}));
|
||||
|
||||
it('should prevent the `mousedown` action for native draggable elements', fakeAsync(() => {
|
||||
const fixture = createComponent(StandaloneDraggable);
|
||||
fixture.detectChanges();
|
||||
const dragElement = fixture.componentInstance.dragElement.nativeElement;
|
||||
|
||||
dragElement.draggable = true;
|
||||
|
||||
const mousedownEvent = createMouseEvent('mousedown', 50, 50);
|
||||
Object.defineProperty(mousedownEvent, 'target', {get: () => dragElement});
|
||||
spyOn(mousedownEvent, 'preventDefault').and.callThrough();
|
||||
dispatchEvent(dragElement, mousedownEvent);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchMouseEvent(document, 'mousemove', 50, 50);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(mousedownEvent.preventDefault).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('touch dragging', () => {
|
||||
@@ -244,6 +264,25 @@ describe('CdkDrag', () => {
|
||||
dispatchTouchEvent(document, 'touchend');
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should not prevent `touchstart` action for native draggable elements', fakeAsync(() => {
|
||||
const fixture = createComponent(StandaloneDraggable);
|
||||
fixture.detectChanges();
|
||||
const dragElement = fixture.componentInstance.dragElement.nativeElement;
|
||||
|
||||
dragElement.draggable = true;
|
||||
|
||||
const touchstartEvent = createTouchEvent('touchstart', 50, 50);
|
||||
Object.defineProperty(touchstartEvent, 'target', {get: () => dragElement});
|
||||
spyOn(touchstartEvent, 'preventDefault').and.callThrough();
|
||||
dispatchEvent(dragElement, touchstartEvent);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchTouchEvent(document, 'touchmove');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(touchstartEvent.preventDefault).not.toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
||||
it('should dispatch an event when the user has started dragging', fakeAsync(() => {
|
||||
|
||||
@@ -82,6 +82,9 @@ export function CDK_DRAG_CONFIG_FACTORY(): CdkDragConfig {
|
||||
/** Options that can be used to bind a passive event listener. */
|
||||
const passiveEventListenerOptions = normalizePassiveListenerOptions({passive: true});
|
||||
|
||||
/** Options that can be used to bind an active event listener. */
|
||||
const activeEventListenerOptions = normalizePassiveListenerOptions({passive: false});
|
||||
|
||||
/** Element that can be moved inside a CdkDropList container. */
|
||||
@Directive({
|
||||
selector: '[cdkDrag]',
|
||||
@@ -279,7 +282,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
|
||||
.pipe(take(1))
|
||||
.subscribe(() => {
|
||||
const rootElement = this._rootElement = this._getRootElement();
|
||||
rootElement.addEventListener('mousedown', this._pointerDown, passiveEventListenerOptions);
|
||||
rootElement.addEventListener('mousedown', this._pointerDown, activeEventListenerOptions);
|
||||
rootElement.addEventListener('touchstart', this._pointerDown, passiveEventListenerOptions);
|
||||
this._handles.changes.pipe(startWith(null)).subscribe(() =>
|
||||
toggleNativeDragInteractions(rootElement, this.getChildHandles().length > 0));
|
||||
@@ -290,7 +293,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
|
||||
// The directive might have been destroyed before the root element is initialized.
|
||||
if (this._rootElement) {
|
||||
this._rootElement.removeEventListener('mousedown', this._pointerDown,
|
||||
passiveEventListenerOptions);
|
||||
activeEventListenerOptions);
|
||||
this._rootElement.removeEventListener('touchstart', this._pointerDown,
|
||||
passiveEventListenerOptions);
|
||||
|
||||
@@ -354,6 +357,16 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
|
||||
// starting another sequence for a draggable parent somewhere up the DOM tree.
|
||||
event.stopPropagation();
|
||||
|
||||
// If the event started from an element with the native HTML drag&drop, it'll interfere
|
||||
// with our own dragging (e.g. `img` tags do it by default). Prevent the default action
|
||||
// to stop it from happening. Note that preventing on `dragstart` also seems to work, but
|
||||
// it's flaky and it fails if the user drags it away quickly. Also note that we only want
|
||||
// to do this for `mousedown` since doing the same for `touchstart` will stop any `click`
|
||||
// events from firing on touch devices.
|
||||
if (event.target && (event.target as HTMLElement).draggable && event.type === 'mousedown') {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// Abort if the user is already dragging or is using a mouse button other than the primary one.
|
||||
if (this._isDragging() || (!this._isTouchEvent(event) && event.button !== 0)) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user