Alfred-Skyblue / vue-draggable-plus

Universal Drag-and-Drop Component Supporting both Vue 3 and Vue 2
https://vue-draggable-plus.pages.dev/en/
MIT License
2.72k stars 106 forks source link

Dual list does not work with getter and setters #77

Closed MartinMalinda closed 1 month ago

MartinMalinda commented 6 months ago

Minimal repro:

https://codesandbox.io/p/sandbox/vue-draggable-plus-dual-list-setters-5wl5qm

If you drag items as part of one list, it works well. Once you try to drag across lists, the setters do not get called.

This worked for me before I switched the original package (vue draggable next) for this one. So something must have broken it after the fork.

MartinMalinda commented 6 months ago

It seems like the behavior is different for changing sort in the list and when moving things across lists.

For changing order I can find list.value = set.

https://github.com/Alfred-Skyblue/vue-draggable-plus/blob/main/src/useDraggable.ts#L175

But for the onAdd and onRemove, there's no setting of list.value which seems like a bug to me? It seems like there's a purposeful unRef and then the native array is sliced (or pushed to). Almost as if to intentionally avoid reactivity?

VincentVanclef commented 5 months ago

Noticed this too. Was extremely happy seeing someone took up the torch to keep vue3 up to date with sortablejs. However, using computed wrapper does not work with this package :/

This is how i've done it using the outdated vuedraggable package which i hoped would still work for this but it doesnt..

        const request = ref<UpdateCustomerProductAccessRequest>({
            customerId: props.customer.id,
            productIds: cloneDeep(props.customer.productAccesses),
        });

         const productsWithoutAccess = computed({
            get: () => sortedProducts.value.filter(product => !request.value.productIds.includes(product.id)),
            set: (v) => {
                v.forEach(product => {
                    const index = request.value.productIds.indexOf(product.id);
                    if (index >= 0) {
                        request.value.productIds.splice(index, 1);
                    }
                });
            },
        });

        const productsWithAccess = computed({
            get: () => sortedProducts.value.filter(product => request.value.productIds.includes(product.id)),
            set: (v) => {
                v.forEach(product => {
                    const index = request.value.productIds.indexOf(product.id);
                    if (index < 0) {
                        request.value.productIds.push(product.id);
                    }
                });
            },
        });

                    <div class="flex flex-col flex-1 col-span-6 overflow-hidden">
                <p class="text-center text-14 mb-3 font-bold">
                    No Access
                </p>

                <Draggable v-model="productsWithoutAccess"
                           :animation="products.length > 50 ? 0 : 200"
                           :sort="false"
                           :disabled="false"
                           class="flex flex-col gap-3 bg-background p-3 flex-1 border border-border overflow-auto"
                           :group="{ name: 'product-accesses', pull: true, put: true }"
                           ghost-class="ghost">
                    <CustomerProductAccessesProduct v-for="product in productsWithoutAccess" :key="product.id" :product="product"/>
                </Draggable>
            </div>
            <div class="flex flex-col flex-1 col-span-6 overflow-hidden">
                <p class="text-center text-14 mb-3 font-bold">
                    Access
                </p>

                <Draggable v-model="productsWithAccess"
                           :animation="products.length > 50 ? 0 : 200"
                           :sort="false"
                           :disabled="false"
                           class="flex flex-col gap-3 bg-background p-3 flex-1 border border-border overflow-auto"
                           :group="{ name: 'product-accesses', pull: true, put: true }"
                           ghost-class="ghost">
                    <CustomerProductAccessesProduct v-for="product in productsWithAccess" :key="product.id" :product="product"/>
                </Draggable>
            </div>
        </div>

the setters are not triggered when moving from list A to list B :/ I could add the data into 2 seperate ref<[]> objects but that requires more boilerplate to ensure they get populated when the api promise returns etc..

ronannnn commented 1 month ago

+1

MartinMalinda commented 1 month ago

I fixed this back then for myself by forking, published here:

https://www.npmjs.com/package/vue-draggable-plus-plus

But beware, I may have broken other functionality while fixing it for my usecase.

(commits: https://github.com/MartinMalinda/vue-draggable-plus/commits/main/)