Skip to content

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 positionActive sidehoveredDraggabledropZone
Top / bottom edgeDraggableset to nodenull
CenterDroppablenullset 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

Released under the MIT License.