@neodrag/vue
A lightweight directive to make your elements draggable.
npm i @neodrag/vue@next
Usage
Basic usage
<script setup>
import { vDraggable } from '@neodrag/vue';
</script>
<template>
<div v-draggable>I am draggable</div>
</template>
With plugins
<script setup>
import { vDraggable, axis, grid } from '@neodrag/vue';
</script>
<template>
<div v-draggable="[axis('x'), grid([10, 10])]">I am draggable</div>
</template>
Defining plugins elsewhere with TypeScript
<script setup lang="ts">
import { vDraggable, axis, grid, type Plugin } from '@neodrag/vue';
const plugins: Plugin[] = [axis('y'), grid([10, 10])];
</script>
<template>
<div v-draggable="plugins">I am draggable</div>
</template>
Reactive Plugins with useCompartment
For dynamic behavior that changes during runtime:
<script setup>
import { ref } from 'vue';
import { vDraggable, axis, useCompartment } from '@neodrag/vue';
const currentAxis = ref('x');
const axisComp = useCompartment(() => axis(currentAxis.value));
const plugins = () => [axisComp];
</script>
<template>
<div>
<div v-draggable="plugins">Current axis: {{ currentAxis }}</div>
<button @click="currentAxis = currentAxis === 'x' ? 'y' : 'x'">
Switch Axis
</button>
</div>
</template>
Multiple reactive compartments
<script setup>
import { ref } from 'vue';
import {
vDraggable,
axis,
bounds,
BoundsFrom,
grid,
useCompartment,
} from '@neodrag/vue';
const currentAxis = ref('x');
const gridSize = ref(20);
const enableBounds = ref(false);
const axisComp = useCompartment(() => axis(currentAxis.value));
const gridComp = useCompartment(() =>
grid([gridSize.value, gridSize.value]),
);
const boundsComp = useCompartment(() =>
enableBounds.value ? bounds(BoundsFrom.parent()) : null,
);
const plugins = () => [axisComp, gridComp, boundsComp];
</script>
<template>
<div>
<div v-draggable="plugins">Reactive draggable</div>
<div>
<select v-model="currentAxis">
<option value="x">X</option>
<option value="y">Y</option>
</select>
<input
type="range"
min="10"
max="50"
v-model.number="gridSize"
/>
<label>
<input type="checkbox" v-model="enableBounds" />
Enable bounds
</label>
</div>
</div>
</template>
Event Handling
<script setup>
import { vDraggable, events } from '@neodrag/vue';
const plugins = [
events({
onDragStart: (data) => console.log('Started:', data.offset),
onDrag: (data) => console.log('Dragging:', data.offset),
onDragEnd: (data) => console.log('Ended:', data.offset),
}),
];
</script>
<template>
<div v-draggable="plugins">Check console while dragging</div>
</template>
Drag Controls
<script setup>
import { vDraggable, controls, ControlFrom } from '@neodrag/vue';
const plugins = [
controls({
allow: ControlFrom.selector('.drag-handle'),
block: ControlFrom.selector('.no-drag'),
}),
];
</script>
<template>
<div v-draggable="plugins">
<div class="drag-handle">🔸 Drag from here</div>
<div>Content area</div>
<div class="no-drag">❌ Can't drag from here</div>
</div>
</template>