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
yarn add @vue-dnd-kit/core@^3
pnpm add @vue-dnd-kit/core@^3
bun add @vue-dnd-kit/core@^3
| パッケージ | 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、オプション |
@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 のドキュメントは、ページ上部のバージョンメニューから引き続き見られます。
おすすめの作業順:
- パッケージをアップグレードし、プロバイダーの props の名前を変えます(後述)。
- 並べ替え可能なリストをそれぞれ
makeSortableListとmakeSortableItemに変え、そのドロップハンドラーを削除します。 - 通常のドラッグ可能な要素とドロップゾーンを移植します。
- カスタムプレビューを、オーバーレイコンポーネントかプロバイダーの
#overlayスロットに移植します。 makeConstraintAreaとmakeAutoScrollをmakeDragAreaに置き換えるか、何も使わないようにします。- v2 のための回避策を削除します:ドラッグ可能な要素の
tabindexとaria-grabbed、アイテム全体のtouch-action: none、行を囲む<TransitionGroup>、プレビューコンポーネントを囲むmarkRaw。
新旧の対応
表の中の対応なしは、v3 にその名前のものがないことを意味します。代わりに何を使うかはセルに書いてあります。
ひと目で
| v2 | v3 |
|---|---|
DnDProvider | DnDProvider。新しい props と #overlay スロットがあります。 |
makeDraggable | 自由なドラッグには makeDraggable、リストの行には makeSortableItem。 |
makeDroppable | 通常のドロップターゲットには makeDroppable、リストとツリーには makeSortableList または makeSortableTree。 |
makeSelectionArea | makeSelectionArea。 |
makeConstraintArea | makeDragArea。 |
makeAutoScroll | 不要:自動スクロールは自動です。makeDragArea かリストの autoScroll で調整します。 |
useDnDProvider | 状態には useDnD、イベントには useDnDMonitor。 |
injectionKey | useDnDContext() と provideDnDContext(app, context)。 |
DragPreview | DragPreview:今はドラッグ中のアイテム 1 つのクローンで、自作のオーバーレイ用です。 |
makeSnappedOverlayPosition、<DragPreview :grid> | snapToGrid モディファイア。 |
e.helpers.suggestSort、suggestSwap、suggestCopy、suggestRemove | リストに組み込み:並べ替えを参照。 |
プロバイダー
| v2 | v3 |
|---|---|
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' または null | useDnD().status:'idle'、'activating'、'dragging'、'dropping' または 'selecting'。'pending'(非同期のドロップ)は 'dropping' に、null は 'idle' になります。 |
pointer.value.current | useDnD().pointer、モディファイア適用後の値。どのイベントにも、開始位置からのオフセットである delta があります。 |
entities.draggingMap | useDnD().active:id、ids、items、data、type、element、rect。 |
entities.selectedSet(要素) | useDnD().selection(id):has、add、delete、toggle、set、clear、ids。 |
hovered.droppable、hovered.draggable | useDnD().over:id、data、element、rect、accepted。 |
delay.progress、distanceProgress | useDnD().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
| v2 | v3 |
|---|---|
makeDraggable(ref, options, payload) | makeDraggable(ref, options):第 3 引数はありません。リストの行は makeSortableItem(ref, { item }) を使います。 |
id、仮想リストで必要 | id:データから、ゲッターとして渡します。デフォルトは useId() から作られ、コンポーネントのインスタンスごとに新しくなります。 |
data: () => card | data:値、ref、またはゲッター。active.data と active.items[i].data で読み戻します。 |
groups: ['dev'] | type: 'dev'。ターゲットの accept と照合されます。グループを参照。 |
dragHandle: '.handle' | handle:セレクターまたは要素の ref。 |
disabled | disabled。 |
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
| v2 | v3 |
|---|---|
makeDroppable(ref, options, () => items) | makeDroppable(ref, options)。リストを持つゾーンは makeSortableList です。 |
groups | accept:'*'、型、型のリスト、または関数。 |
groupMatch: 'every'、'some' | acceptMode: 'all'、'some'。 |
events.onEnter、events.onLeave | onDragEnter、onDragLeave:それぞれ独立したオプションで、events オブジェクトはありません。 |
events.onDrop。false や Promise を返せた | onBeforeDrop が判断し、onDrop が適用します。ドロップの判断を参照。 |
events.onValidate | 残す id を返す onBeforeDrop。 |
data | data。over.data で読み戻します。 |
disabled | disabled。 |
isAllowed を返す | isAccepting。 |
isDragOver を返す:{ top, right, bottom, left, center } | isOver(真偽値)と placement:{ top, right, bottom, left }、ポインターがある側の半分。center はありません。 |
| — | 新規:id、label、priority、overOverlay、overDropAnimation、external(コンピューターからのファイル)。 |
ドロップイベント
v2 IDragEvent | v3 |
|---|---|
e.draggedItems | e.active.items:それぞれ { id, item, data, label, type } で、掴んだアイテムが先頭です。リストは e.items と e.keys も受け取ります。 |
e.draggedItems[0].data | e.active.data。 |
e.draggedItems[i].index、.items | リスト:e.from、{ list, index }。 |
e.dropZone:items、placement、data | e.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.sameList | e.sameList。 |
e.helpers.insertAt、removeAt、removeIndexes、swapAt、getInsertSide、isAtZoneStart、isAtZoneEnd | 対応なし。リストには不要です。通常のドロップターゲットでは、データを好きなように変更してください。 |
e.provider | useDnD()。 |
| — | 新規:e.pointer、e.delta、e.position(ボードにドロップしたアイテムをどこに置くか)、e.mode。 |
並べ替え
| v2 | v3 |
|---|---|
() => 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 と applyToTree | makeSortableTree:コンテナーは 1 つで、行は深さとともにフラットに描画されます。ツリーを参照。 |
v-model を使ったネストしたグループ(グループごとに droppable) | リストの中のリスト。すべて同じ group で、それぞれ items: defineModel()。 |
仮想リスト:すべてのドラッグ可能な要素に id | ドラフトを仮想化ライブラリに渡し、行に id: () => props.item.id。仮想リストを参照。 |
移行前後のコードは後述します。リストのすべては並べ替え可能なリストとアイテムを共有するリストにあります。
グループ
v2 にはすべてに使う groups オプションが 1 つありました。v3 はそれを 2 つに分けます:
| v2 | v3 |
|---|---|
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'。 |
リストのゾーンとその行の groups | makeSortableList の group: 'board'。group: { name, pull, put } で、何が出ていけて何が入れるかを指定します。 |
makeSelectionArea の groups | accept。 |
group のないリストはそれだけで 1 つのグループになり、ほかのどのリストともアイテムをやり取りしません。v2 では、groups のないゾーンはすべてを受け入れていました。ターゲットが受け入れるドラッグを参照してください。
オーバーレイ
| v2 | v3 |
|---|---|
プロバイダーの中に描画され、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>、makeSnappedOverlayPosition | snapToGrid モディファイア。見た目だけでなく、ターゲットを探す位置とドロップの 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)。 |
| ドロップでプレビューが消える | 自分の位置に着地するか、キャンセルで元の位置に戻ります。 |
詳しくはオーバーレイのガイドにあります。
選択とマルチドラッグ
| v2 | v3 |
|---|---|
makeDraggable が返す selected | makeDraggable と makeSortableItem が返す selected。 |
entities.selectedSet、要素のセット | useDnD().selection、id がキー。 |
makeSelectionArea:デフォルトで Ctrl(ControlLeft)を押したまま | デフォルトではキー不要:エリアの空いている場所を押すと選択枠を描きます。modifier: { keys: ['Control'], method: 'every' } で Ctrl に戻せます。 |
KeyboardEvent.code としての modifier.keys | KeyboardEvent.key('Control'、'Shift')または .code('ControlLeft')。 |
strategy: 'toggle'、'select' | 同じです。 |
groups | accept。 |
events.onSelected(elements) | onSelected(ids)。 |
disabled | 同じです。 |
選択をまとめて動かす suggestSort() | リストが動かします。multiple: 'group'(デフォルト)、'gaps'、'stack' で、ほかのアイテムが途中でどうするかを決めます。 |
混在した選択のための onValidate | acceptMode: 'some' と、id を返す onBeforeDrop。一部だけを受け入れるを参照。 |
詳しくはマルチドラッグと選択にあります。
軸、境界、スナップ
| v2 | v3 |
|---|---|
makeConstraintArea(el, { axis: 'x' }) | makeDragArea(el, { axis: 'x' })。その中で始まるドラッグに効きます。axis はドラッグ可能な要素、リスト、ツリー、アイテムにも使えます。 |
axis: 'both' | axis を省きます。 |
restrictToArea: true | makeDragArea(el, { restrictToArea: true })、またはモディファイアの restrictToParent と restrictToElement(el)。 |
| ルールを分けるための、領域ごとのプロバイダー | プロバイダーは 1 つで、領域ごとに makeDragArea。そのアイテムはどこへでも動けます。 |
<DragPreview :grid="20">、makeSnappedOverlayPosition | snapToGrid(20) または snapToGrid({ x, y, origin, smooth }) を、プロバイダー、エリア、リスト、ドラッグ可能な要素の modifiers に入れます。 |
| — | 新規:restrictToWindowEdges、restrictToFirstScrollableAncestor、snapCenterToCursor、そして自作のモディファイア。 |
詳しくは軸、境界、グリッドにあります。
自動スクロール
| v2 | v3 |
|---|---|
<DnDProvider :autoScrollViewport="true">、デフォルトでオフ | デフォルトでオン。ウィンドウとすべてのスクロールコンテナーが対象です。:auto-scroll="false" でオフにします。 |
makeAutoScroll(el, { threshold, speed }) | 不要:overflow: auto や scroll の要素はどれもスクロールします。個別に調整するには makeDragArea(el, { autoScroll: { … } }) またはリストの autoScroll。 |
px 単位、または辺ごとの threshold | threshold:コンテナーのサイズに対する割合(0.2)で、24〜96 px の範囲。辺ごとのしきい値は対応なし。 |
倍率の speed | maxSpeed(1 フレームあたり 25 px)と acceleration(10)。 |
disabled: true | autoScroll: false。 |
isScrolling | isAutoScrolling。makeDragArea または useDnD() から。 |
キーボードとアクセシビリティ
| v2 | v3 |
|---|---|
すべてのドラッグ可能な要素に 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 が言い回しを変えます。 |
詳しくはアクセシビリティにあります。
イベントとドロップの判断
| v2 | v3 |
|---|---|
ドラッグ可能な要素の events.onSelfDragStart | useDnDMonitor({ onDragStart }) で active.ids を確認します。クラスを付けるだけなら isDragging で十分です。 |
onSelfDragMove | モニターの onDragMove。 |
onSelfDragEnd、ドロップでもキャンセルでも | モニターの onDragEnd(ドロップ)と onDragCancel。onDragSettle はオーバーレイが着地した後に両方を受け取ります。 |
onSelfDragCancel | モニターの onDragCancel。 |
ドラッグ可能な要素の onHover、onLeave、そしてその上を通るドラッグの onDragStart… | その要素に makeDroppable:onDragEnter、onDragLeave。リストの行には何も要りません。 |
droppable の events.onEnter、onLeave | onDragEnter、onDragLeave。 |
droppable の events.onDrop | onBeforeDrop が判断し、次に onDrop が適用します。onDrop は同期で、戻り値は無視されます。 |
droppable の events.onValidate | acceptMode: '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 にあります。
型
| v2 | v3 |
|---|---|
IDragEvent | DnDEvent(ドロップターゲット、モニター)、SortableDropEvent(リスト)、DnDEndEvent(onDragEnd)。 |
IDragItem | DragItem。 |
IDropZoneContext | Over。 |
IHoveredDraggableContext | 対応なし。 |
ISuggestSortResult、ISuggestSwapResult、ISuggestCopyResult、ISuggestRemoveResult | SortableDropEvent:result、to、mode、partner。 |
IDropValidateEvent | onBeforeDrop の中の DnDEvent。 |
IPlacement | center のない Placement。 |
ICoordinates | Coordinates。 |
IDragActivationOptions | ActivationOptions。 |
IAutoScrollOptions | AutoScrollOptions。 |
TDnDState | DnDStatus。 |
IDnDProviderExternal | DnDState。useDnD() が返すものです。 |
IDraggableEvents、IDroppableEvents | DraggableOptions、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 2 | peer 依存 @vue-dnd-kit/core 3、Nuxt 4。 |
nuxt.config.ts の modules: ['@vue-dnd-kit/nuxt'] はそのままです。インストールを参照してください。
移行前と移行後
ドラッグ可能な要素とドロップゾーン
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>
- カードは
tabindex、role、aria-*属性を自動で受け取ります。<button>のハンドルはフォーカスでき、キーボードドラッグはそこから始まります。 groupsは、ドラッグ可能な要素ではtypeに、ターゲットではacceptになりました。コールバックはeventsオブジェクトから外に出ました。isAcceptingのないisOverは、そのドラッグを拒否するターゲットです:そこにドロップすると元の位置に戻ります。
並べ替え可能なリスト
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>
- ドラフトである
itemsを描画します:ドラッグ中に並べ替わります。ドロップするとtasksが新しい順序を受け取り、defineModelがupdate:modelValueを発行します。 - インデックスも、ペイロードも、
onDropも、<TransitionGroup>も要りません:行は自分でスライドします。 - ドラッグ中の行は、着地する位置としてリストの中に残ります。隠すのではなく、
isPlaceholderか[data-dnd-placeholder]でスタイルを付けてください。
アイテムを共有するリスト
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>
ドロップは両方の配列に書き込みます:タスクが出ていった配列と、入った配列です。TaskItem.vue は上の並べ替え可能なリストのものと同じです。コピーを配るパレットは suggestCopy() と copyOnly フラグで作っていましたが、今はリストです:
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
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>
- コンポーネントはドラッグを props として受け取ります:
items(ドラッグ中のすべてのアイテム、掴んだものが先頭)、over、accepted、status、mode、to。読むものだけを宣言してください。リストの行では、リストのアイテムはitems[0].itemです。 - そのまま渡します。切り替えるには
shallowRefを使い、深いrefは決して使いません。
プロバイダーのスロットも同じように変わり、コンポーネントが自分で位置を決めることはなくなります:
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>
DragPreview を <Transition> で囲んでいたポップインは、@starting-style 付きの overlay-class になります:持ち上げ、傾け、落ち着かせるを参照してください。
確認してからのドロップ
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),
});
リストも同じように尋ね、Promise を使えば自作のダイアログを開けます。Promise が確定するまで、オーバーレイは離した場所で待ちます:
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
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
});
リストはこれをコードなしで行います: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 とオプションのオブジェクトを受け取り、ペイロードはありません。