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.
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 noonDropcome.helpers.suggestSort(). Na v3,makeSortableListemakeSortableItemfazem 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/coreconté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/devtoolssão novos e opcionais. - Um overlay de verdade. Ele é teleportado para o
bodye 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
groupspara draggables simples e alvos. As listas trocam itens pelo nome dogroup. - Decisões antes do drop.
onBeforeDropaceita, recusa, mantém o arraste em andamento ou fica com parte dos itens.onDropsó 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.
makeDragAreasubstituimakeConstraintAreaemakeAutoScroll, 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
yarn add @vue-dnd-kit/core@^3
pnpm add @vue-dnd-kit/core@^3
bun add @vue-dnd-kit/core@^3
| Pacote | v2 | v3 |
|---|---|---|
@vue-dnd-kit/core | 2.4.x | 3.x |
@vue-dnd-kit/nuxt | 1.x | 3.x |
@vue-dnd-kit/actions | — | 1.x, opcional |
@vue-dnd-kit/devtools | — | 3.x, opcional |
- Um app que tem
"@vue-dnd-kit/core": "^2…"nopackage.jsonfica 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:
- Atualize os pacotes e renomeie as props do provider (abaixo).
- Transforme cada lista ordenável em
makeSortableListemakeSortableItem, e apague o handler de drop dela. - Porte os draggables simples e as zonas de drop.
- Porte as prévias personalizadas para componentes de overlay ou para o slot
#overlaydo provider. - Substitua
makeConstraintAreaemakeAutoScrollpormakeDragArea, ou por nada. - Apague os contornos da v2:
tabindexearia-grabbednos draggables,touch-action: noneem itens inteiros,<TransitionGroup>em volta das linhas,markRawem 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
| v2 | v3 |
|---|---|
DnDProvider | DnDProvider, com props novas e um slot #overlay. |
makeDraggable | makeDraggable para arraste livre; makeSortableItem para as linhas de uma lista. |
makeDroppable | makeDroppable para alvos de drop simples; makeSortableList ou makeSortableTree para listas e árvores. |
makeSelectionArea | makeSelectionArea. |
makeConstraintArea | makeDragArea. |
makeAutoScroll | Nada: a rolagem automática é automática. makeDragArea ou o autoScroll de uma lista a ajustam. |
useDnDProvider | useDnD para o estado, useDnDMonitor para os eventos. |
injectionKey | useDnDContext() e provideDnDContext(app, context). |
DragPreview | DragPreview: agora o clone de um item arrastado, para overlays próprios. |
makeSnappedOverlayPosition, <DragPreview :grid> | O modificador snapToGrid. |
e.helpers.suggestSort, suggestSwap, suggestCopy, suggestRemove | Embutidos nas listas: veja ordenação. |
O provider
| v2 | v3 |
|---|---|
autoScrollViewport, desligado a menos que definido | autoScroll: 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 provider | overlayTo: 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.moveFaster | Sem equivalente. As setas movem; Shift deixa os passos de um arraste livre quatro vezes maiores. |
collision.throttle | Sem 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 aninhado | Aninhar 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 null | useDnD().status: 'idle', 'activating', 'dragging', 'dropping' ou 'selecting'. 'pending' (um drop assíncrono) é 'dropping'; null é 'idle'. |
pointer.value.current | useDnD().pointer, depois dos modificadores. Todo evento também tem delta, o deslocamento desde o início. |
entities.draggingMap | useDnD().active: id, ids, items, data, type, element, rect. |
entities.selectedSet (elementos) | useDnD().selection (ids): has, add, delete, toggle, set, clear, ids. |
hovered.droppable, hovered.draggable | useDnD().over: id, data, element, rect, accepted. |
delay.progress, distanceProgress | useDnD().activation.progress, de 0 a 1. |
preview.position, preview.size, preview.render, preview.to | Sem equivalente. A biblioteca posiciona e dimensiona o overlay. Use overlayTo, o slot #overlay ou componentes de overlay. |
entities.draggableMap, droppableMap, os visible…Set, scrollPosition | Sem 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
| v2 | v3 |
|---|---|
makeDraggable(ref, options, payload) | makeDraggable(ref, options): sem terceiro argumento. As linhas de uma lista usam makeSortableItem(ref, { item }). |
id, necessário em listas virtuais | id: passe-o a partir dos seus dados, como getter. O padrão vem de useId() e é novo a cada instância do componente. |
data: () => card | data: 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. |
disabled | disabled. |
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. |
placementMargins | Sem 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, onSelfDragCancel | Sem equivalente no draggable: useDnDMonitor, ou watch(isDragging). Veja eventos. |
events.onHover, onLeave, e o onDragStart dele… para arrastes sobre ele | Um makeDroppable no mesmo elemento: onDragEnter, onDragLeave. |
Retorna isDragging, selected | O mesmo. |
Retorna isDragOver | Itens ordenáveis retornam indicator (com placeholder: 'indicator') e isPlaceholder; alvos retornam placement. |
Retorna isAllowed | Sem equivalente nos draggables. Alvos retornam isAccepting. |
| — | Novo: label, axis, modifiers, keyboardMode, overlay: false com o transform retornado, dropAnimation, attributes, native. |
makeDroppable
| v2 | v3 |
|---|---|
makeDroppable(ref, options, () => items) | makeDroppable(ref, options). Uma zona que contém uma lista é um makeSortableList. |
groups | accept: '*', um tipo, uma lista de tipos ou uma função. |
groupMatch: 'every', 'some' | acceptMode: 'all', 'some'. |
events.onEnter, events.onLeave | onDragEnter, onDragLeave: opções próprias, sem objeto events. |
events.onDrop, que podia retornar false ou uma promise | onBeforeDrop decide, onDrop aplica. Veja decisões de drop. |
events.onValidate | onBeforeDrop, retornando os ids que ficam. |
data | data, lido de volta como over.data. |
disabled | disabled. |
Retorna isAllowed | isAccepting. |
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 v2 | v3 |
|---|---|
e.draggedItems | e.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].data | e.active.data. |
e.draggedItems[i].index, .items | Listas: e.from, { list, index }. |
e.dropZone: items, placement, data | e.over: id, data, element, rect, accepted. Listas: e.to, { list, index }. |
e.hoveredDraggable | Sem 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.sameList | e.sameList. |
e.helpers.insertAt, removeAt, removeIndexes, swapAt, getInsertSide, isAtZoneStart, isAtZoneEnd | Sem equivalente. As listas não precisam deles; para um alvo simples, mude os seus dados como quiser. |
e.provider | useDnD(). |
| — | Novo: e.pointer, e.delta, e.position (onde colocar um item solto em um quadro), e.mode. |
Ordenação
| v2 | v3 |
|---|---|
Uma lista makeDroppable com () => items | makeSortableList(listRef, { items }). |
Uma linha makeDraggable com () => [index, items] | makeSortableItem(el, { item: () => props.task }). |
| Renderizar o seu array | Renderizar 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 targetItems | Nada: 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, .bottom | placeholder: '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ê mesmo | items como getter, depois e.preventDefault() no onDrop e aplicar e.result. Veja listas controladas. |
Uma árvore recursiva: um droppable por pasta e applyToTree | makeSortableTree: 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 draggable | Passe 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:
| v2 | v3 |
|---|---|
makeDraggable(el, { groups: ['dev'] }) | makeDraggable(el, { type: 'dev' }). |
makeDroppable(el, { groups: ['dev'] }) | makeDroppable(el, { accept: 'dev' }). |
groups: ['dev', 'ops'] em uma zona | accept: ['dev', 'ops'], ou uma função: ({ active, items, types }) => boolean. |
groups: [], ou nenhum | type 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 dela | group: 'board' no makeSortableList. group: { name, pull, put } diz o que pode sair e entrar. |
groups no makeSelectionArea | accept. |
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
| v2 | v3 |
|---|---|
Uma prévia renderizada no provider, teleportada com previewTo | Sempre teleportado, para o body ou para overlayTo. |
Um clone: a mesma tag, o innerHTML e o tamanho | Um 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().entities | overlay: 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.position | O 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 onLeave | overOverlay 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>, makeSnappedOverlayPosition | O 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 DragPreview | overlay-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ão | A caixa assume o tamanho da posição onde vai pousar (overlayFit). |
| A prévia some no drop | Ela pousa na sua posição, ou volta ao lugar no cancelamento. |
Mais no guia do overlay.
Seleção e multi-drag
| v2 | v3 |
|---|---|
selected, retornado por makeDraggable | selected, retornado por makeDraggable e makeSortableItem. |
entities.selectedSet, um conjunto de elementos | useDnD().selection, com ids como chave. |
makeSelectionArea: Ctrl (ControlLeft) pressionado por padrão | Nenhuma 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.code | KeyboardEvent.key ('Control', 'Shift') ou .code ('ControlLeft'). |
strategy: 'toggle', 'select' | O mesmo. |
groups | accept. |
events.onSelected(elements) | onSelected(ids). |
disabled | O mesmo. |
suggestSort() movendo a seleção em bloco | As listas a movem. multiple: 'group' (padrão), 'gaps' ou 'stack' diz o que os outros itens fazem no caminho. |
onValidate para seleções mistas | acceptMode: 'some' e onBeforeDrop retornando ids. Veja aceitar só alguns. |
Mais em multi-drag e seleção.
Eixos, limites e encaixe
| v2 | v3 |
|---|---|
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: true | makeDragArea(el, { restrictToArea: true }), ou os modificadores restrictToParent e restrictToElement(el). |
| Um provider por região, para manter as regras separadas | Um provider e um makeDragArea por região. Os itens dela ainda podem ir para qualquer lugar. |
<DragPreview :grid="20">, makeSnappedOverlayPosition | snapToGrid(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
| v2 | v3 |
|---|---|
<DnDProvider :autoScrollViewport="true">, desligado por padrão | Ligado 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 lado | threshold: uma fração do tamanho do contêiner (0.2), entre 24 e 96 px. Sem equivalente para limiares por lado. |
speed, um multiplicador | maxSpeed (25 px por frame) e acceleration (10). |
disabled: true | autoScroll: false. |
isScrolling | isAutoScrolling, de makeDragArea ou de useDnD(). |
Teclado e acessibilidade
| v2 | v3 |
|---|---|
tabindex="0" em todo draggable | Definido para você, com role, aria-roledescription, aria-describedby, aria-disabled e aria-pressed. Os seus próprios atributos prevalecem. |
:aria-grabbed="isDragging" à mão | Remova: aria-pressed acompanha o arraste. |
Teclas definidas em useDnDProvider().keyboard.keys | A prop keyboard do provider. As combinações batem com event.code ou event.key. |
As setas movem a prévia em keyboard.step | Arrastes 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úncios | Uma 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
| v2 | v3 |
|---|---|
events.onSelfDragStart do draggable | useDnDMonitor({ onDragStart }), verificando active.ids. Para uma classe, isDragging basta. |
onSelfDragMove | O onDragMove do monitor. |
onSelfDragEnd, solto ou cancelado | O onDragEnd do monitor (um drop) e onDragCancel. onDragSettle ouve os dois, depois que o overlay pousou. |
onSelfDragCancel | O onDragCancel do monitor. |
onHover, onLeave do draggable, e o onDragStart dele… para arrastes sobre ele | makeDroppable no elemento: onDragEnter, onDragLeave. As linhas de uma lista não precisam de nada. |
events.onEnter, onLeave do droppable | onDragEnter, onDragLeave. |
events.onDrop do droppable | onBeforeDrop decide, depois onDrop aplica. onDrop é síncrono, e o que ele retorna é ignorado. |
events.onValidate do droppable | onBeforeDrop, 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 retorna | O 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
| v2 | v3 |
|---|---|
IDragEvent | DnDEvent (alvos, monitores), SortableDropEvent (listas), DnDEndEvent (onDragEnd). |
IDragItem | DragItem. |
IDropZoneContext | Over. |
IHoveredDraggableContext | Sem equivalente. |
ISuggestSortResult, ISuggestSwapResult, ISuggestCopyResult, ISuggestRemoveResult | SortableDropEvent: result, to, mode, partner. |
IDropValidateEvent | DnDEvent, no onBeforeDrop. |
IPlacement | Placement, sem center. |
ICoordinates | Coordinates. |
IDragActivationOptions | ActivationOptions. |
IAutoScrollOptions | AutoScrollOptions. |
TDnDState | DnDStatus. |
IDnDProviderExternal | DnDState, o que useDnD() retorna. |
IDraggableEvents, IDroppableEvents | Callbacks 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, useDnDProvider | Importa automaticamente makeDraggable, makeDroppable, makeSortableList, makeSortableItem, makeSortableTree, makeSelectionArea, makeDragArea, useDnD, useDnDMonitor. |
| Nenhum tipo importado automaticamente | Os 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 globais | O mesmo, com uma opção prefix. |
<DragPreview /> no layout, dentro do provider | Remova: o provider renderiza o overlay. DragPreview recebe um id e fica dentro de componentes de overlay. |
Peer @vue-dnd-kit/core 2 | Peer @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
<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>
<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
<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>
<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,rolee os atributosaria-*sozinho. Um handle<button>pode receber foco, e os arrastes por teclado começam a partir dele. groupsviroutypeno draggable eacceptno alvo. Os callbacks saíram do objetoevents.isOversemisAcceptingé um alvo que recusa o arraste: um drop ali volta ao lugar.
Uma lista ordenável
v2
<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>
<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
<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>
<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.tasksrecebe a nova ordem no drop, edefineModelemiteupdate: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
isPlaceholderou[data-dnd-placeholder]em vez de escondê-la.
Listas que compartilham itens
v2
// 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
<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>
<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:
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
makeDraggable(el, {
render: markRaw(TaskOverlay),
data: () => props.task,
});
<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
makeDraggable(el, {
id: () => props.task.id,
overlay: TaskOverlay,
data: () => props.task,
});
<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,modeeto. 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 umrefprofundo.
O slot do provider muda do mesmo jeito, e o seu componente não se posiciona mais sozinho:
v2
<DnDProvider>
<RouterView />
<template #preview>
<MyOverlay /> <!-- moved itself with useDnDProvider().preview.position -->
</template>
</DnDProvider>
v3
<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
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
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:
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
makeDroppable(el, {
groups: ['frontend'],
groupMatch: 'some',
events: {
onDrop: (e) => place(e.draggedItems), // only the items of the group arrive
},
});
v3
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.
^3nopackage.json: umnpm updatemantém^2na v2. - Renderize o rascunho. Uma lista renderiza os
itemsquemakeSortableListretorna, 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
suggestSortdas listas: uma lista grava cada drop em um refitemsgravável depois doonDropdela. Para aplicá-lo você mesmo, chamee.preventDefault()e usee.result. falserecusa o drop. Na v2, umonDropque resolviafalsemantinha o arraste em andamento. Na v3, retorne'continue'doonBeforeDrop. Uma promise retornada doonDropé 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 detransitionemtransform. A biblioteca move as linhas ela mesma, e os movimentos seriam animados duas vezes. - Desfaça o contorno para toque.
touch-action: noneem 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, comactivation: { touch: { distance: 4 } }. - Remova atributos obsoletos. A biblioteca define o
tabindex, orolee osaria-*de que precisa e nunca sobrescreve os seus, então umroleou umaria-grabbedque sobrou fica como você o deixou. - Refaça o estilo do overlay.
.dnd-kit-previewnão existe mais: estilize o que está dentro de[data-dnd-overlay='dragging'], nunca otransformda 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.
makeSelectionAreadesenha a partir de um simples pressionar no espaço vazio dela. Adicione ummodifierpara 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
makeDragAreapor região. - Dois argumentos.
makeDraggableemakeDroppablerecebem um ref e um objeto de opções, sem payload.