Snæbjørn
Snæbjørn

Reputation: 10792

Drag Overlay returns to original position when dropped in dropzone

I'm trying to not show the return to origin animation when a droppable is dropped in the dropzone. I don't want to out right disable the animation. <DragOverlay dropAnimation={null}> The "return to origin" animation should play when the droppable item is dropped in an invalid location, i.e. outside the droppable zone. A <DragOverlay> must be used.

I seem to be missing something basic.

Here's a demo of the issue: https://codesandbox.io/p/sandbox/summer-frost-d6m8kd

export default function App() {
  const [items] = useState(["1", "2", "3", "4", "5"]);
  const [activeId, setActiveId] = useState(null);

  return (
    <DndContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
      {items.map((id) => (
        <Draggable key={id} id={id}>
          <Item value={`Item ${id}`} />
        </Draggable>
      ))}

      <DragOverlay>
        {activeId && <Item value={`Item ${activeId}`} />}
      </DragOverlay>

      <Droppable>
        <div style={{ border: "solid", padding: "15px" }}>Drop here</div>
      </Droppable>
    </DndContext>
  );

  function handleDragStart(event) {
    setActiveId(event.active.id);
  }

  function handleDragEnd() {
    setActiveId(null);
  }
}

Upvotes: 1

Views: 546

Answers (1)

Chidi Benedict
Chidi Benedict

Reputation: 1

You need to add styles to position the drag-overlay component when the 'onDragEnd' event fires.

Update your code to:

export default function App() {
  const [items] = useState(["1", "2", "3", "4", "5"]);
  const [activeId, setActiveId] = useState(null);

  // 1: save position of dropzone
  const [dropZoneRect, setDropZoneRect] = useState(undefined);

  return (
    <DndContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
      {items.map((id) => (
        <Draggable key={id} id={id}>
          <Item value={`Item ${id}`} />
        </Draggable>
      ))}

      <DragOverlay
        dropAnimation={{
          keyframes: (resolver) => {
            return [
              {
                transform: 
                 `translate3d(${resolver.transform.initial.x}px, 
                    ${resolver.transform.initial.y}px, 0)`,
              },
              {
               // 2: add a conditional positioning.
                ...(dropZoneRect
                  ? { position: "fixed", top: "0", left: "0" }
                  : {}),
                transform: 
                  dropZoneRect
                    ? `translate3d(${dropZoneRect?.x}, 
                        ${dropZoneRect?.y}, 0)`
                    : `translate3d(${resolver.transform.final.x}px, 
                        ${resolver.transform.final.y}px, 0)`,
               },
             ];
            },
          }}
      >
        {activeId && <Item value={`Item ${activeId}`} />}
      </DragOverlay>

      <Droppable>
        <div style={{ border: "solid", padding: "15px" }}>Drop here</div>
      </Droppable>
    </DndContext>
  );

  function handleDragStart(event) {
    setActiveId(event.active.id);
  }

  // 3: Update the handleDragEnd function
  const handleDragEnd = (e) => {
    if (e.over?.id) {
      setDropZoneRect({
        top: `${e.over.rect.top}px`,
        left: `${e.over.rect.left}px`,
      });
    }

    setActiveId(null);

    requestAnimationFrame(() => setDropZoneRect(undefined));
  };
}

Trick is to immediately set the style of the drag-overlay element to be positioned over the dropZone and remove the style immediately. DragOverlay docs

Upvotes: 0

Related Questions