MaxLeiter / sortablejs-vue3

A thin wrapper around Sortable.js for Vue 3
https://sortablejs-vue3.maxleiter.com
MIT License
378 stars 19 forks source link

Cancelling Move does not work #66

Closed AlexanderFillbrunn closed 1 year ago

AlexanderFillbrunn commented 1 year ago

In Sortable you can return false from a move event handler in order to cancel a drag operation:

// Event when you move an item in the list or between lists
    onMove: function (/**Event*/evt, /**Event*/originalEvent) {
        // Example: https://jsbin.com/nawahef/edit?js,output
        evt.dragged; // dragged HTMLElement
        evt.draggedRect; // DOMRect {left, top, right, bottom}
        evt.related; // HTMLElement on which have guided
        evt.relatedRect; // DOMRect
        evt.willInsertAfter; // Boolean that is true if Sortable will insert drag element after target by default
        originalEvent.clientY; // mouse position
        // return false; — for cancel
        // return -1; — insert before target
        // return 1; — insert after target
        // return true; — keep default insertion point based on the direction
        // return void; — keep default insertion point based on the direction
    },

However, in Vue this seems to be swallowed by the emit handling:

function emit$1(instance, event, ...rawArgs) {
    //[...]
    if (handler) {
        callWithAsyncErrorHandling(handler, instance, 6 /* ErrorCodes.COMPONENT_EVENT_HANDLER */, args);
    }
    const onceHandler = props[handlerName + `Once`];
    if (onceHandler) {
        if (!instance.emitted) {
            instance.emitted = {};
        }
        else if (instance.emitted[handlerName]) {
            return;
        }
        instance.emitted[handlerName] = true;
        callWithAsyncErrorHandling(onceHandler, instance, 6 /* ErrorCodes.COMPONENT_EVENT_HANDLER */, args);
    }
}

No return value is passed on to Sortable and therefore I cannot cancel. My move event callback looks like this:

function move(evt) {
    return !evt.to.classList.contains("no-drop");
}

Is there any trick to it, or does it not work right now? What I am trying to achieve is to have a list that I can drag out of, but not drag into. Is there any other way to get that behavior?

Edit: Also I noted that the event has a property "returnValue", but setting this to false in my callback also does not seem to work.

Ibz786 commented 1 year ago

Hi @AlexanderFillbrunn

So I too have been having the same issue. Have done some workarounds, albeit a bit "hacky" but its getting the job done.

If you're sorting / moving item(s) between lists, then I came up with the following: I added a data-list property which I was able to track the from (old) and to (new) list

<sortable :list="list" item-key="id" :data-list="props.id" :options="dragOptions" @end="onChangeEnd">

On the onChangeEnd I then simply did the following:

function onChangeEnd(e) {
    if(e.from.dataset.list === e.to.dataset.list && e.oldIndex === e.newIndex) return;
    // rest of logic 
}

Hope it helps!

AlexanderFillbrunn commented 1 year ago

Hi @Ibz786, thanks for sharing your solution! I tried something similar with a class (.undroppable) that I check, but as sortable still modifies the DOM irrespective of the early return, it is a bit tedious to keep everything in sync between my state and the DOM. Would be nice if the onMove return value could be fixed.

MaxLeiter commented 1 year ago

PR's welcome, I won't be able to dig into this for another week or so.

AlexanderFillbrunn commented 1 year ago

Thanks @MaxLeiter. I just had another look and found that the return value of onMoveCapture is passed on, so it is possible to use that instead:

<Sortable :onMoveCapture="onMove"></Sortable>`
function onMove(evt) {
    return !evt.to.classList.contains("no-drop");
}