const DnDProvider: Component<DnDProviderProps>;内側で描画されるすべてのもののドラッグを管理します:入力、オーバーレイ、選択、自動スクロール、スクリーンリーダーの読み上げ(DnDProviderProps を参照)。
<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
</script>
<template>
<DnDProvider :animation="{ duration: 200 }" @drag-end="save">
<RouterView />
<template #overlay="{ items, over, accepted }">
<!-- optional: a custom overlay for every drag -->
</template>
</DnDProvider>
</template>
すべてのコンポーザブルは、プロバイダーの内側に描画されるコンポーネントで実行する必要があります。目指すのはアプリ全体で 1 つのプロバイダーです:ページの各部分は makeDragArea で独自のルールを持てます。プロバイダーのガイドを参照してください。
ほとんどの props は、プロバイダーが必要とした時点で読まれます。1 つを変更すると、次のドラッグからそれが使われます。
デモを読み込み中…
<script setup lang="ts">
import { computed, ref } from 'vue';
import { DnDProvider, type ActivationOptions } from '@vue-dnd-kit/core';
import PropRow from './PropRow.vue';
import DensityList from './DensityList.vue';
const ACTIVATIONS: Record<string, ActivationOptions | undefined> = {
default: undefined,
'{ mouse: { delay: 300 } }': { mouse: { delay: 300, tolerance: 5 } },
};
const overlayFit = ref<'slot' | 'source'>('slot');
const activation = ref('default');
const keyboard = ref<'true' | 'false'>('true');
const copyModifier = ref<'Alt' | 'Shift' | 'null'>('Alt');
// The provider reads its props when it needs them: a change applies to the next drag.
const props = computed(() => ({
overlayFit: overlayFit.value,
activation: ACTIVATIONS[activation.value],
// `false` turns keyboard drags off; an object sets their keys and steps.
keyboard: keyboard.value === 'true' ? undefined : (false as const),
copyModifier: copyModifier.value === 'null' ? null : copyModifier.value,
}));
const compact = ref([
{ id: 'copy', title: 'Write copy' },
{ id: 'login', title: 'Fix login' },
]);
const roomy = ref([
{ id: 'sprint', title: 'Plan the sprint' },
{ id: 'review', title: 'Review the PR' },
]);
</script>
<template>
<DnDProvider v-bind="props">
<div class="demo">
<div class="props" role="group" aria-label="DnDProvider props">
<PropRow v-model="overlayFit" name="overlay-fit" :options="['slot', 'source']" />
<PropRow v-model="activation" name=":activation" :options="Object.keys(ACTIVATIONS)" />
<PropRow v-model="keyboard" name=":keyboard" :options="['true', 'false']" />
<PropRow v-model="copyModifier" name="copy-modifier" :options="['Alt', 'Shift', 'null']" />
</div>
<div class="lists">
<DensityList v-model="compact" title="Compact" compact />
<DensityList v-model="roomy" title="Roomy" />
</div>
</div>
</DnDProvider>
</template>
<style scoped>
/* Room for every row in one list: the lists grow downward and nothing moves under the pointer. */
.demo {
display: flex;
flex-direction: column;
align-items: center;
gap: 18px;
min-height: 430px;
}
.props {
display: grid;
grid-template-columns: auto auto;
align-items: center;
gap: 6px 14px;
max-width: 100%;
padding: 12px 14px;
border: 1px solid color-mix(in srgb, currentColor 12%, transparent);
border-radius: 12px;
background: color-mix(in srgb, currentColor 3%, Canvas);
}
.lists {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
justify-content: center;
gap: 14px;
}
@media (max-width: 480px) {
.props {
grid-template-columns: minmax(0, 1fr);
}
}
</style>オーバーレイの描画先
overlayTo はオーバーレイを body の外へ移します:ページのほかのすべてより上にある、モーダルの <dialog> の中などです。スイッチをオフにすると、ドラッグした行がダイアログの下に消えます。それが必要になる場面はオーバーレイのガイドで説明しています。
デモを読み込み中…
<script setup lang="ts">
import { computed, ref, useTemplateRef } from 'vue';
import { DnDProvider } from '@vue-dnd-kit/core';
import ColumnList from './ColumnList.vue';
const columns = ref(['Name', 'Status', 'Owner', 'Due date', 'Priority']);
const dialog = useTemplateRef<HTMLDialogElement>('dialog');
const open = ref(false);
const intoDialog = ref(true);
// A modal <dialog> sits on the top layer, above everything in <body>, the overlay
// included. While it is open, the overlay renders inside it.
const overlayTo = computed(() => (open.value && intoDialog.value ? '#columns-dialog' : 'body'));
const show = () => {
dialog.value?.showModal();
open.value = true;
};
</script>
<template>
<DnDProvider :overlay-to="overlayTo">
<div class="sheet">
<table>
<thead>
<tr>
<th v-for="column in columns" :key="column">{{ column }}</th>
</tr>
</thead>
<tbody>
<tr v-for="row in 2" :key="row">
<td v-for="column in columns" :key="column"><span class="skeleton" /></td>
</tr>
</tbody>
</table>
<button type="button" class="primary" @click="show">Edit columns</button>
</div>
<dialog id="columns-dialog" ref="dialog" @close="open = false">
<h3>Columns</h3>
<p>Drag to change their order.</p>
<ColumnList v-model:columns="columns" />
<footer>
<label class="switch">
<input v-model="intoDialog" type="checkbox" />
Overlay inside the dialog
</label>
<button type="button" class="primary" @click="dialog?.close()">Done</button>
</footer>
</dialog>
</DnDProvider>
</template>
<style scoped>
.sheet {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
width: 100%;
}
table {
width: min(100%, 520px);
border-collapse: collapse;
font-size: 12px;
}
th {
padding: 8px;
border-bottom: 1px solid color-mix(in srgb, currentColor 15%, transparent);
font-weight: 600;
text-align: left;
white-space: nowrap;
}
td {
padding: 10px 8px;
border-bottom: 1px solid color-mix(in srgb, currentColor 8%, transparent);
}
.skeleton {
display: block;
height: 8px;
border-radius: 4px;
background: color-mix(in srgb, currentColor 10%, transparent);
}
button {
padding: 7px 14px;
border: 0;
border-radius: 8px;
font: 600 12px/1.2 system-ui, sans-serif;
cursor: pointer;
}
.primary {
background: #42b883;
color: #04130c;
}
button:focus-visible {
outline: 2px solid #42b883;
outline-offset: 2px;
}
/* No transform on the dialog: it would move the overlay's fixed position. */
dialog {
width: min(340px, calc(100vw - 32px));
box-sizing: border-box;
margin: auto;
padding: 18px;
border: 1px solid color-mix(in srgb, currentColor 14%, transparent);
border-radius: 16px;
background: Canvas;
color: CanvasText;
box-shadow: 0 30px 60px -20px rgb(0 0 0 / 0.6);
}
dialog::backdrop {
background: rgb(0 0 0 / 0.45);
}
h3 {
margin: 0;
font-size: 15px;
}
p {
margin: 4px 0 14px;
font-size: 12px;
opacity: 0.6;
}
footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 16px;
}
.switch {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
}
.switch input {
accent-color: #42b883;
}
</style>Props
DnDProvider の props。ほとんどはプロバイダーが必要とするときに読み取られます:変更すると、次のドラッグからその値が使われます。
CollisionDetectionドラッグの下にあるターゲットの選び方。デフォルトは pointerFirst。別の組み込みのもの(closestCorners)や独自のものも使えます。
Modifier[]すべてのドラッグに対するモディファイアで、最初に適用されます:ドラッグエリア、ドラッグ可能な要素、リストのものはその後です。
LayoutAnimation並べ替え可能なリストのデフォルトの FLIP アニメーション。
DropAnimationドロップ時にオーバーレイが着地し、キャンセル時に元の位置に戻る仕方。より近くで設定されていないときに使われます。ドロップターゲットの overDropAnimation が最初、次にドラッグされたアイテム自身のもの(またはそのリストのもの)、最後にこれです。
AutoAnimation並べ替え可能なリストとツリーのデフォルトの animateChanges:ドラッグ以外によるアイテムの変更。デフォルトはオフ。
boolean | AutoScrollOptionsドラッグが端に近づくと、コンテナーとページをスクロールします。デフォルトは true。オプションで調整でき、false でオフになります。リスト、ツリー、ドラッグエリアで上書きできます。
ActivationOptions押下がドラッグになる前に満たすべき条件。ポインターの種類ごとに指定します(デフォルトは ActivationOptions を参照)。ドラッグエリア、リスト、ツリー、アイテム、ドラッグ可能な要素で上書きできます。
KeyboardOptions | falseキーボードドラッグ:そのキーとステップ。false でオフになりますが、Escape はどのドラッグも引き続きキャンセルします。
Sensor[]何が入力をドラッグに変換するか。デフォルトは [pointerSensor, keyboardSensor]:どちらかを外す、ラップする、置き換える、あるいは独自のもの(defineSensor)を追加できます。キーボードセンサーは Escape でどのドラッグもキャンセルする役割も担います。keyboard: false はそのドラッグをオフにしますが、この役割は残します。リストはいつでも変更できます:リストから外れたセンサーは停止し、そのドラッグはキャンセルされます。
boolean | HapticsOptionsタッチドラッグの振動。デフォルト:長押しの後にドラッグが始まったときに短く 1 回。オブジェクトでほかの振動(enter、drop、refuse)を追加でき、false ですべてオフにします。
string | null移動をコピーに変えるキー(KeyboardEvent.key)。デフォルトは 'Alt'。null で無効にします。
string | HTMLElementオーバーレイの描画先:セレクターまたは要素。デフォルトは 'body'。モーダルの <dialog> はページのほかの部分より上に表示されます:その中でドラッグするには、オーバーレイをその中に描画する必要があります。
'slot' | 'source'ドラッグ中のオーバーレイのサイズ。'slot'(デフォルト):アイテムが着地する位置に追従するため、別のリストの上ではそのリストのアイテムのサイズになり、ドロップ時にサイズ変更が不要になります。ポインターを中心にサイズを変え、ポインターはボックス内の同じ比率の位置に留まります。'source':ドラッグ中の要素のサイズを保ちます。デフォルトのオーバーレイのコンテンツは新しいサイズでレイアウトし直され、拡大縮小されることはありません。
number | 'all'マルチドラッグ:持ち上げた時点でクローンを作る、ドラッグ中のアイテムの数です。対象は掴んだアイテムと、ドロップ順でその隣にあるほかのアイテムです。デフォルトは 5。'all' は描画されているすべてのアイテムをクローンします。デフォルトのオーバーレイはクローンを表示し、残りを数えます(+N)。独自のレイアウトでは、クローンのないアイテムに対して DragPreview がスロットを描画します。すべてのクローンが飛びます:キャンセル時は自分のアイテムへ戻り、ドロップ時は自分の位置へ、ドロップ先のゾーンへは 1 つずつ順に入ります。クローンはまとめて作られ、スナップショットの処理 1 回につきレイアウトは全体で 1 回ですが、それぞれスタイルの読み取りにコストがかかります:遅いスマートフォンでの長いアイテムに注意してください。
boolean | TransitionPropsドラッグ中にオーバーレイがあるコンポーネントから別のコンポーネントへ変わる仕方(ドロップターゲットの overOverlay に切り替わる、overlay の ref が切り替えられる):true(デフォルト)は 150 ms でクロスフェードし、false はすぐに入れ替え、オブジェクトはコンテンツのルートを囲む Vue の <Transition> の props です(name、mode、GSAP や motion 用の JS フック)。mode: 'out-in' がないと両方が同時に表示されます:去る側に position: absolute を付けて(.<name>-leave-active)、ボックスの中で重なるようにしてください。
string | Record<string, boolean> | Array<string | Record<string, boolean>>オーバーレイのコンテンツ([data-dnd-overlay-content])のクラス。オーバーレイのボックスを満たす要素で、ライブラリがボックスを動かしたりサイズを変えたりする間、全体としてアニメーションさせるのはこの要素です。これに付けたトランジションは、ドラッグの持ち上げ時に @starting-style から実行され、着地時に逆向きに実行されます([data-dnd-overlay='dropping']):ドロップはその完了を待ちます。通常の CSS は <style scoped> の外で(または :global(...) で)、Tailwind は starting: と in-data-[dnd-overlay=dragging]: でこの要素を指定できます。
Partial<Announcements> | false一部のイベント用の独自テキスト(Announcements を参照)。false でライブリージョンを無音にします。
stringキーボードでのドラッグ方法。スクリーンリーダーがすべてのドラッグ可能な要素について読み上げます(その aria-describedby)。デフォルト:Space、Enter、矢印キー、Escape についての英語の説明。
booleanデフォルトの見た目を挿入します:詳細度ゼロでレイヤー化された CSS で、プレースホルダーと、マルチドラッグがその場に残すアイテムを薄くします(opacity: 0.4)。デフォルトは true。false なら見た目はあなたに任されます。ドラッグに必要なものはどちらの場合も挿入されます:ドラッグ中のテキスト選択とスクロールアンカリングの無効化(仮想リストはこれに依存します)、ドラッグカーソル(--dnd-cursor)、iOS で長押ししたときのリンクのコールアウトの無効化。
イベント
| イベント | ペイロード | タイミング |
|---|---|---|
drag-start | DnDEvent | ドラッグが始まった(アクティベーションの後)。 |
drag-move | DnDEvent | ドラッグ中にポインターが動いた(最大で 1 フレームに 1 回)。 |
drag-over | DnDEvent | ドラッグの下のターゲットが変わった。 |
drag-end | DnDEndEvent | ドロップが成立し、書き込まれた。 |
drag-cancel | DnDEvent | ドラッグがキャンセルされた、またはドロップが拒否された。 |
drag-resume | DnDEvent | onBeforeDrop が 'continue' を返した:ドロップは起きず、ドラッグが続く。 |
drag-settle | DnDEndEvent または DnDEvent | ドラッグのオーバーレイが着地した:drag-end または drag-cancel が受け取ったもの。 |
同じイベントに onBeforeDrop を加えたものが、useDnDMonitor を使えばプロバイダーの下のどこでも利用できます。ペイロードはイベントのページにあります。
#overlay スロット
独自のオーバーレイコンポーネントを持たないすべてのドラッグについて、ポインターに追従するものを描画します(オーバーレイのガイドを参照)。何も描画しない場合はクローンにフォールバックします。
オーバーレイが表示するもの:プロバイダーの #overlay スロットと、オーバーレイコンポーネント(ドラッグ可能な要素、並べ替え可能なアイテム、リスト、ツリーの overlay オプション)の props。
Active現在のドラッグ。
DragItem[]ドラッグ中のすべてのアイテム。主となるものが先頭です:マルチドラッグ全体で 1 つのオーバーレイ。
Over | nullドラッグの下のドロップターゲット。決まったドロップがアニメーションしている間は、着地先のターゲット(ドロップ時点のもの)。キャンセルされたドラッグが元の位置に戻る間は null。
booleanover がドラッグ中のアイテムを受け入れるかどうか。何もない上では false。
DnDStatus'dragging'、そしてオーバーレイが着地する(または元の位置に戻る)間は 'dropping'。
DragMode今ドロップしたら何が起こるか(DnDEvent.mode を参照):たとえばコピー中はプラス記号を表示できます。
DropDestination | null今ドロップしたらドラッグ中のアイテムが置かれる、並べ替え可能なリストまたはツリーの中の場所:たとえば、ツリーのドラッグが入るフォルダーに応じてオーバーレイに色を付けます。どのターゲットの外でも枠の場所で、dropOutside: 'placeholder'(デフォルト)はアイテムをそこに残します。ドロップがアイテムをどのリストにも置かない場合は null:通常のドロップターゲットの上、アイテムを拒否するリストの上、dropOutside: 'cancel' または 'remove' でのターゲットの外。決まったドロップがアニメーションしている間は、着地する場所。
キーボード
キーボードドラッグ(プロバイダーの keyboard):そのキーと、自由なドラッグのステップ。
自動スクロール
ドラッグが端に近づいたときのコンテナーのスクロールの仕方(プロバイダーの autoScroll)。
number端の領域。コンテナーのサイズに対する割合です。デフォルトは 0.2(24〜96px の範囲に制限)。
number最大速度。16ms のフレームあたりの px。
numberポインターがコンテナーの端にあるときの速度(1 フレームあたりの px):端の領域の内側では遅く、端を越えると速くなり、最大で maxSpeed です。デフォルト 10。
触覚フィードバック
タッチドラッグがいつ振動するか。振動するのはタッチで始まったドラッグだけです。ブラウザーが振動できない(iOS Safari)、またはブロックしている(まだページがタップされていない)場合は何も起きません。
振動(navigator.vibrate):ms 単位の長さ、またはオン/オフのパターン。true はデフォルトの振動(10ms。refuse では [20, 40, 20])、false または 0 は振動なし。
type HapticPattern = boolean | number | number[]読み上げ
イベントごとのスクリーンリーダー用テキスト。組み込みのテキストを使うには undefined を、何も読み上げないなら '' を返します。context には、並べ替え可能なリストの組み込みテキストが読み上げる位置が入っています。
テキストはプロバイダーのライブリージョンが読み上げます(移動は、ドラッグが 500 ms 止まった時点で)。undefined はそのイベントを次のソースに任せます(並べ替え位置のような連携機能のテキスト、次にデフォルト)。持ち上げた時点で見つかったターゲットについては移動を読み上げません:まだ何も動いていないからです。
(event: DnDEvent, context: AnnouncementContext) => string | undefinedドラッグが始まりました。
(event: DnDEvent, context: AnnouncementContext) => string | undefinedドラッグが別のターゲットの上、またはリスト内の別の位置に移りました(500 ms 止まった時点で)。
(event: DnDEndEvent, context: AnnouncementContext) => string | undefinedドロップが確定したか、ドラッグがどのターゲットの上でもない場所で終わりました。
(event: DnDEvent, context: AnnouncementContext) => string | undefinedドラッグがキャンセルされたか、ドロップが拒否されました。
(event: DnDEvent, context: AnnouncementContext) => string | undefinedonBeforeDrop が 'continue' を返した:ドラッグが続きます。
読み上げがイベント以外に何について伝えるか:組み込みのテキストと同じ内容を伝える独自のテキスト(別の言語、別の言い回し)のためのものです。
AnnouncedPosition並べ替え可能なリストまたはツリーで:ドラッグ中のアイテムがある位置。開始時は持ち上げた位置、移動中は着地する予定の位置(リストの外ではなし)、終了時は着地した位置、または戻った位置です。
{ key: Key; label?: string; position: AnnouncedPosition }入れ替え:ドラッグ中のアイテムと入れ替わるアイテムと、その現在の位置。
numberドラッグが運んでいるアイテムの数。
booleanドラッグがコピーであること(group.pull: 'clone'、コピーのモディファイアキー)。