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
import { controls, ControlFrom, Draggable } from '@neodrag/vanilla';
const allowElement = document.getElementById('allow-only');
const blockElement = document.getElementById('block-some');
new Draggable(allowElement, [
controls({ allow: ControlFrom.selector('.header') }),
]);
new Draggable(blockElement, [
controls({ block: ControlFrom.selector('button') }),
]);
ControlFrom Utilities
Selector-Based Controls
import { controls, ControlFrom, Draggable } from '@neodrag/vanilla';
const single = document.getElementById('single-allow');
const multi = document.getElementById('multi-allow');
const block = document.getElementById('block-some');
new Draggable(single, [
controls({ allow: ControlFrom.selector('.handle') }),
]);
new Draggable(multi, [
controls({ allow: ControlFrom.selector('.handle, .title, .grip') }),
]);
new Draggable(block, [
controls({ block: ControlFrom.selector('button, input, select') }),
]);
Element-Based Controls
import { controls, ControlFrom, Draggable } from '@neodrag/vanilla';
const container = document.getElementById('container');
const handle = document.getElementById('handle');
const blockContainer = document.getElementById('block-container');
const blockElement = document.getElementById('block-element');
new Draggable(container, [
controls({ allow: () => ControlFrom.elements([handle]) }),
]);
new Draggable(blockContainer, [
controls({ block: () => ControlFrom.elements([blockElement]) }),
]);
Advanced Control Logic
Priority Handling
import { controls, ControlFrom, Draggable } from '@neodrag/vanilla';
const allowWins = document.getElementById('allow-wins');
const blockWins = document.getElementById('block-wins');
new Draggable(allowWins, [
controls({
allow: ControlFrom.selector('.drag-zone'),
block: ControlFrom.selector('.no-drag'),
priority: 'allow',
}),
]);
new Draggable(blockWins, [
controls({
allow: ControlFrom.selector('.drag-zone'),
block: ControlFrom.selector('.no-drag'),
priority: 'block',
}),
]);
Real-World Examples
Modal with Header Handle
import {
Draggable,
controls,
ControlFrom,
bounds,
BoundsFrom,
} from '@neodrag/vanilla';
const modal = document.getElementById('modal');
new Draggable(modal, [
controls({
allow: ControlFrom.selector('.modal-header'),
block: ControlFrom.selector('.modal-close, .modal-body button'),
}),
bounds(BoundsFrom.viewport()),
]);
How It Works
The controls plugin manages drag initiation permissions:
- Finds control zones on setup by querying the DOM
- Checks click position in
shouldStart
hook - Determines permission based on which zones contain the click point
- 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 draggingblock
- Control zones that cannot initiate draggingpriority
- Which wins when zones overlap (‘allow’ | ‘block’)
ControlFrom utilities:
ControlFrom.selector(cssSelector)
- Elements matching CSS selectorControlFrom.elements(nodeList)
- Specific DOM elements
Returns: A plugin object for use with draggable.