Skip to content

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

Merged
merged 3 commits into from
Jul 9, 2019
Merged

fix(drag-drop): fix drag start delay behavior to allow scrolling (#16224) #16228

merged 3 commits into from
Jul 9, 2019

Conversation

Aboisier
Copy link
Contributor

@Aboisier Aboisier commented Jun 7, 2019

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

@Aboisier Aboisier requested a review from crisbeto as a code owner June 7, 2019 13:27
@googlebot
Copy link

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. I signed it!) and we'll verify it.


What to do if you already signed the CLA

Individual signers
Corporate signers

ℹ️ Googlers: Go here for more info.

@googlebot googlebot added the cla: no PR author must sign Google's Contributor License Agreement: https://opensource.google.com/docs/cla label Jun 7, 2019
@Aboisier
Copy link
Contributor Author

Aboisier commented Jun 10, 2019

I signed it!

@googlebot
Copy link

CLAs look good, thanks!

ℹ️ Googlers: Go here for more info.

@googlebot googlebot added cla: yes PR author has agreed to Google's Contributor License Agreement and removed cla: no PR author must sign Google's Contributor License Agreement: https://opensource.google.com/docs/cla labels Jun 10, 2019
@Aboisier
Copy link
Contributor Author

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(() => {
Copy link
Member

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?

Copy link
Contributor Author

@Aboisier Aboisier Jun 24, 2019

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';
Copy link
Member

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?

Copy link
Contributor Author

@Aboisier Aboisier Jun 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test used to:

  1. Click (T 0)
  2. Move the mouse (T 750)
  3. Expect that the element did not move (T 750)
  4. Move the mouse again (T 1250)
  5. 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.

@@ -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));
Copy link
Member

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) {
Copy link
Member

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.

Copy link
Contributor Author

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!

Copy link
Member

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.

Copy link
Member

@crisbeto crisbeto left a 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.

* Clears subscriptions and stops the dragging sequence.
* @param event Browser event object that ended the sequence.
*/
private _endDragSequence = (event: MouseEvent | TouchEvent) => {
Copy link
Member

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.

@@ -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);
Copy link
Member

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) {
Copy link
Member

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">
Copy link
Member

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;
Copy link
Member

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.

@Aboisier
Copy link
Contributor Author

Aboisier commented Jul 3, 2019

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. 👍

Antoine Boisier-Michaud added 3 commits July 3, 2019 11:10
)

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
)

Addresses PR comments. Adds a test, removes unnecessary parentheses.
Copy link
Member

@crisbeto crisbeto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@crisbeto crisbeto added P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent pr: lgtm action: merge The PR is ready for merge by the caretaker target: patch This PR is targeted for the next patch release labels Jul 5, 2019
@jelbourn jelbourn merged commit 738f10c into angular:master Jul 9, 2019
@Aboisier Aboisier deleted the drag-and-drop-start-delay-fix branch July 9, 2019 16:25
Aboisier added a commit to cactusoft-ca/components that referenced this pull request Jul 16, 2019
…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)
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 11, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent target: patch This PR is targeted for the next patch release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

drag-drop: Drag delay does not allow scrolling on mobile devices
4 participants