Drag Handles
By default, pointerdown anywhere on a draggable node starts a drag. A
handle restricts that: only a pointerdown on (or inside) a specific
descendant element initiates the drag — the rest of the node’s surface stops
being a drag trigger, while still moving along with the node once a drag
starts from its handle.
Configuration
const mosaic = new Mosaic({ root: document.getElementById("list"), selectors: { node: ".item", handle: ".drag-handle" }});
mosaic.initialize();<div id="list"> <div class="item" data-mosaic-id="a"> <span class="drag-handle">⠿</span> <span>Item content — not a drag trigger on its own</span> </div></div>selectors.handle— the descendant selector that gates drag initiation
When selectors.handle is omitted entirely, the whole node remains
draggable — the pre-handle baseline, unchanged.
Containment Rule
The handle match is resolved via e.target.closest(selectors.handle),
exactly like node/drop-target resolution — so a pointerdown on a
descendant of the handle element (not just the handle element itself) still
counts. The real, load-bearing rule is what happens after that: the
matched handle must be contained within the already-resolved node
(node.contains(handleMatch)). A selectors.handle that happens to match
an ancestor of the node, or an unrelated element elsewhere on the page,
does not gate the node — the containment check rejects it, and no drag
starts.
// DragController#pointerDown, roughly:const node = e.target.closest(selectors.node);if (!node) return;
if (selectors.handle) { const handleMatch = e.target.closest(selectors.handle); if (!handleMatch || !node.contains(handleMatch)) return; // no drag}In practice this means: a pointerdown anywhere on the node whose closest
handle match is not inside that same node — e.g. it bubbled from a handle
element belonging to a different, unrelated node, or from a handle-shaped
element outside the node’s own subtree entirely — never starts a drag.
Interaction with Groups and Drop Targets
The handle check runs first, before group resolution or any drop-target
logic — a pointerdown that fails the handle check never reaches
activeGroup resolution or snapshot capture at all. Handles compose freely
with selectors.group and selectors.dropTarget; none of the three
selectors constrain each other’s matching.
Related
- Drag Lifecycle — where handle resolution fits in the
pointerdownphase, and what happens once a drag actually starts - Drop Targets — a second selector, resolved independently
of
selectors.handle - Group Containers — a third selector, also resolved independently