Draggable Zone
An element can be both a draggable and a droppable at the same time. Register both makeDraggable and makeDroppable on the same ref. Use placementMargins to split the element into zones: pointer near the edges sorts, pointer in the center nests.
Demo
Project tree
Drag via ⠿ handle · hover edges to sort · hover center to nest inside a folder
⠿Frontend
⠿components
⠿Button.vue
⠿Input.vue
⠿App.vue
⠿main.ts
⠿Backend
⠿routes.ts
⠿db.ts
⠿package.json
⠿README.md
Each node is a dual-role element. The handle (⠿) makes it draggable. The same element is also a droppable:
- Top / bottom edge — insert line → sort node among its siblings
- Center — colored glow → nest the dragged item inside this node (works even on leaf nodes)
How it works
ts
const rowRef = useTemplateRef<HTMLElement>('rowRef');
// Dual-role: edge → sort siblings; center → nest inside this node
const { isDragging, isDragOver: placement } = makeDraggable(
rowRef,
{
dragHandle: '.drag-handle',
placementMargins: { top: 12, bottom: 12 },
},
() => [props.index, props.siblings]
);
// Center drop fires when pointer is NOT in the edge margins
const { isDragOver: isOver } = makeDroppable(
rowRef,
{ events: { onDrop: (e) => emit('drop', e) } },
() => props.node.children
);Internal mechanism
hover.ts detects the dual-role situation (isDualRole branch) and activates only one side depending on placement:
| Pointer position | Active side | hoveredDraggable | dropZone |
|---|---|---|---|
| Top / bottom edge | Draggable | set to node | null |
| Center | Droppable | null | set to node |
Drop handler (root)
ts
function applyToTree(oldArr: TreeNode[], newArr: TreeNode[]) {
if (oldArr === tree.value) {
tree.value = newArr;
} else {
findAndReplace(tree.value, oldArr, newArr);
}
}
function handleDrop(e: IDragEvent) {
const r = e.helpers.suggestSort('vertical');
if (!r) return;
const srcArr = e.draggedItems[0]?.items as TreeNode[];
const tgtArr = (e.hoveredDraggable?.items ?? e.dropZone?.items) as TreeNode[];
applyToTree(srcArr, r.sourceItems as TreeNode[]);
if (!r.sameList) applyToTree(tgtArr, r.targetItems as TreeNode[]);
}Source
See also
- Techniques Guide — Draggable Zone and other patterns explained
- makeDraggable —
placementMarginsoption - makeDroppable —
events.onDrop - Tree example — tree with a separate children droppable per node