Documentação/Primeiros passos

Migrando da v2

Leve um app da v2 para a v3 — o que mudou e por quê, a atualização, um mapa de cada API da v2 para a substituta na v3, código antes e depois para os casos comuns, e as armadilhas.

Editar esta página

Esta página foi traduzida por IA e pode conter imprecisões. Ler o original em inglês

A v3 é uma reescrita da v2. O modelo continua: um provider, e composables que você chama nos seus próprios elementos. Mas a maioria dos nomes e das opções mudou, a ordenação passou para dentro da biblioteca, e o handler de drop que você escrevia para cada lista vai embora. Esta página mapeia cada parte da v2 para a v3 e depois mostra os casos comuns antes e depois.

O que mudou

  • A ordenação vem embutida. Na v2, uma lista era uma zona de drop, as linhas dela eram draggables com um payload [index, items], e você ordenava no onDrop com e.helpers.suggestSort(). Na v3, makeSortableList e makeSortableItem fazem isso: a lista renderiza um rascunho do seu array que se reordena enquanto você arrasta, e o drop grava o novo array no seu ref. A maioria dos handlers de drop da v2 é apagada, não portada.
  • As coisas se movem sozinhas. As linhas deslizam para os novos lugares (FLIP), o overlay pousa na sua posição no drop e volta ao lugar no cancelamento. O <TransitionGroup> vai embora.
  • Continua um pacote só. @vue-dnd-kit/core contém listas, árvores, multi-drag, modificadores, sensores e as funções de colisão. O módulo do Nuxt continua; @vue-dnd-kit/actions (arrastes programáticos) e @vue-dnd-kit/devtools são novos e opcionais.
  • Um overlay de verdade. Ele é teleportado para o body e mostra um clone profundo, do tamanho da posição onde vai pousar. Componentes de overlay recebem o arraste como props em vez de ler o funcionamento interno do provider.
  • Ids, não elementos. A seleção, os eventos e o estado do arraste usam como chave ids dos seus dados. Não há mapas de elementos do DOM para ler.
  • Tipos e regras de aceitação substituem groups para draggables simples e alvos. As listas trocam itens pelo nome do group.
  • Decisões antes do drop. onBeforeDrop aceita, recusa, mantém o arraste em andamento ou fica com parte dos itens. onDrop só aplica.
  • Sensores. A entrada de ponteiro e a de teclado são sensores. A ativação é definida por tipo de ponteiro, e os arrastes por toque começam com um toque longo, então as listas continuam rolando sob o dedo.
  • Áreas. makeDragArea substitui makeConstraintArea e makeAutoScroll, e a rolagem automática funciona em todo contêiner com rolagem, sem código.
  • A acessibilidade vem ligada por padrão: atributos ARIA, o foco que volta depois do drop e anúncios para leitores de tela.

Atualização

npm i @vue-dnd-kit/core@^3
Pacotev2v3
@vue-dnd-kit/core2.4.x3.x
@vue-dnd-kit/nuxt1.x3.x
@vue-dnd-kit/actions—1.x, opcional
@vue-dnd-kit/devtools—3.x, opcional
  • Um app que tem "@vue-dnd-kit/core": "^2…" no package.json fica na v2 até a faixa de versões dizer ^3.
  • Vue 3.5 ou mais novo. A v2 não tinha dependências; a v3 tem uma, @vueuse/core (14 ou 15).
  • Navegadores: Chrome e Edge 104+, Safari 15.4+, Firefox 104+.
  • A v2 e a v3 compartilham o nome do pacote, então um app passa para a v3 de uma vez. Não há camada de compatibilidade. A documentação da v2 continua disponível no menu de versões no topo da página.

Uma boa ordem de trabalho:

  1. Atualize os pacotes e renomeie as props do provider (abaixo).
  2. Transforme cada lista ordenável em makeSortableList e makeSortableItem, e apague o handler de drop dela.
  3. Porte os draggables simples e as zonas de drop.
  4. Porte as prévias personalizadas para componentes de overlay ou para o slot #overlay do provider.
  5. Substitua makeConstraintArea e makeAutoScroll por makeDragArea, ou por nada.
  6. Apague os contornos da v2: tabindex e aria-grabbed nos draggables, touch-action: none em itens inteiros, <TransitionGroup> em volta das linhas, markRaw em volta dos componentes de prévia.

Do antigo para o novo

Sem equivalente em uma tabela quer dizer que a v3 não tem nada com aquele nome; a célula diz o que usar no lugar.

Visão geral

v2v3
DnDProviderDnDProvider, com props novas e um slot #overlay.
makeDraggablemakeDraggable para arraste livre; makeSortableItem para as linhas de uma lista.
makeDroppablemakeDroppable para alvos de drop simples; makeSortableList ou makeSortableTree para listas e árvores.
makeSelectionAreamakeSelectionArea.
makeConstraintAreamakeDragArea.
makeAutoScrollNada: a rolagem automática é automática. makeDragArea ou o autoScroll de uma lista a ajustam.
useDnDProvideruseDnD para o estado, useDnDMonitor para os eventos.
injectionKeyuseDnDContext() e provideDnDContext(app, context).
DragPreviewDragPreview: agora o clone de um item arrastado, para overlays próprios.
makeSnappedOverlayPosition, <DragPreview :grid>O modificador snapToGrid.
e.helpers.suggestSort, suggestSwap, suggestCopy, suggestRemoveEmbutidos nas listas: veja ordenação.

O provider

v2v3
autoScrollViewport, desligado a menos que definidoautoScroll: ligado por padrão, para a janela e todo contêiner com rolagem. { threshold, maxSpeed, acceleration }, ou false.
previewTo, por padrão renderizado dentro do provideroverlayTo: um seletor ou um elemento, 'body' por padrão.
Slot #preview="{ preview }"Slot #overlay, com o arraste como props: items, active, over, accepted, status, mode, to. Veja o overlay.
keyboard.keys.forDrag, forDrop, forCancel (definidos em useDnDProvider()):keyboard="{ keys: { start, drop, cancel } }".
keyboard.step:keyboard="{ step }": 25 px por padrão, para arrastes livres.
keyboard.keys.forMove, forMoveFaster, keyboard.moveFasterSem equivalente. As setas movem; Shift deixa os passos de um arraste livre quatro vezes maiores.
collision.throttleSem equivalente. collision escolhe como o alvo é encontrado: pointerFirst (padrão), pointerWithin, rectIntersection, closestCenter, closestCorners ou uma função sua.
Um provider por página, nunca aninhadoAninhar funciona: o provider mais próximo controla cada item. Um provider para o app inteiro continua sendo o objetivo.

Props novas sem correspondente na v2: activation, modifiers, sensors, animation, dropAnimation, animateChanges, haptics, copyModifier, overlayFit, overlayClass, overlayTransition, overlayPreviews, announcements, screenReaderInstructions e defaultStyles. O provider também emite drag-start, drag-move, drag-over, drag-end, drag-cancel, drag-resume e drag-settle. Tudo isso está no guia do provider.

Lendo o estado: useDnD

useDnDProvider()v3
state: 'dragging', 'activating', 'selecting', 'pending' ou nulluseDnD().status: 'idle', 'activating', 'dragging', 'dropping' ou 'selecting'. 'pending' (um drop assíncrono) é 'dropping'; null é 'idle'.
pointer.value.currentuseDnD().pointer, depois dos modificadores. Todo evento também tem delta, o deslocamento desde o início.
entities.draggingMapuseDnD().active: id, ids, items, data, type, element, rect.
entities.selectedSet (elementos)useDnD().selection (ids): has, add, delete, toggle, set, clear, ids.
hovered.droppable, hovered.draggableuseDnD().over: id, data, element, rect, accepted.
delay.progress, distanceProgressuseDnD().activation.progress, de 0 a 1.
preview.position, preview.size, preview.render, preview.toSem equivalente. A biblioteca posiciona e dimensiona o overlay. Use overlayTo, o slot #overlay ou componentes de overlay.
entities.draggableMap, droppableMap, os visible…Set, scrollPositionSem equivalente.
—Novo: useDnD().cancel(), position, isAutoScrolling.

Os eventos foram para o useDnDMonitor: veja eventos e decisões de drop. As formas completas estão na API do useDnD.

makeDraggable

v2v3
makeDraggable(ref, options, payload)makeDraggable(ref, options): sem terceiro argumento. As linhas de uma lista usam makeSortableItem(ref, { item }).
id, necessário em listas virtuaisid: passe-o a partir dos seus dados, como getter. O padrão vem de useId() e é novo a cada instância do componente.
data: () => carddata: um valor, um ref ou um getter. Lido de volta como active.data e active.items[i].data.
groups: ['dev']type: 'dev', comparado com o accept dos alvos. Veja grupos.
dragHandle: '.handle'handle: um seletor ou um ref de elemento.
disableddisabled.
modifier: { keys, method }canDrag: (event) => event.altKey: um filtro para cada pressionamento.
activation: { distance, delay }activation por tipo de ponteiro: { mouse: { distance }, touch: { delay, tolerance }, pen }. Com distance e delay juntos, o arraste começa no que vier primeiro.
activation.distance: { x, y, condition }Sem equivalente. Uma distance por tipo de ponteiro.
render: markRaw(Preview)overlay: Preview: o componente recebe o arraste como props. Veja o overlay.
placementMarginsSem equivalente. Um alvo de drop em um item ordenável conta no meio dele (de 25 a 75%); árvores aninham com nesting: 'hover'. Veja drop dentro de um item ordenável.
events.onSelfDragStart, onSelfDragMove, onSelfDragEnd, onSelfDragCancelSem equivalente no draggable: useDnDMonitor, ou watch(isDragging). Veja eventos.
events.onHover, onLeave, e o onDragStart dele… para arrastes sobre eleUm makeDroppable no mesmo elemento: onDragEnter, onDragLeave.
Retorna isDragging, selectedO mesmo.
Retorna isDragOverItens ordenáveis retornam indicator (com placeholder: 'indicator') e isPlaceholder; alvos retornam placement.
Retorna isAllowedSem equivalente nos draggables. Alvos retornam isAccepting.
—Novo: label, axis, modifiers, keyboardMode, overlay: false com o transform retornado, dropAnimation, attributes, native.

makeDroppable

v2v3
makeDroppable(ref, options, () => items)makeDroppable(ref, options). Uma zona que contém uma lista é um makeSortableList.
groupsaccept: '*', um tipo, uma lista de tipos ou uma função.
groupMatch: 'every', 'some'acceptMode: 'all', 'some'.
events.onEnter, events.onLeaveonDragEnter, onDragLeave: opções próprias, sem objeto events.
events.onDrop, que podia retornar false ou uma promiseonBeforeDrop decide, onDrop aplica. Veja decisões de drop.
events.onValidateonBeforeDrop, retornando os ids que ficam.
datadata, lido de volta como over.data.
disableddisabled.
Retorna isAllowedisAccepting.
Retorna isDragOver: { top, right, bottom, left, center }isOver (um booleano), e placement: { top, right, bottom, left }, as metades em que o ponteiro está. Não há center.
—Novo: id, label, priority, overOverlay, overDropAnimation, external (arquivos do computador).

O evento de drop

IDragEvent da v2v3
e.draggedItemse.active.items: { id, item, data, label, type } cada um, começando pelo que foi pego. Listas recebem e.items e e.keys.
e.draggedItems[0].datae.active.data.
e.draggedItems[i].index, .itemsListas: e.from, { list, index }.
e.dropZone: items, placement, datae.over: id, data, element, rect, accepted. Listas: e.to, { list, index }.
e.hoveredDraggableSem equivalente: as listas calculam e.to.index sozinhas.
e.helpers.suggestSort()As listas gravam o drop; e.result contém o novo array de cada lista que ele muda.
e.helpers.suggestSwap()behavior: 'swap' na lista; e.partner é o item com que ele trocou de lugar.
e.helpers.suggestCopy()group: { name, pull: 'clone' } ou a tecla de cópia (Alt), com o clone da lista; e.mode é 'copy'.
e.helpers.suggestRemove()e.source.remove() em um alvo de drop, ou o dropOutside: 'remove' da lista.
result.sameListe.sameList.
e.helpers.insertAt, removeAt, removeIndexes, swapAt, getInsertSide, isAtZoneStart, isAtZoneEndSem equivalente. As listas não precisam deles; para um alvo simples, mude os seus dados como quiser.
e.provideruseDnD().
—Novo: e.pointer, e.delta, e.position (onde colocar um item solto em um quadro), e.mode.

Ordenação

v2v3
Uma lista makeDroppable com () => itemsmakeSortableList(listRef, { items }).
Uma linha makeDraggable com () => [index, items]makeSortableItem(el, { item: () => props.task }).
Renderizar o seu arrayRenderizar os items retornados: o rascunho que se reordena enquanto você arrasta.
suggestSort('vertical'), suggestSort('horizontal')strategy: 'vertical' (padrão), 'horizontal' ou 'grid'.
Comparações de identidade para achar os arrays de origem e de destino, e depois atribuir sourceItems e targetItemsNada: o drop grava cada lista que ele muda.
<TransitionGroup>Embutido. animateChanges: true anima as mudanças que nenhum arraste fez (adicionar, remover, embaralhar).
Uma linha de inserção a partir de isDragOver.top, .bottomplaceholder: 'indicator' e o indicator.side do item. Veja uma linha de inserção.
suggestSwap()behavior: 'swap'.
Uma paleta que só copia (suggestCopy() e uma flag copyOnly)group: { name, pull: 'clone', put: false }, sort: false e clone.
Uma lixeira com suggestRemove()makeDroppable(trash, { onDrop: (e) => e.source?.remove() }), ou dropOutside: 'remove'.
Pinia ou dados imutáveis: atribuir o resultado você mesmoitems como getter, depois e.preventDefault() no onDrop e aplicar e.result. Veja listas controladas.
Uma árvore recursiva: um droppable por pasta e applyToTreemakeSortableTree: um contêiner só, linhas renderizadas de forma plana com a profundidade delas. Veja árvores.
Grupos aninhados com v-model (um droppable por grupo)Uma lista dentro de uma lista, todas em um mesmo group, cada uma com items: defineModel().
Listas virtuais: id em todo draggablePasse o rascunho para o virtualizador, id: () => props.item.id nas linhas. Veja listas virtuais.

O código antes e depois está abaixo. Tudo sobre listas está em listas ordenáveis e listas que compartilham itens.

Grupos

A v2 tinha uma opção groups para tudo. A v3 a divide em duas:

v2v3
makeDraggable(el, { groups: ['dev'] })makeDraggable(el, { type: 'dev' }).
makeDroppable(el, { groups: ['dev'] })makeDroppable(el, { accept: 'dev' }).
groups: ['dev', 'ops'] em uma zonaaccept: ['dev', 'ops'], ou uma função: ({ active, items, types }) => boolean.
groups: [], ou nenhumtype ou accept deixados de fora. As regras da v2 valem: um draggable sem tipo se encaixa em qualquer regra, e um alvo sem accept aceita tudo.
groupMatch: 'every', 'some'acceptMode: 'all', 'some'.
groups na zona de uma lista e nas linhas delagroup: 'board' no makeSortableList. group: { name, pull, put } diz o que pode sair e entrar.
groups no makeSelectionAreaaccept.

Uma lista sem group é um grupo próprio e não troca itens com nenhuma outra lista. Na v2, uma zona sem grupos aceitava tudo. Veja quais arrastes um alvo aceita.

O overlay

v2v3
Uma prévia renderizada no provider, teleportada com previewToSempre teleportado, para o body ou para overlayTo.
Um clone: a mesma tag, o innerHTML e o tamanhoUm clone profundo: valores de formulário, pixels de <canvas>, estilos herdados e células de tabela vêm junto. Marque o que fica para trás com data-dnd-no-clone.
render: markRaw(Preview), que lê useDnDProvider().entitiesoverlay: Preview, que recebe items, active, over, accepted, status, mode e to como props. Sem markRaw; use um shallowRef para trocá-lo.
O slot #preview, posicionado a partir de preview.positionO slot #overlay: a biblioteca posiciona e dimensiona a caixa. Se não renderizar nada, volta a usar o clone.
useDnDProvider().preview.render.value = …O slot #overlay, ou um shallowRef passado como overlay.
Uma aparência nova sobre uma zona, a partir de onEnter e onLeaveoverOverlay no makeDroppable ou em uma lista, ou ler over no componente de overlay. Veja alvos que mudam o overlay.
<DragPreview v-slot="{ draggingMap }">Os items das props do overlay; <DragPreview :id="item.id" /> renderiza um clone.
<DragPreview :grid>, makeSnappedOverlayPositionO modificador snapToGrid. Ele encaixa onde os alvos são encontrados e também a position do drop, não só a imagem.
<Transition> ou o AnimatePresence do motion em volta do DragPreviewoverlay-class com @starting-style para o início do arraste, dropAnimation para o pouso, overlayTransition entre componentes. Veja erguer, inclinar, assentar.
.dnd-kit-preview[data-dnd-overlay] ("dragging", "dropping") na caixa, [data-dnd-overlay-content] dentro dela.
preview.size, mantido atualizado à mãoA caixa assume o tamanho da posição onde vai pousar (overlayFit).
A prévia some no dropEla pousa na sua posição, ou volta ao lugar no cancelamento.

Mais no guia do overlay.

Seleção e multi-drag

v2v3
selected, retornado por makeDraggableselected, retornado por makeDraggable e makeSortableItem.
entities.selectedSet, um conjunto de elementosuseDnD().selection, com ids como chave.
makeSelectionArea: Ctrl (ControlLeft) pressionado por padrãoNenhuma tecla por padrão: pressionar o espaço vazio da área desenha o retângulo. modifier: { keys: ['Control'], method: 'every' } traz o Ctrl de volta.
modifier.keys como KeyboardEvent.codeKeyboardEvent.key ('Control', 'Shift') ou .code ('ControlLeft').
strategy: 'toggle', 'select'O mesmo.
groupsaccept.
events.onSelected(elements)onSelected(ids).
disabledO mesmo.
suggestSort() movendo a seleção em blocoAs listas a movem. multiple: 'group' (padrão), 'gaps' ou 'stack' diz o que os outros itens fazem no caminho.
onValidate para seleções mistasacceptMode: 'some' e onBeforeDrop retornando ids. Veja aceitar só alguns.

Mais em multi-drag e seleção.

Eixos, limites e encaixe

v2v3
makeConstraintArea(el, { axis: 'x' })makeDragArea(el, { axis: 'x' }), para os arrastes que começam dentro dela. axis também funciona em um draggable, uma lista, uma árvore ou um item.
axis: 'both'axis deixado de fora.
restrictToArea: truemakeDragArea(el, { restrictToArea: true }), ou os modificadores restrictToParent e restrictToElement(el).
Um provider por região, para manter as regras separadasUm provider e um makeDragArea por região. Os itens dela ainda podem ir para qualquer lugar.
<DragPreview :grid="20">, makeSnappedOverlayPositionsnapToGrid(20) ou snapToGrid({ x, y, origin, smooth }), nos modifiers do provider, de uma área, de uma lista ou de um draggable.
—Novo: restrictToWindowEdges, restrictToFirstScrollableAncestor, snapCenterToCursor e modificadores seus.

Mais em eixos, limites e grades.

Rolagem automática

v2v3
<DnDProvider :autoScrollViewport="true">, desligado por padrãoLigado por padrão, para a janela e todo contêiner com rolagem. :auto-scroll="false" a desliga.
makeAutoScroll(el, { threshold, speed })Nada: qualquer elemento com overflow: auto ou scroll rola. Para ajustar um: makeDragArea(el, { autoScroll: { … } }) ou o autoScroll da lista.
threshold em px, ou por ladothreshold: uma fração do tamanho do contêiner (0.2), entre 24 e 96 px. Sem equivalente para limiares por lado.
speed, um multiplicadormaxSpeed (25 px por frame) e acceleration (10).
disabled: trueautoScroll: false.
isScrollingisAutoScrolling, de makeDragArea ou de useDnD().

Teclado e acessibilidade

v2v3
tabindex="0" em todo draggableDefinido para você, com role, aria-roledescription, aria-describedby, aria-disabled e aria-pressed. Os seus próprios atributos prevalecem.
:aria-grabbed="isDragging" à mãoRemova: aria-pressed acompanha o arraste.
Teclas definidas em useDnDProvider().keyboard.keysA prop keyboard do provider. As combinações batem com event.code ou event.key.
As setas movem a prévia em keyboard.stepArrastes livres: keyboard.step px. Listas: de posição em posição, e para a próxima lista do grupo. keyboardMode: 'droppables' salta entre alvos.
Remapear as setas (forMove, forMoveFaster)Sem equivalente.
Sem anúnciosUma live region anuncia cada início de arraste, movimento, drop e cancelamento. label dá nome às coisas; announcements e screenReaderInstructions mudam o texto.

Mais em acessibilidade.

Eventos e decisões de drop

v2v3
events.onSelfDragStart do draggableuseDnDMonitor({ onDragStart }), verificando active.ids. Para uma classe, isDragging basta.
onSelfDragMoveO onDragMove do monitor.
onSelfDragEnd, solto ou canceladoO onDragEnd do monitor (um drop) e onDragCancel. onDragSettle ouve os dois, depois que o overlay pousou.
onSelfDragCancelO onDragCancel do monitor.
onHover, onLeave do draggable, e o onDragStart dele… para arrastes sobre elemakeDroppable no elemento: onDragEnter, onDragLeave. As linhas de uma lista não precisam de nada.
events.onEnter, onLeave do droppableonDragEnter, onDragLeave.
events.onDrop do droppableonBeforeDrop decide, depois onDrop aplica. onDrop é síncrono, e o que ele retorna é ignorado.
events.onValidate do droppableonBeforeDrop, com acceptMode: 'some'.
—Novo: @drag-start … @drag-settle do provider, e onDragOver, onBeforeDrop e onDragResume do monitor.

Um onDrop da v2 podia retornar um valor, ou uma promise de um valor, para decidir o drop. Na v3 esse é o papel do onBeforeDrop, em listas, árvores, alvos de drop e monitores:

O onDrop da v2 retornaO onBeforeDrop da v3 retorna
Nada, ou true: o drop é concluído'accept', true ou nada.
false: este drop é descartado, o arraste continua ativo'continue'. false agora recusa o drop.
Uma promise rejeitada, ou um throw: o arraste é cancelado'reject' ou false. Um throw também rejeita.
Uma promise; enquanto isso state é 'pending'Uma promise; enquanto isso status é 'dropping' e o overlay espera com data-dnd-overlay="dropping".
onValidate retorna IDragItem[]Um array de ids: só esses são soltos, os outros voltam ao lugar.

Os monitores respondem primeiro, depois o alvo. Então o onDrop do alvo roda, uma lista grava os novos arrays, e o onDragEnd vem em seguida. Mais em decidindo um drop e na API de eventos.

Tipos

v2v3
IDragEventDnDEvent (alvos, monitores), SortableDropEvent (listas), DnDEndEvent (onDragEnd).
IDragItemDragItem.
IDropZoneContextOver.
IHoveredDraggableContextSem equivalente.
ISuggestSortResult, ISuggestSwapResult, ISuggestCopyResult, ISuggestRemoveResultSortableDropEvent: result, to, mode, partner.
IDropValidateEventDnDEvent, no onBeforeDrop.
IPlacementPlacement, sem center.
ICoordinatesCoordinates.
IDragActivationOptionsActivationOptions.
IAutoScrollOptionsAutoScrollOptions.
TDnDStateDnDStatus.
IDnDProviderExternalDnDState, o que useDnD() retorna.
IDraggableEvents, IDroppableEventsCallbacks em DraggableOptions, DroppableOptions e DnDMonitor.

Nuxt

npm i @vue-dnd-kit/nuxt@^3 @vue-dnd-kit/core@^3
v2 (@vue-dnd-kit/nuxt 1.x)v3 (@vue-dnd-kit/nuxt 3.x)
Importa automaticamente makeDraggable, makeDroppable, makeSelectionArea, makeConstraintArea, makeAutoScroll, useDnDProviderImporta automaticamente makeDraggable, makeDroppable, makeSortableList, makeSortableItem, makeSortableTree, makeSelectionArea, makeDragArea, useDnD, useDnDMonitor.
Nenhum tipo importado automaticamenteOs tipos principais são (DnDEvent, SortableDropEvent, OverlayProps…). Os genéricos (Key, Active, Over) são importados à mão.
—Modificadores, funções de colisão e sensores sob demanda: vueDndKit: { autoImports: { modifiers: true, collision: true, sensors: true } }.
DnDProvider e DragPreview globaisO mesmo, com uma opção prefix.
<DragPreview /> no layout, dentro do providerRemova: o provider renderiza o overlay. DragPreview recebe um id e fica dentro de componentes de overlay.
Peer @vue-dnd-kit/core 2Peer @vue-dnd-kit/core 3; Nuxt 4.

modules: ['@vue-dnd-kit/nuxt'] no nuxt.config.ts continua como estava. Veja instalação.

Antes e depois

Um draggable e uma zona de drop

v2

Card.vue
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { makeDraggable } from '@vue-dnd-kit/core';

const props = defineProps<{ card: Card }>();
const el = useTemplateRef<HTMLElement>('el');

const { isDragging } = makeDraggable(el, {
  groups: ['task'],
  dragHandle: '.handle',
  data: () => props.card,
});
</script>

<template>
  <div ref="el" tabindex="0" :class="{ dragging: isDragging }">
    <span class="handle">⋮⋮</span>
    {{ card.title }}
  </div>
</template>
Zone.vue
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { makeDroppable } from '@vue-dnd-kit/core';

const emit = defineEmits<{ drop: [cards: Card[]] }>();
const el = useTemplateRef<HTMLElement>('el');

const { isDragOver, isAllowed } = makeDroppable(el, {
  groups: ['task'],
  events: {
    onDrop: (e) => emit('drop', e.draggedItems.map((item) => item.data as Card)),
  },
});
</script>

<template>
  <div ref="el" :class="{ over: isDragOver && isAllowed }"><slot /></div>
</template>

v3

Card.vue
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { makeDraggable } from '@vue-dnd-kit/core';

const props = defineProps<{ card: Card }>();
const el = useTemplateRef<HTMLElement>('el');

const { isDragging } = makeDraggable(el, {
  id: () => props.card.id,   // read back as `active.id`
  type: 'task',              // matched by the targets' `accept`
  handle: '.handle',
  data: () => props.card,
});
</script>

<template>
  <div ref="el" :class="{ dragging: isDragging }">
    <button type="button" class="handle" :aria-label="`Move ${card.title}`">⋮⋮</button>
    {{ card.title }}
  </div>
</template>
Zone.vue
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { makeDroppable } from '@vue-dnd-kit/core';

const emit = defineEmits<{ drop: [cards: Card[]] }>();
const el = useTemplateRef<HTMLElement>('el');

const { isOver, isAccepting } = makeDroppable(el, {
  accept: 'task',
  onDrop: (e) => emit('drop', e.active.items.map((item) => item.data as Card)),
});
</script>

<template>
  <div ref="el" :class="{ over: isOver && isAccepting }"><slot /></div>
</template>
  • O card recebe tabindex, role e os atributos aria-* sozinho. Um handle <button> pode receber foco, e os arrastes por teclado começam a partir dele.
  • groups virou type no draggable e accept no alvo. Os callbacks saíram do objeto events.
  • isOver sem isAccepting é um alvo que recusa o arraste: um drop ali volta ao lugar.

Uma lista ordenável

v2

TaskList.vue
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { makeDroppable, type IDragEvent } from '@vue-dnd-kit/core';
import TaskItem from './TaskItem.vue';

const tasks = defineModel<Task[]>({ required: true });
const el = useTemplateRef<HTMLElement>('el');

makeDroppable(
  el,
  {
    events: {
      onDrop(e: IDragEvent) {
        const r = e.helpers.suggestSort('vertical');
        if (r) tasks.value = r.targetItems as Task[];
      },
    },
  },
  () => tasks.value,
);
</script>

<template>
  <div ref="el">
    <TransitionGroup name="list">
      <TaskItem v-for="(task, index) in tasks" :key="task.id" :task="task" :index="index" :tasks="tasks" />
    </TransitionGroup>
  </div>
</template>
TaskItem.vue
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { makeDraggable } from '@vue-dnd-kit/core';

const props = defineProps<{ task: Task; index: number; tasks: Task[] }>();
const el = useTemplateRef<HTMLElement>('el');

const { isDragging } = makeDraggable(el, { id: props.task.id }, () => [props.index, props.tasks]);
</script>

<template>
  <div ref="el" :class="{ 'is-dragging': isDragging }">{{ task.title }}</div>
</template>

v3

TaskList.vue
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { makeSortableList } from '@vue-dnd-kit/core';
import TaskItem from './TaskItem.vue';

const tasks = defineModel<Task[]>({ required: true });
const el = useTemplateRef<HTMLElement>('el');

const { items } = makeSortableList(el, { items: tasks });
</script>

<template>
  <ul ref="el">
    <TaskItem v-for="task in items" :key="task.id" :task="task" />
  </ul>
</template>
TaskItem.vue
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { makeSortableItem } from '@vue-dnd-kit/core';

const props = defineProps<{ task: Task }>();
const el = useTemplateRef<HTMLElement>('el');

const { isPlaceholder } = makeSortableItem(el, { item: () => props.task });
</script>

<template>
  <li ref="el" :class="{ slot: isPlaceholder }">{{ task.title }}</li>
</template>
  • Renderize items, o rascunho: ele se reordena enquanto você arrasta. tasks recebe a nova ordem no drop, e defineModel emite update:modelValue.
  • Sem índice, sem payload, sem onDrop, sem <TransitionGroup>: as linhas deslizam sozinhas.
  • A linha arrastada fica na lista como a posição onde vai pousar. Estilize-a com isPlaceholder ou [data-dnd-placeholder] em vez de escondê-la.

Listas que compartilham itens

v2

Board.vue
// Each column's makeDroppable calls this from its onDrop, with its array as the payload.
function applySort(e: IDragEvent) {
  const r = e.helpers.suggestSort('vertical');
  if (!r) return;

  const src = e.draggedItems[0]?.items;
  const tgt = e.hoveredDraggable?.items ?? e.dropZone?.items;

  if (src === todo.value) todo.value = r.sourceItems as Task[];
  else if (src === done.value) done.value = r.sourceItems as Task[];

  if (!r.sameList) {
    if (tgt === todo.value) todo.value = r.targetItems as Task[];
    else if (tgt === done.value) done.value = r.targetItems as Task[];
  }
}

v3

Column.vue
<script setup lang="ts">
import { toRef, useTemplateRef } from 'vue';
import { makeSortableList } from '@vue-dnd-kit/core';
import TaskItem from './TaskItem.vue';

const props = defineProps<{ column: { id: string; tasks: Task[] } }>();
const el = useTemplateRef<HTMLElement>('el');

const { items } = makeSortableList(el, {
  id: props.column.id,                   // `from.list` and `to.list` in drop events
  items: toRef(props.column, 'tasks'),   // writable: the parent passes a reactive column
  group: 'board',                        // same name: tasks move between these lists
});
</script>

<template>
  <ul ref="el">
    <TaskItem v-for="task in items" :key="task.id" :task="task" />
  </ul>
</template>
Board.vue
<script setup lang="ts">
import { reactive } from 'vue';
import Column from './Column.vue';

const columns = reactive([
  { id: 'todo', tasks: [{ id: 't1', title: 'Write the docs' }] },
  { id: 'done', tasks: [] },
]);
</script>

<template>
  <Column v-for="column in columns" :key="column.id" :column="column" />
</template>

O drop grava os dois arrays, o que a tarefa deixou e o em que ela entrou. TaskItem.vue é o da lista ordenável acima. Uma paleta que distribui cópias era suggestCopy() e uma flag copyOnly; agora é uma lista:

Palette.vue
makeSortableList(el, {
  items: blocks,
  group: { name: 'board', pull: 'clone', put: false },  // copies go out, nothing comes in
  sort: false,                                          // its own order stays
  clone: (block) => ({ ...block, id: crypto.randomUUID() }),
});

Para um store Pinia ou outro estado que você não grava diretamente, passe items como getter, chame e.preventDefault() no onDrop e aplique e.result: veja listas controladas.

Um overlay personalizado

v2

TaskItem.vue
makeDraggable(el, {
  render: markRaw(TaskOverlay),
  data: () => props.task,
});
TaskOverlay.vue
<script setup lang="ts">
import { computed } from 'vue';
import { useDnDProvider } from '@vue-dnd-kit/core';

const { entities } = useDnDProvider();
const task = computed(() => {
  const [node] = entities.draggingMap.keys();
  return node ? (entities.draggableMap.get(node)?.data?.() as Task) : null;
});
</script>

<template>
  <div class="task-preview">{{ task?.title }}</div>
</template>

v3

TaskItem.vue
makeDraggable(el, {
  id: () => props.task.id,
  overlay: TaskOverlay,
  data: () => props.task,
});
TaskOverlay.vue
<script setup lang="ts">
import { computed } from 'vue';
import type { DragItem, Over } from '@vue-dnd-kit/core';

const props = defineProps<{ items: DragItem[]; over: Over | null; accepted: boolean }>();
const task = computed(() => props.items[0]!.data as Task);
</script>

<template>
  <!-- Fills the overlay box, which takes the size of the slot. -->
  <div class="task-preview" :class="{ refused: over && !accepted }">
    {{ task.title }}
    <span v-if="items.length > 1" class="count">{{ items.length }}</span>
  </div>
</template>

<style scoped>
.task-preview { width: 100%; height: 100%; }
</style>
  • O componente recebe o arraste como props: items (cada item arrastado, começando pelo que foi pego), over, accepted, status, mode e to. Declare só as que você lê. Para as linhas de uma lista, o item da lista é items[0].item.
  • Passe-o como está. Troque-o com um shallowRef, nunca com um ref profundo.

O slot do provider muda do mesmo jeito, e o seu componente não se posiciona mais sozinho:

v2

App.vue
<DnDProvider>
  <RouterView />
  <template #preview>
    <MyOverlay />  <!-- moved itself with useDnDProvider().preview.position -->
  </template>
</DnDProvider>

v3

App.vue
<DnDProvider>
  <RouterView />
  <template #overlay="{ items, over, accepted }">
    <MyOverlay :items="items" :over="over" :accepted="accepted" />
  </template>
</DnDProvider>

Uma entrada animada que envolvia o DragPreview em um <Transition> vira overlay-class com @starting-style: veja erguer, inclinar, assentar.

Um drop que você confirma

v2

Zone.vue
makeDroppable(el, {
  events: {
    onDrop: (e) =>
      new Promise<void | boolean>((resolve) => {
        if (!window.confirm('Drop here?')) return resolve(false); // keep dragging
        place(e.draggedItems);
        resolve();
      }),
  },
});

v3

Zone.vue
makeDroppable(el, {
  onBeforeDrop: () => (window.confirm('Drop here?') ? 'accept' : 'continue'),
  onDrop: (e) => place(e.active.items),
});

Uma lista pergunta do mesmo jeito, e uma promise permite abrir um diálogo seu. O overlay espera onde foi solto até a promise terminar:

Column.vue
makeSortableList(el, {
  items: toRef(props.column, 'tasks'),
  group: 'board',
  // `ask` opens a dialog and resolves 'accept', 'continue' or 'reject'.
  onBeforeDrop: (e) => (e.sameList ? 'accept' : ask(e)),
});

Uma zona que aceitava só a parte correspondente de uma seleção mista (groupMatch: 'some'):

v2

Zone.vue
makeDroppable(el, {
  groups: ['frontend'],
  groupMatch: 'some',
  events: {
    onDrop: (e) => place(e.draggedItems), // only the items of the group arrive
  },
});

v3

Zone.vue
import type { DragItem } from '@vue-dnd-kit/core';

const fits = (item: DragItem) => item.type.length === 0 || item.type.includes('frontend');

makeDroppable(el, {
  accept: 'frontend',
  acceptMode: 'some',
  onBeforeDrop: (e) => e.active.items.filter(fits).map((item) => item.id), // the others fly back
  onDrop: (e) => place(e.active.items.filter(fits)), // `active.items` still lists every dragged item
});

As listas fazem isso sem código: com acceptMode: 'some', os itens que o group.put delas aceita pousam, e os outros voltam ao lugar.

Armadilhas ao atualizar

  • Instale a v3 com a faixa dela. ^3 no package.json: um npm update mantém ^2 na v2.
  • Renderize o rascunho. Uma lista renderiza os items que makeSortableList retorna, não o seu array. Renderizando o seu array, nada se move até o drop.
  • Não ordene duas vezes. Apague o código de suggestSort das listas: uma lista grava cada drop em um ref items gravável depois do onDrop dela. Para aplicá-lo você mesmo, chame e.preventDefault() e use e.result.
  • false recusa o drop. Na v2, um onDrop que resolvia false mantinha o arraste em andamento. Na v3, retorne 'continue' do onBeforeDrop. Uma promise retornada do onDrop é ignorada.
  • Listas só compartilham dentro de um group. Uma lista sem um não troca itens com nenhuma outra lista.
  • Use ids como key das linhas. :key="index" anima as linhas erradas. Passe ids a partir dos seus dados também: um id padrão é novo a cada instância do componente.
  • Nada de <TransitionGroup>, nada de transition em transform. A biblioteca move as linhas ela mesma, e os movimentos seriam animados duas vezes.
  • Desfaça o contorno para toque. touch-action: none em um item inteiro impede a página de rolar sob o dedo; a v3 começa os arrastes por toque com um toque longo. Mantenha-o em um handle que deve arrastar na hora, com activation: { touch: { distance: 4 } }.
  • Remova atributos obsoletos. A biblioteca define o tabindex, o role e os aria-* de que precisa e nunca sobrescreve os seus, então um role ou um aria-grabbed que sobrou fica como você o deixou.
  • Refaça o estilo do overlay. .dnd-kit-preview não existe mais: estilize o que está dentro de [data-dnd-overlay='dragging'], nunca o transform da caixa. Dê width: 100%; height: 100% à raiz de um componente de overlay, e tire <DragPreview /> dos seus layouts.
  • A rolagem automática está ligada. Passe :auto-scroll="false" onde a v2 nunca rolava.
  • Um retângulo de seleção não precisa de tecla. makeSelectionArea desenha a partir de um simples pressionar no espaço vazio dela. Adicione um modifier para manter o Ctrl da v2.
  • Um provider, áreas dentro dele. Itens não podem passar de um provider para outro. Uma página que usava um provider por região para manter as regras separadas precisa de um provider e de um makeDragArea por região.
  • Dois argumentos. makeDraggable e makeDroppable recebem um ref e um objeto de opções, sem payload.