-
Notifications
You must be signed in to change notification settings - Fork 6.8k
fix(drag-drop): fix drag start delay behavior to allow scrolling (#16224) #16228
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -602,14 +602,38 @@ 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(); | ||
|
||
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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The changes shouldn't have an effect on this test. Why did this need to be reduced? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test used to:
This did not reflect the new drag start delay behaviour I am suggesting. The first mouse move, because it is performed before the drag start delay, now cancels the drag sequence. The moving before the delay is elapsed is tested here. This test ensures that moving after the delay is elapsed moves the element. |
||
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(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -509,7 +509,13 @@ export class DragRef<T = any> { | |
// 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm following it correctly, it seems like we'll keep starting and ending dragging sequences until the delay has elapsed. Don't we want to not start the sequence in the first place? This feels like it could lead some issues like events being dispatched when they shouldn't be. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the pointer moves before the drag start delay has elapsed, then the drag sequence is ended. Since ending the sequence removes the subscription to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright, I tried it out and it doesn't seem to be an issue. |
||
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<T = any> { | |
|
||
/** 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<T = any> { | |
this._lastTouchEventTime = Date.now(); | ||
} | ||
|
||
this._toggleNativeDragInteractions(); | ||
|
||
if (this._dropContainer) { | ||
const element = this._rootElement; | ||
|
||
|
@@ -686,7 +702,6 @@ export class DragRef<T = any> { | |
rootElement.style.webkitTapHighlightColor = 'transparent'; | ||
} | ||
|
||
this._toggleNativeDragInteractions(); | ||
this._hasStartedDragging = this._hasMoved = false; | ||
this._initialContainer = this._dropContainer!; | ||
|
||
|
@@ -999,7 +1014,7 @@ export class DragRef<T = any> { | |
return; | ||
} | ||
|
||
const shouldEnable = this.disabled || this._handles.length > 0; | ||
const shouldEnable = this._handles.length > 0 || !this.isDragging(); | ||
|
||
if (shouldEnable !== this._nativeInteractionsEnabled) { | ||
this._nativeInteractionsEnabled = shouldEnable; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like the old test case is still valid. Should we have one that checks when it's disabled and another one if not dragging?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense! I'll add it back. I foresee only one small tweak I'm going to have to do to the test; the
webkitUserDrag
will now be falsy by default. This is to allow scrolling.