diff --git a/src/cdk/drag-drop/directives/drag.spec.ts b/src/cdk/drag-drop/directives/drag.spec.ts
index d3b847f0f8c..4b5899c2fad 100644
--- a/src/cdk/drag-drop/directives/drag.spec.ts
+++ b/src/cdk/drag-drop/directives/drag.spec.ts
@@ -703,6 +703,36 @@ describe('CdkDrag', () => {
}).toThrowError(/^cdkDrag must be attached to an element node/);
}));
+ it('should allow for the dragging sequence to be delayed', fakeAsync(() => {
+ // We can't use Jasmine's `clock` because Zone.js interferes with it.
+ spyOn(Date, 'now').and.callFake(() => currentTime);
+ let currentTime = 0;
+
+ const fixture = createComponent(StandaloneDraggable);
+ fixture.componentInstance.dragStartDelay = 1000;
+ fixture.detectChanges();
+ const dragElement = fixture.componentInstance.dragElement.nativeElement;
+
+ expect(dragElement.style.transform).toBeFalsy('Expected element not to be moved by default.');
+
+ startDraggingViaMouse(fixture, dragElement);
+ currentTime += 750;
+ dispatchMouseEvent(document, 'mousemove', 50, 100);
+ fixture.detectChanges();
+
+ expect(dragElement.style.transform)
+ .toBeFalsy('Expected element not to be moved if the drag timeout has not passed.');
+
+ // The first `mousemove` here starts the sequence and the second one moves the element.
+ currentTime += 500;
+ dispatchMouseEvent(document, 'mousemove', 50, 100);
+ dispatchMouseEvent(document, 'mousemove', 50, 100);
+ fixture.detectChanges();
+
+ expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)',
+ 'Expected element to be dragged after all the time has passed.');
+ }));
+
});
describe('draggable with a handle', () => {
@@ -2832,6 +2862,7 @@ describe('CdkDrag', () => {
implements AfterViewInit, OnChanges, OnDestroy {
*/
@Input('cdkDragBoundary') boundaryElementSelector: string;
+ /**
+ * Amount of milliseconds to wait after the user has put their
+ * pointer down before starting to drag the element.
+ */
+ @Input('cdkDragStartDelay') dragStartDelay: number = 0;
+
/** Whether starting to drag this element is disabled. */
@Input('cdkDragDisabled')
get disabled(): boolean {
@@ -303,6 +309,7 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy {
ref.disabled = this.disabled;
ref.lockAxis = this.lockAxis;
+ ref.dragStartDelay = this.dragStartDelay;
ref
.withBoundaryElement(this._getBoundaryElement())
.withPlaceholderTemplate(placeholder)
diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts
index 904a4d6d498..5acd65c94a1 100644
--- a/src/cdk/drag-drop/drag-ref.ts
+++ b/src/cdk/drag-drop/drag-ref.ts
@@ -167,6 +167,9 @@ export class DragRef {
*/
private _lastTouchEventTime: number;
+ /** Time at which the last dragging sequence was started. */
+ private _dragStartTime: number;
+
/** Cached reference to the boundary element. */
private _boundaryElement: HTMLElement | null = null;
@@ -200,6 +203,12 @@ export class DragRef {
/** Axis along which dragging is locked. */
lockAxis: 'x' | 'y';
+ /**
+ * Amount of milliseconds to wait after the user has put their
+ * pointer down before starting to drag the element.
+ */
+ dragStartDelay: number = 0;
+
/** Whether starting to drag this element is disabled. */
get disabled(): boolean {
return this._disabled || !!(this._dropContainer && this._dropContainer.disabled);
@@ -474,12 +483,13 @@ export class DragRef {
const pointerPosition = this._getPointerPositionOnPage(event);
const distanceX = Math.abs(pointerPosition.x - this._pickupPositionOnPage.x);
const distanceY = Math.abs(pointerPosition.y - this._pickupPositionOnPage.y);
+ const isOverThreshold = distanceX + distanceY >= this._config.dragStartThreshold;
// Only start dragging after the user has moved more than the minimum distance in either
// direction. Note that this is preferrable over doing something like `skip(minimumDistance)`
// in the `pointerMove` subscription, because we're not guaranteed to have one move event
// per pixel of movement (e.g. if the user moves their pointer quickly).
- if (distanceX + distanceY >= this._config.dragStartThreshold) {
+ if (isOverThreshold && (Date.now() >= this._dragStartTime + (this.dragStartDelay || 0))) {
this._hasStartedDragging = true;
this._ngZone.run(() => this._startDragSequence(event));
}
@@ -675,6 +685,7 @@ export class DragRef {
const pointerPosition = this._pickupPositionOnPage = this._getPointerPositionOnPage(event);
this._pointerDirectionDelta = {x: 0, y: 0};
this._pointerPositionAtLastDirectionChange = {x: pointerPosition.x, y: pointerPosition.y};
+ this._dragStartTime = Date.now();
this._dragDropRegistry.startDragging(this, event);
}
diff --git a/tools/public_api_guard/cdk/drag-drop.d.ts b/tools/public_api_guard/cdk/drag-drop.d.ts
index e314d47d217..e829feda098 100644
--- a/tools/public_api_guard/cdk/drag-drop.d.ts
+++ b/tools/public_api_guard/cdk/drag-drop.d.ts
@@ -14,6 +14,7 @@ export declare class CdkDrag implements AfterViewInit, OnChanges, OnDes
boundaryElementSelector: string;
data: T;
disabled: boolean;
+ dragStartDelay: number;
dropContainer: CdkDropList;
dropped: EventEmitter>;
element: ElementRef;
@@ -204,6 +205,7 @@ export declare class DragRef {
beforeStarted: Subject;
data: T;
disabled: boolean;
+ dragStartDelay: number;
dropped: Subject<{
previousIndex: number;
currentIndex: number;