controls

controls

Define which areas can initiate dragging

The controls plugin defines which parts of an element can be used to start dragging. Think modal headers, card handles, or toolbar grips - only specific areas should be draggable while the rest remains interactive.

controls({ allow: ControlFrom.selector('.handle') }); // Only handle can drag
controls({ block: ControlFrom.selector('.button') }); // Buttons can't drag
controls({
  allow: ControlFrom.selector('.header'),
  block: ControlFrom.selector('.close'),
}); // Header drags, close button doesn't

Basic Usage

<script setup>
import { controls, ControlFrom, vDraggable } from '@neodrag/vue';

const allowControls = [
  controls({ allow: ControlFrom.selector('.header') }),
];
const blockControls = [
  controls({ block: ControlFrom.selector('button') }),
];
</script>

<template>
  <div>
    <div v-draggable="allowControls">
      <div class="header">Drag handle</div>
      <div class="content">Content (not draggable)</div>
    </div>

    <div v-draggable="blockControls">
      <p>Drag anywhere in this area</p>
      <button>Can't drag from here</button>
    </div>
  </div>
</template>

ControlFrom Utilities

Selector-Based Controls

<script setup>
import { controls, ControlFrom, vDraggable } from '@neodrag/vue';

const singleAllow = [
  controls({ allow: ControlFrom.selector('.handle') }),
];
const multiAllow = [
  controls({ allow: ControlFrom.selector('.handle, .title, .grip') }),
];
const blockSome = [
  controls({ block: ControlFrom.selector('button, input, select') }),
];
</script>

<template>
  <div>
    <div v-draggable="singleAllow">
      <div class="handle">Drag me</div>
      <div>Not draggable</div>
    </div>

    <div v-draggable="multiAllow">
      <div class="title">Title bar</div>
      <div class="handle">Handle</div>
      <div class="grip">⋮⋮</div>
      <div class="content">Content</div>
    </div>

    <div v-draggable="blockSome">
      <p>Draggable area</p>
      <button>Button (blocked)</button>
      <input placeholder="Input (blocked)" />
    </div>
  </div>
</template>

Element-Based Controls

<script setup>
import { ref } from 'vue';
import { controls, ControlFrom, vDraggable } from '@neodrag/vue';

const handleElement = ref();
const blockElement = ref();

const allowControls = () => [
  controls({
    allow: () => ControlFrom.elements([handleElement.value]),
  }),
];

const blockControls = () => [
  controls({
    block: () => ControlFrom.elements([blockElement.value]),
  }),
];
</script>

<template>
  <div>
    <div v-draggable="allowControls">
      <div ref="handleElement">Specific handle element</div>
      <div>Not draggable</div>
    </div>

    <div v-draggable="blockControls">
      <p>Draggable area</p>
      <div ref="blockElement">Blocked element</div>
    </div>
  </div>
</template>

Advanced Control Logic

Priority Handling

<script setup>
import { controls, ControlFrom, vDraggable } from '@neodrag/vue';

const allowWins = [
  controls({
    allow: ControlFrom.selector('.drag-zone'),
    block: ControlFrom.selector('.no-drag'),
    priority: 'allow',
  }),
];

const blockWins = [
  controls({
    allow: ControlFrom.selector('.drag-zone'),
    block: ControlFrom.selector('.no-drag'),
    priority: 'block',
  }),
];
</script>

<template>
  <div>
    <div v-draggable="allowWins">
      <div class="drag-zone">
        Drag zone
        <div class="no-drag">Allow wins here</div>
      </div>
    </div>

    <div v-draggable="blockWins">
      <div class="drag-zone">
        Drag zone
        <div class="no-drag">Block wins here</div>
      </div>
    </div>
  </div>
</template>

Real-World Examples

<script setup>
import {
  vDraggable,
  controls,
  ControlFrom,
  bounds,
  BoundsFrom,
} from '@neodrag/vue';

const modalPlugins = [
  controls({
    allow: ControlFrom.selector('.modal-header'),
    block: ControlFrom.selector('.modal-close, .modal-body button'),
  }),
  bounds(BoundsFrom.viewport()),
];
</script>

<template>
  <div v-draggable="modalPlugins">
    <div class="modal-header">
      <h3>Modal Title</h3>
      <button class="modal-close">×</button>
    </div>
    <div class="modal-body">
      <p>Modal content</p>
      <button>Action Button</button>
    </div>
  </div>
</template>

How It Works

The controls plugin manages drag initiation permissions:

  1. Finds control zones on setup by querying the DOM
  2. Checks click position in shouldStart hook
  3. Determines permission based on which zones contain the click point
  4. Returns boolean to allow/prevent drag start

Zone priority logic:

  • If allow zones exist and click is outside all of them → block
  • If click is in both allow and block zones → use priority option
  • Smaller (nested) zones take precedence over larger ones

API Reference

function controls(options?: {
  allow?: ReturnType<
    typeof ControlFrom.selector | typeof ControlFrom.elements
  >;
  block?: ReturnType<
    typeof ControlFrom.selector | typeof ControlFrom.elements
  >;
  priority?: 'allow' | 'block';
}): Plugin;

Options:

  • allow - Control zones that can initiate dragging
  • block - Control zones that cannot initiate dragging
  • priority - Which wins when zones overlap (‘allow’ | ‘block’)

ControlFrom utilities:

  • ControlFrom.selector(cssSelector) - Elements matching CSS selector
  • ControlFrom.elements(nodeList) - Specific DOM elements

Returns: A plugin object for use with draggable.