From bcf278179bedab9d9be7be7ec5f41077caa0f1a8 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 22 Feb 2019 16:04:40 +0100 Subject: [PATCH] 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. --- src/cdk/drag-drop/directives/drag.spec.ts | 32 +++++++++++++++++++++++ src/cdk/drag-drop/directives/drag.ts | 7 +++++ src/cdk/drag-drop/drag-ref.ts | 13 ++++++++- tools/public_api_guard/cdk/drag-drop.d.ts | 2 ++ 4 files changed, 53 insertions(+), 1 deletion(-) 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;