Home Science Vue DnD Kit v2: революция в мире Drag N Drop для Vue.js

Vue DnD Kit v2: революция в мире Drag N Drop для Vue.js

Если вы когда-нибудь пробовали сделать drag-and-drop на Vue по-настоящему гибким – с кастомным overlay, вложенными зонами, multi-drag и анимацией при отпускании – вы знаете, что большинство библиотек держат вас в клетке. Vue DnD Kit v2 эту клетку сломал.

Сегодня выходит v2. Рассказываю, что внутри и почему я уверен что это прям революция.


Composable API: любой элемент становится draggable за три строки

Никаких компонентов-обёрток и . В библиотеки — чистые composables, которые работают с любым элементом через ref:




Ваш div, ваша разметка, ваши классы. Библиотека только добавляет логику — не диктует структуру. А так же позволяет работать с компонентами других библиотек потому что используется ref.

То же для droppable:

makeDroppable(zoneRef, {
  events: {
    onDrop(e) {
      const r = e.helpers.suggestSort('vertical');
      if (r) items.value = r.targetItems as Item[];
    }
  }
}, () => items.value);

Умные хелперы: suggestSort вместо ручного splice

Самая болезненная часть любого DnD — логика «куда вставить элемент». Считать индексы, определять, выше или ниже курсора, делать splice… В v2 это решено на уровне API.

Каждый drop-event несёт объект helpers со всем нужным:

function onDrop(e: IDragEvent) {
  const r = e.helpers.suggestSort('vertical'); // сам смотрит на позицию курсора
  if (r) items.value = r.targetItems as Item[];
}

suggestSort анализирует, где именно курсор относительно элемента, и возвращает уже готовый новый массив. Никаких getBoundingClientRect в вашем коде.

Полный набор хелперов:

Хелпер

Что делает

suggestSort(axis?)

Сортировка с определением позиции по курсору

suggestSwap()

Обмен местами двух элементов

suggestCopy(axis?)

Копирование в целевой список

suggestRemove()

Удаление из исходного списка

insertAt(items, index, ...els)

Низкоуровневая вставка

swapAt(items, i, j)

Низкоуровневый обмен

Но!) Вы всегда можете сами обработать все если вам это нужно, все хелперы лишь используют то вы получаете в event.

Перетаскивание между двумя списками — двенадцать строк с читабельной логикой:

function onDrop(e: IDragEvent) {
  const r = e.helpers.suggestSort('vertical');
  if (!r) return;

  if (r.sameList) {
    listA.value = r.targetItems as Item[];
  } else {
    if (r.sourceItems === listA.value) listA.value = r.sourceItems as Item[];
    if (r.targetItems === listB.value) listB.value = r.targetItems as Item[];
  }
}

Обратите внимание: r.sourceItems === listA.value — identity-сравнение массивов вместо поиска. O(1), никакого find.


Multi-drag из коробки

Выделяете несколько элементов — тащите всё сразу. Вы можете это сделать с помощью selected от драга и назначить на какой нибудь чекбокс через v-model или через makeSelectionArea которая создаёт область выделения прямо как на Windows:






e.draggedItems в обработчике drop содержит все выделенные элементы. suggestSort и suggestSwap корректно обрабатывают их без каких-либо изменений в вашем коде.


Деревья, Kanban, вложенные зоны

Вложенность — исторически слабое место DnD-библиотек. В v2 реализован чёткий алгоритм определения целевого массива: если курсор над draggable-элементом внутри droppable-зоны — вставляем в массив этого элемента, а не зоны. Это работает автоматически.

Дерево на v2:

function onDrop(e: IDragEvent) {
  const r = e.helpers.suggestSort('vertical');
  if (!r) return;
  // r.targetItems уже указывает на нужный массив — children узла или корень
  applyToTree(r.sourceItems, r.targetItems, r);
}

Никакого специального кода для определения глубины. Библиотека разбирается сама.


DragPreview: полная свобода в кастомизации overlay

DragPreview — встроенный компонент, который вы оборачиваете во что угодно. CSS , motion-v, GSAP — что хотите.

Простая CSS-анимация появления:


  



Spring-физика через motion-v — с анимацией появления и исчезновения:




AnimatePresence здесь ключевой: DragPreview использует v-if внутри, и без него анимация исчезновения просто не успеет сыграть — элемент уйдёт из DOM раньше.

Можно менять preview динамически — в зависимости от зоны под курсором:



Кастомный preview per-item

Каждый draggable может указать свой компонент для рендера в overlay:

makeDraggable(itemRef, {
  render: markRaw(TaskCard),
  data: () => ({ id: props.id, title: props.title, priority: props.priority }),
});

TaskCard рендерится внутри DragPreview и читает данные через useDnDProvider().entities — полный контроль над тем, как выглядит то, что тащит пользователь.


Остальное, что есть из коробки

Ограничения движения — только по оси или не выходить за пределы контейнера:

makeConstraintArea(containerRef, {
  axis: 'y',
  restrictToArea: true,
});

Автоскролл viewport и отдельных контейнеров:

...
makeAutoScroll(scrollableRef, { threshold: 80, speed: 1.5 });

Async drop — показываем диалог, preview ждёт результата:

function onDrop(e: IDragEvent) {
  return new Promise((resolve, reject) => {
    showConfirmDialog({
      message: `Переместить "${e.draggedItems[0].item}"?`,
      onConfirm: () => { applySort(e); resolve(); },
      onCancel: reject,
    });
  });
}

Keyboard navigation — из коробки, с настраиваемыми клавишами.

Zero dependencies (кроме Vue 3), tree-shakeable, полная TypeScript типизация.


Попробовать

npm install @vue-dnd-kit/core

Всех желающий ознакомится приглашаю к себе в репозиторий и в документацию))
Документация и playground: vue-dnd-kit.dev

GitHub: github.com/zizigy/vue-dnd-kit

Если попробуете и найдёте что-то странное — открывайте issue, я читаю всё. Звёзды тоже принимаю 🙂
А так же любую помощь <3

Read More

NO COMMENTS

LEAVE A REPLY

Please enter your comment!
Please enter your name here