Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support dragging a copy (leave original in place) #216

Open
rogerjak opened this issue Dec 4, 2017 · 122 comments
Open

Support dragging a copy (leave original in place) #216

rogerjak opened this issue Dec 4, 2017 · 122 comments

Comments

@rogerjak
Copy link

rogerjak commented Dec 4, 2017

Is it possible to keep the dragged source in the list and instead drop a copy of it?
I have four lists/columns:
| Available objects | When | And | Then |
and I need the items in the first column to always be there.

@alexreardon
Copy link
Collaborator

At this time we are not supporting a copy drag. This goes against the physicality this library is aiming for

@alexreardon
Copy link
Collaborator

Thanks for reaching out! Perhaps this suggestion would be useful to you: #155

@Geczy
Copy link

Geczy commented Dec 19, 2017

I have the same requirement, are you sure this isn't possible?

#221

@rogerjak
Copy link
Author

@Geczy I moved on to react-sortable, after some hacking I got what I wanted.
Granted; not as nice as beautiful-dnd would be...

@HenrikBechmann
Copy link

Damn. Not supporting copy is a showstopper for me.

@alexmcmanus
Copy link

alexmcmanus commented Mar 23, 2018

It's not that hard to implement clone/copy already. In your source Droppable, set isDropDisabled={true}. This stops the source re-ordering when you drag an item - but it will leave a blank space behind when you start dragging. To avoid the blank space, in your Draggable, customize the rendering of the placeholder. If placeholder is not null, render whatever you'd like left behind, at the same width and height as the original. E.g. { provided.placeholder ? (this.renderMyOwnPlaceholder()) : null }.

Then, you just need to make sure in your endDrag() that you don't remove the item from the source collection.

The only caveat is that the custom placeholder part will probably stop working when the library is upgraded to React 16, as there are numerous mentions in the docs of plans for the explicit placeholder to go away. @alexreardon, it would be great if the updated library were to provide a hook for rendering the placeholder to support this kind of customization.

@alexreardon
Copy link
Collaborator

alexreardon commented Mar 23, 2018 via email

@pchr-srf
Copy link

pchr-srf commented May 3, 2018

Regarding the physicality: Maybe the "cloning" could be regarded as an infinite pile of the same item? :)

@t1mmen
Copy link

t1mmen commented Jun 8, 2018

We are considering react-beautiful-dnd as react-dnd replacement, but no copy would be a showstopper for us.

@alexreardon Is this still feature request something you're considering? Does the workaround outlined here work still?

PS: Demos & docs make react-beautiful-dnd look exceptionally well done 👏

@rivneglee
Copy link

I have same requirement for copy drag because I need to implement a toolbox component.

@jerryliu
Copy link

jerryliu commented Jul 8, 2018

I have same requirement for clone drag

@dvirben123
Copy link

@rivneglee did you find something for implement your toolbox component?

@alexreardon
Copy link
Collaborator

I am happy to think a little bit more about this one. But for now it is fairly low priority

@alexreardon alexreardon reopened this Aug 6, 2018
@yingchangwu
Copy link

I updated two files and it works pretty good so far. render a copy of the draggable item when isDragging is true, and this will leave a palceholder space, so I updated the placeholder file to hide the placeholder if you pass variable isCopy = true. you can see an working example from https://hungry-curran-3f8f19.netlify.com under single vertial list -> copy clone

@circlingthesun
Copy link

This would be quite useful in form builders where you drag new elements onto a page.

@vrsttl
Copy link

vrsttl commented Aug 29, 2018

I understand how the physicality of the idea would be damaged, still, I think this would be a reasonable addition.
Edit: anyway, I would imagine a copy function's physicality as getting cookie dough on your fingers and pulling it out of the batch. I think if you guys implemented something like this, the programmers of the world would relate and rejoice :)

@Aarbel
Copy link
Contributor

Aarbel commented Aug 31, 2018

could be great to put a higher priority on this feature proposition ;)

@TrySound
Copy link
Contributor

TrySound commented Sep 2, 2018

One more request for some kind of dnd builders.

@pixelass
Copy link

pixelass commented Sep 22, 2018

+1 for this feature.

Here's an idea for a more or less stable workaround: (use case: dnd builders)

Code sandbox example

(cloned from https://codesandbox.io/s/ql08j35j3q via https://github.com/atlassian/react-beautiful-dnd#basic-usage-examples)

  • while dragging a static item, it is rendered twice. One with injected properties and the other one without. (This way the second one will stay in place as a "ghost")
{(provided, snapshot) => (
	<React.Fragment>
		<Item
			innerRef={provided.innerRef}
			{...provided.draggableProps}
			{...provided.dragHandleProps}>
			{item.content}
		</Item>
		{snapshot.isDragging && (
			<Clone>{item.content}</Clone>
		)}
	</React.Fragment>
)}
  • The div after the "duplicate" is then hidden via display: none!important and the + div selector.
const Item = styled.div`
  /* item styles */
 `;

const Clone = styled(Item)`
  + div {
    display: none!important;
  }
`;
  • this only happens in static lists. moving between dynamic list still performs as originally intended.

This idea should pretty much solve the problem without any hard mutations and enough flexibility to build something that can easily be refactored when the feature gets implemented into core.

dnd4

Since the suggestions with using {snapshot.placeholder && SOMETHING} don't work (placeholder is always null unless a new item is added (moved) to the list, which is not the case if dropping is disabled or elements are reordered), I looked around and stumbled upon the following comment several times: #155 (comment).
It took me a while to realize that the answer was hidden within. 🎉
This video helped a lot to understand blocking drags and drops: https://egghead.io/lessons/react-conditionally-allow-movement-using-react-beautiful-dnd-draggable-and-droppable-props

@NunoCardoso
Copy link

NunoCardoso commented Sep 26, 2018

Very cool, pixelass, I was looking for a solutioin like that.
I still have a problem, when I drop into the target area, the animatiom is still a retreat to the source area.
Any idea why this happens, and how to fix it?

Edit: it was a problem in my code, it works now. Still, great tip on how to do sticky draggables.

@pixelass
Copy link

@NunoCardoso you can render the placeholder as a custom ghost.
This would then animate the target Items correctly.

The source element can be customized but there is no way of knowing the width of the target container unless it's fixed.

A setup like this could help get perfectly smooth transitions.

  • Source Items
    • type: card
    • width: dynamic
    • height: dynamic
  • Target Items
    • type: custom field (anything you want)
    • height: fixed (can be unique per container)
    • width: fixed (something you know or can store if same for all drop-containers)

Here's an earlier prototype of what I',m working on: (you can see there are still issues with the size.)

dnd-ex3

I played around with a toggle mechanism for the items but height is mostly an issue when dragging tall items.
The width issue is on hold since I'm currently creating the entire layout of my app and I hope it will help solve the problems I've had.

@instadrew
Copy link

@alexreardon : If this feature were to be implemented, it would also be nice to be able to drag one of the copies back to the starting area as a way of removing it from wherever it had first been dragged, but without the draggables in the starting area moving around. Just a reverse of the drag from the starting area.

@faddah
Copy link

faddah commented Nov 20, 2018

+1 for the copy/drag feature — it has precedent in other OS's — Mac OS has long let you copy an item, rather than just move it, in the Finder by holding down the 'option' key while dragging; in Windows, you do the same holding down the 'control' key. would love to see this feature here, also.

@vmlopezr
Copy link

@TJHdev How did you get the copied draggable working with react-window? I'm having issues getting the copied item working correctly. At the moment the copy shows up, but the item below is cleared and the list moves up.

Here's a sandbox forked from the react-window example. This just tries to implement the draggable item, with a copy left behind (i.e. the list should not be flickering/moving)

https://codesandbox.io/s/simple-virtual-list-dark-forked-wc1g4

@Kaytmazov
Copy link

Kaytmazov commented Nov 16, 2020

@NunoCardoso you can render the placeholder as a custom ghost.
This would then animate the target Items correctly.

The source element can be customized but there is no way of knowing the width of the target container unless it's fixed.

A setup like this could help get perfectly smooth transitions.

  • Source Items

    • type: card
    • width: dynamic
    • height: dynamic
  • Target Items

    • type: custom field (anything you want)
    • height: fixed (can be unique per container)
    • width: fixed (something you know or can store if same for all drop-containers)

Here's an earlier prototype of what I',m working on: (you can see there are still issues with the size.)

dnd-ex3

I played around with a toggle mechanism for the items but height is mostly an issue when dragging tall items.
The width issue is on hold since I'm currently creating the entire layout of my app and I hope it will help solve the problems I've had.

Hey. Can you please upload this example to Codesandbox?

@pixelass
Copy link

@Kaytmazov I never made a sandbox for this. It was just a prototype and I dropped the project I needed this for.

I extracted all the important parts and provided sandboxes for all features, you just have to put them together.

Keep in mind: "the clone can be any other component, therefore this is entirely up to you"

@Kaytmazov
Copy link

@Kaytmazov I never made a sandbox for this. It was just a prototype and I dropped the project I needed this for.

I extracted all the important parts and provided sandboxes for all features, you just have to put them together.

Keep in mind: "the clone can be any other component, therefore this is entirely up to you"

I'm trying to make form builder. I have issue with nested dropareas. When I put two droparea inside flex parent or make droparea 'inline-block' i get this:
formbuilder

@pixelass
Copy link

@Kaytmazov I'm not sure what I'm looking for here. If you want the Fields to only be accepted to Dropzones within a Layout, you need to add logic for that. There are some examples for this: e.g https://react-beautiful-dnd.netlify.app/?path=/story/board--simple

But again, I'm not sure what you're talking about. Instead of writing "This happens:" and adding a gif you could be more precise.

Here's an example of describing errors to make them understandable.

Expected:
When I say foo
Then bar should say baz

Actual:
When I say foo
Then bar says qux

All together this issue is really just about a copy option which has been provided by various workaround suggestions. If your issue is not related to the copy please look for related issues or open a new one. If you need support you might want to check some online forums/chats instead.

@kelvinkoko
Copy link

+1 for the copy on drag
And then resource here is great! i will try the hack to see whether it still work in latest version :)

Just a suggestion, i understand it may not be implemented officially (though i hope it will)
But maybe it will be great to put this as an unofficial example (community example)?
It take quite a lot time to finish reading this thread😅

@pixelass
Copy link

pixelass commented Dec 2, 2020

I'll happily provide new examples/hacks if it breaks for certain versions.
@alexreardon what do you think about adding examples with this workaround/hack?

If you agree, I would open a PR with a story in: https://github.com/atlassian/react-beautiful-dnd/tree/master/stories

@Denisbeder
Copy link

I found an alternative library that supports copying, maybe help you.

https://kutlugsahin.github.io/smooth-dnd-demo

https://github.com/kutlugsahin/react-smooth-dnd

@pixelass
Copy link

pixelass commented Dec 7, 2020

I found an alternative library that supports copying, maybe help you.

https://kutlugsahin.github.io/smooth-dnd-demo

https://github.com/kutlugsahin/react-smooth-dnd

The main problem I see here is that it's not accessible at all. Actually the entire demo page is 0% accessible which makes me wonder.

@gmariani
Copy link

@vmlopezr Take a look at this example: https://codesandbox.io/s/q3717y1jq4

For me the trick was adding this:
.item ~ div {
transform: none !important;
}

Is this documented anywhere other than examples?

@alii13
Copy link

alii13 commented Dec 10, 2020

@vmlopezr I have implemented the copying it took me a while to get the exact thing what I want here is the resultant gif

copying-min

You can play with the builder on https://w-builder.netlify.app/ and let me know if this the thing you want.
I will share the code😁😁

@xuzhanhh
Copy link

xuzhanhh commented Jan 7, 2021

I solve this with following code :

// keypoint is :
 transform: snapshot.isDragging ? draggableProvided.draggableProps.style?.transform : 'translate(0px, 0px)',
// and 
{snapshot.isDragging &&
  <div style={{ transform: 'none !important' }}>
    {menu}
  </div>}
// full demo
 <Draggable key={index} draggableId={`${index}`} index={index}>
  {(draggableProvided, snapshot) => {
    return (
      <>
        <div
          ref={draggableProvided.innerRef}
          {...draggableProvided.draggableProps}
          {...draggableProvided.dragHandleProps}
          className="cursor-move"
          style={{
            ...draggableProvided.draggableProps.style,
            transform: snapshot.isDragging ? draggableProvided.draggableProps.style?.transform : 'translate(0px, 0px)',
          }}
        >
          {menu}
        </div>
        {snapshot.isDragging &&
          <div style={{ transform: 'none !important' }}>
            {menu}
          </div>}
      </>
    )
  }}
</Draggable>

@edmund-io
Copy link

+1 for this feature.

Here's an idea for a more or less stable workaround: (use case: dnd builders)

Code sandbox example

(cloned from https://codesandbox.io/s/ql08j35j3q via https://github.com/atlassian/react-beautiful-dnd#basic-usage-examples)

  • while dragging a static item, it is rendered twice. One with injected properties and the other one without. (This way the second one will stay in place as a "ghost")
{(provided, snapshot) => (
	<React.Fragment>
		<Item
			innerRef={provided.innerRef}
			{...provided.draggableProps}
			{...provided.dragHandleProps}>
			{item.content}
		</Item>
		{snapshot.isDragging && (
			<Clone>{item.content}</Clone>
		)}
	</React.Fragment>
)}
  • The div after the "duplicate" is then hidden via display: none!important and the + div selector.
const Item = styled.div`
  /* item styles */
 `;

const Clone = styled(Item)`
  + div {
    display: none!important;
  }
`;
  • this only happens in static lists. moving between dynamic list still performs as originally intended.

This idea should pretty much solve the problem without any hard mutations and enough flexibility to build something that can easily be refactored when the feature gets implemented into core.

dnd4

Since the suggestions with using {snapshot.placeholder && SOMETHING} don't work (placeholder is always null unless a new item is added (moved) to the list, which is not the case if dropping is disabled or elements are reordered), I looked around and stumbled upon the following comment several times: #155 (comment).
It took me a while to realize that the answer was hidden within. 🎉
This video helped a lot to understand blocking drags and drops: https://egghead.io/lessons/react-conditionally-allow-movement-using-react-beautiful-dnd-draggable-and-droppable-props

Hi there,

I am currently trying to implement your solution to my own project but not getting the same desired effect. I have added the conditional and the Clone styles but what happens is when I try to drag it removes the next item from the list of draggables instead.

Could it be an issue with how I am setting state for the list of draggables?

Would appreciate your help with this and many thanks in advanced!

My codesandbox:
https://codesandbox.io/s/gallant-cohen-gqm5u

@pixelass
Copy link

@munjyong Please look through the comments. I provided several new hacks for different use-cases and versions. AFAIK the example above does not work since v10 of RBDND

@edmund-io
Copy link

edmund-io commented Mar 28, 2021

@munjyong Please look through the comments. I provided several new hacks for different use-cases and versions. AFAIK the example above does not work since v10 of RBDND

Thank you for your reply. Which comment specifically can I ask? I am struggling to find a compatible one for example the one with the fruits and shopping bag requires a snapshot prop draggingFromThisWith but that is not a prop of snapshot in the latest version. I do see an isClone prop for snapshot in the latest version but can't find anywhere how to use it.

@AbhisheshPradhan
Copy link

AbhisheshPradhan commented Apr 1, 2021

@munjyong I used @xuzhanhh solution to fix this issue.

<Kiosk
    ref={provided.innerRef}
    {...provided.droppableProps}
    isDraggingOver={snapshot.isDraggingOver}
>
  {ITEMS.map((item, index) => (
    <Draggable key={item.id} draggableId={item.id} index={index}>
      {(provided, snapshot) => (
        <>
          <Item
            ref={provided.innerRef}
            {...provided.draggableProps}
            {...provided.dragHandleProps}
            style={{
              ...provided.draggableProps.style,
              transform: snapshot.isDragging
                ? provided.draggableProps.style?.transform
                : 'translate(0px, 0px)',
            }}
          >
            {item.content}
          </Item>
          {snapshot.isDragging && (
            <Item style={{ transform: 'none !important' }}>
              {item.content}
            </Item>
          )}
        </>
      )}
    </Draggable>
  ))}
</Kiosk>

@akarpov91
Copy link

Hi @pixelass thanks for you examples.

I'm trying to implement your solution, but i'm running into an issue where the dragged item doesn't get added to list.

I'm getting the below error:
Droppable setup issue [droppableId: "WIDGETS"]:DroppableProvided > placeholder could not be found.Please be sure to add the {provided.placeholder} React Node as a child of your Droppable.

However, I have {provided.placeholder} added.

Screen Shot 2021-06-25 at 11 13 29 AM

codeSandbox here.

Thanks in advance.

@akarpov91
Copy link

akarpov91 commented Jun 26, 2021

@vmlopezr I have implemented the copying it took me a while to get the exact thing what I want here is the resultant gif

copying-min

You can play with the builder on https://w-builder.netlify.app/ and let me know if this the thing you want.
I will share the code😁😁

@alii13 This looks great, would you share the code example?

@pixelass
Copy link

pixelass commented Jul 2, 2021

@akarpov91 All I did is provide some hacks which I'll try to maintain/extend when required. Your question is related to state handling or API questions. "This issue" is probably not the right place to ask that question.

You can try Stackoverflow or a similar help-forum. If you think you encountered a bug please open a new ticket

Thank you for understanding

@steamappman
Copy link

@munjyong I used @xuzhanhh solution to fix this issue.

<Kiosk
    ref={provided.innerRef}
    {...provided.droppableProps}
    isDraggingOver={snapshot.isDraggingOver}
>
  {ITEMS.map((item, index) => (
    <Draggable key={item.id} draggableId={item.id} index={index}>
      {(provided, snapshot) => (
        <>
          <Item
            ref={provided.innerRef}
            {...provided.draggableProps}
            {...provided.dragHandleProps}
            style={{
              ...provided.draggableProps.style,
              transform: snapshot.isDragging
                ? provided.draggableProps.style?.transform
                : 'translate(0px, 0px)',
            }}
          >
            {item.content}
          </Item>
          {snapshot.isDragging && (
            <Item style={{ transform: 'none !important' }}>
              {item.content}
            </Item>
          )}
        </>
      )}
    </Draggable>
  ))}
</Kiosk>

Just an update with latest version for anyone want to try, this solution works without any issue for me. The one about setting display: none for clone only works for old version.

@wibed
Copy link

wibed commented Oct 15, 2021

const Kiosk = useMemo(() => {
        return ({ provided, item, snapshot }) => {
            const currentScroll = useMemo(() => document.documentElement.scrollTop,[summaryItems])
            const elementTop = provided.draggableProps.style.top
            return (
                <div>
                <div
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                    ref={provided.innerRef}
                    style={{...provided.draggableProps.style,
                            paddingBottom: '8px',
                            transform: snapshot.isDragging ? provided.draggableProps.style?.transform : 'translate(0px, 0px)',
                    }}
                >
                  <div style={{border: `1px solid black`}}>
                    <h1>{item.title}</h1>
                  </div>
                </div>
                {snapshot.isDragging && (
                    <div style={{...provided.draggableProps.style,
                        position: "absolute",
                        top: currentScroll + elementTop,
                        transform: 'none !important',
                        paddingBottom: '8px',
                    }}>
                        <div style={{position: "relative", border: '1px dashed black'}}>
                            <h1>{item.title}</h1>
                        </div>
                    </div>
                )}
              </div>
            )
        }
    },[summaryItems])

an update with the latest version using virtual lists.

@abrman
Copy link

abrman commented Oct 28, 2021

@wibed I've been trying to implement your solution for virtual lists but I'm without success. Would you be willing to provide a codesandbox example?

I've been able to create the clone in place of the item I'm trying to copy, however all the items below in the virtual list shift up and under my clone. What's the idea on dealing with the shift? I'm using react-window.

@abrman
Copy link

abrman commented Oct 28, 2021

I got the transition you did implemented. Realized my style was being overwritten by provided style in my ListItem component. All good 👍

@gowtham-ramk
Copy link

gowtham-ramk commented Dec 2, 2021

I also have a similar requirement where I need to have the copy of the main tasks in a selected tasks tasks. So to remove the task from the selected tasks column, I have provided a delete button in the task. When the user clicks the delete button, the task gets removed. But now, when I try to drag the task again from the all tasks to selected tasks, i am getting draggle id not found error. This is my code: https://codesandbox.io/s/unruffled-waterfall-wdyc5?file=/src/initial-data.js:437-445 . Please take a look and give a solution if possible.
CPT2112021333-794x707

@EdmundsEcho
Copy link

I was able to get the "drag and copy" feature going by treating the drag event as purely an "event generator". What I mean by that, is when you drag from a source that is meant to be copied, your event is essentially "create a new task using what is being dragged as a template". "as a template" is critical because that forces you to realize that you need to create a new and distinct id for your new task.

The "feature" that I needed from the beautiful dnd package was the ability to prevent changing the list from which the item was being dragged. In order to accomplish this, and in this sense, I was creating a "clone": one to remain in place, the other to drag to a new destination. The drop event into the "selected tasks" column is the event that I need to realize my intent as discussed in the first paragraph (i.e., go and build a new task using what I'm dragging as a template). So, it's an event generator because I use the event to subsequently determine if the particular drop event is in fact a "copy" this task event.

I did not see where in your code that you generate a new instance of the task component with a new, and unique id. I suspect that you don't.

I hope this helps.

@ghost
Copy link

ghost commented Jul 14, 2022

could be great to put a higher priority on this feature proposition ;)

I think so

@linpan
Copy link

linpan commented Aug 15, 2024

@munjyong I used @xuzhanhh solution to fix this issue. this code in here..
nextjs14-react-beautiful-dnd-copy-clone-demo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests