Documentación/Primeros pasos
Migrar desde la v2
Pasa una app de la v2 a la v3 — qué cambió y por qué, la actualización, un mapa de cada API de la v2 a su sustituta en la v3, código de antes y después para los casos habituales, y los escollos.
Esta página la ha traducido una IA y puede contener imprecisiones. Leer el original en inglés
La v3 es una reescritura de la v2. El modelo se mantiene: un provider, y composables que llamas sobre tus propios elementos. Pero la mayoría de los nombres y de las opciones cambiaron, la ordenación pasó a la biblioteca, y el handler de drop que escribías para cada lista desaparece. Esta página relaciona cada parte de la v2 con la v3 y luego muestra los casos habituales antes y después.
Qué cambió
- La ordenación viene incluida. En la v2, una lista era una zona de drop, sus filas eran
elementos arrastrables con un payload
[index, items], y ordenabas enonDropcone.helpers.suggestSort(). En la v3 lo hacenmakeSortableListymakeSortableItem: la lista renderiza un borrador de tu array que se reordena mientras arrastras, y el drop escribe el nuevo array en tu ref. La mayoría de los handlers de drop de la v2 se borran, no se portan. - Las cosas se mueven solas. Las filas se deslizan a sus nuevos sitios (FLIP), el overlay
aterriza en su hueco al soltar y vuelve a su sitio al cancelar.
<TransitionGroup>desaparece. - Sigue siendo un solo paquete.
@vue-dnd-kit/corecontiene las listas, los árboles, el multi-drag, los modificadores, los sensores y las funciones de colisión. El módulo de Nuxt se mantiene;@vue-dnd-kit/actions(arrastres programáticos) y@vue-dnd-kit/devtoolsson nuevos y opcionales. - Un overlay de verdad. Se teletransporta a
bodyy muestra un clon profundo, con el tamaño del hueco en el que aterrizará. Los componentes de overlay reciben el arrastre como props en lugar de leer las interioridades del provider. - Ids, no elementos. La selección, los eventos y el estado del arrastre usan como clave ids de tus datos. No hay mapas de elementos del DOM que leer.
- Los tipos y las reglas de aceptación sustituyen a
groupspara los elementos arrastrables simples y los destinos. Las listas intercambian ítems por el nombre de sugroup. - Decisiones antes del drop.
onBeforeDropacepta, rechaza, mantiene el arrastre en curso o se queda con parte de los ítems.onDropsolo aplica. - Sensores. La entrada de puntero y la de teclado son sensores. La activación se configura por tipo de puntero, y los arrastres táctiles empiezan con una pulsación larga, así que las listas se siguen desplazando bajo el dedo.
- Áreas.
makeDragAreasustituye amakeConstraintAreaymakeAutoScroll, y el desplazamiento automático funciona en todo contenedor desplazable sin código. - La accesibilidad viene activada por defecto: atributos ARIA, un foco que vuelve tras el drop y anuncios para lectores de pantalla.
Actualizar
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
| Paquete | 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 |
- Una app que tiene
"@vue-dnd-kit/core": "^2…"en supackage.jsonse queda en la v2 hasta que el rango diga^3. - Vue 3.5 o posterior. La v2 no tenía dependencias; la v3 tiene una,
@vueuse/core(14 o 15). - Navegadores: Chrome y Edge 104+, Safari 15.4+, Firefox 104+.
- La v2 y la v3 comparten nombre de paquete, así que una app pasa a la v3 de una vez. No hay capa de compatibilidad. La documentación de la v2 sigue disponible desde el menú de versiones de la parte superior de la página.
Un buen orden de trabajo:
- Actualiza los paquetes y renombra las props del provider (más abajo).
- Convierte cada lista ordenable en
makeSortableListymakeSortableItem, y borra su handler de drop. - Porta los elementos arrastrables simples y las zonas de drop.
- Porta las vistas previas personalizadas a componentes de overlay o al slot
#overlaydel provider. - Sustituye
makeConstraintAreaymakeAutoScrollpormakeDragArea, o por nada. - Borra los apaños de la v2:
tabindexyaria-grabbeden los elementos arrastrables,touch-action: noneen ítems enteros,<TransitionGroup>alrededor de las filas,markRawalrededor de los componentes de vista previa.
De lo antiguo a lo nuevo
Sin equivalente en una tabla significa que la v3 no tiene nada con ese nombre; la celda dice qué usar en su lugar.
De un vistazo
| v2 | v3 |
|---|---|
DnDProvider | DnDProvider, con props nuevas y un slot #overlay. |
makeDraggable | makeDraggable para el arrastre libre; makeSortableItem para las filas de una lista. |
makeDroppable | makeDroppable para destinos de drop simples; makeSortableList o makeSortableTree para listas y árboles. |
makeSelectionArea | makeSelectionArea. |
makeConstraintArea | makeDragArea. |
makeAutoScroll | Nada: el desplazamiento automático es automático. makeDragArea o el autoScroll de una lista lo ajustan. |
useDnDProvider | useDnD para el estado, useDnDMonitor para los eventos. |
injectionKey | useDnDContext() y provideDnDContext(app, context). |
DragPreview | DragPreview: ahora el clon de un ítem arrastrado, para overlays propios. |
makeSnappedOverlayPosition, <DragPreview :grid> | El modificador snapToGrid. |
e.helpers.suggestSort, suggestSwap, suggestCopy, suggestRemove | Integrados en las listas: consulta ordenación. |
El provider
| v2 | v3 |
|---|---|
autoScrollViewport, desactivado salvo que se active | autoScroll: activado por defecto, para la ventana y todo contenedor desplazable. { threshold, maxSpeed, acceleration }, o false. |
previewTo, renderizado por defecto dentro del provider | overlayTo: un selector o un elemento, 'body' por defecto. |
Slot #preview="{ preview }" | Slot #overlay, con el arrastre como props: items, active, over, accepted, status, mode, to. Consulta el overlay. |
keyboard.keys.forDrag, forDrop, forCancel (configurados en useDnDProvider()) | :keyboard="{ keys: { start, drop, cancel } }". |
keyboard.step | :keyboard="{ step }": 25 px por defecto, para arrastres libres. |
keyboard.keys.forMove, forMoveFaster, keyboard.moveFaster | Sin equivalente. Las flechas mueven; Shift hace cuatro veces más grandes los pasos de un arrastre libre. |
collision.throttle | Sin equivalente. collision elige cómo se encuentra el destino: pointerFirst (por defecto), pointerWithin, rectIntersection, closestCenter, closestCorners o una función tuya. |
| Un provider por página, nunca anidado | Anidar funciona: el provider más cercano gestiona cada ítem. Un provider para toda la app sigue siendo el objetivo. |
Props nuevas sin equivalente en la v2: activation, modifiers, sensors, animation,
dropAnimation, animateChanges, haptics, copyModifier, overlayFit, overlayClass,
overlayTransition, overlayPreviews, announcements, screenReaderInstructions y
defaultStyles. El provider también emite drag-start, drag-move, drag-over, drag-end,
drag-cancel, drag-resume y drag-settle. Todo está en la guía del provider.
Leer el estado: useDnD
useDnDProvider() | v3 |
|---|---|
state: 'dragging', 'activating', 'selecting', 'pending' o null | useDnD().status: 'idle', 'activating', 'dragging', 'dropping' o 'selecting'. 'pending' (un drop asíncrono) es 'dropping'; null es 'idle'. |
pointer.value.current | useDnD().pointer, tras los modificadores. Cada evento tiene además delta, el desplazamiento desde el inicio. |
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 | Sin equivalente. La biblioteca coloca y dimensiona el overlay. Usa overlayTo, el slot #overlay o componentes de overlay. |
entities.draggableMap, droppableMap, los visible…Set, scrollPosition | Sin equivalente. |
| — | Nuevo: useDnD().cancel(), position, isAutoScrolling. |
Los eventos pasaron a useDnDMonitor: consulta eventos y decisiones de drop.
Las formas completas están en la API de useDnD.
makeDraggable
| v2 | v3 |
|---|---|
makeDraggable(ref, options, payload) | makeDraggable(ref, options): sin tercer argumento. Las filas de una lista usan makeSortableItem(ref, { item }). |
id, necesario en listas virtuales | id: pásalo desde tus datos, como getter. El valor por defecto viene de useId() y es nuevo en cada instancia del componente. |
data: () => card | data: un valor, una ref o un getter. Se lee como active.data y active.items[i].data. |
groups: ['dev'] | type: 'dev', comparado con el accept de los destinos. Consulta grupos. |
dragHandle: '.handle' | handle: un selector o una ref de elemento. |
disabled | disabled. |
modifier: { keys, method } | canDrag: (event) => event.altKey: un filtro para cada pulsación. |
activation: { distance, delay } | activation por tipo de puntero: { mouse: { distance }, touch: { delay, tolerance }, pen }. Con distance y delay a la vez, el arrastre empieza con lo que llegue primero. |
activation.distance: { x, y, condition } | Sin equivalente. Una distance por tipo de puntero. |
render: markRaw(Preview) | overlay: Preview: el componente recibe el arrastre como props. Consulta el overlay. |
placementMargins | Sin equivalente. Un destino de drop sobre un ítem ordenable cuenta en su parte central (del 25 al 75 %); los árboles anidan con nesting: 'hover'. Consulta soltar dentro de un ítem ordenable. |
events.onSelfDragStart, onSelfDragMove, onSelfDragEnd, onSelfDragCancel | Sin equivalente en el elemento arrastrable: useDnDMonitor, o watch(isDragging). Consulta eventos. |
events.onHover, onLeave, y su onDragStart… para los arrastres que pasan por encima | Un makeDroppable en el mismo elemento: onDragEnter, onDragLeave. |
Devuelve isDragging, selected | Lo mismo. |
Devuelve isDragOver | Los ítems ordenables devuelven indicator (con placeholder: 'indicator') e isPlaceholder; los destinos devuelven placement. |
Devuelve isAllowed | Sin equivalente en los elementos arrastrables. Los destinos devuelven isAccepting. |
| — | Nuevo: label, axis, modifiers, keyboardMode, overlay: false con el transform devuelto, dropAnimation, attributes, native. |
makeDroppable
| v2 | v3 |
|---|---|
makeDroppable(ref, options, () => items) | makeDroppable(ref, options). Una zona que contiene una lista es un makeSortableList. |
groups | accept: '*', un tipo, una lista de tipos o una función. |
groupMatch: 'every', 'some' | acceptMode: 'all', 'some'. |
events.onEnter, events.onLeave | onDragEnter, onDragLeave: opciones propias, sin objeto events. |
events.onDrop, que podía devolver false o una promesa | onBeforeDrop decide, onDrop aplica. Consulta decisiones de drop. |
events.onValidate | onBeforeDrop, devolviendo los ids que se aceptan. |
data | data, que se lee como over.data. |
disabled | disabled. |
Devuelve isAllowed | isAccepting. |
Devuelve isDragOver: { top, right, bottom, left, center } | isOver (un booleano), y placement: { top, right, bottom, left }, las mitades en las que está el puntero. No hay center. |
| — | Nuevo: id, label, priority, overOverlay, overDropAnimation, external (archivos del ordenador). |
El evento de drop
IDragEvent de la v2 | v3 |
|---|---|
e.draggedItems | e.active.items: { id, item, data, label, type } cada uno, empezando por el que se agarró. Las listas reciben e.items y 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 | Sin equivalente: las listas calculan e.to.index por sí mismas. |
e.helpers.suggestSort() | Las listas escriben el drop; e.result contiene el nuevo array de cada lista que cambia. |
e.helpers.suggestSwap() | behavior: 'swap' en la lista; e.partner es el ítem con el que se intercambió. |
e.helpers.suggestCopy() | group: { name, pull: 'clone' } o la tecla de copia (Alt), con el clone de la lista; e.mode es 'copy'. |
e.helpers.suggestRemove() | e.source.remove() en un destino de drop, o el dropOutside: 'remove' de la lista. |
result.sameList | e.sameList. |
e.helpers.insertAt, removeAt, removeIndexes, swapAt, getInsertSide, isAtZoneStart, isAtZoneEnd | Sin equivalente. Las listas no los necesitan; para un destino simple, cambia tus datos como quieras. |
e.provider | useDnD(). |
| — | Nuevo: e.pointer, e.delta, e.position (dónde poner un ítem soltado sobre un tablero), e.mode. |
Ordenación
| v2 | v3 |
|---|---|
Una lista makeDroppable con () => items | makeSortableList(listRef, { items }). |
Una fila makeDraggable con () => [index, items] | makeSortableItem(el, { item: () => props.task }). |
| Renderizar tu array | Renderizar los items devueltos: el borrador que se reordena mientras arrastras. |
suggestSort('vertical'), suggestSort('horizontal') | strategy: 'vertical' (por defecto), 'horizontal' o 'grid'. |
Comprobaciones de identidad para encontrar los arrays de origen y de destino, y luego asignar sourceItems y targetItems | Nada: el drop escribe cada lista que cambia. |
<TransitionGroup> | Incluido. animateChanges: true anima los cambios que no hizo ningún arrastre (añadir, quitar, barajar). |
Una línea de inserción a partir de isDragOver.top, .bottom | placeholder: 'indicator' y el indicator.side del ítem. Consulta una línea de inserción. |
suggestSwap() | behavior: 'swap'. |
Una paleta que solo copia (suggestCopy() y un flag copyOnly) | group: { name, pull: 'clone', put: false }, sort: false y clone. |
Una papelera con suggestRemove() | makeDroppable(trash, { onDrop: (e) => e.source?.remove() }), o dropOutside: 'remove'. |
| Pinia o datos inmutables: asignar tú el resultado | items como getter, luego e.preventDefault() en onDrop y aplicar e.result. Consulta listas controladas. |
Un árbol recursivo: un droppable por carpeta y applyToTree | makeSortableTree: un solo contenedor, filas renderizadas en plano con su profundidad. Consulta árboles. |
Grupos anidados con v-model (un droppable por grupo) | Una lista dentro de una lista, todas en un mismo group, cada una con items: defineModel(). |
Listas virtuales: id en cada elemento arrastrable | Pasa el borrador al virtualizador, id: () => props.item.id en las filas. Consulta listas virtuales. |
El código de antes y después está más abajo. Todo sobre las listas está en listas ordenables y listas que comparten ítems.
Grupos
La v2 tenía una sola opción groups para todo. La v3 la divide en dos:
| v2 | v3 |
|---|---|
makeDraggable(el, { groups: ['dev'] }) | makeDraggable(el, { type: 'dev' }). |
makeDroppable(el, { groups: ['dev'] }) | makeDroppable(el, { accept: 'dev' }). |
groups: ['dev', 'ops'] en una zona | accept: ['dev', 'ops'], o una función: ({ active, items, types }) => boolean. |
groups: [], o ninguno | Sin type ni accept. Las reglas de la v2 se mantienen: un elemento arrastrable sin tipo encaja en cualquier regla, y un destino sin accept lo acepta todo. |
groupMatch: 'every', 'some' | acceptMode: 'all', 'some'. |
groups en la zona de una lista y en sus filas | group: 'board' en makeSortableList. group: { name, pull, put } dice qué puede salir y entrar. |
groups en makeSelectionArea | accept. |
Una lista sin group es un grupo propio y no intercambia con ninguna otra lista. En la v2, una zona
sin grupos lo aceptaba todo. Consulta qué arrastres acepta un destino.
El overlay
| v2 | v3 |
|---|---|
Una vista previa renderizada en el provider, teletransportada con previewTo | Siempre teletransportado, a body o a overlayTo. |
Un clon: la misma etiqueta, su innerHTML y su tamaño | Un clon profundo: los valores de los formularios, los píxeles de <canvas>, los estilos heredados y las celdas de tabla vienen incluidos. Marca lo que se queda fuera con data-dnd-no-clone. |
render: markRaw(Preview), que lee useDnDProvider().entities | overlay: Preview, que recibe items, active, over, accepted, status, mode y to como props. Sin markRaw; usa un shallowRef para cambiarlo. |
El slot #preview, posicionado a partir de preview.position | El slot #overlay: la biblioteca coloca y dimensiona la caja. Si no renderiza nada, se vuelve al clon. |
useDnDProvider().preview.render.value = … | El slot #overlay, o un shallowRef pasado como overlay. |
Un aspecto nuevo sobre una zona, desde onEnter y onLeave | overOverlay en makeDroppable o en una lista, o leer over en el componente de overlay. Consulta destinos que cambian el overlay. |
<DragPreview v-slot="{ draggingMap }"> | Los items de las props del overlay; <DragPreview :id="item.id" /> renderiza un clon. |
<DragPreview :grid>, makeSnappedOverlayPosition | El modificador snapToGrid. Ajusta a la cuadrícula el punto donde se buscan los destinos y también la position del drop, no solo la imagen. |
<Transition> o el AnimatePresence de motion alrededor de DragPreview | overlay-class con @starting-style para el agarre, dropAnimation para el aterrizaje, overlayTransition entre componentes. Consulta levantar, inclinar, asentar. |
.dnd-kit-preview | [data-dnd-overlay] ("dragging", "dropping") en la caja, [data-dnd-overlay-content] dentro de ella. |
preview.size, actualizado a mano | La caja toma el tamaño del hueco en el que aterrizará (overlayFit). |
| La vista previa desaparece al soltar | Aterriza en su hueco, o vuelve a su sitio al cancelar. |
Más en la guía del overlay.
Selección y multi-drag
| v2 | v3 |
|---|---|
selected, devuelto por makeDraggable | selected, devuelto por makeDraggable y makeSortableItem. |
entities.selectedSet, un conjunto de elementos | useDnD().selection, con ids como clave. |
makeSelectionArea: Ctrl (ControlLeft) pulsado por defecto | Sin tecla por defecto: una pulsación en el espacio vacío del área dibuja el rectángulo. modifier: { keys: ['Control'], method: 'every' } recupera Ctrl. |
modifier.keys como KeyboardEvent.code | KeyboardEvent.key ('Control', 'Shift') o .code ('ControlLeft'). |
strategy: 'toggle', 'select' | Lo mismo. |
groups | accept. |
events.onSelected(elements) | onSelected(ids). |
disabled | Lo mismo. |
suggestSort() moviendo la selección en bloque | Las listas la mueven. multiple: 'group' (por defecto), 'gaps' o 'stack' dice qué hacen los demás ítems por el camino. |
onValidate para selecciones mixtas | acceptMode: 'some' y onBeforeDrop devolviendo ids. Consulta aceptar solo algunos. |
Más en multi-drag y selección.
Ejes, límites y ajuste
| v2 | v3 |
|---|---|
makeConstraintArea(el, { axis: 'x' }) | makeDragArea(el, { axis: 'x' }), para los arrastres que empiezan dentro de ella. axis también funciona en un elemento arrastrable, una lista, un árbol o un ítem. |
axis: 'both' | Sin axis. |
restrictToArea: true | makeDragArea(el, { restrictToArea: true }), o los modificadores restrictToParent y restrictToElement(el). |
| Un provider por región, para mantener las reglas separadas | Un provider y un makeDragArea por región. Sus ítems pueden seguir moviéndose a cualquier sitio. |
<DragPreview :grid="20">, makeSnappedOverlayPosition | snapToGrid(20) o snapToGrid({ x, y, origin, smooth }), en los modifiers del provider, de un área, de una lista o de un elemento arrastrable. |
| — | Nuevo: restrictToWindowEdges, restrictToFirstScrollableAncestor, snapCenterToCursor y modificadores propios. |
Más en ejes, límites y cuadrículas.
Desplazamiento automático
| v2 | v3 |
|---|---|
<DnDProvider :autoScrollViewport="true">, desactivado por defecto | Activado por defecto, para la ventana y todo contenedor desplazable. :auto-scroll="false" lo desactiva. |
makeAutoScroll(el, { threshold, speed }) | Nada: cualquier elemento con overflow: auto o scroll se desplaza. Para ajustar uno: makeDragArea(el, { autoScroll: { … } }) o el autoScroll de la lista. |
threshold en px, o por lado | threshold: una fracción del tamaño del contenedor (0.2), entre 24 y 96 px. Sin equivalente para umbrales por lado. |
speed, un multiplicador | maxSpeed (25 px por frame) y acceleration (10). |
disabled: true | autoScroll: false. |
isScrolling | isAutoScrolling, de makeDragArea o de useDnD(). |
Teclado y accesibilidad
| v2 | v3 |
|---|---|
tabindex="0" en cada elemento arrastrable | Se pone por ti, con role, aria-roledescription, aria-describedby, aria-disabled y aria-pressed. Tus propios atributos tienen prioridad. |
:aria-grabbed="isDragging" a mano | Quítalo: aria-pressed sigue el arrastre. |
Teclas configuradas en useDnDProvider().keyboard.keys | La prop keyboard del provider. Las asignaciones coinciden con event.code o event.key. |
Las flechas mueven la vista previa keyboard.step | Arrastres libres: keyboard.step px. Listas: de hueco en hueco, y hacia la siguiente lista del grupo. keyboardMode: 'droppables' salta entre destinos. |
Reasignar las flechas (forMove, forMoveFaster) | Sin equivalente. |
| Sin anuncios | Una región viva anuncia cada agarre, movimiento, drop y cancelación. label pone nombre a las cosas; announcements y screenReaderInstructions cambian su redacción. |
Más en accesibilidad.
Eventos y decisiones de drop
| v2 | v3 |
|---|---|
events.onSelfDragStart del elemento arrastrable | useDnDMonitor({ onDragStart }), comprobando active.ids. Para una clase, basta con isDragging. |
onSelfDragMove | El onDragMove del monitor. |
onSelfDragEnd, soltado o cancelado | El onDragEnd del monitor (un drop) y onDragCancel. onDragSettle escucha ambos, una vez que el overlay ha aterrizado. |
onSelfDragCancel | El onDragCancel del monitor. |
onHover, onLeave del elemento arrastrable, y su onDragStart… para los arrastres que pasan por encima | makeDroppable en el elemento: onDragEnter, onDragLeave. Las filas de una lista no necesitan nada. |
events.onEnter, onLeave del droppable | onDragEnter, onDragLeave. |
events.onDrop del droppable | onBeforeDrop decide, luego onDrop aplica. onDrop es síncrono, y lo que devuelve se ignora. |
events.onValidate del droppable | onBeforeDrop, con acceptMode: 'some'. |
| — | Nuevo: los @drag-start … @drag-settle del provider, y los onDragOver, onBeforeDrop y onDragResume del monitor. |
Un onDrop de la v2 podía devolver un valor, o una promesa de uno, para decidir el drop. En la v3
esa es la tarea de onBeforeDrop, en listas, árboles, destinos de drop y monitores:
onDrop de la v2 devuelve | onBeforeDrop de la v3 devuelve |
|---|---|
Nada, o true: el drop se completa | 'accept', true o nada. |
false: este drop se descarta, el arrastre sigue activo | 'continue'. false ahora rechaza el drop. |
| Una promesa rechazada, o una excepción: el arrastre se cancela | 'reject' o false. Una excepción también rechaza. |
Una promesa; mientras tanto state es 'pending' | Una promesa; mientras tanto status es 'dropping' y el overlay espera con data-dnd-overlay="dropping". |
onValidate devuelve IDragItem[] | Un array de ids: solo esos se sueltan, los demás vuelven a su sitio. |
Los monitores responden primero, luego el destino. Después se ejecuta el onDrop del destino, una
lista escribe sus nuevos arrays y le sigue onDragEnd. Más en decidir un drop
y en la API de eventos.
Tipos
| v2 | v3 |
|---|---|
IDragEvent | DnDEvent (destinos, monitores), SortableDropEvent (listas), DnDEndEvent (onDragEnd). |
IDragItem | DragItem. |
IDropZoneContext | Over. |
IHoveredDraggableContext | Sin equivalente. |
ISuggestSortResult, ISuggestSwapResult, ISuggestCopyResult, ISuggestRemoveResult | SortableDropEvent: result, to, mode, partner. |
IDropValidateEvent | DnDEvent, en onBeforeDrop. |
IPlacement | Placement, sin center. |
ICoordinates | Coordinates. |
IDragActivationOptions | ActivationOptions. |
IAutoScrollOptions | AutoScrollOptions. |
TDnDState | DnDStatus. |
IDnDProviderExternal | DnDState, lo que devuelve useDnD(). |
IDraggableEvents, IDroppableEvents | Callbacks en DraggableOptions, DroppableOptions y 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 automáticamente makeDraggable, makeDroppable, makeSelectionArea, makeConstraintArea, makeAutoScroll, useDnDProvider | Importa automáticamente makeDraggable, makeDroppable, makeSortableList, makeSortableItem, makeSortableTree, makeSelectionArea, makeDragArea, useDnD, useDnDMonitor. |
| Ningún tipo importado automáticamente | Los tipos principales sí (DnDEvent, SortableDropEvent, OverlayProps…). Los genéricos (Key, Active, Over) se importan a mano. |
| — | Modificadores, funciones de colisión y sensores, a petición: vueDndKit: { autoImports: { modifiers: true, collision: true, sensors: true } }. |
DnDProvider y DragPreview globales | Lo mismo, con una opción prefix. |
<DragPreview /> en el layout, dentro del provider | Quítalo: el provider renderiza el overlay. DragPreview recibe un id y va dentro de los componentes de overlay. |
Dependencia peer @vue-dnd-kit/core 2 | Dependencia peer @vue-dnd-kit/core 3; Nuxt 4. |
modules: ['@vue-dnd-kit/nuxt'] en nuxt.config.ts se queda como estaba. Consulta
instalación.
Antes y después
Un elemento arrastrable y una 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>
- La tarjeta recibe
tabindex,roley los atributosaria-*por sí sola. Un asa<button>puede recibir el foco, y los arrastres con teclado empiezan desde ella. groupspasó a sertypeen el elemento arrastrable yaccepten el destino. Los callbacks salieron del objetoevents.isOversinisAcceptinges un destino que rechaza el arrastre: un drop ahí vuelve a su sitio.
Una lista ordenable
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>
- Renderiza
items, el borrador: se reordena mientras arrastras.tasksrecibe el nuevo orden al soltar, ydefineModelemiteupdate:modelValue. - Sin índice, sin payload, sin
onDrop, sin<TransitionGroup>: las filas se deslizan solas. - La fila arrastrada se queda en la lista como el hueco en el que aterrizará. Dale estilo con
isPlaceholdero[data-dnd-placeholder]en lugar de ocultarla.
Listas que comparten ítems
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>
El drop escribe los dos arrays, el que la tarea dejó y aquel en el que entró. TaskItem.vue es el de
la lista ordenable de arriba. Una paleta que reparte copias era suggestCopy() y un flag copyOnly;
ahora es una 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 un store de Pinia u otro estado que no escribes directamente, pasa items como getter, llama a
e.preventDefault() en onDrop y aplica e.result: consulta
listas controladas.
Un 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>
- El componente recibe el arrastre como props:
items(cada ítem arrastrado, empezando por el que se agarró),over,accepted,status,modeyto. Declara solo las que lees. Para las filas de una lista, el ítem de la lista esitems[0].item. - Pásalo tal cual. Cámbialo con un
shallowRef, nunca con unarefprofunda.
El slot del provider cambia de la misma forma, y tu componente ya no se posiciona a sí mismo:
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>
Una aparición que envolvía DragPreview en un <Transition> pasa a ser overlay-class con
@starting-style: consulta levantar, inclinar, asentar.
Un drop que confirmas
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),
});
Una lista pregunta de la misma forma, y una promesa te permite abrir un diálogo propio. El overlay espera donde se soltó hasta que la promesa se resuelve:
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)),
});
Una zona que aceptaba solo la parte coincidente de una selección mixta (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
});
Las listas lo hacen sin código: con acceptMode: 'some', aterrizan los ítems que acepta su
group.put, y los demás vuelven a su sitio.
Escollos al actualizar
- Instala la v3 con su rango.
^3enpackage.json: unnpm updatemantiene^2en la v2. - Renderiza el borrador. Una lista renderiza los
itemsque devuelvemakeSortableList, no tu array. Si renderizas tu array, nada se mueve hasta el drop. - No ordenes dos veces. Borra el código de
suggestSortde las listas: una lista escribe cada drop en una refitemsescribible después de suonDrop. Para aplicarlo tú, llama ae.preventDefault()y usae.result. falserechaza el drop. En la v2, unonDropque resolvíafalsemantenía el arrastre en curso. En la v3, devuelve'continue'desdeonBeforeDrop. Una promesa devuelta desdeonDropse ignora.- Las listas solo comparten dentro de un
group. Una lista sin él no intercambia con ninguna otra lista. - Da a las filas el id como clave.
:key="index"anima las filas equivocadas. Pasa también ids desde tus datos: un id por defecto es nuevo en cada instancia del componente. - Sin
<TransitionGroup>, sintransitionentransform. La biblioteca mueve las filas ella misma, y los movimientos se animarían dos veces. - Deshaz el apaño táctil.
touch-action: noneen un ítem entero impide que la página se desplace bajo el dedo; la v3 empieza los arrastres táctiles con una pulsación larga. Mantenlo en un asa que deba arrastrar enseguida, conactivation: { touch: { distance: 4 } }. - Quita los atributos obsoletos. La biblioteca pone el
tabindex, elroley losaria-*que necesita y nunca sobrescribe los tuyos, así que unroleo unaria-grabbedque hayan quedado se mantienen como los dejaste. - Vuelve a dar estilo al overlay.
.dnd-kit-previewya no existe: da estilo a lo que hay dentro de[data-dnd-overlay='dragging'], nunca altransformde la caja. Dalewidth: 100%; height: 100%a la raíz de un componente de overlay, y saca<DragPreview />de tus layouts. - El desplazamiento automático está activado. Pasa
:auto-scroll="false"donde la v2 nunca se desplazaba. - Un rectángulo no necesita tecla.
makeSelectionAreadibuja con una pulsación simple en su espacio vacío. Añade unmodifierpara conservar el Ctrl de la v2. - Un provider, áreas dentro. Los ítems no pueden pasar de un provider a otro. Una página que usaba
un provider por región para mantener separadas sus reglas necesita un provider y un
makeDragAreapor región. - Dos argumentos.
makeDraggableymakeDroppablereciben una ref y un objeto de opciones, sin payload.