Documentation/Prise en main

Migrer depuis la v2

Passez une application de la v2 à la v3 — ce qui a changé et pourquoi, la mise à jour, une correspondance de chaque API v2 vers son remplaçant en v3, du code avant et après pour les cas courants, et les pièges.

Modifier cette page

Cette page a été traduite par IA et peut contenir des inexactitudes. Lire l'original en anglais

La v3 est une réécriture de la v2. Le modèle reste : un provider, et des composables que vous appelez sur vos propres éléments. Mais la plupart des noms et des options ont changé, le tri est passé dans la bibliothèque, et le handler de dépôt que vous écriviez pour chaque liste disparaît. Cette page fait correspondre chaque partie de la v2 à la v3, puis montre les cas courants avant et après.

Ce qui a changé

  • Le tri est intégré. En v2, une liste était une zone de dépôt, ses lignes étaient des éléments déplaçables avec un payload [index, items], et vous triiez dans onDrop avec e.helpers.suggestSort(). En v3, makeSortableList et makeSortableItem s'en chargent : la liste rend un brouillon de votre tableau qui se réordonne pendant le glissement, et le dépôt écrit le nouveau tableau dans votre ref. La plupart des handlers de dépôt de la v2 sont supprimés, pas portés.
  • Les choses bougent toutes seules. Les lignes glissent vers leurs nouvelles places (FLIP), l'overlay atterrit sur son emplacement au dépôt et revient à sa place à l'annulation. <TransitionGroup> disparaît.
  • Toujours un seul paquet. @vue-dnd-kit/core contient les listes, les arbres, le multi-drag, les modificateurs, les capteurs et les fonctions de collision. Le module Nuxt reste ; @vue-dnd-kit/actions (glissements programmatiques) et @vue-dnd-kit/devtools sont nouveaux et optionnels.
  • Un vrai overlay. Il est téléporté dans body et montre un clone profond, à la taille de l'emplacement où il va atterrir. Les composants d'overlay reçoivent le glissement en props au lieu de lire les rouages internes du provider.
  • Des ids, pas des éléments. La sélection, les événements et l'état du glissement sont indexés par des ids issus de vos données. Il n'y a plus de maps d'éléments du DOM à lire.
  • Les types et les règles d'acceptation remplacent groups pour les éléments déplaçables simples et les cibles. Les listes échangent des items par le nom de leur group.
  • Des décisions avant le dépôt. onBeforeDrop accepte, refuse, laisse le glissement continuer ou ne prend qu'une partie des items. onDrop ne fait qu'appliquer.
  • Des capteurs. Les entrées au pointeur et au clavier sont des capteurs. L'activation se règle par type de pointeur, et les glissements tactiles commencent par un appui long, si bien que les listes continuent de défiler sous le doigt.
  • Des zones. makeDragArea remplace makeConstraintArea et makeAutoScroll, et le défilement automatique fonctionne dans chaque conteneur défilable sans code.
  • L'accessibilité est activée par défaut : attributs ARIA, un focus qui revient après le dépôt, et des annonces pour les lecteurs d'écran.

Mise à jour

npm i @vue-dnd-kit/core@^3
Paquetv2v3
@vue-dnd-kit/core2.4.x3.x
@vue-dnd-kit/nuxt1.x3.x
@vue-dnd-kit/actions—1.x, optionnel
@vue-dnd-kit/devtools—3.x, optionnel
  • Une application qui a "@vue-dnd-kit/core": "^2…" dans son package.json reste en v2 tant que la plage de versions n'indique pas ^3.
  • Vue 3.5 ou plus récent. La v2 n'avait aucune dépendance ; la v3 en a une, @vueuse/core (14 ou 15).
  • Navigateurs : Chrome et Edge 104+, Safari 15.4+, Firefox 104+.
  • La v2 et la v3 partagent un nom de paquet, donc une application passe à la v3 d'un seul coup. Il n'y a pas de couche de compatibilité. La documentation de la v2 reste disponible depuis le menu des versions en haut de la page.

Un bon ordre de travail :

  1. Mettez à jour les paquets et renommez les props du provider (plus bas).
  2. Transformez chaque liste triable en makeSortableList et makeSortableItem, et supprimez son handler de dépôt.
  3. Portez les éléments déplaçables simples et les zones de dépôt.
  4. Portez les aperçus personnalisés vers des composants d'overlay ou le slot #overlay du provider.
  5. Remplacez makeConstraintArea et makeAutoScroll par makeDragArea, ou par rien.
  6. Supprimez les contournements de la v2 : tabindex et aria-grabbed sur les éléments déplaçables, touch-action: none sur des items entiers, <TransitionGroup> autour des lignes, markRaw autour des composants d'aperçu.

De l'ancien au nouveau

Pas d'équivalent dans un tableau signifie que la v3 n'a rien de ce nom ; la cellule indique quoi utiliser à la place.

En un coup d'œil

v2v3
DnDProviderDnDProvider, avec de nouvelles props et un slot #overlay.
makeDraggablemakeDraggable pour le glissement libre ; makeSortableItem pour les lignes d'une liste.
makeDroppablemakeDroppable pour les cibles de dépôt simples ; makeSortableList ou makeSortableTree pour les listes et les arbres.
makeSelectionAreamakeSelectionArea.
makeConstraintAreamakeDragArea.
makeAutoScrollRien : le défilement automatique est automatique. makeDragArea ou l'autoScroll d'une liste le règlent.
useDnDProvideruseDnD pour l'état, useDnDMonitor pour les événements.
injectionKeyuseDnDContext() et provideDnDContext(app, context).
DragPreviewDragPreview : désormais le clone d'un item glissé, pour vos propres overlays.
makeSnappedOverlayPosition, <DragPreview :grid>Le modificateur snapToGrid.
e.helpers.suggestSort, suggestSwap, suggestCopy, suggestRemoveIntégrés aux listes : voir le tri.

Le provider

v2v3
autoScrollViewport, désactivé sauf si définiautoScroll : activé par défaut, pour la fenêtre et chaque conteneur défilable. { threshold, maxSpeed, acceleration }, ou false.
previewTo, rendu par défaut à l'intérieur du provideroverlayTo : un sélecteur ou un élément, 'body' par défaut.
Slot #preview="{ preview }"Slot #overlay, avec le glissement en props : items, active, over, accepted, status, mode, to. Voir l'overlay.
keyboard.keys.forDrag, forDrop, forCancel (définis sur useDnDProvider()):keyboard="{ keys: { start, drop, cancel } }".
keyboard.step:keyboard="{ step }" : 25 px par défaut, pour les glissements libres.
keyboard.keys.forMove, forMoveFaster, keyboard.moveFasterPas d'équivalent. Les flèches déplacent ; Shift rend les pas d'un glissement libre quatre fois plus grands.
collision.throttlePas d'équivalent. collision choisit comment la cible est trouvée : pointerFirst (par défaut), pointerWithin, rectIntersection, closestCenter, closestCorners ou une fonction à vous.
Un provider par page, jamais imbriquéL'imbrication fonctionne : le provider le plus proche gère un item. Un seul provider pour toute l'application reste l'objectif.

Nouvelles props sans équivalent en v2 : activation, modifiers, sensors, animation, dropAnimation, animateChanges, haptics, copyModifier, overlayFit, overlayClass, overlayTransition, overlayPreviews, announcements, screenReaderInstructions et defaultStyles. Le provider émet aussi drag-start, drag-move, drag-over, drag-end, drag-cancel, drag-resume et drag-settle. Tout cela se trouve dans le guide du provider.

Lire l'état : useDnD

useDnDProvider()v3
state : 'dragging', 'activating', 'selecting', 'pending' ou nulluseDnD().status : 'idle', 'activating', 'dragging', 'dropping' ou 'selecting'. 'pending' (un dépôt asynchrone) devient 'dropping' ; null devient 'idle'.
pointer.value.currentuseDnD().pointer, après les modificateurs. Chaque événement a aussi delta, le décalage depuis le début.
entities.draggingMapuseDnD().active : id, ids, items, data, type, element, rect.
entities.selectedSet (éléments)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 à 1.
preview.position, preview.size, preview.render, preview.toPas d'équivalent. La bibliothèque place et dimensionne l'overlay. Utilisez overlayTo, le slot #overlay ou des composants d'overlay.
entities.draggableMap, droppableMap, les visible…Set, scrollPositionPas d'équivalent.
—Nouveau : useDnD().cancel(), position, isAutoScrolling.

Les événements sont passés à useDnDMonitor : voir événements et décisions de dépôt. Les formes complètes sont dans l'API de useDnD.

makeDraggable

v2v3
makeDraggable(ref, options, payload)makeDraggable(ref, options) : pas de troisième argument. Les lignes d'une liste utilisent makeSortableItem(ref, { item }).
id, nécessaire dans les listes virtuellesid : passez-le depuis vos données, en getter. La valeur par défaut vient de useId() et change à chaque instance du composant.
data: () => carddata : une valeur, une ref ou un getter. Relu sous la forme active.data et active.items[i].data.
groups: ['dev']type: 'dev', comparé à l'accept des cibles. Voir les groupes.
dragHandle: '.handle'handle : un sélecteur ou une ref d'élément.
disableddisabled.
modifier: { keys, method }canDrag: (event) => event.altKey : un filtre pour chaque appui.
activation: { distance, delay }activation par type de pointeur : { mouse: { distance }, touch: { delay, tolerance }, pen }. Avec distance et delay ensemble, le glissement commence au premier des deux atteint.
activation.distance: { x, y, condition }Pas d'équivalent. Une distance par type de pointeur.
render: markRaw(Preview)overlay: Preview : le composant reçoit le glissement en props. Voir l'overlay.
placementMarginsPas d'équivalent. Une cible de dépôt sur un item triable compte dans son milieu (de 25 à 75 %) ; les arbres imbriquent avec nesting: 'hover'. Voir déposer dans un item triable.
events.onSelfDragStart, onSelfDragMove, onSelfDragEnd, onSelfDragCancelPas d'équivalent sur l'élément déplaçable : useDnDMonitor, ou watch(isDragging). Voir les événements.
events.onHover, onLeave, et son onDragStart… pour les glissements qui le survolentUn makeDroppable sur le même élément : onDragEnter, onDragLeave.
Renvoie isDragging, selectedPareil.
Renvoie isDragOverLes items triables renvoient indicator (avec placeholder: 'indicator') et isPlaceholder ; les cibles renvoient placement.
Renvoie isAllowedPas d'équivalent sur les éléments déplaçables. Les cibles renvoient isAccepting.
—Nouveau : label, axis, modifiers, keyboardMode, overlay: false avec le transform renvoyé, dropAnimation, attributes, native.

makeDroppable

v2v3
makeDroppable(ref, options, () => items)makeDroppable(ref, options). Une zone qui contient une liste est un makeSortableList.
groupsaccept : '*', un type, une liste de types ou une fonction.
groupMatch: 'every', 'some'acceptMode: 'all', 'some'.
events.onEnter, events.onLeaveonDragEnter, onDragLeave : des options à part entière, sans objet events.
events.onDrop, qui pouvait renvoyer false ou une promesseonBeforeDrop décide, onDrop applique. Voir les décisions de dépôt.
events.onValidateonBeforeDrop, qui renvoie les ids à garder.
datadata, relu sous la forme over.data.
disableddisabled.
Renvoie isAllowedisAccepting.
Renvoie isDragOver : { top, right, bottom, left, center }isOver (un booléen), et placement : { top, right, bottom, left }, les moitiés où se trouve le pointeur. Il n'y a pas de center.
—Nouveau : id, label, priority, overOverlay, overDropAnimation, external (des fichiers de l'ordinateur).

L'événement de dépôt

IDragEvent de la v2v3
e.draggedItemse.active.items : { id, item, data, label, type } pour chacun, en commençant par celui qui a été saisi. Les listes reçoivent e.items et e.keys.
e.draggedItems[0].datae.active.data.
e.draggedItems[i].index, .itemsListes : e.from, { list, index }.
e.dropZone : items, placement, datae.over : id, data, element, rect, accepted. Listes : e.to, { list, index }.
e.hoveredDraggablePas d'équivalent : les listes calculent e.to.index elles-mêmes.
e.helpers.suggestSort()Les listes écrivent le dépôt ; e.result contient le nouveau tableau de chaque liste qu'il modifie.
e.helpers.suggestSwap()behavior: 'swap' sur la liste ; e.partner est l'item avec lequel l'échange a eu lieu.
e.helpers.suggestCopy()group: { name, pull: 'clone' } ou la touche de copie (Alt), avec le clone de la liste ; e.mode vaut 'copy'.
e.helpers.suggestRemove()e.source.remove() dans une cible de dépôt, ou le dropOutside: 'remove' de la liste.
result.sameListe.sameList.
e.helpers.insertAt, removeAt, removeIndexes, swapAt, getInsertSide, isAtZoneStart, isAtZoneEndPas d'équivalent. Les listes n'en ont pas besoin ; pour une cible simple, modifiez vos données comme vous le voulez.
e.provideruseDnD().
—Nouveau : e.pointer, e.delta, e.position (où placer un item déposé sur un tableau), e.mode.

Le tri

v2v3
Une liste makeDroppable avec () => itemsmakeSortableList(listRef, { items }).
Une ligne makeDraggable avec () => [index, items]makeSortableItem(el, { item: () => props.task }).
Rendre votre tableauRendre les items renvoyés : le brouillon qui se réordonne pendant le glissement.
suggestSort('vertical'), suggestSort('horizontal')strategy: 'vertical' (par défaut), 'horizontal' ou 'grid'.
Des comparaisons d'identité pour trouver les tableaux source et cible, puis l'affectation de sourceItems et targetItemsRien : le dépôt écrit chaque liste qu'il modifie.
<TransitionGroup>Intégré. animateChanges: true anime les changements qui ne viennent pas d'un glissement (ajout, suppression, mélange).
Une ligne d'insertion tirée de isDragOver.top, .bottomplaceholder: 'indicator' et l'indicator.side de l'item. Voir une ligne d'insertion.
suggestSwap()behavior: 'swap'.
Une palette qui ne fait que copier (suggestCopy() et un drapeau copyOnly)group: { name, pull: 'clone', put: false }, sort: false et clone.
Une corbeille avec suggestRemove()makeDroppable(trash, { onDrop: (e) => e.source?.remove() }), ou dropOutside: 'remove'.
Pinia ou des données immuables : affecter le résultat vous-mêmeitems en getter, puis e.preventDefault() dans onDrop, et appliquer e.result. Voir les listes contrôlées.
Un arbre récursif : un droppable par dossier et applyToTreemakeSortableTree : un seul conteneur, des lignes rendues à plat avec leur profondeur. Voir les arbres.
Des groupes imbriqués avec v-model (un droppable par groupe)Une liste dans une liste, toutes dans un même group, chacune avec items: defineModel().
Listes virtuelles : id sur chaque élément déplaçableDonnez le brouillon au virtualiseur, id: () => props.item.id sur les lignes. Voir les listes virtuelles.

Le code avant et après se trouve plus bas. Tout sur les listes se trouve dans listes triables et listes qui partagent des items.

Les groupes

La v2 avait une seule option groups pour tout. La v3 la sépare en deux :

v2v3
makeDraggable(el, { groups: ['dev'] })makeDraggable(el, { type: 'dev' }).
makeDroppable(el, { groups: ['dev'] })makeDroppable(el, { accept: 'dev' }).
groups: ['dev', 'ops'] sur une zoneaccept: ['dev', 'ops'], ou une fonction : ({ active, items, types }) => boolean.
groups: [], ou rientype ou accept omis. Les règles de la v2 tiennent : un élément déplaçable sans type correspond à n'importe quelle règle, et une cible sans accept accepte tout.
groupMatch: 'every', 'some'acceptMode: 'all', 'some'.
groups sur la zone d'une liste et sur ses lignesgroup: 'board' sur makeSortableList. group: { name, pull, put } dit ce qui peut sortir et entrer.
groups sur makeSelectionAreaaccept.

Une liste sans group forme un groupe à elle seule et n'échange avec aucune autre liste. En v2, une zone sans groupes acceptait tout. Voir les glissements qu'une cible accepte.

L'overlay

v2v3
Un aperçu rendu dans le provider, téléporté avec previewToToujours téléporté, dans body ou dans overlayTo.
Un clone : la même balise, son innerHTML et sa tailleUn clone profond : les valeurs des formulaires, les pixels des <canvas>, les styles hérités et les cellules de tableau suivent. Marquez ce qui reste en arrière avec data-dnd-no-clone.
render: markRaw(Preview), qui lit useDnDProvider().entitiesoverlay: Preview, qui reçoit items, active, over, accepted, status, mode et to en props. Pas de markRaw ; utilisez un shallowRef pour le changer.
Le slot #preview, positionné à partir de preview.positionLe slot #overlay : la bibliothèque place et dimensionne la boîte. S'il ne rend rien, le clone est utilisé.
useDnDProvider().preview.render.value = …Le slot #overlay, ou un shallowRef passé en overlay.
Un nouvel aspect au-dessus d'une zone, depuis onEnter et onLeaveoverOverlay sur makeDroppable ou une liste, ou lire over dans le composant d'overlay. Voir cibles qui changent l'overlay.
<DragPreview v-slot="{ draggingMap }">Les items des props de l'overlay ; <DragPreview :id="item.id" /> rend un clone.
<DragPreview :grid>, makeSnappedOverlayPositionLe modificateur snapToGrid. Il aligne aussi l'endroit où les cibles sont cherchées et la position du dépôt, pas seulement l'image.
<Transition> ou l'AnimatePresence de motion autour de DragPreviewoverlay-class avec @starting-style pour la saisie, dropAnimation pour l'atterrissage, overlayTransition entre composants. Voir soulever, incliner, reposer.
.dnd-kit-preview[data-dnd-overlay] ("dragging", "dropping") sur la boîte, [data-dnd-overlay-content] à l'intérieur.
preview.size, tenu à jour à la mainLa boîte prend la taille de l'emplacement où elle va atterrir (overlayFit).
L'aperçu disparaît au dépôtIl atterrit sur son emplacement, ou revient à sa place à l'annulation.

Plus de détails dans le guide de l'overlay.

Sélection et multi-drag

v2v3
selected, renvoyé par makeDraggableselected, renvoyé par makeDraggable et makeSortableItem.
entities.selectedSet, un ensemble d'élémentsuseDnD().selection, indexé par id.
makeSelectionArea : Ctrl (ControlLeft) enfoncé par défautAucune touche par défaut : un appui sur l'espace vide de la zone trace le rectangle. modifier: { keys: ['Control'], method: 'every' } ramène Ctrl.
modifier.keys en KeyboardEvent.codeKeyboardEvent.key ('Control', 'Shift') ou .code ('ControlLeft').
strategy: 'toggle', 'select'Pareil.
groupsaccept.
events.onSelected(elements)onSelected(ids).
disabledPareil.
suggestSort() qui déplace la sélection en blocLes listes la déplacent. multiple: 'group' (par défaut), 'gaps' ou 'stack' dit ce que font les autres items en chemin.
onValidate pour les sélections mixtesacceptMode: 'some' et onBeforeDrop qui renvoie des ids. Voir n'en accepter qu'une partie.

Plus de détails dans multi-drag et sélection.

Axes, limites et alignement

v2v3
makeConstraintArea(el, { axis: 'x' })makeDragArea(el, { axis: 'x' }), pour les glissements qui commencent à l'intérieur. axis fonctionne aussi sur un élément déplaçable, une liste, un arbre ou un item.
axis: 'both'axis omis.
restrictToArea: truemakeDragArea(el, { restrictToArea: true }), ou les modificateurs restrictToParent et restrictToElement(el).
Un provider par région, pour séparer les règlesUn provider et un makeDragArea par région. Ses items peuvent toujours aller n'importe où.
<DragPreview :grid="20">, makeSnappedOverlayPositionsnapToGrid(20) ou snapToGrid({ x, y, origin, smooth }), dans les modifiers du provider, d'une zone, d'une liste ou d'un élément déplaçable.
—Nouveau : restrictToWindowEdges, restrictToFirstScrollableAncestor, snapCenterToCursor, et vos propres modificateurs.

Plus de détails dans axes, limites et grilles.

Défilement automatique

v2v3
<DnDProvider :autoScrollViewport="true">, désactivé par défautActivé par défaut, pour la fenêtre et chaque conteneur défilable. :auto-scroll="false" le désactive.
makeAutoScroll(el, { threshold, speed })Rien : tout élément en overflow: auto ou scroll défile. Pour en régler un : makeDragArea(el, { autoScroll: { … } }) ou l'autoScroll de la liste.
threshold en px, ou par côtéthreshold : une fraction de la taille du conteneur (0.2), entre 24 et 96 px. Pas d'équivalent pour les seuils par côté.
speed, un multiplicateurmaxSpeed (25 px par frame) et acceleration (10).
disabled: trueautoScroll: false.
isScrollingisAutoScrolling, depuis makeDragArea ou useDnD().

Clavier et accessibilité

v2v3
tabindex="0" sur chaque élément déplaçableDéfini pour vous, avec role, aria-roledescription, aria-describedby, aria-disabled et aria-pressed. Vos propres attributs l'emportent.
:aria-grabbed="isDragging" à la mainRetirez-le : aria-pressed suit le glissement.
Touches définies sur useDnDProvider().keyboard.keysLa prop keyboard du provider. Les raccourcis correspondent à event.code ou event.key.
Les flèches déplacent l'aperçu de keyboard.stepGlissements libres : keyboard.step px. Listes : d'emplacement en emplacement, et vers la liste suivante du groupe. keyboardMode: 'droppables' saute d'une cible à l'autre.
Réassigner les flèches (forMove, forMoveFaster)Pas d'équivalent.
Pas d'annoncesUne région live annonce chaque saisie, déplacement, dépôt et annulation. label nomme les choses ; announcements et screenReaderInstructions les reformulent.

Plus de détails dans accessibilité.

Événements et décisions de dépôt

v2v3
events.onSelfDragStart de l'élément déplaçableuseDnDMonitor({ onDragStart }), en vérifiant active.ids. Pour une classe, isDragging suffit.
onSelfDragMoveL'onDragMove du moniteur.
onSelfDragEnd, déposé ou annuléL'onDragEnd du moniteur (un dépôt) et onDragCancel. onDragSettle entend les deux, une fois l'overlay atterri.
onSelfDragCancelL'onDragCancel du moniteur.
onHover, onLeave de l'élément déplaçable, et son onDragStart… pour les glissements qui le survolentmakeDroppable sur l'élément : onDragEnter, onDragLeave. Les lignes d'une liste n'ont besoin de rien.
events.onEnter, onLeave du droppableonDragEnter, onDragLeave.
events.onDrop du droppableonBeforeDrop décide, puis onDrop applique. onDrop est synchrone, et ce qu'il renvoie est ignoré.
events.onValidate du droppableonBeforeDrop, avec acceptMode: 'some'.
—Nouveau : les @drag-start … @drag-settle du provider, et les onDragOver, onBeforeDrop et onDragResume du moniteur.

Un onDrop de la v2 pouvait renvoyer une valeur, ou une promesse de valeur, pour décider du dépôt. En v3, c'est le rôle d'onBeforeDrop, sur les listes, les arbres, les cibles de dépôt et les moniteurs :

onDrop de la v2 renvoieonBeforeDrop de la v3 renvoie
Rien, ou true : le dépôt aboutit'accept', true ou rien.
false : ce dépôt est écarté, le glissement reste actif'continue'. false refuse désormais le dépôt.
Une promesse rejetée, ou une exception : le glissement est annulé'reject' ou false. Une exception refuse aussi.
Une promesse ; pendant ce temps, state vaut 'pending'Une promesse ; pendant ce temps, status vaut 'dropping' et l'overlay attend avec data-dnd-overlay="dropping".
onValidate renvoie IDragItem[]Un tableau d'ids : seuls ceux-là sont déposés, les autres reviennent à leur place.

Les moniteurs répondent d'abord, puis la cible. Ensuite l'onDrop de la cible s'exécute, une liste écrit ses nouveaux tableaux, et onDragEnd suit. Plus de détails dans décider d'un dépôt et dans l'API des événements.

Types

v2v3
IDragEventDnDEvent (cibles, moniteurs), SortableDropEvent (listes), DnDEndEvent (onDragEnd).
IDragItemDragItem.
IDropZoneContextOver.
IHoveredDraggableContextPas d'équivalent.
ISuggestSortResult, ISuggestSwapResult, ISuggestCopyResult, ISuggestRemoveResultSortableDropEvent : result, to, mode, partner.
IDropValidateEventDnDEvent, dans onBeforeDrop.
IPlacementPlacement, sans center.
ICoordinatesCoordinates.
IDragActivationOptionsActivationOptions.
IAutoScrollOptionsAutoScrollOptions.
TDnDStateDnDStatus.
IDnDProviderExternalDnDState, ce que renvoie useDnD().
IDraggableEvents, IDroppableEventsDes callbacks dans DraggableOptions, DroppableOptions et 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)
Importe automatiquement makeDraggable, makeDroppable, makeSelectionArea, makeConstraintArea, makeAutoScroll, useDnDProviderImporte automatiquement makeDraggable, makeDroppable, makeSortableList, makeSortableItem, makeSortableTree, makeSelectionArea, makeDragArea, useDnD, useDnDMonitor.
Aucun type importé automatiquementLes principaux types le sont (DnDEvent, SortableDropEvent, OverlayProps…). Les génériques (Key, Active, Over) s'importent à la main.
—Modificateurs, fonctions de collision et capteurs sur demande : vueDndKit: { autoImports: { modifiers: true, collision: true, sensors: true } }.
DnDProvider et DragPreview globauxPareil, avec une option prefix.
<DragPreview /> dans le layout, à l'intérieur du providerRetirez-le : le provider rend l'overlay. DragPreview prend un id et a sa place dans les composants d'overlay.
Peer @vue-dnd-kit/core 2Peer @vue-dnd-kit/core 3 ; Nuxt 4.

modules: ['@vue-dnd-kit/nuxt'] dans nuxt.config.ts reste tel quel. Voir installation.

Avant et après

Un élément déplaçable et une zone de dépôt

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>
  • La carte reçoit tabindex, role et les attributs aria-* d'elle-même. Une poignée <button> peut recevoir le focus, et les glissements au clavier partent d'elle.
  • groups est devenu type sur l'élément déplaçable et accept sur la cible. Les callbacks ont quitté l'objet events.
  • isOver sans isAccepting, c'est une cible qui refuse le glissement : un dépôt à cet endroit revient à sa place.

Une liste triable

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>
  • Rendez items, le brouillon : il se réordonne pendant le glissement. tasks reçoit le nouvel ordre au dépôt, et defineModel émet update:modelValue.
  • Pas d'index, pas de payload, pas d'onDrop, pas de <TransitionGroup> : les lignes glissent toutes seules.
  • La ligne glissée reste dans la liste, comme l'emplacement où elle va atterrir. Stylez-la avec isPlaceholder ou [data-dnd-placeholder] au lieu de la masquer.

Listes qui partagent des items

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>

Le dépôt écrit les deux tableaux, celui que la tâche a quitté et celui où elle est entrée. TaskItem.vue est celui de la liste triable ci-dessus. Une palette qui distribue des copies, c'était suggestCopy() et un drapeau copyOnly ; désormais, c'est une liste :

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() }),
});

Pour un store Pinia ou un autre état que vous n'écrivez pas directement, passez items en getter, appelez e.preventDefault() dans onDrop et appliquez e.result : voir les listes contrôlées.

Un overlay personnalisé

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>
  • Le composant reçoit le glissement en props : items (chaque item glissé, en commençant par celui qui a été saisi), over, accepted, status, mode et to. Ne déclarez que celles que vous lisez. Pour les lignes d'une liste, l'item de la liste est items[0].item.
  • Passez-le tel quel. Changez-le avec un shallowRef, jamais avec une ref profonde.

Le slot du provider change de la même façon, et votre composant ne se positionne plus lui-même :

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>

Une apparition qui enveloppait DragPreview dans une <Transition> devient overlay-class avec @starting-style : voir soulever, incliner, reposer.

Un dépôt que vous confirmez

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),
});

Une liste demande de la même façon, et une promesse vous permet d'ouvrir votre propre boîte de dialogue. L'overlay attend là où il a été lâché jusqu'à ce que la promesse soit réglée :

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)),
});

Une zone qui ne prenait que la partie correspondante d'une sélection mixte (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
});

Les listes le font sans code : avec acceptMode: 'some', les items que leur group.put accepte atterrissent, et les autres reviennent à leur place.

Les pièges de la mise à jour

  • Installez la v3 avec sa plage de versions. ^3 dans package.json : un npm update garde ^2 en v2.
  • Rendez le brouillon. Une liste rend les items que renvoie makeSortableList, pas votre tableau. Si vous rendez votre tableau, rien ne bouge avant le dépôt.
  • Ne triez pas deux fois. Supprimez le code suggestSort des listes : une liste écrit chaque dépôt dans une ref items modifiable après son onDrop. Pour l'appliquer vous-même, appelez e.preventDefault() et utilisez e.result.
  • false refuse le dépôt. En v2, un onDrop qui résolvait false laissait le glissement continuer. En v3, renvoyez 'continue' depuis onBeforeDrop. Une promesse renvoyée par onDrop est ignorée.
  • Les listes ne partagent qu'au sein d'un group. Une liste sans group n'échange avec aucune autre liste.
  • Indexez les lignes par id. :key="index" anime les mauvaises lignes. Passez aussi des ids issus de vos données : un id par défaut change à chaque instance du composant.
  • Pas de <TransitionGroup>, pas de transition sur transform. La bibliothèque déplace les lignes elle-même, et les déplacements seraient animés deux fois.
  • Défaites le contournement tactile. touch-action: none sur un item entier empêche la page de défiler sous le doigt ; la v3 démarre plutôt les glissements tactiles par un appui long. Gardez-le sur une poignée qui doit glisser tout de suite, avec activation: { touch: { distance: 4 } }.
  • Retirez les attributs périmés. La bibliothèque définit le tabindex, le role et les aria-* dont elle a besoin et n'écrase jamais les vôtres, donc un role ou un aria-grabbed restant reste tel que vous l'avez laissé.
  • Refaites le style de l'overlay. .dnd-kit-preview n'existe plus : stylez ce qui se trouve dans [data-dnd-overlay='dragging'], jamais le transform de la boîte. Donnez width: 100%; height: 100% à la racine d'un composant d'overlay, et retirez <DragPreview /> de vos layouts.
  • Le défilement automatique est activé. Passez :auto-scroll="false" là où la v2 ne défilait jamais.
  • Un rectangle de sélection n'a pas besoin de touche. makeSelectionArea trace à partir d'un simple appui sur son espace vide. Ajoutez un modifier pour garder le Ctrl de la v2.
  • Un provider, des zones à l'intérieur. Les items ne peuvent pas passer d'un provider à l'autre. Une page qui utilisait un provider par région pour séparer leurs règles a besoin d'un provider et d'un makeDragArea par région.
  • Deux arguments. makeDraggable et makeDroppable prennent une ref et un objet d'options, pas de payload.