components: BUG: CdkDropList drop position after scroll is wrong

Reopening #13823 as it was closed as duplicate of #13588 but it is not.

#13588 is about implementing automatic scrolling after drag overshoot

I have already implemented a automatic scrolling (it is a very specific solution for our use case). When item is dropped after automatic scroll , drop receiver is miscalculated.

Bug

CdkDropList: Drop position is wrong after scrolling

What is the expected behavior?

Drop target should match element under mouse pointer after scroll

What is the current behavior?

Scrolling is not taken into consideration while calculating drop element

What are the steps to reproduce?

https://stackblitz.com/edit/cdk-drop-list-scroll

Steps:

1.Start dragging top most element 2. Drag past container towards down 3. Wait till everything is scrolled down 4. now drag element back into container 5. Drop the element

More info

https://github.com/angular/material2/blob/26c73e04350fa9a159a8e0319b9d4837df04ea5b/src/cdk/drag-drop/drop-list.ts#L388

_cachePositions need to be called again whenever drag is active and scroll container is scrolled.

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

Angular CLI: 7.0.3 Node: 10.12.0 OS: win32 x64 Angular: 7.0.1 … animations, cdk, common, compiler, compiler-cli, core, forms … http, language-service, material, platform-browser … platform-browser-dynamic, router

Package Version

@angular-devkit/architect 0.10.3 @angular-devkit/build-angular 0.10.3 @angular-devkit/build-optimizer 0.10.3 @angular-devkit/build-webpack 0.10.3 @angular-devkit/core 7.0.3 @angular-devkit/schematics 7.0.3 @angular/cli 7.0.3 @ngtools/webpack 7.0.3 @schematics/angular 7.0.3 @schematics/update 0.10.3 rxjs 6.3.3 typescript 3.1.3 webpack 4.19.1

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 6
  • Comments: 30 (1 by maintainers)

Most upvoted comments

Hello,

I developped a directive allowing a drop list to automatically scroll horizontally/vertically and refresh drop positions. Simply put the dropListScroller directive on the CdkDropList. Note that I had to use a private property of DropListRef: _itemPositions.

Here is the stackblitz: drop-list-scroller

Let me know if it fit your needs.

@crisbeto Do you think it would be possible to make _itemPositions public in DropListRef ? What do you think about this kind of directive ?

It may be related to #13588.

@abdulkareemnalband Here is a diff of the changes I had to make the the CDK library to get a working work-around. It is not ideal, and it is given without warranty or guarantee that it will work in any situation other than my own. However it should give you a place to start to find your own solution.

By viewing this diff you agree that this code is not supported by me or the Angular Material team it is given only as an example with no guarantee... Click here to view diff.

diff --git a/src/cdk/drag-drop/drop-list-ref.ts b/src/cdk/drag-drop/drop-list-ref.ts
index a99e89c9..ff81d21f 100644
--- a/src/cdk/drag-drop/drop-list-ref.ts
+++ b/src/cdk/drag-drop/drop-list-ref.ts
@@ -160,6 +160,9 @@ export class DropListRef<T = any> {
 
   /** Amount of connected siblings that currently have a dragged item. */
   private _activeSiblings = 0;
+  /** Scroll Offset */
+  private _scrollXOffset = 0;
+  private _scrollYOffset = 0;
 
   constructor(
     public element: ElementRef<HTMLElement>,
@@ -192,6 +195,8 @@ export class DropListRef<T = any> {
     this._activeDraggables = this._draggables.slice();
     this._cachePositions();
     this._positionCache.siblings.forEach(sibling => sibling.drop._toggleIsReceiving(true));
+    this._scrollXOffset = this.element.nativeElement.scrollLeft;
+    this._scrollYOffset = this.element.nativeElement.scrollTop;
   }
 
   /**
@@ -334,8 +339,11 @@ export class DropListRef<T = any> {
       return;
     }
 
+    const newItemX = pointerX + (this.element.nativeElement.scrollLeft - this._scrollXOffset)
+    const newItemY = pointerY + (this.element.nativeElement.scrollTop - this._scrollYOffset)
+
     const siblings = this._positionCache.items;
-    const newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY, pointerDelta);
+    const newIndex = this._getItemIndexFromPointerPosition(item, newItemX, newItemY, pointerDelta);
 
     if (newIndex === -1 && siblings.length > 0) {
       return;
@@ -463,6 +471,8 @@ export class DropListRef<T = any> {
     this._positionCache.siblings = [];
     this._previousSwap.drag = null;
     this._previousSwap.delta = 0;
+    this._scrollXOffset = 0;
+    this._scrollYOffset = 0;
   }
 
   /**

@techy2493 thanks I will try this later today

Difference from the original reported example is that Angular here handles the scrolling itself, there is no custom code for it. So it doesn't allow the big "overshoot" of drag outside of the element, you need to keep the element close to bottom or to drag (or scroll using a mouse wheel).

This approach is not working from 9.2.1 CDK version. It can be fixed by adding cdkScrollable to scrollable container, and scrollable directive will call withScrollableParents method itself. I’ve modified your example: stackblitz

Hello, I’ll try to have a look asap but no guaranty, I’m in a rush for now.

Hello Arora, Sorry for the late reply, I didn’t have any time to give an eye on the issue you complained about. I’m glad you found a workaround based on the solution I provided. Don not hesitate to share a stackblitz with the working workaround for others 😃 (And me)