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 inonDropmite.helpers.suggestSort()sortiert. In v3 erledigen dasmakeSortableListundmakeSortableItem: 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/coreenthä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/devtoolssind neu und optional. - Ein echtes Overlay. Es wird nach
bodyteleportiert 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
groupsfür einfache ziehbare Elemente und Ziele. Listen tauschen Elemente über den Namen ihrergroupaus. - Entscheidungen vor dem Drop.
onBeforeDropnimmt an, lehnt ab, lässt den Drag weiterlaufen oder nimmt nur einen Teil der Elemente.onDropwendet 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.
makeDragAreaersetztmakeConstraintAreaundmakeAutoScroll, 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
yarn add @vue-dnd-kit/core@^3
pnpm add @vue-dnd-kit/core@^3
bun add @vue-dnd-kit/core@^3
| Paket | 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, optional |
@vue-dnd-kit/devtools | — | 3.x, optional |
- Eine App, die
"@vue-dnd-kit/core": "^2…"in ihrerpackage.jsonhat, bleibt auf v2, bis der Versionsbereich^3sagt. - 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:
- Aktualisiere die Pakete und benenne die Props des Providers um (unten).
- Mach aus jeder sortierbaren Liste
makeSortableListundmakeSortableItem, und lösche ihren Drop-Handler. - Portiere die einfachen ziehbaren Elemente und Drop-Zonen.
- Portiere eigene Vorschauen auf Overlay-Komponenten oder den Slot
#overlaydes Providers. - Ersetze
makeConstraintAreaundmakeAutoScrolldurchmakeDragAreaoder durch nichts. - Lösche die v2-Workarounds:
tabindexundaria-grabbedauf ziehbaren Elementen,touch-action: noneauf ganzen Elementen,<TransitionGroup>um Zeilen,markRawum 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
| v2 | v3 |
|---|---|
DnDProvider | DnDProvider, mit neuen Props und einem Slot #overlay. |
makeDraggable | makeDraggable für freies Ziehen; makeSortableItem für die Zeilen einer Liste. |
makeDroppable | makeDroppable für einfache Drop-Ziele; makeSortableList oder makeSortableTree für Listen und Bäume. |
makeSelectionArea | makeSelectionArea. |
makeConstraintArea | makeDragArea. |
makeAutoScroll | Nichts: Auto-Scroll läuft automatisch. makeDragArea oder das autoScroll einer Liste stimmen es ab. |
useDnDProvider | useDnD für den Zustand, useDnDMonitor für die Events. |
injectionKey | useDnDContext() und provideDnDContext(app, context). |
DragPreview | DragPreview: jetzt der Klon eines gezogenen Elements, für eigene Overlays. |
makeSnappedOverlayPosition, <DragPreview :grid> | Der Modifier snapToGrid. |
e.helpers.suggestSort, suggestSwap, suggestCopy, suggestRemove | In Listen eingebaut: siehe Sortieren. |
Der Provider
| v2 | v3 |
|---|---|
autoScrollViewport, aus, sofern nicht gesetzt | autoScroll: standardmäßig an, für das Fenster und jeden Scroll-Container. { threshold, maxSpeed, acceleration } oder false. |
previewTo, standardmäßig im Provider gerendert | overlayTo: 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.moveFaster | Keine Entsprechung. Die Pfeiltasten bewegen; Shift macht die Schritte eines freien Drags viermal so groß. |
collision.throttle | Keine Entsprechung. collision wählt, wie das Ziel gefunden wird: pointerFirst (Standard), pointerWithin, rectIntersection, closestCenter, closestCorners oder eine eigene Funktion. |
| Ein Provider pro Seite, nie verschachtelt | Verschachteln 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 null | useDnD().status: 'idle', 'activating', 'dragging', 'dropping' oder 'selecting'. 'pending' (ein asynchroner Drop) ist 'dropping'; null ist 'idle'. |
pointer.value.current | useDnD().pointer, nach den Modifiern. Jedes Event hat außerdem delta, den Versatz seit dem Start. |
entities.draggingMap | useDnD().active: id, ids, items, data, type, element, rect. |
entities.selectedSet (Elemente) | 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, von 0 bis 1. |
preview.position, preview.size, preview.render, preview.to | Keine 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, scrollPosition | Keine 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
| v2 | v3 |
|---|---|
makeDraggable(ref, options, payload) | makeDraggable(ref, options): kein drittes Argument. Die Zeilen einer Liste verwenden makeSortableItem(ref, { item }). |
id, in virtuellen Listen nötig | id: Übergib sie aus deinen Daten, als Getter. Der Standardwert kommt aus useId() und ist für jede Komponenteninstanz neu. |
data: () => card | data: 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. |
disabled | disabled. |
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. |
placementMargins | Keine 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, onSelfDragCancel | Keine Entsprechung am ziehbaren Element: useDnDMonitor oder watch(isDragging). Siehe Events. |
events.onHover, onLeave und sein onDragStart… für Drags darüber | Ein makeDroppable auf demselben Element: onDragEnter, onDragLeave. |
Gibt isDragging, selected zurück | Dasselbe. |
Gibt isDragOver zurück | Sortierbare Elemente geben indicator (mit placeholder: 'indicator') und isPlaceholder zurück; Ziele geben placement zurück. |
Gibt isAllowed zurück | Keine 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
| v2 | v3 |
|---|---|
makeDroppable(ref, options, () => items) | makeDroppable(ref, options). Eine Zone, die eine Liste enthält, ist ein makeSortableList. |
groups | accept: '*', ein Typ, eine Liste von Typen oder eine Funktion. |
groupMatch: 'every', 'some' | acceptMode: 'all', 'some'. |
events.onEnter, events.onLeave | onDragEnter, onDragLeave: eigene Optionen, kein events-Objekt. |
events.onDrop, das false oder ein Promise zurückgeben konnte | onBeforeDrop entscheidet, onDrop wendet an. Siehe Drop-Entscheidungen. |
events.onValidate | onBeforeDrop, das die IDs zurückgibt, die bleiben sollen. |
data | data, zurückgelesen als over.data. |
disabled | disabled. |
Gibt isAllowed zurück | isAccepting. |
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 IDragEvent | v3 |
|---|---|
e.draggedItems | e.active.items: jeweils { id, item, data, label, type }, das gegriffene zuerst. Listen bekommen e.items und e.keys. |
e.draggedItems[0].data | e.active.data. |
e.draggedItems[i].index, .items | Listen: e.from, { list, index }. |
e.dropZone: items, placement, data | e.over: id, data, element, rect, accepted. Listen: e.to, { list, index }. |
e.hoveredDraggable | Keine 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.sameList | e.sameList. |
e.helpers.insertAt, removeAt, removeIndexes, swapAt, getInsertSide, isAtZoneStart, isAtZoneEnd | Keine Entsprechung. Listen brauchen sie nicht; bei einem einfachen Ziel änderst du deine Daten, wie du willst. |
e.provider | useDnD(). |
| — | Neu: e.pointer, e.delta, e.position (wohin ein auf einem Board abgelegtes Element kommt), e.mode. |
Sortieren
| v2 | v3 |
|---|---|
Eine makeDroppable-Liste mit () => items | makeSortableList(listRef, { items }). |
Eine makeDraggable-Zeile mit () => [index, items] | makeSortableItem(el, { item: () => props.task }). |
| Dein Array rendern | Die 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 zuweisen | Nichts: 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, .bottom | placeholder: '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 zuweisen | items als Getter, dann e.preventDefault() in onDrop und e.result anwenden. Siehe Kontrollierte Listen. |
Ein rekursiver Baum: ein Droppable pro Ordner und applyToTree | makeSortableTree: 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 Element | Gib 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:
| v2 | v3 |
|---|---|
makeDraggable(el, { groups: ['dev'] }) | makeDraggable(el, { type: 'dev' }). |
makeDroppable(el, { groups: ['dev'] }) | makeDroppable(el, { accept: 'dev' }). |
groups: ['dev', 'ops'] auf einer Zone | accept: ['dev', 'ops'] oder eine Funktion: ({ active, items, types }) => boolean. |
groups: [] oder keine | type 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 Zeilen | group: 'board' auf makeSortableList. group: { name, pull, put } sagt, was hinaus- und hineindarf. |
groups auf makeSelectionArea | accept. |
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
| v2 | v3 |
|---|---|
Eine Vorschau, im Provider gerendert, mit previewTo teleportiert | Immer teleportiert, nach body oder nach overlayTo. |
Ein Klon: dasselbe Tag, sein innerHTML und seine Größe | Ein 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 liest | overlay: 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.position | Der 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 onLeave | overOverlay 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>, makeSnappedOverlayPosition | Der 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 DragPreview | overlay-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 gehalten | Die Box nimmt die Größe des Slots an, in dem sie landen wird (overlayFit). |
| Die Vorschau verschwindet beim Drop | Sie landet in ihrem Slot oder fliegt beim Abbrechen zurück. |
Mehr im Leitfaden Das Overlay.
Auswahl und Multi-Drag
| v2 | v3 |
|---|---|
selected, von makeDraggable zurückgegeben | selected, von makeDraggable und makeSortableItem zurückgegeben. |
entities.selectedSet, ein Set von Elementen | useDnD().selection, nach ID geschlüsselt. |
makeSelectionArea: standardmäßig Strg (ControlLeft) gedrückt | Standardmäß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.code | KeyboardEvent.key ('Control', 'Shift') oder .code ('ControlLeft'). |
strategy: 'toggle', 'select' | Dasselbe. |
groups | accept. |
events.onSelected(elements) | onSelected(ids). |
disabled | Dasselbe. |
suggestSort(), das die Auswahl als Block verschiebt | Listen verschieben sie. multiple: 'group' (Standard), 'gaps' oder 'stack' sagt, was die anderen Elemente unterwegs tun. |
onValidate für gemischte Auswahlen | acceptMode: 'some' und onBeforeDrop, das IDs zurückgibt. Siehe Nur einige annehmen. |
Mehr in Multi-Drag und Auswahl.
Achsen, Grenzen und Einrasten
| v2 | v3 |
|---|---|
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: true | makeDragArea(el, { restrictToArea: true }) oder die Modifier restrictToParent und restrictToElement(el). |
| Ein Provider pro Region, um Regeln getrennt zu halten | Ein Provider und ein makeDragArea pro Region. Ihre Elemente können sich trotzdem überallhin bewegen. |
<DragPreview :grid="20">, makeSnappedOverlayPosition | snapToGrid(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
| v2 | v3 |
|---|---|
<DnDProvider :autoScrollViewport="true">, standardmäßig aus | Standardmäß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 Seite | threshold: ein Bruchteil der Containergröße (0.2), zwischen 24 und 96 px. Keine Entsprechung für Schwellen pro Seite. |
speed, ein Multiplikator | maxSpeed (25 px pro Frame) und acceleration (10). |
disabled: true | autoScroll: false. |
isScrolling | isAutoScrolling, aus makeDragArea oder useDnD(). |
Tastatur und Barrierefreiheit
| v2 | v3 |
|---|---|
tabindex="0" auf jedem ziehbaren Element | Wird für dich gesetzt, mit role, aria-roledescription, aria-describedby, aria-disabled und aria-pressed. Deine eigenen Attribute haben Vorrang. |
:aria-grabbed="isDragging" von Hand | Entferne es: aria-pressed folgt dem Drag. |
Tasten in useDnDProvider().keyboard.keys gesetzt | Die Prop keyboard des Providers. Tastenbelegungen passen auf event.code oder event.key. |
Die Pfeiltasten bewegen die Vorschau um keyboard.step | Freie 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 Ansagen | Eine 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
| v2 | v3 |
|---|---|
events.onSelfDragStart des ziehbaren Elements | useDnDMonitor({ onDragStart }), mit Prüfung von active.ids. Für eine Klasse genügt isDragging. |
onSelfDragMove | onDragMove des Monitors. |
onSelfDragEnd, abgelegt oder abgebrochen | onDragEnd des Monitors (ein Drop) und onDragCancel. onDragSettle hört beides, sobald das Overlay gelandet ist. |
onSelfDragCancel | onDragCancel des Monitors. |
onHover, onLeave des ziehbaren Elements und sein onDragStart… für Drags darüber | makeDroppable auf dem Element: onDragEnter, onDragLeave. Zeilen einer Liste brauchen nichts. |
events.onEnter, onLeave des Droppables | onDragEnter, onDragLeave. |
events.onDrop des Droppables | onBeforeDrop entscheidet, dann wendet onDrop an. onDrop ist synchron, und sein Rückgabewert wird ignoriert. |
events.onValidate des Droppables | onBeforeDrop, 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ück | v3 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ück | Ein 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
| v2 | v3 |
|---|---|
IDragEvent | DnDEvent (Ziele, Monitore), SortableDropEvent (Listen), DnDEndEvent (onDragEnd). |
IDragItem | DragItem. |
IDropZoneContext | Over. |
IHoveredDraggableContext | Keine Entsprechung. |
ISuggestSortResult, ISuggestSwapResult, ISuggestCopyResult, ISuggestRemoveResult | SortableDropEvent: result, to, mode, partner. |
IDropValidateEvent | DnDEvent, in onBeforeDrop. |
IPlacement | Placement, ohne center. |
ICoordinates | Coordinates. |
IDragActivationOptions | ActivationOptions. |
IAutoScrollOptions | AutoScrollOptions. |
TDnDState | DnDStatus. |
IDnDProviderExternal | DnDState, was useDnD() zurückgibt. |
IDraggableEvents, IDroppableEvents | Callbacks 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, useDnDProvider | Importiert automatisch makeDraggable, makeDroppable, makeSortableList, makeSortableItem, makeSortableTree, makeSelectionArea, makeDragArea, useDnD, useDnDMonitor. |
| Keine Typen automatisch importiert | Die 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 global | Dasselbe, mit einer Option prefix. |
<DragPreview /> im Layout, innerhalb des Providers | Entferne es: Der Provider rendert das Overlay. DragPreview nimmt eine id und gehört in Overlay-Komponenten. |
Peer @vue-dnd-kit/core 2 | Peer @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
<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>
- Die Karte bekommt
tabindex,roleund diearia-*-Attribute von selbst. Ein<button>als Handle ist fokussierbar, und Drags per Tastatur starten von ihm aus. - Aus
groupswurdetypeauf dem ziehbaren Element undacceptauf dem Ziel. Die Callbacks haben dasevents-Objekt verlassen. isOverohneisAcceptingist ein Ziel, das den Drag ablehnt: Ein Drop dort fliegt zurück.
Eine sortierbare Liste
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>
- Rendere
items, den Entwurf: Er ordnet sich während des Ziehens neu.tasksbekommt beim Drop die neue Reihenfolge, unddefineModelemittiertupdate: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
isPlaceholderoder[data-dnd-placeholder], statt sie zu verstecken.
Listen mit gemeinsamen Elementen
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>
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:
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
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>
- Die Komponente bekommt den Drag als Props:
items(jedes gezogene Element, das gegriffene zuerst),over,accepted,status,modeundto. Deklariere nur die, die du liest. Bei den Zeilen einer Liste ist das Listenelementitems[0].item. - Übergib sie, wie sie ist. Wechsle sie mit einem
shallowRef, nie mit einem tiefenref.
Der Slot des Providers ändert sich genauso, und deine Komponente positioniert sich nicht mehr selbst:
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>
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
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),
});
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:
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
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
});
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.
^3inpackage.json: Einnpm updatehält^2auf v2. - Rendere den Entwurf. Eine Liste rendert die
items, diemakeSortableListzurü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 ihremonDropin ein beschreibbaresitems-Ref. Um ihn selbst anzuwenden, rufee.preventDefault()auf und verwendee.result. falselehnt den Drop ab. In v2 hielt einonDrop, dasfalseauflöste, den Drag am Laufen. In v3 gibst du'continue'ausonBeforeDropzurück. Ein Promise, dasonDropzurückgibt, wird ignoriert.- Listen teilen nur innerhalb einer
group. Eine Liste ohnegrouptauscht 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>, keintransitionauftransform. Die Bibliothek bewegt die Zeilen selbst, und Bewegungen würden doppelt animiert. - Mach den Touch-Workaround rückgängig.
touch-action: noneauf 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, mitactivation: { touch: { distance: 4 } }. - Entferne veraltete Attribute. Die Bibliothek setzt
tabindex,roleund diearia-*, die sie braucht, und überschreibt deine nie, also bleibt ein übrig gebliebenesroleoderaria-grabbedso, wie du es hinterlassen hast. - Style das Overlay neu.
.dnd-kit-previewgibt es nicht mehr: Style, was in[data-dnd-overlay='dragging']steckt, nie dastransformder Box. Gib der Wurzel einer Overlay-Komponentewidth: 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.
makeSelectionAreazeichnet bei einem einfachen Druck auf seine leere Fläche. Füge einenmodifierhinzu, 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
makeDragAreapro Region. - Zwei Argumente.
makeDraggableundmakeDroppablenehmen ein Ref und ein Optionsobjekt, kein Payload.