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 { createSignal } from 'solid-js';
import { controls, ControlFrom, useDraggable } from '@neodrag/solid';
function ControlledDragging() {
const [allowRef, setAllowRef] = createSignal<HTMLElement | null>(
null,
);
const [blockRef, setBlockRef] = createSignal<HTMLElement | null>(
null,
);
useDraggable(allowRef, [
controls({ allow: ControlFrom.selector('.header') }),
]);
useDraggable(blockRef, [
controls({ block: ControlFrom.selector('button') }),
]);
return (
<div>
<div ref={setAllowRef}>
<div class="header">Drag handle</div>
<div class="content">Content (not draggable)</div>
</div>
<div ref={setBlockRef}>
<p>Drag anywhere in this area</p>
<button>Can't drag from here</button>
</div>
</div>
);
}
ControlFrom Utilities
Selector-Based Controls
import { createSignal } from 'solid-js';
import { controls, ControlFrom, useDraggable } from '@neodrag/solid';
function SelectorControls() {
const [singleRef, setSingleRef] = createSignal<HTMLElement | null>(
null,
);
const [multiRef, setMultiRef] = createSignal<HTMLElement | null>(
null,
);
const [blockRef, setBlockRef] = createSignal<HTMLElement | null>(
null,
);
useDraggable(singleRef, [
controls({ allow: ControlFrom.selector('.handle') }),
]);
useDraggable(multiRef, [
controls({
allow: ControlFrom.selector('.handle, .title, .grip'),
}),
]);
useDraggable(blockRef, [
controls({
block: ControlFrom.selector('button, input, select'),
}),
]);
return (
<div>
<div ref={setSingleRef}>
<div class="handle">Drag me</div>
<div>Not draggable</div>
</div>
<div ref={setMultiRef}>
<div class="title">Title bar</div>
<div class="handle">Handle</div>
<div class="grip">⋮⋮</div>
<div class="content">Content</div>
</div>
<div ref={setBlockRef}>
<p>Draggable area</p>
<button>Button (blocked)</button>
<input placeholder="Input (blocked)" />
</div>
</div>
);
}
Element-Based Controls
import { createSignal } from 'solid-js';
import { controls, ControlFrom, useDraggable } from '@neodrag/solid';
function ElementControls() {
const [container, setContainer] = createSignal<HTMLElement | null>(
null,
);
const [handle, setHandle] = createSignal<HTMLElement | null>(null);
const [blockContainer, setBlockContainer] =
createSignal<HTMLElement | null>(null);
const [block, setBlock] = createSignal<HTMLElement | null>(null);
useDraggable(container, [
controls({ allow: () => ControlFrom.elements([handle()!]) }),
]);
useDraggable(blockContainer, [
controls({ block: () => ControlFrom.elements([block()!]) }),
]);
return (
<div>
<div ref={setContainer}>
<div ref={setHandle}>Specific handle element</div>
<div>Not draggable</div>
</div>
<div ref={setBlockContainer}>
<p>Draggable area</p>
<div ref={setBlock}>Blocked element</div>
</div>
</div>
);
}
Advanced Control Logic
Priority Handling
import { createSignal } from 'solid-js';
import { controls, ControlFrom, useDraggable } from '@neodrag/solid';
function PriorityControls() {
const [allowWins, setAllowWins] = createSignal<HTMLElement | null>(
null,
);
const [blockWins, setBlockWins] = createSignal<HTMLElement | null>(
null,
);
useDraggable(allowWins, [
controls({
allow: ControlFrom.selector('.drag-zone'),
block: ControlFrom.selector('.no-drag'),
priority: 'allow',
}),
]);
useDraggable(blockWins, [
controls({
allow: ControlFrom.selector('.drag-zone'),
block: ControlFrom.selector('.no-drag'),
priority: 'block',
}),
]);
return (
<div>
<div ref={setAllowWins}>
<div class="drag-zone">
Drag zone
<div class="no-drag">Allow wins here</div>
</div>
</div>
<div ref={setBlockWins}>
<div class="drag-zone">
Drag zone
<div class="no-drag">Block wins here</div>
</div>
</div>
</div>
);
}
Real-World Examples
Modal with Header Handle
import { createSignal } from 'solid-js';
import {
useDraggable,
controls,
ControlFrom,
bounds,
BoundsFrom,
} from '@neodrag/solid';
function DraggableModal() {
const [modal, setModal] = createSignal<HTMLElement | null>(null);
useDraggable(modal, [
controls({
allow: ControlFrom.selector('.modal-header'),
block: ControlFrom.selector('.modal-close, .modal-body button'),
}),
bounds(BoundsFrom.viewport()),
]);
return (
<div ref={setModal}>
<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.