ドキュメント/はじめに

v2 からの移行

v2 のアプリを v3 へ移行します。何がなぜ変わったか、アップグレードの手順、v2 のすべての API と v3 での置き換え先の対応表、よくあるケースの移行前後のコード、そして注意点。

このページを編集

このページは AI によって翻訳されており、不正確な箇所が含まれている可能性があります。 英語の原文を読む

v3 は v2 を書き直したものです。モデルは同じです:1 つのプロバイダーと、自分の要素に対して呼び出すコンポーザブル。ただし、ほとんどの名前とオプションが変わり、並べ替えはライブラリの中に移り、リストごとに書いていたドロップハンドラーは不要になります。このページでは、v2 の各部分を v3 に対応付け、よくあるケースを移行前と移行後で示します。

変更点

  • 並べ替えは組み込みです。v2 ではリストはドロップゾーンで、その行は [index, items] のペイロードを持つドラッグ可能な要素であり、onDrop の中で e.helpers.suggestSort() を使って並べ替えていました。v3 では makeSortableList と makeSortableItem がそれを行います:リストは配列のドラフトを描画し、それがドラッグ中に並べ替わり、ドロップすると新しい配列が ref に書き込まれます。v2 のドロップハンドラーのほとんどは、移植するのではなく削除します。
  • ものは自分で動きます。行は新しい位置へスライドし(FLIP)、オーバーレイはドロップで自分の位置に着地し、キャンセルで元の位置に戻ります。<TransitionGroup> は不要になります。
  • パッケージは今も 1 つです。@vue-dnd-kit/core にリスト、ツリー、マルチドラッグ、モディファイア、センサー、衝突判定の関数が入っています。Nuxt モジュールは引き続きあり、@vue-dnd-kit/actions(プログラムによるドラッグ)と @vue-dnd-kit/devtools は新しいオプションのパッケージです。
  • 本物のオーバーレイです。body にテレポートされ、着地する位置と同じサイズのディープクローンを表示します。オーバーレイコンポーネントは、プロバイダーの内部を読む代わりに、ドラッグを props として受け取ります。
  • 要素ではなく idです。選択、イベント、ドラッグの状態は、データの id をキーにしています。読むべき DOM 要素のマップはありません。
  • 型と accept ルールが、通常のドラッグ可能な要素とドロップターゲットの groups に代わります。リストは group の名前でアイテムをやり取りします。
  • 判断はドロップの前に行います。onBeforeDrop が受け入れる、拒否する、ドラッグを続ける、またはアイテムの一部だけを受け取るかを決めます。onDrop は適用するだけです。
  • センサー。ポインターとキーボードの入力はセンサーです。アクティベーションはポインターの種類ごとに設定し、タッチのドラッグは長押しで始まるので、指の下でもリストはスクロールし続けます。
  • エリア。makeDragArea が makeConstraintArea と makeAutoScroll に代わり、自動スクロールはコードなしですべてのスクロールコンテナーで動きます。
  • アクセシビリティはデフォルトでオンです:ARIA 属性、ドロップ後に戻ってくるフォーカス、スクリーンリーダーへの読み上げ。

アップグレード

npm i @vue-dnd-kit/core@^3
パッケージv2v3
@vue-dnd-kit/core2.4.x3.x
@vue-dnd-kit/nuxt1.x3.x
@vue-dnd-kit/actions—1.x、オプション
@vue-dnd-kit/devtools—3.x、オプション
  • package.json に "@vue-dnd-kit/core": "^2…" と書かれたアプリは、範囲を ^3 にするまで v2 のままです。
  • Vue 3.5 以降。v2 には依存関係がありませんでしたが、v3 には @vueuse/core(14 または 15)が 1 つあります。
  • ブラウザー:Chrome と Edge 104+、Safari 15.4+、Firefox 104+。
  • v2 と v3 は同じパッケージ名なので、アプリは一度に v3 へ移行します。互換レイヤーはありません。v2 のドキュメントは、ページ上部のバージョンメニューから引き続き見られます。

おすすめの作業順:

  1. パッケージをアップグレードし、プロバイダーの props の名前を変えます(後述)。
  2. 並べ替え可能なリストをそれぞれ makeSortableList と makeSortableItem に変え、そのドロップハンドラーを削除します。
  3. 通常のドラッグ可能な要素とドロップゾーンを移植します。
  4. カスタムプレビューを、オーバーレイコンポーネントかプロバイダーの #overlay スロットに移植します。
  5. makeConstraintArea と makeAutoScroll を makeDragArea に置き換えるか、何も使わないようにします。
  6. v2 のための回避策を削除します:ドラッグ可能な要素の tabindex と aria-grabbed、アイテム全体の touch-action: none、行を囲む <TransitionGroup>、プレビューコンポーネントを囲む markRaw。

新旧の対応

表の中の対応なしは、v3 にその名前のものがないことを意味します。代わりに何を使うかはセルに書いてあります。

ひと目で

v2v3
DnDProviderDnDProvider。新しい props と #overlay スロットがあります。
makeDraggable自由なドラッグには makeDraggable、リストの行には makeSortableItem。
makeDroppable通常のドロップターゲットには makeDroppable、リストとツリーには makeSortableList または makeSortableTree。
makeSelectionAreamakeSelectionArea。
makeConstraintAreamakeDragArea。
makeAutoScroll不要:自動スクロールは自動です。makeDragArea かリストの autoScroll で調整します。
useDnDProvider状態には useDnD、イベントには useDnDMonitor。
injectionKeyuseDnDContext() と provideDnDContext(app, context)。
DragPreviewDragPreview:今はドラッグ中のアイテム 1 つのクローンで、自作のオーバーレイ用です。
makeSnappedOverlayPosition、<DragPreview :grid>snapToGrid モディファイア。
e.helpers.suggestSort、suggestSwap、suggestCopy、suggestRemoveリストに組み込み:並べ替えを参照。

プロバイダー

v2v3
autoScrollViewport、設定しなければオフautoScroll:デフォルトでオン。ウィンドウとすべてのスクロールコンテナーが対象です。{ threshold, maxSpeed, acceleration } または false。
previewTo、デフォルトではプロバイダーの中に描画overlayTo:セレクターまたは要素。デフォルトは 'body'。
#preview="{ preview }" スロットドラッグを props として受け取る #overlay スロット:items、active、over、accepted、status、mode、to。オーバーレイを参照。
keyboard.keys.forDrag、forDrop、forCancel(useDnDProvider() で設定):keyboard="{ keys: { start, drop, cancel } }"。
keyboard.step:keyboard="{ step }":デフォルトは 25 px で、自由なドラッグ用です。
keyboard.keys.forMove、forMoveFaster、keyboard.moveFaster対応なし。矢印キーで動かします。:kbd{value="Shift"} で自由なドラッグのステップが 4 倍になります。
collision.throttle対応なし。collision でターゲットの見つけ方を選びます:pointerFirst(デフォルト)、pointerWithin、rectIntersection、closestCenter、closestCorners、または自作の関数。
ページに 1 つのプロバイダー、ネストは不可ネストできます:アイテムは最も近いプロバイダーのものです。アプリ全体で 1 つのプロバイダーが今も目標です。

v2 に対応するものがない新しい props:activation、modifiers、sensors、animation、dropAnimation、animateChanges、haptics、copyModifier、overlayFit、overlayClass、overlayTransition、overlayPreviews、announcements、screenReaderInstructions、defaultStyles。プロバイダーは drag-start、drag-move、drag-over、drag-end、drag-cancel、drag-resume、drag-settle も発行します。すべてプロバイダーのガイドにあります。

状態を読む:useDnD

useDnDProvider()v3
state:'dragging'、'activating'、'selecting'、'pending' または nulluseDnD().status:'idle'、'activating'、'dragging'、'dropping' または 'selecting'。'pending'(非同期のドロップ)は 'dropping' に、null は 'idle' になります。
pointer.value.currentuseDnD().pointer、モディファイア適用後の値。どのイベントにも、開始位置からのオフセットである delta があります。
entities.draggingMapuseDnD().active:id、ids、items、data、type、element、rect。
entities.selectedSet(要素)useDnD().selection(id):has、add、delete、toggle、set、clear、ids。
hovered.droppable、hovered.draggableuseDnD().over:id、data、element、rect、accepted。
delay.progress、distanceProgressuseDnD().activation.progress、0 から 1 まで。
preview.position、preview.size、preview.render、preview.to対応なし。オーバーレイの配置とサイズはライブラリが決めます。overlayTo、#overlay スロット、またはオーバーレイコンポーネントを使います。
entities.draggableMap、droppableMap、visible…Set の各セット、scrollPosition対応なし。
—新規:useDnD().cancel()、position、isAutoScrolling。

イベントは useDnDMonitor に移りました:イベントとドロップの判断を参照。完全な型は useDnD の API にあります。

makeDraggable

v2v3
makeDraggable(ref, options, payload)makeDraggable(ref, options):第 3 引数はありません。リストの行は makeSortableItem(ref, { item }) を使います。
id、仮想リストで必要id:データから、ゲッターとして渡します。デフォルトは useId() から作られ、コンポーネントのインスタンスごとに新しくなります。
data: () => carddata:値、ref、またはゲッター。active.data と active.items[i].data で読み戻します。
groups: ['dev']type: 'dev'。ターゲットの accept と照合されます。グループを参照。
dragHandle: '.handle'handle:セレクターまたは要素の ref。
disableddisabled。
modifier: { keys, method }canDrag: (event) => event.altKey:押すたびに通るゲートです。
activation: { distance, delay }ポインターの種類ごとの activation:{ mouse: { distance }, touch: { delay, tolerance }, pen }。distance と delay の両方があると、先に満たされた方でドラッグが始まります。
activation.distance: { x, y, condition }対応なし。ポインターの種類ごとに distance は 1 つです。
render: markRaw(Preview)overlay: Preview:コンポーネントはドラッグを props として受け取ります。オーバーレイを参照。
placementMargins対応なし。並べ替え可能なアイテム上のドロップターゲットは、その中央(25〜75%)で判定されます。ツリーは nesting: 'hover' でネストします。並べ替え可能なアイテムへのドロップを参照。
events.onSelfDragStart、onSelfDragMove、onSelfDragEnd、onSelfDragCancelドラッグ可能な要素には対応なし:useDnDMonitor か watch(isDragging) を使います。イベントを参照。
events.onHover、onLeave、そしてその上を通るドラッグの onDragStart…同じ要素に makeDroppable:onDragEnter、onDragLeave。
isDragging、selected を返す同じです。
isDragOver を返す並べ替え可能なアイテムは indicator(placeholder: 'indicator' のとき)と isPlaceholder を返し、ドロップターゲットは placement を返します。
isAllowed を返すドラッグ可能な要素には対応なし。ドロップターゲットは isAccepting を返します。
—新規:label、axis、modifiers、keyboardMode、返される transform と組み合わせる overlay: false、dropAnimation、attributes、native。

makeDroppable

v2v3
makeDroppable(ref, options, () => items)makeDroppable(ref, options)。リストを持つゾーンは makeSortableList です。
groupsaccept:'*'、型、型のリスト、または関数。
groupMatch: 'every'、'some'acceptMode: 'all'、'some'。
events.onEnter、events.onLeaveonDragEnter、onDragLeave:それぞれ独立したオプションで、events オブジェクトはありません。
events.onDrop。false や Promise を返せたonBeforeDrop が判断し、onDrop が適用します。ドロップの判断を参照。
events.onValidate残す id を返す onBeforeDrop。
datadata。over.data で読み戻します。
disableddisabled。
isAllowed を返すisAccepting。
isDragOver を返す:{ top, right, bottom, left, center }isOver(真偽値)と placement:{ top, right, bottom, left }、ポインターがある側の半分。center はありません。
—新規:id、label、priority、overOverlay、overDropAnimation、external(コンピューターからのファイル)。

ドロップイベント

v2 IDragEventv3
e.draggedItemse.active.items:それぞれ { id, item, data, label, type } で、掴んだアイテムが先頭です。リストは e.items と e.keys も受け取ります。
e.draggedItems[0].datae.active.data。
e.draggedItems[i].index、.itemsリスト:e.from、{ list, index }。
e.dropZone:items、placement、datae.over:id、data、element、rect、accepted。リスト:e.to、{ list, index }。
e.hoveredDraggable対応なし:リストは e.to.index を自分で求めます。
e.helpers.suggestSort()リストがドロップを書き込みます。e.result には、そのドロップで変わったすべてのリストの新しい配列が入ります。
e.helpers.suggestSwap()リストに behavior: 'swap'。e.partner は入れ替えた相手のアイテムです。
e.helpers.suggestCopy()group: { name, pull: 'clone' } またはコピーキー(:kbd{value="Alt"})と、リストの clone。e.mode は 'copy' です。
e.helpers.suggestRemove()ドロップターゲットでの e.source.remove()、またはリストの dropOutside: 'remove'。
result.sameListe.sameList。
e.helpers.insertAt、removeAt、removeIndexes、swapAt、getInsertSide、isAtZoneStart、isAtZoneEnd対応なし。リストには不要です。通常のドロップターゲットでは、データを好きなように変更してください。
e.provideruseDnD()。
—新規:e.pointer、e.delta、e.position(ボードにドロップしたアイテムをどこに置くか)、e.mode。

並べ替え

v2v3
() => items を渡した makeDroppable のリストmakeSortableList(listRef, { items })。
() => [index, items] を渡した makeDraggable の行makeSortableItem(el, { item: () => props.task })。
自分の配列を描画返された items を描画:ドラッグ中に並べ替わるドラフトです。
suggestSort('vertical')、suggestSort('horizontal')strategy: 'vertical'(デフォルト)、'horizontal'、または 'grid'。
参照の比較で元の配列と先の配列を見つけ、sourceItems と targetItems を代入不要:ドロップが、変わったすべてのリストに書き込みます。
<TransitionGroup>組み込み。animateChanges: true で、ドラッグ以外による変化(追加、削除、シャッフル)をアニメーションします。
isDragOver.top、.bottom による挿入ラインplaceholder: 'indicator' とアイテムの indicator.side。挿入ラインを参照。
suggestSwap()behavior: 'swap'。
コピー専用のパレット(suggestCopy() と copyOnly フラグ)group: { name, pull: 'clone', put: false }、sort: false、clone。
suggestRemove() を使うゴミ箱ゾーンmakeDroppable(trash, { onDrop: (e) => e.source?.remove() })、または dropOutside: 'remove'。
Pinia やイミュータブルなデータ:結果を自分で代入items をゲッターにして、onDrop で e.preventDefault() を呼び、e.result を適用します。制御されたリストを参照。
再帰的なツリー:フォルダーごとの droppable と applyToTreemakeSortableTree:コンテナーは 1 つで、行は深さとともにフラットに描画されます。ツリーを参照。
v-model を使ったネストしたグループ(グループごとに droppable)リストの中のリスト。すべて同じ group で、それぞれ items: defineModel()。
仮想リスト:すべてのドラッグ可能な要素に idドラフトを仮想化ライブラリに渡し、行に id: () => props.item.id。仮想リストを参照。

移行前後のコードは後述します。リストのすべては並べ替え可能なリストとアイテムを共有するリストにあります。

グループ

v2 にはすべてに使う groups オプションが 1 つありました。v3 はそれを 2 つに分けます:

v2v3
makeDraggable(el, { groups: ['dev'] })makeDraggable(el, { type: 'dev' })。
makeDroppable(el, { groups: ['dev'] })makeDroppable(el, { accept: 'dev' })。
ゾーンの groups: ['dev', 'ops']accept: ['dev', 'ops']、または関数:({ active, items, types }) => boolean。
groups: []、または指定なしtype や accept を省きます。v2 のルールはそのままです:型のないドラッグ可能な要素はどのルールにも合い、accept のないターゲットはすべてを受け入れます。
groupMatch: 'every'、'some'acceptMode: 'all'、'some'。
リストのゾーンとその行の groupsmakeSortableList の group: 'board'。group: { name, pull, put } で、何が出ていけて何が入れるかを指定します。
makeSelectionArea の groupsaccept。

group のないリストはそれだけで 1 つのグループになり、ほかのどのリストともアイテムをやり取りしません。v2 では、groups のないゾーンはすべてを受け入れていました。ターゲットが受け入れるドラッグを参照してください。

オーバーレイ

v2v3
プロバイダーの中に描画され、previewTo でテレポートされるプレビュー常にテレポートされます。行き先は body か overlayTo。
クローン:同じタグ、その innerHTML、そのサイズディープクローン:フォームの値、<canvas> のピクセル、継承されたスタイル、表のセルも一緒に来ます。残すものには data-dnd-no-clone を付けます。
render: markRaw(Preview)。useDnDProvider().entities を読むoverlay: Preview。items、active、over、accepted、status、mode、to を props として受け取ります。markRaw は不要で、切り替えるには shallowRef を使います。
preview.position で位置を決める #preview スロット#overlay スロット:ボックスの配置とサイズはライブラリが決めます。何も描画しなければクローンにフォールバックします。
useDnDProvider().preview.render.value = …#overlay スロット、または overlay として渡す shallowRef。
onEnter と onLeave による、ゾーンの上での見た目の変更makeDroppable やリストの overOverlay、またはオーバーレイコンポーネントで over を読みます。オーバーレイを変えるターゲットを参照。
<DragPreview v-slot="{ draggingMap }">オーバーレイの props の items。<DragPreview :id="item.id" /> はクローンを 1 つ描画します。
<DragPreview :grid>、makeSnappedOverlayPositionsnapToGrid モディファイア。見た目だけでなく、ターゲットを探す位置とドロップの position もスナップします。
DragPreview を囲む <Transition> や motion の AnimatePresence持ち上げには @starting-style 付きの overlay-class、着地には dropAnimation、コンポーネント間には overlayTransition。持ち上げ、傾け、落ち着かせるを参照。
.dnd-kit-previewボックスの [data-dnd-overlay]("dragging"、"dropping")と、その中の [data-dnd-overlay-content]。
手作業で更新する preview.sizeボックスは着地する位置のサイズになります(overlayFit)。
ドロップでプレビューが消える自分の位置に着地するか、キャンセルで元の位置に戻ります。

詳しくはオーバーレイのガイドにあります。

選択とマルチドラッグ

v2v3
makeDraggable が返す selectedmakeDraggable と makeSortableItem が返す selected。
entities.selectedSet、要素のセットuseDnD().selection、id がキー。
makeSelectionArea:デフォルトで Ctrl(ControlLeft)を押したままデフォルトではキー不要:エリアの空いている場所を押すと選択枠を描きます。modifier: { keys: ['Control'], method: 'every' } で Ctrl に戻せます。
KeyboardEvent.code としての modifier.keysKeyboardEvent.key('Control'、'Shift')または .code('ControlLeft')。
strategy: 'toggle'、'select'同じです。
groupsaccept。
events.onSelected(elements)onSelected(ids)。
disabled同じです。
選択をまとめて動かす suggestSort()リストが動かします。multiple: 'group'(デフォルト)、'gaps'、'stack' で、ほかのアイテムが途中でどうするかを決めます。
混在した選択のための onValidateacceptMode: 'some' と、id を返す onBeforeDrop。一部だけを受け入れるを参照。

詳しくはマルチドラッグと選択にあります。

軸、境界、スナップ

v2v3
makeConstraintArea(el, { axis: 'x' })makeDragArea(el, { axis: 'x' })。その中で始まるドラッグに効きます。axis はドラッグ可能な要素、リスト、ツリー、アイテムにも使えます。
axis: 'both'axis を省きます。
restrictToArea: truemakeDragArea(el, { restrictToArea: true })、またはモディファイアの restrictToParent と restrictToElement(el)。
ルールを分けるための、領域ごとのプロバイダープロバイダーは 1 つで、領域ごとに makeDragArea。そのアイテムはどこへでも動けます。
<DragPreview :grid="20">、makeSnappedOverlayPositionsnapToGrid(20) または snapToGrid({ x, y, origin, smooth }) を、プロバイダー、エリア、リスト、ドラッグ可能な要素の modifiers に入れます。
—新規:restrictToWindowEdges、restrictToFirstScrollableAncestor、snapCenterToCursor、そして自作のモディファイア。

詳しくは軸、境界、グリッドにあります。

自動スクロール

v2v3
<DnDProvider :autoScrollViewport="true">、デフォルトでオフデフォルトでオン。ウィンドウとすべてのスクロールコンテナーが対象です。:auto-scroll="false" でオフにします。
makeAutoScroll(el, { threshold, speed })不要:overflow: auto や scroll の要素はどれもスクロールします。個別に調整するには makeDragArea(el, { autoScroll: { … } }) またはリストの autoScroll。
px 単位、または辺ごとの thresholdthreshold:コンテナーのサイズに対する割合(0.2)で、24〜96 px の範囲。辺ごとのしきい値は対応なし。
倍率の speedmaxSpeed(1 フレームあたり 25 px)と acceleration(10)。
disabled: trueautoScroll: false。
isScrollingisAutoScrolling。makeDragArea または useDnD() から。

キーボードとアクセシビリティ

v2v3
すべてのドラッグ可能な要素に tabindex="0"自動で設定され、role、aria-roledescription、aria-describedby、aria-disabled、aria-pressed も付きます。自分で付けた属性が優先されます。
手書きの :aria-grabbed="isDragging"削除します:aria-pressed がドラッグに追従します。
useDnDProvider().keyboard.keys で設定したキープロバイダーの keyboard prop。割り当ては event.code または event.key と照合されます。
矢印キーでプレビューを keyboard.step ずつ動かす自由なドラッグ:keyboard.step px ずつ。リスト:位置ごとに、そしてグループの次のリストへ。keyboardMode: 'droppables' はターゲット間をジャンプします。
矢印キーの再割り当て(forMove、forMoveFaster)対応なし。
読み上げなしライブリージョンが、持ち上げ、移動、ドロップ、キャンセルのたびに読み上げます。label が名前を付け、announcements と screenReaderInstructions が言い回しを変えます。

詳しくはアクセシビリティにあります。

イベントとドロップの判断

v2v3
ドラッグ可能な要素の events.onSelfDragStartuseDnDMonitor({ onDragStart }) で active.ids を確認します。クラスを付けるだけなら isDragging で十分です。
onSelfDragMoveモニターの onDragMove。
onSelfDragEnd、ドロップでもキャンセルでもモニターの onDragEnd(ドロップ)と onDragCancel。onDragSettle はオーバーレイが着地した後に両方を受け取ります。
onSelfDragCancelモニターの onDragCancel。
ドラッグ可能な要素の onHover、onLeave、そしてその上を通るドラッグの onDragStart…その要素に makeDroppable:onDragEnter、onDragLeave。リストの行には何も要りません。
droppable の events.onEnter、onLeaveonDragEnter、onDragLeave。
droppable の events.onDroponBeforeDrop が判断し、次に onDrop が適用します。onDrop は同期で、戻り値は無視されます。
droppable の events.onValidateacceptMode: 'some' と組み合わせた onBeforeDrop。
—新規:プロバイダーの @drag-start … @drag-settle、そしてモニターの onDragOver、onBeforeDrop、onDragResume。

v2 の onDrop は、値やその Promise を返してドロップを決められました。v3 ではそれは onBeforeDrop の役目で、リスト、ツリー、ドロップターゲット、モニターにあります:

v2 の onDrop の戻り値v3 の onBeforeDrop の戻り値
何も返さない、または true:ドロップが成立する'accept'、true、または何も返さない。
false:このドロップは見送られ、ドラッグは続く'continue'。今は false でドロップが拒否されます。
reject された Promise や例外:ドラッグがキャンセルされる'reject' または false。例外も拒否になります。
Promise。その間 state は 'pending'Promise。その間 status は 'dropping' で、オーバーレイは data-dnd-overlay="dropping" の状態で待ちます。
onValidate が IDragItem[] を返すid の配列:それらだけがドロップされ、ほかは元の位置に戻ります。

まずモニターが答え、次にターゲットが答えます。そのあとターゲットの onDrop が実行され、リストが新しい配列を書き込み、onDragEnd が続きます。詳しくはドロップの判断とイベントの API にあります。

型

v2v3
IDragEventDnDEvent(ドロップターゲット、モニター)、SortableDropEvent(リスト)、DnDEndEvent(onDragEnd)。
IDragItemDragItem。
IDropZoneContextOver。
IHoveredDraggableContext対応なし。
ISuggestSortResult、ISuggestSwapResult、ISuggestCopyResult、ISuggestRemoveResultSortableDropEvent:result、to、mode、partner。
IDropValidateEventonBeforeDrop の中の DnDEvent。
IPlacementcenter のない Placement。
ICoordinatesCoordinates。
IDragActivationOptionsActivationOptions。
IAutoScrollOptionsAutoScrollOptions。
TDnDStateDnDStatus。
IDnDProviderExternalDnDState。useDnD() が返すものです。
IDraggableEvents、IDroppableEventsDraggableOptions、DroppableOptions、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)
makeDraggable、makeDroppable、makeSelectionArea、makeConstraintArea、makeAutoScroll、useDnDProvider を自動インポートmakeDraggable、makeDroppable、makeSortableList、makeSortableItem、makeSortableTree、makeSelectionArea、makeDragArea、useDnD、useDnDMonitor を自動インポート。
型は自動インポートされない主な型は自動インポートされます(DnDEvent、SortableDropEvent、OverlayProps…)。汎用的な型(Key、Active、Over)は手動でインポートします。
—モディファイア、衝突判定の関数、センサーは必要に応じて:vueDndKit: { autoImports: { modifiers: true, collision: true, sensors: true } }。
DnDProvider と DragPreview がグローバル同じです。prefix オプションがあります。
レイアウトの、プロバイダーの中の <DragPreview />削除します:オーバーレイはプロバイダーが描画します。DragPreview は id を受け取り、オーバーレイコンポーネントの中で使います。
peer 依存 @vue-dnd-kit/core 2peer 依存 @vue-dnd-kit/core 3、Nuxt 4。

nuxt.config.ts の modules: ['@vue-dnd-kit/nuxt'] はそのままです。インストールを参照してください。

移行前と移行後

ドラッグ可能な要素とドロップゾーン

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>
  • カードは tabindex、role、aria-* 属性を自動で受け取ります。<button> のハンドルはフォーカスでき、キーボードドラッグはそこから始まります。
  • groups は、ドラッグ可能な要素では type に、ターゲットでは accept になりました。コールバックは events オブジェクトから外に出ました。
  • isAccepting のない isOver は、そのドラッグを拒否するターゲットです:そこにドロップすると元の位置に戻ります。

並べ替え可能なリスト

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>
  • ドラフトである items を描画します:ドラッグ中に並べ替わります。ドロップすると tasks が新しい順序を受け取り、defineModel が update:modelValue を発行します。
  • インデックスも、ペイロードも、onDrop も、<TransitionGroup> も要りません:行は自分でスライドします。
  • ドラッグ中の行は、着地する位置としてリストの中に残ります。隠すのではなく、isPlaceholder か [data-dnd-placeholder] でスタイルを付けてください。

アイテムを共有するリスト

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>

ドロップは両方の配列に書き込みます:タスクが出ていった配列と、入った配列です。TaskItem.vue は上の並べ替え可能なリストのものと同じです。コピーを配るパレットは suggestCopy() と copyOnly フラグで作っていましたが、今はリストです:

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

Pinia のストアや、直接書き込まないほかの状態では、items をゲッターで渡し、onDrop で e.preventDefault() を呼んで e.result を適用します:制御されたリストを参照してください。

カスタムオーバーレイ

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>
  • コンポーネントはドラッグを props として受け取ります:items(ドラッグ中のすべてのアイテム、掴んだものが先頭)、over、accepted、status、mode、to。読むものだけを宣言してください。リストの行では、リストのアイテムは items[0].item です。
  • そのまま渡します。切り替えるには shallowRef を使い、深い ref は決して使いません。

プロバイダーのスロットも同じように変わり、コンポーネントが自分で位置を決めることはなくなります:

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>

DragPreview を <Transition> で囲んでいたポップインは、@starting-style 付きの overlay-class になります:持ち上げ、傾け、落ち着かせるを参照してください。

確認してからのドロップ

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

リストも同じように尋ね、Promise を使えば自作のダイアログを開けます。Promise が確定するまで、オーバーレイは離した場所で待ちます:

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

混在した選択のうち、一致する部分だけを受け取っていたゾーン(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
});

リストはこれをコードなしで行います:acceptMode: 'some' にすると、group.put が受け入れるアイテムは着地し、ほかは元の位置に戻ります。

アップグレードの注意点

  • v3 は範囲を指定してインストールします。package.json に ^3:npm update では ^2 は v2 のままです。
  • ドラフトを描画します。リストが描画するのは makeSortableList が返す items で、自分の配列ではありません。自分の配列を描画すると、ドロップするまで何も動きません。
  • 二重に並べ替えません。リストから suggestSort のコードを削除してください:リストは onDrop の後に、書き込み可能な items の ref に各ドロップを書き込みます。自分で適用するには、e.preventDefault() を呼んで e.result を使います。
  • false はドロップを拒否します。v2 では、false で解決した onDrop はドラッグを続けました。v3 では onBeforeDrop から 'continue' を返します。onDrop から返した Promise は無視されます。
  • リストが共有するのは同じ group の中だけです。group のないリストは、ほかのどのリストともやり取りしません。
  • 行のキーは id にします。:key="index" では違う行がアニメーションします。id もデータから渡してください:デフォルトの id はコンポーネントのインスタンスごとに新しくなります。
  • <TransitionGroup> も、transform への transition も使いません。ライブラリが自分で行を動かすので、動きが二重にアニメーションしてしまいます。
  • タッチの回避策を元に戻します。アイテム全体の touch-action: none は、指の下でページがスクロールするのを妨げます。v3 は代わりに長押しでタッチのドラッグを始めます。すぐにドラッグしたいハンドルには、activation: { touch: { distance: 4 } } と一緒に残してください。
  • 古い属性を削除します。ライブラリは tabindex、role、必要な aria-* を設定し、自分で付けた属性は決して上書きしません。そのため、残った role や aria-grabbed は残したままになります。
  • オーバーレイのスタイルを付け直します。.dnd-kit-preview はなくなりました:[data-dnd-overlay='dragging'] の中身にスタイルを付け、ボックスの transform には決して触れません。オーバーレイコンポーネントのルートには width: 100%; height: 100% を指定し、レイアウトから <DragPreview /> を取り除きます。
  • 自動スクロールはオンです。v2 でスクロールしなかった場所には :auto-scroll="false" を渡します。
  • 選択枠にキーは要りません。makeSelectionArea は、空いている場所を普通に押すだけで描画します。v2 の Ctrl を残すには modifier を追加します。
  • プロバイダーは 1 つ、エリアはその中に。アイテムはプロバイダーをまたげません。ルールを分けるために領域ごとにプロバイダーを使っていたページでは、プロバイダーを 1 つにし、領域ごとに makeDragArea を使います。
  • 引数は 2 つです。makeDraggable と makeDroppable は ref とオプションのオブジェクトを受け取り、ペイロードはありません。