Skip to content

feat(drag-drop): allow for dragging sequence to be delayed #14732

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/cdk/drag-drop/directives/drag.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,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', () => {
Expand Down Expand Up @@ -2804,6 +2834,7 @@ describe('CdkDrag', () => {
<div
cdkDrag
[cdkDragBoundary]="boundarySelector"
[cdkDragStartDelay]="dragStartDelay"
(cdkDragStarted)="startedSpy($event)"
(cdkDragReleased)="releasedSpy($event)"
(cdkDragEnded)="endedSpy($event)"
Expand All @@ -2819,6 +2850,7 @@ class StandaloneDraggable {
endedSpy = jasmine.createSpy('ended spy');
releasedSpy = jasmine.createSpy('released spy');
boundarySelector: string;
dragStartDelay: number;
}

@Component({
Expand Down
7 changes: 7 additions & 0 deletions src/cdk/drag-drop/directives/drag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ export class CdkDrag<T = any> 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 {
Expand Down Expand Up @@ -300,6 +306,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {

ref.disabled = this.disabled;
ref.lockAxis = this.lockAxis;
ref.dragStartDelay = this.dragStartDelay;
ref
.withBoundaryElement(this._getBoundaryElement())
.withPlaceholderTemplate(placeholder)
Expand Down
13 changes: 12 additions & 1 deletion src/cdk/drag-drop/drag-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ export class DragRef<T = any> {
*/
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;

Expand Down Expand Up @@ -200,6 +203,12 @@ export class DragRef<T = any> {
/** 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);
Expand Down Expand Up @@ -474,12 +483,13 @@ export class DragRef<T = any> {
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));
}
Expand Down Expand Up @@ -675,6 +685,7 @@ export class DragRef<T = any> {
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);
}

Expand Down
2 changes: 2 additions & 0 deletions tools/public_api_guard/cdk/drag-drop.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export declare class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDes
boundaryElementSelector: string;
data: T;
disabled: boolean;
dragStartDelay: number;
dropContainer: CdkDropList;
dropped: EventEmitter<CdkDragDrop<any>>;
element: ElementRef<HTMLElement>;
Expand Down Expand Up @@ -204,6 +205,7 @@ export declare class DragRef<T = any> {
beforeStarted: Subject<void>;
data: T;
disabled: boolean;
dragStartDelay: number;
dropped: Subject<{
previousIndex: number;
currentIndex: number;
Expand Down