diff --git a/src/cdk/drag-drop/BUILD.bazel b/src/cdk/drag-drop/BUILD.bazel index 8c5c989007bb..bbd4d7bbefd5 100644 --- a/src/cdk/drag-drop/BUILD.bazel +++ b/src/cdk/drag-drop/BUILD.bazel @@ -17,6 +17,7 @@ ng_module( module_name = "@angular/cdk/drag-drop", deps = [ "//src:dev_mode_types", + "//src/cdk/a11y", "//src/cdk/bidi", "//src/cdk/coercion", "//src/cdk/platform", diff --git a/src/cdk/drag-drop/directives/drag.spec.ts b/src/cdk/drag-drop/directives/drag.spec.ts index 6ef5498537c8..d2d486f2d79a 100644 --- a/src/cdk/drag-drop/directives/drag.spec.ts +++ b/src/cdk/drag-drop/directives/drag.spec.ts @@ -253,6 +253,33 @@ describe('CdkDrag', () => { expect(mousedownEvent.preventDefault).toHaveBeenCalled(); })); + it('should not start dragging an element with a fake mousedown event', fakeAsync(() => { + const fixture = createComponent(StandaloneDraggable); + fixture.detectChanges(); + const dragElement = fixture.componentInstance.dragElement.nativeElement; + const event = createMouseEvent('mousedown', 0, 0); + + Object.defineProperties(event, { + offsetX: {get: () => 0}, + offsetY: {get: () => 0} + }); + + expect(dragElement.style.transform).toBeFalsy(); + + dispatchEvent(dragElement, event); + fixture.detectChanges(); + + dispatchMouseEvent(document, 'mousemove', 20, 100); + fixture.detectChanges(); + dispatchMouseEvent(document, 'mousemove', 50, 100); + fixture.detectChanges(); + + dispatchMouseEvent(document, 'mouseup'); + fixture.detectChanges(); + + expect(dragElement.style.transform).toBeFalsy(); + })); + }); describe('touch dragging', () => { @@ -350,6 +377,34 @@ describe('CdkDrag', () => { expect(touchstartEvent.preventDefault).not.toHaveBeenCalled(); })); + + it('should not start dragging an element with a fake touchstart event', fakeAsync(() => { + const fixture = createComponent(StandaloneDraggable); + fixture.detectChanges(); + const dragElement = fixture.componentInstance.dragElement.nativeElement; + const event = createTouchEvent('touchstart', 50, 50) as TouchEvent; + + Object.defineProperties(event.touches[0], { + identifier: {get: () => -1}, + radiusX: {get: () => null}, + radiusY: {get: () => null} + }); + + expect(dragElement.style.transform).toBeFalsy(); + + dispatchEvent(dragElement, event); + fixture.detectChanges(); + + dispatchTouchEvent(document, 'touchmove', 20, 100); + fixture.detectChanges(); + dispatchTouchEvent(document, 'touchmove', 50, 100); + fixture.detectChanges(); + + dispatchTouchEvent(document, 'touchend'); + fixture.detectChanges(); + + expect(dragElement.style.transform).toBeFalsy(); + })); }); it('should dispatch an event when the user has started dragging', fakeAsync(() => { diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts index 5faf9b943af4..05c3db7df8b6 100644 --- a/src/cdk/drag-drop/drag-ref.ts +++ b/src/cdk/drag-drop/drag-ref.ts @@ -15,6 +15,10 @@ import { _getShadowRoot, } from '@angular/cdk/platform'; import {coerceBooleanProperty, coerceElement} from '@angular/cdk/coercion'; +import { + isFakeMousedownFromScreenReader, + isFakeTouchstartFromScreenReader, +} from '@angular/cdk/a11y'; import {Subscription, Subject, Observable} from 'rxjs'; import {DropListRefInternal as DropListRef} from './drop-list-ref'; import {DragDropRegistry} from './drag-drop-registry'; @@ -864,6 +868,8 @@ export class DragRef { const target = _getEventTarget(event); const isSyntheticEvent = !isTouchSequence && this._lastTouchEventTime && this._lastTouchEventTime + MOUSE_EVENT_IGNORE_TIME > Date.now(); + const isFakeEvent = isTouchSequence ? isFakeTouchstartFromScreenReader(event as TouchEvent) : + isFakeMousedownFromScreenReader(event as MouseEvent); // 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 @@ -876,7 +882,7 @@ export class DragRef { } // Abort if the user is already dragging or is using a mouse button other than the primary one. - if (isDragging || isAuxiliaryMouseButton || isSyntheticEvent) { + if (isDragging || isAuxiliaryMouseButton || isSyntheticEvent || isFakeEvent) { return; }