Skip to content

Commit bcf2781

Browse files
crisbetojelbourn
authored andcommitted
feat(drag-drop): allow for dragging sequence to be delayed (#14732)
Adds the `cdkDragStartDelay` input that allows for the dragging sequence to be delayed by X amount of milliseconds. Fixes #13908.
1 parent 5d3f515 commit bcf2781

File tree

4 files changed

+53
-1
lines changed

4 files changed

+53
-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
@@ -703,6 +703,36 @@ describe('CdkDrag', () => {
703703
}).toThrowError(/^cdkDrag must be attached to an element node/);
704704
}));
705705

706+
it('should allow for the dragging sequence to be delayed', fakeAsync(() => {
707+
// We can't use Jasmine's `clock` because Zone.js interferes with it.
708+
spyOn(Date, 'now').and.callFake(() => currentTime);
709+
let currentTime = 0;
710+
711+
const fixture = createComponent(StandaloneDraggable);
712+
fixture.componentInstance.dragStartDelay = 1000;
713+
fixture.detectChanges();
714+
const dragElement = fixture.componentInstance.dragElement.nativeElement;
715+
716+
expect(dragElement.style.transform).toBeFalsy('Expected element not to be moved by default.');
717+
718+
startDraggingViaMouse(fixture, dragElement);
719+
currentTime += 750;
720+
dispatchMouseEvent(document, 'mousemove', 50, 100);
721+
fixture.detectChanges();
722+
723+
expect(dragElement.style.transform)
724+
.toBeFalsy('Expected element not to be moved if the drag timeout has not passed.');
725+
726+
// The first `mousemove` here starts the sequence and the second one moves the element.
727+
currentTime += 500;
728+
dispatchMouseEvent(document, 'mousemove', 50, 100);
729+
dispatchMouseEvent(document, 'mousemove', 50, 100);
730+
fixture.detectChanges();
731+
732+
expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)',
733+
'Expected element to be dragged after all the time has passed.');
734+
}));
735+
706736
});
707737

708738
describe('draggable with a handle', () => {
@@ -2832,6 +2862,7 @@ describe('CdkDrag', () => {
28322862
<div
28332863
cdkDrag
28342864
[cdkDragBoundary]="boundarySelector"
2865+
[cdkDragStartDelay]="dragStartDelay"
28352866
(cdkDragStarted)="startedSpy($event)"
28362867
(cdkDragReleased)="releasedSpy($event)"
28372868
(cdkDragEnded)="endedSpy($event)"
@@ -2847,6 +2878,7 @@ class StandaloneDraggable {
28472878
endedSpy = jasmine.createSpy('ended spy');
28482879
releasedSpy = jasmine.createSpy('released spy');
28492880
boundarySelector: string;
2881+
dragStartDelay: number;
28502882
}
28512883

28522884
@Component({

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

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

113+
/**
114+
* Amount of milliseconds to wait after the user has put their
115+
* pointer down before starting to drag the element.
116+
*/
117+
@Input('cdkDragStartDelay') dragStartDelay: number = 0;
118+
113119
/** Whether starting to drag this element is disabled. */
114120
@Input('cdkDragDisabled')
115121
get disabled(): boolean {
@@ -303,6 +309,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
303309

304310
ref.disabled = this.disabled;
305311
ref.lockAxis = this.lockAxis;
312+
ref.dragStartDelay = this.dragStartDelay;
306313
ref
307314
.withBoundaryElement(this._getBoundaryElement())
308315
.withPlaceholderTemplate(placeholder)

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

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

170+
/** Time at which the last dragging sequence was started. */
171+
private _dragStartTime: number;
172+
170173
/** Cached reference to the boundary element. */
171174
private _boundaryElement: HTMLElement | null = null;
172175

@@ -200,6 +203,12 @@ export class DragRef<T = any> {
200203
/** Axis along which dragging is locked. */
201204
lockAxis: 'x' | 'y';
202205

206+
/**
207+
* Amount of milliseconds to wait after the user has put their
208+
* pointer down before starting to drag the element.
209+
*/
210+
dragStartDelay: number = 0;
211+
203212
/** Whether starting to drag this element is disabled. */
204213
get disabled(): boolean {
205214
return this._disabled || !!(this._dropContainer && this._dropContainer.disabled);
@@ -474,12 +483,13 @@ export class DragRef<T = any> {
474483
const pointerPosition = this._getPointerPositionOnPage(event);
475484
const distanceX = Math.abs(pointerPosition.x - this._pickupPositionOnPage.x);
476485
const distanceY = Math.abs(pointerPosition.y - this._pickupPositionOnPage.y);
486+
const isOverThreshold = distanceX + distanceY >= this._config.dragStartThreshold;
477487

478488
// Only start dragging after the user has moved more than the minimum distance in either
479489
// direction. Note that this is preferrable over doing something like `skip(minimumDistance)`
480490
// in the `pointerMove` subscription, because we're not guaranteed to have one move event
481491
// per pixel of movement (e.g. if the user moves their pointer quickly).
482-
if (distanceX + distanceY >= this._config.dragStartThreshold) {
492+
if (isOverThreshold && (Date.now() >= this._dragStartTime + (this.dragStartDelay || 0))) {
483493
this._hasStartedDragging = true;
484494
this._ngZone.run(() => this._startDragSequence(event));
485495
}
@@ -675,6 +685,7 @@ export class DragRef<T = any> {
675685
const pointerPosition = this._pickupPositionOnPage = this._getPointerPositionOnPage(event);
676686
this._pointerDirectionDelta = {x: 0, y: 0};
677687
this._pointerPositionAtLastDirectionChange = {x: pointerPosition.x, y: pointerPosition.y};
688+
this._dragStartTime = Date.now();
678689
this._dragDropRegistry.startDragging(this, event);
679690
}
680691

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

Lines changed: 2 additions & 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>;
@@ -204,6 +205,7 @@ export declare class DragRef<T = any> {
204205
beforeStarted: Subject<void>;
205206
data: T;
206207
disabled: boolean;
208+
dragStartDelay: number;
207209
dropped: Subject<{
208210
previousIndex: number;
209211
currentIndex: number;

0 commit comments

Comments
 (0)