3

I'm trying to drag items across two lists. The bottom list is a typical sorted list (like an "inventory"), but I want the top items to be unsorted and droppable anywhere (like a "game board").

I have it MOSTLY working, however when dropping into the top box event.currentIndex is always 0. But when dragging out from there I get different event.previousIndex values, which means the model and DOM elements don't always match.

Here's a stackblitz showing what I mean. Drag a few items into the top box and play around with it, you'll notice sometimes the wrong item gets moved.

It's most notable when you interact in reverse order, for example:

  1. Drag items "One", "Two", "Three" into the top box (in that order)
  2. Try to put back items "Three", "Two", "One" back into bottom box (in that order)

enter image description here

Oren
  • 5,055
  • 3
  • 34
  • 52

1 Answers1

1

cdkDropListSortingDisabled option only works when moving items within the same container. If you move from one container to another then Angular sorts position of blocks:

this._itemPositions = this._activeDraggables.map(drag => {
  const elementToMeasure = drag.getVisibleElement();
  return {drag, offset: 0, clientRect: getMutableClientRect(elementToMeasure)};
}).sort((a, b) => {
  return isHorizontal ? a.clientRect.left - b.clientRect.left :
                        a.clientRect.top - b.clientRect.top;
});

Since you didn't provide orientation and default is vertical then it is sorted by top position.

The top box event.currentIndex is always 0 because you use absolute positioning and placeholder is always at the top.

Try adding the following style to see where the placeholder is displayed:

.cdk-drag-placeholder {
  opacity: 1;
  background: red;
}

enter image description here

To fix it you can calculate currentIndex by yourself, e.g. like this:

const isWithinSameContainer = event.previousContainer === event.container;

let toIndex = event.currentIndex;
if (event.container.sortingDisabled) {
  const arr = event.container.data.sort((a, b) => a.top - b.top);
  const targetIndex = arr.findIndex(item => item.top > top);

  toIndex =
    targetIndex === -1
      ? isWithinSameContainer
        ? arr.length - 1
        : arr.length
      : targetIndex;
}

const item = event.previousContainer.data[event.previousIndex];
item.top = top;
item.left = left;

if (isWithinSameContainer) {
  moveItemInArray(event.container.data, event.previousIndex, toIndex);
} else {
  transferArrayItem(
    event.previousContainer.data,
    event.container.data,
    event.previousIndex,
    toIndex
  );
}

Forked Stackblitz

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • The problem persists even when not dragging across the two lists. In your forked version, add all three items and then just move them around within that group. You'll notice every now and then a piece will jump because the wrong index. (very much appreciate the help btw) – Oren May 04 '20 at 05:02
  • 1
    I forgot to check the same group. Can you please test it again? https://stackblitz.com/edit/two-drop-list-problem-zp556d?file=src/app/cdk-drag-drop-connected-sorting-group-example.ts – yurzui May 04 '20 at 08:04
  • OMG you solved it! Been banging my head at this for days, tysm!! – Oren May 04 '20 at 17:48
  • How would this work given 2 non-overlapping elements at the same height (ie top)? Seems like there should be a secondary sort... – Oren May 05 '20 at 03:03
  • If you want you can also apply this secondary sort https://github.com/angular/components/blob/4e02a95b9092860a639ef32b14d6c4e93446ad9a/src/cdk/drag-drop/directives/drop-list.ts#L208-L216 – yurzui May 05 '20 at 03:15
  • i've tried updating this project, but the object goes back to jumping across the window, as you can see here. Any idea why? https://stackblitz.com/edit/angular-ivy-1jvbnn?file=src%2Fapp%2Fapp.component.ts,src%2Fapp%2Fapp.component.html,src%2Fapp%2Fapp.component.css,src%2Fapp%2Fapp.module.ts,src%2Fstyles.css,package.json – seems Sep 07 '22 at 08:08