Skip to content

Commit 6ac4537

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 4b35cad commit 6ac4537

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
@@ -679,6 +679,36 @@ describe('CdkDrag', () => {
679679
}).toThrowError(/^cdkDrag must be attached to an element node/);
680680
}));
681681

682+
it('should allow for the dragging sequence to be delayed', fakeAsync(() => {
683+
// We can't use Jasmine's `clock` because Zone.js interferes with it.
684+
spyOn(Date, 'now').and.callFake(() => currentTime);
685+
let currentTime = 0;
686+
687+
const fixture = createComponent(StandaloneDraggable);
688+
fixture.componentInstance.dragStartDelay = 1000;
689+
fixture.detectChanges();
690+
const dragElement = fixture.componentInstance.dragElement.nativeElement;
691+
692+
expect(dragElement.style.transform).toBeFalsy('Expected element not to be moved by default.');
693+
694+
startDraggingViaMouse(fixture, dragElement);
695+
currentTime += 750;
696+
dispatchMouseEvent(document, 'mousemove', 50, 100);
697+
fixture.detectChanges();
698+
699+
expect(dragElement.style.transform)
700+
.toBeFalsy('Expected element not to be moved if the drag timeout has not passed.');
701+
702+
// The first `mousemove` here starts the sequence and the second one moves the element.
703+
currentTime += 500;
704+
dispatchMouseEvent(document, 'mousemove', 50, 100);
705+
dispatchMouseEvent(document, 'mousemove', 50, 100);
706+
fixture.detectChanges();
707+
708+
expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)',
709+
'Expected element to be dragged after all the time has passed.');
710+
}));
711+
682712
});
683713

684714
describe('draggable with a handle', () => {
@@ -2803,6 +2833,7 @@ describe('CdkDrag', () => {
28032833
<div
28042834
cdkDrag
28052835
[cdkDragBoundary]="boundarySelector"
2836+
[cdkDragStartDelay]="dragStartDelay"
28062837
(cdkDragStarted)="startedSpy($event)"
28072838
(cdkDragReleased)="releasedSpy($event)"
28082839
(cdkDragEnded)="endedSpy($event)"
@@ -2818,6 +2849,7 @@ class StandaloneDraggable {
28182849
endedSpy = jasmine.createSpy('ended spy');
28192850
releasedSpy = jasmine.createSpy('released spy');
28202851
boundarySelector: string;
2852+
dragStartDelay: number;
28212853
}
28222854

28232855
@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);
@@ -455,12 +464,13 @@ export class DragRef<T = any> {
455464
const pointerPosition = this._getPointerPositionOnPage(event);
456465
const distanceX = Math.abs(pointerPosition.x - this._pickupPositionOnPage.x);
457466
const distanceY = Math.abs(pointerPosition.y - this._pickupPositionOnPage.y);
467+
const isOverThreshold = distanceX + distanceY >= this._config.dragStartThreshold;
458468

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

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)