dnd-kit icon indicating copy to clipboard operation
dnd-kit copied to clipboard

DragOverlay misplaced when sorting multiple elements

Open tozz opened this issue 2 years ago • 12 comments

I've used the multi column multi sort example (https://github.com/clauderic/dnd-kit/pull/588) but I can't figure out how to actually get the overlay to get to the right position when removing more than one node from the list, as happens in the storybook.

https://user-images.githubusercontent.com/705677/193299052-33b7ceec-da18-4589-a25e-ea6d7c2e002e.mov

I made a minimal example here https://github.com/tozz/dnd-multisort where you can yarn start to test in your browser, the files are in src/js/ and component.tsx is the main entry point for the DnD stuff. I tried to keep the example as minimal as possible.

tozz avatar Sep 30 '22 15:09 tozz

I am having the same problem. Were you able to solve this issue?

carlosloureda avatar Dec 06 '22 02:12 carlosloureda

I am having the same problem. Were you able to solve this issue?

Unfortunately not, I moved on to other things but it would be great if someone has a fix :)

tozz avatar Dec 06 '22 09:12 tozz

@tozz Thanks. I just found that the example at #588 was using outdated versions of the packages:

@dnd-kit/[email protected] @dnd-kit/[email protected] @dnd-kit/[email protected]

Using those versions seem to fix the problem of the cursor position. But now some things (like moving into another container are broken on my example).

carlosloureda avatar Dec 07 '22 10:12 carlosloureda

Not so a good code, but I come up with a workaround. It is how to delay updating the state on onDragStart.

In this example, delay updating filteredItems to prevent the number of items from changing. This will help calculate the correct position.

onDragStart={e => {
  setTimeout(() => {
    doSomething()
  }, 1)
}}

megos avatar Jan 21 '23 04:01 megos

Did someone find other solution to this because using setTimeout creates a lot of other issues and wrapping it with promise does not solve problem because delaying whole events like onDragEnd until execution of onDragStart breaks dnd behavior and delaying only parts of it creates yet another issues. One of these issues is that user can start dragging something and drop it so quickly (very hard to reproduce) that "onDragEnd" event is not called at all

kamilkazmierczakMtab avatar Apr 05 '23 10:04 kamilkazmierczakMtab

it would be nice to have a onBeforeDragStart where it is safe to cause rerenders without messing with the position.

maloguertin avatar Aug 14 '23 17:08 maloguertin

same. I don't think the cursor position is wrong, the overlay should be at the current mouse dragging location.

linxianxi avatar Sep 12 '23 03:09 linxianxi

I'm facing the same problem, fortunately the setTimeout did work for me.

williamisnotdefined avatar Nov 10 '23 17:11 williamisnotdefined

I have pretty simple workaround that just shifts overlay to correct position :)

Keep in mind that you have to sort selectedIds from top to bottom in order for it to be correctly be calculated.

`drag-overlay.tsx`

type props = {
  activeId: string | null;
  selectedIds: string[];
};
export function MyDragOverlay({ activeId, selectedIds }: props) {
  // Calculates offset based on witch index is active item in a selection and shifts it times items height
  // ITEM_HEIGHT is height plus any gap between items
  const offset = activeId && selectedIds.length > 1 ? selectedIds.indexOf(activeId) * ITEM_HEIGHT : 0;

  return (
    <DragOverlay style={{ translate: `0 ${offset}px` }}>
      {"Your overlay"}
    </DragOverlay>
  );
}

x-Foz3R-x avatar Jan 19 '24 13:01 x-Foz3R-x

I'm facing the same problem, fortunately the setTimeout did work for me. @williamisnotdefined williamisnotdefined could you provide your example?

ricardomejiasilva avatar Jan 26 '24 18:01 ricardomejiasilva

@ricardomejiasilva omg, sorry for the late answer, i didn't see your comment!

<DndContext
      ...
      onDragStart={(event) => {
        /**
         * This tricky timeout is needed
         * to prevent the overlay from being misplaced
         * in relation to the mouse cursor
         */
        setTimeout(() => {
          handleDragStart(event);
        }, 1);
      }}
      ...
>

williamisnotdefined avatar Apr 17 '24 12:04 williamisnotdefined

Not so a good code, but I come up with a workaround. It is how to delay updating the state on onDragStart.

In this example, delay updating filteredItems to prevent the number of items from changing. This will help calculate the correct position.

onDragStart={e => {
  setTimeout(() => {
    doSomething()
  }, 1)
}}

@megos I found a better way, we dont need and we shouldn't use setTimeout as @kamilkazmierczakMtab has mentioned, I fixed the case by adding the modifier snapCenterToCursor

<DndContext
      sensors={sensors}
      collisionDetection={closestCenter}
      onDragStart={handleDragStart}
      onDragEnd={handleDragEnd}
      onDragCancel={handleDragCancel}
      modifiers={[snapCenterToCursor]}  // << it should fix the case, if I am not mistaken
      measuring={{
        droppable: { strategy: MeasuringStrategy.Always },
      }}
    >

Plus, I had to remove the modifiers of my DragOverlay to make it work properly. Hope this help you guys!

williamisnotdefined avatar Jul 04 '24 02:07 williamisnotdefined