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>
import { controls, ControlFrom } from '@neodrag/svelte';
</script>
<!-- Only header can initiate dragging -->
<div
{@attach draggable([
controls({ allow: ControlFrom.selector('.header') }),
])}
>
<div class="header">Drag handle</div>
<div class="content">Content (not draggable)</div>
</div>
<!-- Anywhere except buttons -->
<div
{@attach draggable([
controls({ block: ControlFrom.selector('button') }),
])}
>
<p>Drag anywhere in this area</p>
<button>Can't drag from here</button>
</div>
ControlFrom Utilities
Selector-Based Controls
<script>
import { controls, ControlFrom } from '@neodrag/svelte';
</script>
<!-- Single selector -->
<div
{@attach draggable([
controls({ allow: ControlFrom.selector('.handle') }),
])}
>
<div class="handle">Drag me</div>
<div>Not draggable</div>
</div>
<!-- Multiple selectors -->
<div
{@attach draggable([
controls({
allow: ControlFrom.selector('.handle, .title, .grip'),
}),
])}
>
<div class="title">Title bar</div>
<div class="handle">Handle</div>
<div class="grip">⋮⋮</div>
<div class="content">Content</div>
</div>
<!-- Block specific elements -->
<div
{@attach draggable([
controls({
block: ControlFrom.selector('button, input, select'),
}),
])}
>
<p>Draggable area</p>
<button>Button (blocked)</button>
<input placeholder="Input (blocked)" />
</div>
Element-Based Controls
<script>
import { controls, ControlFrom } from '@neodrag/svelte';
let handleElement;
let blockElement;
</script>
<div
{@attach draggable([
controls({
allow: () => ControlFrom.elements([handleElement]),
}),
])}
>
<div bind:this={handleElement}>Specific handle element</div>
<div>Not draggable</div>
</div>
<div
{@attach draggable([
controls({
block: () => ControlFrom.elements([blockElement]),
}),
])}
>
<p>Draggable area</p>
<div bind:this={blockElement}>Blocked element</div>
</div>
Advanced Control Logic
Priority Handling
<script>
import { controls, ControlFrom } from '@neodrag/svelte';
</script>
<!-- Allow wins when zones overlap -->
<div
{@attach draggable([
controls({
allow: ControlFrom.selector('.drag-zone'),
block: ControlFrom.selector('.no-drag'),
priority: 'allow',
}),
])}
>
<div class="drag-zone">
Drag zone
<div class="no-drag">Allow wins here</div>
</div>
</div>
<!-- Block wins when zones overlap -->
<div
{@attach draggable([
controls({
allow: ControlFrom.selector('.drag-zone'),
block: ControlFrom.selector('.no-drag'),
priority: 'block',
}),
])}
>
<div class="drag-zone">
Drag zone
<div class="no-drag">Block wins here</div>
</div>
</div>
Real-World Examples
Modal with Header Handle
<script>
import {
draggable,
controls,
ControlFrom,
bounds,
BoundsFrom,
} from '@neodrag/svelte';
</script>
<div
{@attach draggable([
controls({
allow: ControlFrom.selector('.modal-header'),
block: ControlFrom.selector('.modal-close, .modal-body button'),
}),
bounds(BoundsFrom.viewport()),
])}
>
<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>
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.