JSPM

  • Created
  • Published
  • Downloads 3176
  • Score
    100M100P100Q118314F
  • License MIT

Drag and Drop for Vue3

Package Exports

  • vue3-dnd

Readme

npm version npm downloads

Vue3 DnD

React Dnd implementation in Vue3

中文 | English

Using

npm install vue3-dnd
yarn add vue3-dnd
pnpm install vue3-dnd
// App.vue
<script>
import { DndProvider } from 'vue3-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import Home from './Home.vue'
</script>

<template>
    <DndProvider :backend="HTML5Backend">
        <Home></Home>
    </DndProvider>
</template>
// Home.vue
<script>
import { useDrag, useDrop, useDragLayer } from 'vue3-dnd'
// Write your own code
</script>

Docs

Please refer to the react-dnd documentation and github example, and we will supplement our documentation later.

Notice

  1. Because of composition-API limitations, please do not attempt to deconstruct assignment for the collect parameter from hooks such as useDrag and useDrop, otherwise it will lose its responsiveness, Such as:

    import { useDrag } from 'vue3-dnd'
    import { toRefs } from '@vueuse/core'
    
    const [collect, drag] = useDrag(() => ({
        type: props.type,
        item: {name: props.name},
        collect: monitor => ({
            opacity: monitor.isDragging() ? 0.4 : 1,
        }),
    }))
    
    // good
    const opacity = computed(() => unref(collect).opacity)
    // using vueuse toRefs api
    const { opacity } = toRefs(collect)
    // bad
    const { opacity } = collect.value
  2. The drag drop dragPreview ref is a function, using template please using v-bind:ref="drag", You can also set the value to it using a new function

<template>
  <div :ref="drag">box</div>
  <div :ref="setDrop">drop div
    <section>
      drop section
    </section>
  </div>
</template>
<script lang="ts" setup>
import { useDrag, useDrop } from 'vue3-dnd'

const [, drag] = useDrag(() => ({
    type: 'Box',
}))
const [, drop] = useDrop(() => ({
  type: 'Box'
}))

// You can also set the value to it using a new function
const setDrop = (el: HTMLDivElement | null) => {
    drop.value(el)
    // or
    drop.value(el?.querySelector('section') || null)
}


</script>

example

App.vue

<script setup lang="ts">
import { DndProvider } from 'vue3-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import Example from './Example.vue'
</script>

<template>
    <DndProvider :backend="HTML5Backend">
        <Example></Example>
    </DndProvider>
</template>

Example.vue

<script lang="ts" setup>
import { useDrag, useDrop } from 'vue3-dnd'
import { computed, unref } from 'vue'

const [dropCollect, drop] = useDrop(() => ({
    accept: 'Box',
    drop: () => ({ name: 'Dustbin' }),
    collect: monitor => ({
        isOver: monitor.isOver(),
        canDrop: monitor.canDrop(),
    }),
}))
const canDrop = computed(() => unref(dropCollect).canDrop)
const isOver = computed(() => unref(dropCollect).isOver)
const isActive = computed(() => unref(canDrop) && unref(isOver))
const backgroundColor = computed(() =>
    unref(isActive) ? 'darkgreen' : unref(canDrop) ? 'darkkhaki' : '#222'
)

const [collect, drag] = useDrag(() => ({
    type: 'Box',
    item: () => ({
        name: 'Box',
    }),
    end: (item, monitor) => {
        const dropResult = monitor.getDropResult<{ name: string }>()
        if (item && dropResult) {
            alert(`You dropped ${item.name} into ${dropResult.name}!`)
        }
    },
    collect: monitor => ({
        isDragging: monitor.isDragging(),
        handlerId: monitor.getHandlerId(),
    }),
}))
const isDragging = computed(() => collect.value.isDragging)

const opacity = computed(() => (unref(isDragging) ? 0.4 : 1))
</script>

<template>
    <div>
        <div :style="{ overflow: 'hidden', clear: 'both' }">
            <div
                :ref="drop"
                role="Dustbin"
                class="drop-container"
                :style="{ backgroundColor }"
            >
                {{ isActive ? 'Release to drop' : 'Drag a box here' }}
            </div>
        </div>
        <div :style="{ overflow: 'hidden', clear: 'both' }">
            <div :ref="drag" class="box" role="Box" :style="{ opacity }">Box</div>
        </div>
    </div>
</template>

<style lang="less" scoped>
.drop-container {
    height: 12rem;
    width: 12rem;
    margin-right: 1.5rem;
    margin-bottom: 1.5rem;
    color: white;
    padding: 1rem;
    text-align: center;
    font-size: 1rem;
    line-height: normal;
    float: left;
}
.box {
    border: 1px solid gray;
    background-color: white;
    padding: 0.5rem 1rem;
    margin-right: 1.5rem;
    margin-bottom: 1.5rem;
    cursor: move;
    float: left;

    &.dragging {
        opacity: 0.4;
    }
}
</style>

Q/A

Q: The data does not change during or after the drag is complete

A: Check if your spec or item is a function, If your item is a static object, you really don't get reactive data changes during drag and drop

// The following situations may result in don't have reactive
const [collect, connectDrag] = useDrag({
    type: 'box',
    item: { id: props.id },
})
// The correct way to write it
const [collect, connectDrag] = useDrag({
    type: 'box',
    item: () => ({ id: props.id }),
})
const [collect, connectDrag] = useDrag(() => ({
    type: 'box',
    item:  { id: props.id },
}))

Stargazers

Stargazers repo roster for vue3-dnd

Thanks

React-Dnd