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! |
| })); | ||
|
|
||
| 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.
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.
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.
|
|
||
| const fixture = createComponent(StandaloneDraggable); | ||
| fixture.componentInstance.dragStartDelay = '1000'; | ||
| fixture.componentInstance.dragStartDelay = '500'; |
There was a problem hiding this comment.
The changes shouldn't have an effect on this test. Why did this need to be reduced?
There was a problem hiding this comment.
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.
| 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.
You don't need the parentheses around the entire expression here.
| // 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.
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.
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.
Alright, I tried it out and it doesn't seem to be an issue.
crisbeto
left a comment
There was a problem hiding this comment.
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.
| * 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.
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.
| 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.
This can be moved inside the isOverThreshold check and potentially inlined.
| // 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.
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.
Can we leave this at 0 so it matches the default?
| }) | ||
| export class DragAndDropDemo { | ||
| axisLock: 'x' | 'y'; | ||
| dragStartDelay: number = 0; |
There was a problem hiding this comment.
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
…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