Dokumentation/Erste Schritte

Migration von v2

Bring eine v2-App auf v3 – was sich geändert hat und warum, das Upgrade, eine Zuordnung jeder v2-API zu ihrem Ersatz in v3, Code vorher und nachher für die häufigen Fälle und die Stolperfallen.

Diese Seite wurde von KI übersetzt und kann Ungenauigkeiten enthalten. Englisches Original lesen

v3 ist eine Neufassung von v2. Das Modell bleibt: ein Provider und Composables, die du auf deinen eigenen Elementen aufrufst. Aber die meisten Namen und Optionen haben sich geändert, das Sortieren ist in die Bibliothek gewandert, und der Drop-Handler, den du für jede Liste geschrieben hast, fällt weg. Diese Seite ordnet jeden Teil von v2 seinem Gegenstück in v3 zu und zeigt dann die häufigen Fälle vorher und nachher.

Was sich geändert hat

  • Sortieren ist eingebaut. In v2 war eine Liste eine Drop-Zone, ihre Zeilen waren ziehbare Elemente mit einem Payload [index, items], und du hast in onDrop mit e.helpers.suggestSort() sortiert. In v3 erledigen das makeSortableList und makeSortableItem: Die Liste rendert einen Entwurf deines Arrays, der sich während des Ziehens neu ordnet, und der Drop schreibt das neue Array in dein Ref. Die meisten v2-Drop-Handler werden gelöscht, nicht portiert.
  • Dinge bewegen sich von selbst. Zeilen gleiten an ihre neuen Plätze (FLIP), das Overlay landet beim Drop in seinem Slot und fliegt beim Abbrechen zurück. <TransitionGroup> fällt weg.
  • Weiterhin ein Paket. @vue-dnd-kit/core enthält Listen, Bäume, Multi-Drag, Modifier, Sensoren und die Kollisionsfunktionen. Das Nuxt-Modul bleibt; @vue-dnd-kit/actions (programmatisches Ziehen) und @vue-dnd-kit/devtools sind neu und optional.
  • Ein echtes Overlay. Es wird nach body teleportiert und zeigt einen tiefen Klon, so groß wie der Slot, in dem es landen wird. Overlay-Komponenten bekommen den Drag als Props, statt die Interna des Providers zu lesen.
  • IDs statt Elemente. Die Auswahl, die Events und der Zustand des Drags sind nach IDs aus deinen Daten geschlüsselt. Es gibt keine Maps von DOM-Elementen mehr zu lesen.
  • Typen und Annahmeregeln ersetzen groups für einfache ziehbare Elemente und Ziele. Listen tauschen Elemente über den Namen ihrer group aus.
  • Entscheidungen vor dem Drop. onBeforeDrop nimmt an, lehnt ab, lässt den Drag weiterlaufen oder nimmt nur einen Teil der Elemente. onDrop wendet nur noch an.
  • Sensoren. Zeiger- und Tastatureingaben sind Sensoren. Die Aktivierung wird pro Zeigerart festgelegt, und Touch-Drags beginnen mit langem Drücken, sodass Listen unter dem Finger weiter scrollen.
  • Bereiche. makeDragArea ersetzt makeConstraintArea und makeAutoScroll, und Auto-Scroll funktioniert ohne Code in jedem Scroll-Container.
  • Barrierefreiheit ist standardmäßig an: ARIA-Attribute, ein Fokus, der nach dem Drop zurückkehrt, und Ansagen für Screenreader.

Upgrade

npm i @vue-dnd-kit/core@^3
Paketv2v3
@vue-dnd-kit/core2.4.x3.x
@vue-dnd-kit/nuxt1.x3.x
@vue-dnd-kit/actions—1.x, optional
@vue-dnd-kit/devtools—3.x, optional
  • Eine App, die "@vue-dnd-kit/core": "^2…" in ihrer package.json hat, bleibt auf v2, bis der Versionsbereich ^3 sagt.
  • Vue 3.5 oder neuer. v2 hatte keine Abhängigkeiten; v3 hat eine, @vueuse/core (14 oder 15).
  • Browser: Chrome und Edge 104+, Safari 15.4+, Firefox 104+.
  • v2 und v3 teilen sich einen Paketnamen, also wechselt eine App in einem Schritt auf v3. Es gibt keine Kompatibilitätsschicht. Die v2-Dokumentation bleibt über das Versionsmenü oben auf der Seite erreichbar.

Eine gute Reihenfolge:

  1. Aktualisiere die Pakete und benenne die Props des Providers um (unten).
  2. Mach aus jeder sortierbaren Liste makeSortableList und makeSortableItem, und lösche ihren Drop-Handler.
  3. Portiere die einfachen ziehbaren Elemente und Drop-Zonen.
  4. Portiere eigene Vorschauen auf Overlay-Komponenten oder den Slot #overlay des Providers.
  5. Ersetze makeConstraintArea und makeAutoScroll durch makeDragArea oder durch nichts.
  6. Lösche die v2-Workarounds: tabindex und aria-grabbed auf ziehbaren Elementen, touch-action: none auf ganzen Elementen, <TransitionGroup> um Zeilen, markRaw um Vorschau-Komponenten.

Alt zu neu

Keine Entsprechung in einer Tabelle heißt, dass v3 nichts unter diesem Namen hat; die Zelle sagt, was du stattdessen verwendest.

Auf einen Blick

v2v3
DnDProviderDnDProvider, mit neuen Props und einem Slot #overlay.
makeDraggablemakeDraggable für freies Ziehen; makeSortableItem für die Zeilen einer Liste.
makeDroppablemakeDroppable für einfache Drop-Ziele; makeSortableList oder makeSortableTree für Listen und Bäume.
makeSelectionAreamakeSelectionArea.
makeConstraintAreamakeDragArea.
makeAutoScrollNichts: Auto-Scroll läuft automatisch. makeDragArea oder das autoScroll einer Liste stimmen es ab.
useDnDProvideruseDnD für den Zustand, useDnDMonitor für die Events.
injectionKeyuseDnDContext() und provideDnDContext(app, context).
DragPreviewDragPreview: jetzt der Klon eines gezogenen Elements, für eigene Overlays.
makeSnappedOverlayPosition, <DragPreview :grid>Der Modifier snapToGrid.
e.helpers.suggestSort, suggestSwap, suggestCopy, suggestRemoveIn Listen eingebaut: siehe Sortieren.

Der Provider

v2v3
autoScrollViewport, aus, sofern nicht gesetztautoScroll: standardmäßig an, für das Fenster und jeden Scroll-Container. { threshold, maxSpeed, acceleration } oder false.
previewTo, standardmäßig im Provider gerendertoverlayTo: ein Selektor oder ein Element, standardmäßig 'body'.
Slot #preview="{ preview }"Slot #overlay, mit dem Drag als Props: items, active, over, accepted, status, mode, to. Siehe das Overlay.
keyboard.keys.forDrag, forDrop, forCancel (in useDnDProvider() gesetzt):keyboard="{ keys: { start, drop, cancel } }".
keyboard.step:keyboard="{ step }": standardmäßig 25 px, für freie Drags.
keyboard.keys.forMove, forMoveFaster, keyboard.moveFasterKeine Entsprechung. Die Pfeiltasten bewegen; Shift macht die Schritte eines freien Drags viermal so groß.
collision.throttleKeine Entsprechung. collision wählt, wie das Ziel gefunden wird: pointerFirst (Standard), pointerWithin, rectIntersection, closestCenter, closestCorners oder eine eigene Funktion.
Ein Provider pro Seite, nie verschachteltVerschachteln funktioniert: Der nächstgelegene Provider verantwortet ein Element. Ein Provider für die ganze App bleibt das Ziel.

Neue Props ohne Gegenstück in v2: activation, modifiers, sensors, animation, dropAnimation, animateChanges, haptics, copyModifier, overlayFit, overlayClass, overlayTransition, overlayPreviews, announcements, screenReaderInstructions und defaultStyles. Der Provider emittiert außerdem drag-start, drag-move, drag-over, drag-end, drag-cancel, drag-resume und drag-settle. Alles davon steht im Leitfaden Der Provider.

Den Zustand lesen: useDnD

useDnDProvider()v3
state: 'dragging', 'activating', 'selecting', 'pending' oder nulluseDnD().status: 'idle', 'activating', 'dragging', 'dropping' oder 'selecting'. 'pending' (ein asynchroner Drop) ist 'dropping'; null ist 'idle'.
pointer.value.currentuseDnD().pointer, nach den Modifiern. Jedes Event hat außerdem delta, den Versatz seit dem Start.
entities.draggingMapuseDnD().active: id, ids, items, data, type, element, rect.
entities.selectedSet (Elemente)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, von 0 bis 1.
preview.position, preview.size, preview.render, preview.toKeine Entsprechung. Die Bibliothek platziert das Overlay und bestimmt seine Größe. Verwende overlayTo, den Slot #overlay oder Overlay-Komponenten.
entities.draggableMap, droppableMap, die visible…Sets, scrollPositionKeine Entsprechung.
—Neu: useDnD().cancel(), position, isAutoScrolling.

Die Events sind zu useDnDMonitor gewandert: siehe Events und Drop-Entscheidungen. Die vollständigen Formen stehen in der useDnD-API.

makeDraggable

v2v3
makeDraggable(ref, options, payload)makeDraggable(ref, options): kein drittes Argument. Die Zeilen einer Liste verwenden makeSortableItem(ref, { item }).
id, in virtuellen Listen nötigid: Übergib sie aus deinen Daten, als Getter. Der Standardwert kommt aus useId() und ist für jede Komponenteninstanz neu.
data: () => carddata: ein Wert, ein Ref oder ein Getter. Zurückgelesen als active.data und active.items[i].data.
groups: ['dev']type: 'dev', abgeglichen mit dem accept der Ziele. Siehe Gruppen.
dragHandle: '.handle'handle: ein Selektor oder ein Element-Ref.
disableddisabled.
modifier: { keys, method }canDrag: (event) => event.altKey: eine Schranke für jedes Drücken.
activation: { distance, delay }activation pro Zeigerart: { mouse: { distance }, touch: { delay, tolerance }, pen }. Mit distance und delay zusammen startet der Drag bei dem, was zuerst eintritt.
activation.distance: { x, y, condition }Keine Entsprechung. Eine distance pro Zeigerart.
render: markRaw(Preview)overlay: Preview: Die Komponente bekommt den Drag als Props. Siehe das Overlay.
placementMarginsKeine Entsprechung. Ein Drop-Ziel auf einem sortierbaren Element zählt in seiner Mitte (25 bis 75 %); Bäume verschachteln mit nesting: 'hover'. Siehe In ein sortierbares Element ablegen.
events.onSelfDragStart, onSelfDragMove, onSelfDragEnd, onSelfDragCancelKeine Entsprechung am ziehbaren Element: useDnDMonitor oder watch(isDragging). Siehe Events.
events.onHover, onLeave und sein onDragStart… für Drags darüberEin makeDroppable auf demselben Element: onDragEnter, onDragLeave.
Gibt isDragging, selected zurückDasselbe.
Gibt isDragOver zurückSortierbare Elemente geben indicator (mit placeholder: 'indicator') und isPlaceholder zurück; Ziele geben placement zurück.
Gibt isAllowed zurückKeine Entsprechung bei ziehbaren Elementen. Ziele geben isAccepting zurück.
—Neu: label, axis, modifiers, keyboardMode, overlay: false mit dem zurückgegebenen transform, dropAnimation, attributes, native.

makeDroppable

v2v3
makeDroppable(ref, options, () => items)makeDroppable(ref, options). Eine Zone, die eine Liste enthält, ist ein makeSortableList.
groupsaccept: '*', ein Typ, eine Liste von Typen oder eine Funktion.
groupMatch: 'every', 'some'acceptMode: 'all', 'some'.
events.onEnter, events.onLeaveonDragEnter, onDragLeave: eigene Optionen, kein events-Objekt.
events.onDrop, das false oder ein Promise zurückgeben konnteonBeforeDrop entscheidet, onDrop wendet an. Siehe Drop-Entscheidungen.
events.onValidateonBeforeDrop, das die IDs zurückgibt, die bleiben sollen.
datadata, zurückgelesen als over.data.
disableddisabled.
Gibt isAllowed zurückisAccepting.
Gibt isDragOver zurück: { top, right, bottom, left, center }isOver (ein Boolean) und placement: { top, right, bottom, left }, die Hälften, in denen der Zeiger ist. Es gibt kein center.
—Neu: id, label, priority, overOverlay, overDropAnimation, external (Dateien vom Computer).

Das Drop-Event

v2 IDragEventv3
e.draggedItemse.active.items: jeweils { id, item, data, label, type }, das gegriffene zuerst. Listen bekommen e.items und e.keys.
e.draggedItems[0].datae.active.data.
e.draggedItems[i].index, .itemsListen: e.from, { list, index }.
e.dropZone: items, placement, datae.over: id, data, element, rect, accepted. Listen: e.to, { list, index }.
e.hoveredDraggableKeine Entsprechung: Listen ermitteln e.to.index selbst.
e.helpers.suggestSort()Listen schreiben den Drop; e.result enthält das neue Array jeder Liste, die er ändert.
e.helpers.suggestSwap()behavior: 'swap' auf der Liste; e.partner ist das Element, mit dem getauscht wurde.
e.helpers.suggestCopy()group: { name, pull: 'clone' } oder die Kopiertaste (Alt), mit dem clone der Liste; e.mode ist 'copy'.
e.helpers.suggestRemove()e.source.remove() in einem Drop-Ziel oder dropOutside: 'remove' der Liste.
result.sameListe.sameList.
e.helpers.insertAt, removeAt, removeIndexes, swapAt, getInsertSide, isAtZoneStart, isAtZoneEndKeine Entsprechung. Listen brauchen sie nicht; bei einem einfachen Ziel änderst du deine Daten, wie du willst.
e.provideruseDnD().
—Neu: e.pointer, e.delta, e.position (wohin ein auf einem Board abgelegtes Element kommt), e.mode.

Sortieren

v2v3
Eine makeDroppable-Liste mit () => itemsmakeSortableList(listRef, { items }).
Eine makeDraggable-Zeile mit () => [index, items]makeSortableItem(el, { item: () => props.task }).
Dein Array rendernDie zurückgegebenen items rendern: den Entwurf, der sich während des Ziehens neu ordnet.
suggestSort('vertical'), suggestSort('horizontal')strategy: 'vertical' (Standard), 'horizontal' oder 'grid'.
Identitätsprüfungen, um Quell- und Ziel-Array zu finden, dann sourceItems und targetItems zuweisenNichts: Der Drop schreibt jede Liste, die er ändert.
<TransitionGroup>Eingebaut. animateChanges: true animiert Änderungen, die kein Drag ausgelöst hat (Hinzufügen, Entfernen, Mischen).
Eine Einfügelinie aus isDragOver.top, .bottomplaceholder: 'indicator' und indicator.side des Elements. Siehe Eine Einfügelinie.
suggestSwap()behavior: 'swap'.
Eine Palette, die nur kopiert (suggestCopy() und ein Flag copyOnly)group: { name, pull: 'clone', put: false }, sort: false und clone.
Ein Papierkorb mit suggestRemove()makeDroppable(trash, { onDrop: (e) => e.source?.remove() }) oder dropOutside: 'remove'.
Pinia oder unveränderliche Daten: das Ergebnis selbst zuweisenitems als Getter, dann e.preventDefault() in onDrop und e.result anwenden. Siehe Kontrollierte Listen.
Ein rekursiver Baum: ein Droppable pro Ordner und applyToTreemakeSortableTree: ein Container, Zeilen flach mit ihrer Tiefe gerendert. Siehe Bäume.
Verschachtelte Gruppen mit v-model (ein Droppable pro Gruppe)Eine Liste in einer Liste, alle in einer group, jede mit items: defineModel().
Virtuelle Listen: id auf jedem ziehbaren ElementGib den Entwurf an den Virtualizer, id: () => props.item.id auf den Zeilen. Siehe Virtuelle Listen.

Der Code vorher und nachher steht unten. Alles über Listen steht in Sortierbare Listen und Listen mit gemeinsamen Elementen.

Gruppen

v2 hatte eine Option groups für alles. v3 teilt sie in zwei:

v2v3
makeDraggable(el, { groups: ['dev'] })makeDraggable(el, { type: 'dev' }).
makeDroppable(el, { groups: ['dev'] })makeDroppable(el, { accept: 'dev' }).
groups: ['dev', 'ops'] auf einer Zoneaccept: ['dev', 'ops'] oder eine Funktion: ({ active, items, types }) => boolean.
groups: [] oder keinetype oder accept weggelassen. Die Regeln von v2 gelten: Ein ziehbares Element ohne Typ passt zu jeder Regel, und ein Ziel ohne accept nimmt alles an.
groupMatch: 'every', 'some'acceptMode: 'all', 'some'.
groups auf der Zone einer Liste und ihren Zeilengroup: 'board' auf makeSortableList. group: { name, pull, put } sagt, was hinaus- und hineindarf.
groups auf makeSelectionAreaaccept.

Eine Liste ohne group ist eine eigene Gruppe und tauscht mit keiner anderen Liste. In v2 nahm eine Zone ohne Gruppen alles an. Siehe Welche Drags ein Ziel annimmt.

Das Overlay

v2v3
Eine Vorschau, im Provider gerendert, mit previewTo teleportiertImmer teleportiert, nach body oder nach overlayTo.
Ein Klon: dasselbe Tag, sein innerHTML und seine GrößeEin tiefer Klon: Formularwerte, <canvas>-Pixel, geerbte Styles und Tabellenzellen kommen mit. Markiere, was zurückbleibt, mit data-dnd-no-clone.
render: markRaw(Preview), das useDnDProvider().entities liestoverlay: Preview, das items, active, over, accepted, status, mode und to als Props bekommt. Kein markRaw; nimm ein shallowRef, um es zu wechseln.
Der Slot #preview, positioniert anhand von preview.positionDer Slot #overlay: Die Bibliothek platziert die Box und bestimmt ihre Größe. Rendert er nichts, kommt der Klon zum Einsatz.
useDnDProvider().preview.render.value = …Der Slot #overlay oder ein shallowRef, als overlay übergeben.
Ein neues Aussehen über einer Zone, aus onEnter und onLeaveoverOverlay auf makeDroppable oder einer Liste, oder over in der Overlay-Komponente lesen. Siehe Ziele, die das Overlay ändern.
<DragPreview v-slot="{ draggingMap }">Die items der Overlay-Props; <DragPreview :id="item.id" /> rendert einen Klon.
<DragPreview :grid>, makeSnappedOverlayPositionDer Modifier snapToGrid. Er rastet auch die Stelle ein, an der Ziele gesucht werden, und die position des Drops, nicht nur das Bild.
<Transition> oder AnimatePresence von motion um DragPreviewoverlay-class mit @starting-style für das Aufnehmen, dropAnimation für das Landen, overlayTransition zwischen Komponenten. Siehe Das Overlay: anheben, neigen, ablegen.
.dnd-kit-preview[data-dnd-overlay] ("dragging", "dropping") auf der Box, [data-dnd-overlay-content] darin.
preview.size, von Hand aktuell gehaltenDie Box nimmt die Größe des Slots an, in dem sie landen wird (overlayFit).
Die Vorschau verschwindet beim DropSie landet in ihrem Slot oder fliegt beim Abbrechen zurück.

Mehr im Leitfaden Das Overlay.

Auswahl und Multi-Drag

v2v3
selected, von makeDraggable zurückgegebenselected, von makeDraggable und makeSortableItem zurückgegeben.
entities.selectedSet, ein Set von ElementenuseDnD().selection, nach ID geschlüsselt.
makeSelectionArea: standardmäßig Strg (ControlLeft) gedrücktStandardmäßig keine Taste: Ein Druck auf eine leere Stelle des Bereichs zeichnet den Rahmen. modifier: { keys: ['Control'], method: 'every' } bringt Strg zurück.
modifier.keys als KeyboardEvent.codeKeyboardEvent.key ('Control', 'Shift') oder .code ('ControlLeft').
strategy: 'toggle', 'select'Dasselbe.
groupsaccept.
events.onSelected(elements)onSelected(ids).
disabledDasselbe.
suggestSort(), das die Auswahl als Block verschiebtListen verschieben sie. multiple: 'group' (Standard), 'gaps' oder 'stack' sagt, was die anderen Elemente unterwegs tun.
onValidate für gemischte AuswahlenacceptMode: 'some' und onBeforeDrop, das IDs zurückgibt. Siehe Nur einige annehmen.

Mehr in Multi-Drag und Auswahl.

Achsen, Grenzen und Einrasten

v2v3
makeConstraintArea(el, { axis: 'x' })makeDragArea(el, { axis: 'x' }), für die Drags, die darin beginnen. axis funktioniert auch auf einem ziehbaren Element, einer Liste, einem Baum oder einem Element.
axis: 'both'axis weggelassen.
restrictToArea: truemakeDragArea(el, { restrictToArea: true }) oder die Modifier restrictToParent und restrictToElement(el).
Ein Provider pro Region, um Regeln getrennt zu haltenEin Provider und ein makeDragArea pro Region. Ihre Elemente können sich trotzdem überallhin bewegen.
<DragPreview :grid="20">, makeSnappedOverlayPositionsnapToGrid(20) oder snapToGrid({ x, y, origin, smooth }), in modifiers auf dem Provider, einem Bereich, einer Liste oder einem ziehbaren Element.
—Neu: restrictToWindowEdges, restrictToFirstScrollableAncestor, snapCenterToCursor und eigene Modifier.

Mehr in Achsen, Grenzen und Raster.

Auto-Scroll

v2v3
<DnDProvider :autoScrollViewport="true">, standardmäßig ausStandardmäßig an, für das Fenster und jeden Scroll-Container. :auto-scroll="false" schaltet es ab.
makeAutoScroll(el, { threshold, speed })Nichts: Jedes Element mit overflow: auto oder scroll scrollt. Um eines abzustimmen: makeDragArea(el, { autoScroll: { … } }) oder das autoScroll der Liste.
threshold in px oder pro Seitethreshold: ein Bruchteil der Containergröße (0.2), zwischen 24 und 96 px. Keine Entsprechung für Schwellen pro Seite.
speed, ein MultiplikatormaxSpeed (25 px pro Frame) und acceleration (10).
disabled: trueautoScroll: false.
isScrollingisAutoScrolling, aus makeDragArea oder useDnD().

Tastatur und Barrierefreiheit

v2v3
tabindex="0" auf jedem ziehbaren ElementWird für dich gesetzt, mit role, aria-roledescription, aria-describedby, aria-disabled und aria-pressed. Deine eigenen Attribute haben Vorrang.
:aria-grabbed="isDragging" von HandEntferne es: aria-pressed folgt dem Drag.
Tasten in useDnDProvider().keyboard.keys gesetztDie Prop keyboard des Providers. Tastenbelegungen passen auf event.code oder event.key.
Die Pfeiltasten bewegen die Vorschau um keyboard.stepFreie Drags: keyboard.step px. Listen: von Slot zu Slot und in die nächste Liste der Gruppe. keyboardMode: 'droppables' springt zwischen Zielen.
Die Pfeiltasten umbelegen (forMove, forMoveFaster)Keine Entsprechung.
Keine AnsagenEine Live-Region sagt jedes Aufnehmen, Bewegen, Ablegen und Abbrechen an. label benennt die Dinge; announcements und screenReaderInstructions formulieren sie um.

Mehr in Barrierefreiheit.

Events und Drop-Entscheidungen

v2v3
events.onSelfDragStart des ziehbaren ElementsuseDnDMonitor({ onDragStart }), mit Prüfung von active.ids. Für eine Klasse genügt isDragging.
onSelfDragMoveonDragMove des Monitors.
onSelfDragEnd, abgelegt oder abgebrochenonDragEnd des Monitors (ein Drop) und onDragCancel. onDragSettle hört beides, sobald das Overlay gelandet ist.
onSelfDragCancelonDragCancel des Monitors.
onHover, onLeave des ziehbaren Elements und sein onDragStart… für Drags darübermakeDroppable auf dem Element: onDragEnter, onDragLeave. Zeilen einer Liste brauchen nichts.
events.onEnter, onLeave des DroppablesonDragEnter, onDragLeave.
events.onDrop des DroppablesonBeforeDrop entscheidet, dann wendet onDrop an. onDrop ist synchron, und sein Rückgabewert wird ignoriert.
events.onValidate des DroppablesonBeforeDrop, mit acceptMode: 'some'.
—Neu: @drag-start … @drag-settle des Providers sowie onDragOver, onBeforeDrop und onDragResume des Monitors.

Ein v2-onDrop konnte einen Wert oder ein Promise darauf zurückgeben, um über den Drop zu entscheiden. In v3 ist das die Aufgabe von onBeforeDrop, auf Listen, Bäumen, Drop-Zielen und Monitoren:

v2 onDrop gibt zurückv3 onBeforeDrop gibt zurück
Nichts oder true: Der Drop geht durch'accept', true oder nichts.
false: Dieser Drop wird ausgeschlagen, der Drag bleibt aktiv'continue'. false lehnt den Drop jetzt ab.
Ein abgelehntes Promise oder ein Throw: Der Drag wird abgebrochen'reject' oder false. Ein Throw lehnt ebenfalls ab.
Ein Promise; währenddessen ist state 'pending'Ein Promise; währenddessen ist status 'dropping', und das Overlay wartet mit data-dnd-overlay="dropping".
onValidate gibt IDragItem[] zurückEin Array von IDs: Nur diese werden abgelegt, die anderen fliegen zurück.

Die Monitore antworten zuerst, dann das Ziel. Danach läuft das onDrop des Ziels, eine Liste schreibt ihre neuen Arrays, und onDragEnd folgt. Mehr in Über einen Drop entscheiden und in der Events-API.

Typen

v2v3
IDragEventDnDEvent (Ziele, Monitore), SortableDropEvent (Listen), DnDEndEvent (onDragEnd).
IDragItemDragItem.
IDropZoneContextOver.
IHoveredDraggableContextKeine Entsprechung.
ISuggestSortResult, ISuggestSwapResult, ISuggestCopyResult, ISuggestRemoveResultSortableDropEvent: result, to, mode, partner.
IDropValidateEventDnDEvent, in onBeforeDrop.
IPlacementPlacement, ohne center.
ICoordinatesCoordinates.
IDragActivationOptionsActivationOptions.
IAutoScrollOptionsAutoScrollOptions.
TDnDStateDnDStatus.
IDnDProviderExternalDnDState, was useDnD() zurückgibt.
IDraggableEvents, IDroppableEventsCallbacks in DraggableOptions, DroppableOptions und 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)
Importiert automatisch makeDraggable, makeDroppable, makeSelectionArea, makeConstraintArea, makeAutoScroll, useDnDProviderImportiert automatisch makeDraggable, makeDroppable, makeSortableList, makeSortableItem, makeSortableTree, makeSelectionArea, makeDragArea, useDnD, useDnDMonitor.
Keine Typen automatisch importiertDie wichtigsten Typen schon (DnDEvent, SortableDropEvent, OverlayProps…). Generische (Key, Active, Over) werden von Hand importiert.
—Modifier, Kollisionsfunktionen und Sensoren auf Wunsch: vueDndKit: { autoImports: { modifiers: true, collision: true, sensors: true } }.
DnDProvider und DragPreview globalDasselbe, mit einer Option prefix.
<DragPreview /> im Layout, innerhalb des ProvidersEntferne es: Der Provider rendert das Overlay. DragPreview nimmt eine id und gehört in Overlay-Komponenten.
Peer @vue-dnd-kit/core 2Peer @vue-dnd-kit/core 3; Nuxt 4.

modules: ['@vue-dnd-kit/nuxt'] in nuxt.config.ts bleibt, wie es war. Siehe Installation.

Vorher und nachher

Ein ziehbares Element und eine Drop-Zone

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>
  • Die Karte bekommt tabindex, role und die aria-*-Attribute von selbst. Ein <button> als Handle ist fokussierbar, und Drags per Tastatur starten von ihm aus.
  • Aus groups wurde type auf dem ziehbaren Element und accept auf dem Ziel. Die Callbacks haben das events-Objekt verlassen.
  • isOver ohne isAccepting ist ein Ziel, das den Drag ablehnt: Ein Drop dort fliegt zurück.

Eine sortierbare Liste

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>
  • Rendere items, den Entwurf: Er ordnet sich während des Ziehens neu. tasks bekommt beim Drop die neue Reihenfolge, und defineModel emittiert update:modelValue.
  • Kein Index, kein Payload, kein onDrop, kein <TransitionGroup>: Die Zeilen gleiten von selbst.
  • Die gezogene Zeile bleibt als der Slot, in dem sie landen wird, in der Liste. Style sie mit isPlaceholder oder [data-dnd-placeholder], statt sie zu verstecken.

Listen mit gemeinsamen Elementen

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>

Der Drop schreibt beide Arrays, das, das die Aufgabe verlassen hat, und das, in das sie gekommen ist. TaskItem.vue ist das aus der sortierbaren Liste oben. Eine Palette, die Kopien austeilt, war suggestCopy() und ein Flag copyOnly; jetzt ist sie eine 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() }),
});

Für einen Pinia-Store oder anderen Zustand, den du nicht direkt schreibst, übergib items als Getter, rufe e.preventDefault() in onDrop auf und wende e.result an: siehe Kontrollierte Listen.

Ein eigenes Overlay

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>
  • Die Komponente bekommt den Drag als Props: items (jedes gezogene Element, das gegriffene zuerst), over, accepted, status, mode und to. Deklariere nur die, die du liest. Bei den Zeilen einer Liste ist das Listenelement items[0].item.
  • Übergib sie, wie sie ist. Wechsle sie mit einem shallowRef, nie mit einem tiefen ref.

Der Slot des Providers ändert sich genauso, und deine Komponente positioniert sich nicht mehr selbst:

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>

Ein Einblenden, das DragPreview in ein <Transition> gepackt hat, wird zu overlay-class mit @starting-style: siehe Das Overlay: anheben, neigen, ablegen.

Ein Drop, den du bestätigst

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

Eine Liste fragt genauso, und mit einem Promise kannst du einen eigenen Dialog öffnen. Das Overlay wartet dort, wo es losgelassen wurde, bis das Promise erledigt ist:

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

Eine Zone, die nur den passenden Teil einer gemischten Auswahl angenommen hat (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
});

Listen tun das ohne Code: Mit acceptMode: 'some' landen die Elemente, die ihr group.put annimmt, und die anderen fliegen zurück.

Stolperfallen beim Upgrade

  • Installiere v3 mit ihrem Versionsbereich. ^3 in package.json: Ein npm update hält ^2 auf v2.
  • Rendere den Entwurf. Eine Liste rendert die items, die makeSortableList zurückgibt, nicht dein Array. Renderst du dein Array, bewegt sich bis zum Drop nichts.
  • Sortiere nicht doppelt. Lösche den suggestSort-Code aus Listen: Eine Liste schreibt jeden Drop nach ihrem onDrop in ein beschreibbares items-Ref. Um ihn selbst anzuwenden, rufe e.preventDefault() auf und verwende e.result.
  • false lehnt den Drop ab. In v2 hielt ein onDrop, das false auflöste, den Drag am Laufen. In v3 gibst du 'continue' aus onBeforeDrop zurück. Ein Promise, das onDrop zurückgibt, wird ignoriert.
  • Listen teilen nur innerhalb einer group. Eine Liste ohne group tauscht mit keiner anderen Liste.
  • Schlüssle Zeilen nach ID. :key="index" animiert die falschen Zeilen. Übergib auch IDs aus deinen Daten: Eine Standard-ID ist für jede Komponenteninstanz neu.
  • Kein <TransitionGroup>, kein transition auf transform. Die Bibliothek bewegt die Zeilen selbst, und Bewegungen würden doppelt animiert.
  • Mach den Touch-Workaround rückgängig. touch-action: none auf einem ganzen Element verhindert, dass die Seite unter dem Finger scrollt; v3 startet Touch-Drags stattdessen mit langem Drücken. Behalte es auf einem Handle, das sofort ziehen soll, mit activation: { touch: { distance: 4 } }.
  • Entferne veraltete Attribute. Die Bibliothek setzt tabindex, role und die aria-*, die sie braucht, und überschreibt deine nie, also bleibt ein übrig gebliebenes role oder aria-grabbed so, wie du es hinterlassen hast.
  • Style das Overlay neu. .dnd-kit-preview gibt es nicht mehr: Style, was in [data-dnd-overlay='dragging'] steckt, nie das transform der Box. Gib der Wurzel einer Overlay-Komponente width: 100%; height: 100%, und nimm <DragPreview /> aus deinen Layouts.
  • Auto-Scroll ist an. Übergib :auto-scroll="false", wo v2 nie gescrollt hat.
  • Ein Auswahlrahmen braucht keine Taste. makeSelectionArea zeichnet bei einem einfachen Druck auf seine leere Fläche. Füge einen modifier hinzu, um das Strg von v2 zu behalten.
  • Ein Provider, Bereiche darin. Elemente können nicht zwischen Providern wechseln. Eine Seite, die einen Provider pro Region verwendet hat, um deren Regeln zu trennen, braucht einen Provider und ein makeDragArea pro Region.
  • Zwei Argumente. makeDraggable und makeDroppable nehmen ein Ref und ein Optionsobjekt, kein Payload.