clauderic / dnd-kit

The modern, lightweight, performant, accessible and extensible drag & drop toolkit for React.
http://dndkit.com
MIT License
12.89k stars 640 forks source link

How to drag by copying? #456

Closed Albert-Gao closed 3 years ago

Albert-Gao commented 3 years ago

Thanks for this great lib! I manage to re-create a POC within 8 hrs, so sweet.

Just 1 question, how to drag by copying?

The requirement is, to move a copy of an element, then the same element could be moved over and over, just like the web flow left panel.

My Current structure is I am dragging components from the left panel to the main panel to create UI, it all works.

everything works, just 1 problem.

After dropping, the <DragOverlay> is visually moving back to its original position..... So visually, it looks strange, the component is been added after dropping, then back to its original position, just like in this example, https://5fc05e08a4a65d0021ae0bf2-hbqxtqukzi.chromatic.com/?path=/story/core-draggable-components-dragoverlay--basic-setup

how to prevent this <DragOverlay> to go back but disappear in place? Thanks :)

This is the code of the DragOverlay:

export const ComponentsFrameItemDragOverlay: React.FC = () => {
  const currentDraggingElementId = useAppSelector(
    (state) => state.frames.dnd.draggingElementId,
  )
  const list = useAppSelector((state) => state.frames.componentsFrame)
  const elementInfo = list.find((item) => item.id === currentDraggingElementId)

  return (
    <DragOverlay modifiers={[restrictToWindowEdges]}>
      {elementInfo ? (
        <div
          style={{ height: '104px', width: '104px', border: '1px solid black' }}
        >
          {elementInfo.componentInfo.displayName}
        </div>
      ) : null}
    </DragOverlay>
  )
}

This is the DnDContext

export const DnDContext: React.FC = ({ children }) => {
  const dispatch = useAppDispatch()

  function onDragStart(event: DragStartEvent) {
    dispatch(framesSlice.actions.setDraggingElementId(event.active.id))
  }

  function onDragEnd(event: DragEndEvent) {
    // drop over the mainframe
    if (event.over?.id === Constants.MainFrameId) {
      dispatch(framesSlice.actions.addCurrentDragginComponentToMainFrame())
      return
    }

    // drop over a child
    if (event.over && event.over.data.current?.isLayout) {
      dispatch(
        framesSlice.actions.addCurrentDragginComponentAdChildren({
          parentId: event.over.id,
        }),
      )
      return
    }
  }

  return (
    <DndContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
      {children}
    </DndContext>
  )
}

this is the Draggable, as you see I do not use transform here as we are moving overlay, and I do not want to move the original one.

export const Draggable: React.FC<{ id: string; className?: string }> = ({
  id,
  className,
  children,
}) => {
  const { isDragging, attributes, listeners, setNodeRef } = useDraggable({
    id,
  })

  return (
    <div
      className={classNames(className, {
        'border-dashed border-blue-300 border-2 text-blue-300': isDragging,
      })}
      ref={setNodeRef}
      {...listeners}
      {...attributes}
    >
      {children}
    </div>
  )
}
Albert-Gao commented 3 years ago

I think I figure out why, it is because after dragging into the layout, the id is changed, it is not the id when dragging, it's another id since I need to create a copy of a component... Then the DnDContext lost sight because it seems an item with a new Id has been added. But in my case, I do not want to move the original one, since I want to remove a copy of it, the original component should be there so the user can drag it multiple times to the UI

A possible solution might be create this id when start dragging, but the problem is the onDragStart is not on the <Draggable> , it's on the DnDContext, anyway, we can pass some info to Draggable? I tried, seems after the dragging starting, the data being passed is locked, setState on the drag side won't affect the drop side.

hmm.. drag from outside by copying.. I think this is the pattern I want to implement. Seems i can not find an storybook example here, what am I missing?

Might related to this?: https://github.com/clauderic/dnd-kit/issues/58

clauderic commented 3 years ago

@Albert-Gao the solution to this type of problem is to treat the sidebar from which you are dragging elements as a sort of vending machine for new unique draggable items.

So instead of having a sidebar that looks like:

[ id: "text-section" ]
[ id: "image-gallery" ]
[ id: "slider" ]
[ id: "rich-text" ]

You would make sure that each item in the sidebar from which you are dragging has a unique ID, and when you drop that item you keep that same unique id and generate a new one for the sidebar to replace the item that was just moved from the sidebar to your other droppable region:

[ id: "text-section-81yb" ]
[ id: "image-gallery-2f3y" ]
[ id: "slider-39b2" ]
[ id: "rich-text-3u4i" ]
rmstsd commented 1 year ago

@clauderic

https://codesandbox.io/p/sandbox/determined-shaw-dggmrv

I want to copy the 'a' on the left side to the right side and generate a new id on the right side, and after copying it, I can't drag the left side the 'a'. It is very queer.

taziksh commented 1 year ago

https://codesandbox.io/p/sandbox/determined-shaw-dggmrv

I want to copy the 'a' on the left side to the right side and generate a new id on the right side, and after copying it, I can't drag the left side the 'a'. It is very queer.

@rmstsd take a look at the code here by @penleychan: https://github.com/clauderic/dnd-kit/issues/45#issuecomment-1325938227

tisdadd commented 10 months ago

For my own project, after looking through this and a few other threads, I made this wrapping context provider. It should make it easier to copy elements when using multiple sortable containers, or at least provide some easy to use stories to extract from.