javascripttypescriptdrag-and-dropsvelteclone

How to drag and clone elements in Svelte?


I'm working on a visual programming app which has two columns.

The column on the left is a tool box of parts from which users can drag 'items' to a column on the right which represents a script which the user can assemble. Visually it looks like this example.

I'm trying to implement this in Svelte. Each of my two columns has an each section to populate it with items.

I'm having difficulty getting drag and drop to work using svelte-dnd-action.

The first thing is that it seems like this library assumes that when you drag something somewhere else that you move that item. Because of this, eventually the toolbox becomes empty when I drag things out of it. So my first problem is that I need to find a way to clone the item when the users begins to drag it out.

Another less serious thing which I find inconvenient is that everything has to have an ID. Right now both columns are being loaded from JSON content on page load. So what I ended up doing is to write a walker function which traverses the parsed objects and assigns uuids to all the elements. Then on drag I have been trying to clone and recursiviley regenerating the ids of the dragged element.

I've tried various 'callbacks' on:consider, on:finalize etc but the way those are setup it's almost as if the drag has already been done (they append the item to the end of the list).

I've gotten various errors having to do with the ids (items in an each must be unique) to the good old 'nothing happens bug' where I can initiate the drag action, but the dragged item isn't accepted on the other end.

How can I make this work? I'd consider switching to another drag and drop library if necessary.


Solution

  • The documentation has a list of examples, one shows how elements could be copied on drag.

    Handler code for copying but not into the source list:

    import { dndzone, TRIGGERS, SHADOW_ITEM_MARKER_PROPERTY_NAME } from 'svelte-dnd-action';
    // ...
    let shouldIgnoreDndEvents = false;
    
    function handleDndConsider(e) {
        const {trigger, id} = e.detail.info;
        if (trigger === TRIGGERS.DRAG_STARTED) {
            const idx = items.findIndex(item => item.id === id);
            const newId = `${id}_copy_${Math.round(Math.random() * 100000)}`;
            // the line below was added in order to be compatible with version svelte-dnd-action 0.7.4 and above 
            e.detail.items = e.detail.items.filter(item => !item[SHADOW_ITEM_MARKER_PROPERTY_NAME]);
            e.detail.items.splice(idx, 0, { ...items[idx], id: newId });
            items = e.detail.items;
            shouldIgnoreDndEvents = true;
        }
        else if (!shouldIgnoreDndEvents) {
            items = e.detail.items;
        }
        else {
            items = [...items];
        }
    }
    function handleDndFinalize(e) {
        if (!shouldIgnoreDndEvents) {
            items = e.detail.items;
        }
        else {
            items = [...items];
            shouldIgnoreDndEvents = false;
        }
    }
    

    This does a shallow copy via spread and assigns a new ID. If the item deeper or more complex, you might need some more elaborate logic there.