@neodrag/vue

@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>