-
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
fix(drag-drop): fix drag start delay behavior to allow scrolling (#16224) #16228
Conversation
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). 📝 Please visit https://cla.developers.google.com/ to sign. Once you've signed (or fixed any issues), please reply here (e.g. What to do if you already signed the CLAIndividual signers
Corporate signers
ℹ️ Googlers: Go here for more info. |
I signed it! |
CLAs look good, thanks! ℹ️ Googlers: Go here for more info. |
Hey, @crisbeto ! I don't mean to be bothersome or anything, I noticed most PRs get reviewed within a few day. Since it's been almost two weeks since I created this PR, I was wondering if it fell between the cracks or if it was normal for outside contributions to take a bit longer. Thanks a lot! |
@@ -596,18 +596,28 @@ describe('CdkDrag', () => { | |||
expect(dragElement.style.transform).toBeFalsy(); | |||
})); | |||
|
|||
it('should enable native drag interactions if dragging is disabled', fakeAsync(() => { | |||
it('should enable native drag interactions if not dragging', fakeAsync(() => { |
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?
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.
@@ -798,22 +847,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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
The test used to:
- Click (T 0)
- Move the mouse (T 750)
- Expect that the element did not move (T 750)
- Move the mouse again (T 1250)
- Expect the object to have moved (T 1250)
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.
src/cdk/drag-drop/drag-ref.ts
Outdated
@@ -504,12 +504,18 @@ export class DragRef<T = any> { | |||
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; | |||
const isDelayElapsed = (Date.now() >= this._dragStartTime + (this.dragStartDelay || 0)); |
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.
You don't need the parentheses around the entire expression here.
|
||
// 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 (isOverThreshold && (Date.now() >= this._dragStartTime + (this.dragStartDelay || 0))) { | ||
if (isOverThreshold) { | ||
if (!isDelayElapsed) { |
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.
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 comment
The 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 pointermove
, this method won't be called until the user releases the click and initializes a new sequence. This means the sequence will not be started/ended a bunch of times. At least, that's my understanding. I might be missing something here!
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.
Alright, I tried it out and it doesn't seem to be an issue.
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.
Sorry for the delays, the notification for this keeps getting buried in other emails. Left a few more comments, but it should be good to go afterwards.
src/cdk/drag-drop/drag-ref.ts
Outdated
* Clears subscriptions and stops the dragging sequence. | ||
* @param event Browser event object that ended the sequence. | ||
*/ | ||
private _endDragSequence = (event: MouseEvent | TouchEvent) => { |
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.
This doesn't have to be an arrow function since we're not binding it as an event listener. You can make it into a regular method.
src/cdk/drag-drop/drag-ref.ts
Outdated
@@ -504,12 +504,18 @@ export class DragRef<T = any> { | |||
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; | |||
const isDelayElapsed = (Date.now() >= this._dragStartTime + this.dragStartDelay || 0); |
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.
This can be moved inside the isOverThreshold
check and potentially inlined.
|
||
// 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 (isOverThreshold && (Date.now() >= this._dragStartTime + (this.dragStartDelay || 0))) { | ||
if (isOverThreshold) { | ||
if (!isDelayElapsed) { |
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.
Alright, I tried it out and it doesn't seem to be an issue.
<h2>Drag start delay</h2> | ||
|
||
<mat-form-field> | ||
<input matInput placeholder="Drag start delay" value="100" [(ngModel)]="dragStartDelay"> |
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.
Can we leave this at 0 so it matches the default?
@@ -21,6 +21,7 @@ import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag | |||
}) | |||
export class DragAndDropDemo { | |||
axisLock: 'x' | 'y'; | |||
dragStartDelay: number = 0; |
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.
You can omit the : number
here. We're only explicit about it in the source code so that our doc generation tooling can pick it up.
Don't worry about it, thanks for the review! I believe I addressed all your comments. It seems like there's an issue with the build though. I assume it will be fixed by #16436 . EDIT: It indeed fixed it. 👍 |
) The current implementation of the drag start delay does not allow scrolling on mobile devices. Instead, the draggable element gets teleported to the cursor once the delay is elapsed. In order to handle this use case, we cancel the drag sequence if the cursor moves before the drag start delay is elapsed and we disable native drag interactions only when the drag sequence is started instead of when it is initialized. The drag start delay was also integrated to the drag drop demo. Fixes #16224
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.
LGTM
…ular#16228) The current implementation of the drag start delay does not allow scrolling on mobile devices. Instead, the draggable element gets teleported to the cursor once the delay is elapsed. In order to handle this use case, we cancel the drag sequence if the cursor moves before the drag start delay is elapsed and we disable native drag interactions only when the drag sequence is started instead of when it is initialized. The drag start delay was also integrated to the drag drop demo. Fixes angular#16224 (cherry picked from commit 738f10c)
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
The current implementation of the drag start delay does not allow scrolling on mobile devices.
Instead, the draggable element gets teleported to the cursor once the delay is elapsed.
In order to handle this use case, we cancel the drag sequence if the cursor moves before
the drag start delay is elapsed and we disable native drag interactions only when the
drag sequence is started instead of when it is initialized.
The drag start delay was also integrated to the drag drop demo.
Fixes #16224