Skip to content

Commit 46a35c6

Browse files
committed
feat(drag-drop): allow for dragging sequence to be delayed
Adds the `cdkDragStartDelay` input that allows for the dragging sequence to be delayed by X amount of milliseconds. Fixes #13908.
1 parent 73d0fb9 commit 46a35c6

File tree

4 files changed

+52
-1
lines changed

4 files changed

+52
-1
lines changed

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,36 @@ describe('CdkDrag', () => {
645645
}).toThrowError(/^cdkDrag must be attached to an element node/);
646646
}));
647647

648+
it('should allow for the dragging sequence to be delayed', fakeAsync(() => {
649+
// We can't use Jasmine's `clock` because Zone.js interferes with it.
650+
spyOn(Date, 'now').and.callFake(() => currentTime);
651+
let currentTime = 0;
652+
653+
const fixture = createComponent(StandaloneDraggable);
654+
fixture.componentInstance.dragStartDelay = 1000;
655+
fixture.detectChanges();
656+
const dragElement = fixture.componentInstance.dragElement.nativeElement;
657+
658+
expect(dragElement.style.transform).toBeFalsy('Expected element not to be moved by default.');
659+
660+
startDraggingViaMouse(fixture, dragElement);
661+
currentTime += 750;
662+
dispatchMouseEvent(document, 'mousemove', 50, 100);
663+
fixture.detectChanges();
664+
665+
expect(dragElement.style.transform)
666+
.toBeFalsy('Expected element not to be moved if the drag timeout has not passed.');
667+
668+
// The first `mousemove` here starts the sequence and the second one moves the element.
669+
currentTime += 500;
670+
dispatchMouseEvent(document, 'mousemove', 50, 100);
671+
dispatchMouseEvent(document, 'mousemove', 50, 100);
672+
fixture.detectChanges();
673+
674+
expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)',
675+
'Expected element to be dragged after all the time has passed.');
676+
}));
677+
648678
});
649679

650680
describe('draggable with a handle', () => {
@@ -2712,6 +2742,7 @@ describe('CdkDrag', () => {
27122742
<div
27132743
cdkDrag
27142744
[cdkDragBoundary]="boundarySelector"
2745+
[cdkDragStartDelay]="dragStartDelay"
27152746
(cdkDragStarted)="startedSpy($event)"
27162747
(cdkDragReleased)="releasedSpy($event)"
27172748
(cdkDragEnded)="endedSpy($event)"
@@ -2727,6 +2758,7 @@ class StandaloneDraggable {
27272758
endedSpy = jasmine.createSpy('ended spy');
27282759
releasedSpy = jasmine.createSpy('released spy');
27292760
boundarySelector: string;
2761+
dragStartDelay: number;
27302762
}
27312763

27322764
@Component({

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
107107
*/
108108
@Input('cdkDragBoundary') boundaryElementSelector: string;
109109

110+
/**
111+
* Amount of milliseconds to wait after the user has put their
112+
* pointer down before starting to drag the element.
113+
*/
114+
@Input('cdkDragStartDelay') dragStartDelay: number = 0;
115+
110116
/** Whether starting to drag this element is disabled. */
111117
@Input('cdkDragDisabled')
112118
get disabled(): boolean {
@@ -277,6 +283,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
277283

278284
ref.disabled = this.disabled;
279285
ref.lockAxis = this.lockAxis;
286+
ref.dragStartDelay = this.dragStartDelay;
280287
ref.withBoundaryElement(this._getBoundaryElement());
281288
placeholder ? ref.withPlaceholderTemplate(placeholder.templateRef, placeholder.data) :
282289
ref.withPlaceholderTemplate(null);

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ export class DragRef<T = any> {
160160
*/
161161
private _lastTouchEventTime: number;
162162

163+
/** Time at which the last dragging sequence was started. */
164+
private _dragStartTime: number;
165+
163166
/** Cached reference to the boundary element. */
164167
private _boundaryElement: HTMLElement | null = null;
165168

@@ -187,6 +190,12 @@ export class DragRef<T = any> {
187190
/** Axis along which dragging is locked. */
188191
lockAxis: 'x' | 'y';
189192

193+
/**
194+
* Amount of milliseconds to wait after the user has put their
195+
* pointer down before starting to drag the element.
196+
*/
197+
dragStartDelay: number = 0;
198+
190199
/** Whether starting to drag this element is disabled. */
191200
get disabled(): boolean {
192201
return this._disabled || !!(this.dropContainer && this.dropContainer.disabled);
@@ -454,12 +463,13 @@ export class DragRef<T = any> {
454463
const pointerPosition = this._getPointerPositionOnPage(event);
455464
const distanceX = Math.abs(pointerPosition.x - this._pickupPositionOnPage.x);
456465
const distanceY = Math.abs(pointerPosition.y - this._pickupPositionOnPage.y);
466+
const isOverThreshold = distanceX + distanceY >= this._config.dragStartThreshold;
457467

458468
// Only start dragging after the user has moved more than the minimum distance in either
459469
// direction. Note that this is preferrable over doing something like `skip(minimumDistance)`
460470
// in the `pointerMove` subscription, because we're not guaranteed to have one move event
461471
// per pixel of movement (e.g. if the user moves their pointer quickly).
462-
if (distanceX + distanceY >= this._config.dragStartThreshold) {
472+
if (isOverThreshold && (Date.now() >= this._dragStartTime + (this.dragStartDelay || 0))) {
463473
this._hasStartedDragging = true;
464474
this._ngZone.run(() => this._startDragSequence(event));
465475
}
@@ -653,6 +663,7 @@ export class DragRef<T = any> {
653663
const pointerPosition = this._pickupPositionOnPage = this._getPointerPositionOnPage(event);
654664
this._pointerDirectionDelta = {x: 0, y: 0};
655665
this._pointerPositionAtLastDirectionChange = {x: pointerPosition.x, y: pointerPosition.y};
666+
this._dragStartTime = Date.now();
656667
this._dragDropRegistry.startDragging(this, event);
657668
}
658669

tools/public_api_guard/cdk/drag-drop.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export declare class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDes
1414
boundaryElementSelector: string;
1515
data: T;
1616
disabled: boolean;
17+
dragStartDelay: number;
1718
dropContainer: CdkDropList;
1819
dropped: EventEmitter<CdkDragDrop<any>>;
1920
element: ElementRef<HTMLElement>;

0 commit comments

Comments
 (0)