diff --git a/src/cdk/drag-drop/directives/drag.spec.ts b/src/cdk/drag-drop/directives/drag.spec.ts index 885120435701..1cc8700f9837 100644 --- a/src/cdk/drag-drop/directives/drag.spec.ts +++ b/src/cdk/drag-drop/directives/drag.spec.ts @@ -602,7 +602,7 @@ describe('CdkDrag', () => { const dragElement = fixture.componentInstance.dragElement.nativeElement; const styles = dragElement.style; - expect(styles.touchAction || (styles as any).webkitUserDrag).toBe('none'); + expect(styles.touchAction || (styles as any).webkitUserDrag).toBeFalsy(); fixture.componentInstance.dragInstance.disabled = true; fixture.detectChanges(); @@ -610,6 +610,30 @@ describe('CdkDrag', () => { expect(styles.touchAction || (styles as any).webkitUserDrag).toBeFalsy(); })); + it('should enable native drag interactions if not dragging', fakeAsync(() => { + const fixture = createComponent(StandaloneDraggable); + fixture.detectChanges(); + const dragElement = fixture.componentInstance.dragElement.nativeElement; + const styles = dragElement.style; + + expect(styles.touchAction || (styles as any).webkitUserDrag).toBeFalsy(); + })); + + it('should disable native drag interactions if dragging', fakeAsync(() => { + const fixture = createComponent(StandaloneDraggable); + fixture.detectChanges(); + const dragElement = fixture.componentInstance.dragElement.nativeElement; + const styles = dragElement.style; + + expect(styles.touchAction || (styles as any).webkitUserDrag).toBeFalsy(); + + startDraggingViaMouse(fixture, dragElement); + dispatchMouseEvent(document, 'mousemove', 50, 100); + fixture.detectChanges(); + + expect(styles.touchAction || (styles as any).webkitUserDrag).toBe('none'); + })); + it('should stop propagation for the drag sequence start event', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -762,7 +786,7 @@ describe('CdkDrag', () => { }).toThrowError(/^cdkDrag must be attached to an element node/); })); - it('should allow for the dragging sequence to be delayed', fakeAsync(() => { + it('should cancel drag if the mouse moves before the delay is elapsed', fakeAsync(() => { // We can't use Jasmine's `clock` because Zone.js interferes with it. spyOn(Date, 'now').and.callFake(() => currentTime); let currentTime = 0; @@ -777,13 +801,52 @@ describe('CdkDrag', () => { startDraggingViaMouse(fixture, dragElement); currentTime += 750; dispatchMouseEvent(document, 'mousemove', 50, 100); + currentTime += 500; fixture.detectChanges(); expect(dragElement.style.transform) - .toBeFalsy('Expected element not to be moved if the drag timeout has not passed.'); + .toBeFalsy('Expected element not to be moved if the mouse moved before the delay.'); + })); - // The first `mousemove` here starts the sequence and the second one moves the element. + it('should enable native drag interactions if mouse moves before the delay', 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; + const styles = dragElement.style; + + expect(dragElement.style.transform).toBeFalsy('Expected element not to be moved by default.'); + + startDraggingViaMouse(fixture, dragElement); + currentTime += 750; + dispatchMouseEvent(document, 'mousemove', 50, 100); currentTime += 500; + fixture.detectChanges(); + + expect(styles.touchAction || (styles as any).webkitUserDrag).toBeFalsy(); + })); + + it('should allow dragging after the drag start delay is elapsed', 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 = 500; + fixture.detectChanges(); + const dragElement = fixture.componentInstance.dragElement.nativeElement; + + expect(dragElement.style.transform).toBeFalsy('Expected element not to be moved by default.'); + + dispatchMouseEvent(dragElement, 'mousedown'); + fixture.detectChanges(); + currentTime += 750; + + // The first `mousemove` here starts the sequence and the second one moves the element. dispatchMouseEvent(document, 'mousemove', 50, 100); dispatchMouseEvent(document, 'mousemove', 50, 100); fixture.detectChanges(); @@ -798,22 +861,17 @@ describe('CdkDrag', () => { let currentTime = 0; const fixture = createComponent(StandaloneDraggable); - fixture.componentInstance.dragStartDelay = '1000'; + fixture.componentInstance.dragStartDelay = '500'; 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); + dispatchMouseEvent(dragElement, 'mousedown'); fixture.detectChanges(); - - expect(dragElement.style.transform) - .toBeFalsy('Expected element not to be moved if the drag timeout has not passed.'); + currentTime += 750; // 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(); diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts index 34dc1c1c4fb3..85973d858a73 100644 --- a/src/cdk/drag-drop/drag-ref.ts +++ b/src/cdk/drag-drop/drag-ref.ts @@ -509,7 +509,13 @@ export class DragRef { // 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 (isOverThreshold && (Date.now() >= this._dragStartTime + (this.dragStartDelay || 0))) { + if (isOverThreshold) { + const isDelayElapsed = Date.now() >= this._dragStartTime + (this.dragStartDelay || 0); + if (!isDelayElapsed) { + this._endDragSequence(event); + return; + } + // Prevent other drag sequences from starting while something in the container is still // being dragged. This can happen while we're waiting for the drop animation to finish // and can cause errors, because some elements might still be moving around. @@ -572,6 +578,14 @@ export class DragRef { /** Handler that is invoked when the user lifts their pointer up, after initiating a drag. */ private _pointerUp = (event: MouseEvent | TouchEvent) => { + this._endDragSequence(event); + } + + /** + * Clears subscriptions and stops the dragging sequence. + * @param event Browser event object that ended the sequence. + */ + private _endDragSequence(event: MouseEvent | TouchEvent) { // Note that here we use `isDragging` from the service, rather than from `this`. // The difference is that the one from the service reflects whether a dragging sequence // has been initiated, whereas the one on `this` includes whether the user has passed @@ -624,6 +638,8 @@ export class DragRef { this._lastTouchEventTime = Date.now(); } + this._toggleNativeDragInteractions(); + if (this._dropContainer) { const element = this._rootElement; @@ -686,7 +702,6 @@ export class DragRef { rootElement.style.webkitTapHighlightColor = 'transparent'; } - this._toggleNativeDragInteractions(); this._hasStartedDragging = this._hasMoved = false; this._initialContainer = this._dropContainer!; @@ -999,7 +1014,7 @@ export class DragRef { return; } - const shouldEnable = this.disabled || this._handles.length > 0; + const shouldEnable = this._handles.length > 0 || !this.isDragging(); if (shouldEnable !== this._nativeInteractionsEnabled) { this._nativeInteractionsEnabled = shouldEnable; diff --git a/src/dev-app/drag-drop/drag-drop-demo-module.ts b/src/dev-app/drag-drop/drag-drop-demo-module.ts index 461ddd6100f3..b166503dae9b 100644 --- a/src/dev-app/drag-drop/drag-drop-demo-module.ts +++ b/src/dev-app/drag-drop/drag-drop-demo-module.ts @@ -12,6 +12,7 @@ import {NgModule} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatIconModule} from '@angular/material/icon'; +import {MatInputModule} from '@angular/material/input'; import {MatSelectModule} from '@angular/material/select'; import {RouterModule} from '@angular/router'; import {DragAndDropDemo} from './drag-drop-demo'; @@ -23,6 +24,7 @@ import {DragAndDropDemo} from './drag-drop-demo'; FormsModule, MatFormFieldModule, MatIconModule, + MatInputModule, MatSelectModule, RouterModule.forChild([{path: '', component: DragAndDropDemo}]), ], diff --git a/src/dev-app/drag-drop/drag-drop-demo.html b/src/dev-app/drag-drop/drag-drop-demo.html index af30924db583..2e7ca73813bd 100644 --- a/src/dev-app/drag-drop/drag-drop-demo.html +++ b/src/dev-app/drag-drop/drag-drop-demo.html @@ -48,7 +48,7 @@

Horizontal list

Free dragging

-
Drag me around
+
Drag me around
@@ -69,3 +69,11 @@

Axis locking

+ +
+

Drag start delay

+ + + + +
diff --git a/src/dev-app/drag-drop/drag-drop-demo.ts b/src/dev-app/drag-drop/drag-drop-demo.ts index 40ddc027ac6d..eb80610c3bcf 100644 --- a/src/dev-app/drag-drop/drag-drop-demo.ts +++ b/src/dev-app/drag-drop/drag-drop-demo.ts @@ -21,6 +21,7 @@ import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag }) export class DragAndDropDemo { axisLock: 'x' | 'y'; + dragStartDelay = 0; todo = [ 'Go out for Lunch', 'Make a cool app',