Документация/API
DnDProvider
Компонент, который отвечает за драг — ввод, оверлей, выделение, автоскролл и live-регион — для всего, что отрендерено внутри него.
Страница переведена ИИ и может содержать неточности. Читать английский оригинал
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>
Каждый композабл должен вызываться в компоненте, отрендеренном внутри провайдера. Цель —
один провайдер на всё приложение: части страницы получают свои правила через
makeDragArea. См. руководство по провайдеру.
Большинство пропсов читается тогда, когда они нужны провайдеру: измените один, и следующий драг его использует.
Загружаем демо…
<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>Пропсы
Пропсы DnDProvider. Большинство читается тогда, когда они нужны провайдеру: измените проп — и следующий драг его использует.
CollisionDetectionКак выбирается цель под драгом. По умолчанию pointerFirst; другая встроенная функция (closestCorners) или ваша собственная.
Modifier[]Модификаторы для каждого драга, применяются первыми: модификаторы областей перетаскивания, перетаскиваемых элементов и списков идут после.
LayoutAnimationFLIP-анимация по умолчанию для сортируемых списков.
DropAnimationКак оверлей приземляется при броске и летит обратно при отмене, если ничто ближе не задаёт свою: сначала идёт overDropAnimation цели броска, затем собственная анимация перетаскиваемого элемента (или его списка), затем эта.
AutoAnimationanimateChanges по умолчанию для сортируемых списков и деревьев: изменения их элементов, сделанные не драгом. По умолчанию выключено.
boolean | AutoScrollOptionsПрокручивает контейнеры и страницу, когда драг приближается к их краям. По умолчанию true; опции настраивают его, false выключает. Списки, деревья и области перетаскивания могут его переопределить.
ActivationOptionsЧто должно произойти с нажатием, прежде чем оно станет драгом, для каждого типа указателя (значения по умолчанию — в ActivationOptions). Области перетаскивания, списки, деревья, элементы списков и перетаскиваемые элементы могут его переопределить.
KeyboardOptions | falseПеретаскивание с клавиатуры: его клавиши и шаг. false выключает его; Escape по-прежнему отменяет любой драг.
Sensor[]Что превращает ввод в драги. По умолчанию [pointerSensor, keyboardSensor]: уберите один, оберните или замените его либо добавьте свой (defineSensor). Сенсор клавиатуры также отменяет любой драг по Escape; keyboard: false выключает его драги, но это оставляет. Список может меняться в любой момент: сенсоры, которые из него ушли, останавливаются и отменяют свой драг.
boolean | HapticsOptionsВибрация для драгов касанием. По умолчанию — короткая, когда драг начинается после долгого нажатия; объект добавляет другие (enter, drop, refuse); false выключает все.
string | nullКлавиша (KeyboardEvent.key), которая превращает перемещение в копирование. По умолчанию 'Alt'; null отключает.
string | HTMLElementКуда рендерится оверлей: селектор или элемент. По умолчанию 'body'. Модальный <dialog> находится поверх остальной страницы: драгам внутри него оверлей нужен там.
'slot' | 'source'Размер оверлея во время драга. 'slot' (по умолчанию): он следует за слотом, куда приземлится элемент, поэтому над другим списком принимает размер его элементов, и при броске размер менять не нужно. Он меняет размер вокруг указателя, который остаётся в той же доле блока. 'source': оверлей сохраняет размер перетаскиваемого элемента. Содержимое стандартного оверлея заново раскладывается под новый размер и никогда не масштабируется.
number | 'all'Мульти-драг: сколько перетаскиваемых элементов получают клон при захвате — взятый и соседние с ним в порядке броска. По умолчанию 5; 'all' клонирует все, что отрендерены. Стандартный оверлей показывает клоны и считает остальные (+N); в своей раскладке DragPreview рендерит свой слот для элемента без клона. Каждый клон летит: обратно к своему элементу при отмене, на свой слот при броске, а в зону, куда идёт бросок, — один за другим. Клоны снимаются вместе, с одним layout на весь проход снятия, но каждый стоит чтения его стилей: помните о длинных элементах на медленных телефонах.
boolean | TransitionPropsКак оверлей меняется с одного компонента на другой во время драга (его подменяет overOverlay цели, переключается ref overlay): true (по умолчанию) — кроссфейд за 150 мс, false — мгновенная смена, а объект — это пропсы Vue <Transition> вокруг корня содержимого (name, mode, JS-хуки для GSAP или motion). Без mode: 'out-in' оба видны одновременно: дайте уходящему position: absolute (.<name>-leave-active), чтобы они наложились друг на друга в блоке.
string | Record<string, boolean> | Array<string | Record<string, boolean>>Классы для содержимого оверлея ([data-dnd-overlay-content]) — элемента, который заполняет блок оверлея: именно его анимируют целиком, пока библиотека двигает блок и задаёт его размер. CSS-переход на нём запускается от @starting-style при захвате и идёт обратно, когда оверлей приземляется ([data-dnd-overlay='dropping']): бросок его ждёт. Обычный CSS достаёт до него вне <style scoped> (или через :global(...)), Tailwind — через starting: и in-data-[dnd-overlay=dragging]:.
Partial<Announcements> | falseСвои тексты для некоторых событий (см. Announcements); false заставляет live-регион молчать.
stringКак перетаскивать с клавиатуры — скринридеры зачитывают это для каждого перетаскиваемого элемента (его aria-describedby). По умолчанию: инструкции на английском для Space, Enter, стрелок и Escape.
booleanВнедрять внешний вид по умолчанию: CSS в слоях с нулевой специфичностью, который приглушает плейсхолдеры и элементы, оставленные мульти-драгом на месте (opacity: 0.4). По умолчанию true; при false внешний вид остаётся за вами. То, что нужно драгам, внедряется в любом случае: без выделения текста и без scroll anchoring во время драга (на это полагаются виртуальные списки), курсор драга (--dnd-cursor) и без всплывающего меню ссылки при долгом нажатии на iOS.
События
| Событие | Данные | Когда |
|---|---|---|
drag-start | DnDEvent | Драг начался (после своей активации). |
drag-move | DnDEvent | Указатель сдвинулся во время драга (не чаще раза за кадр). |
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 у перетаскиваемых элементов, элементов сортируемых списков, списков и деревьев).
ActiveДраг.
DragItem[]Все перетаскиваемые элементы, основной — первым: один оверлей на весь мульти-драг.
Over | nullЦель под драгом. Пока решённый бросок анимируется — цель, на которую он приземляется (какой она была в момент броска); null, пока отменённый драг улетает обратно.
booleanПринимает ли over перетаскиваемые элементы; false, если под драгом ничего нет.
DnDStatus'dragging', затем 'dropping', пока оверлей приземляется (или летит обратно).
DragModeЧто сделал бы бросок сейчас (см. DnDEvent.mode): например, чтобы показать плюс, пока он копирует.
DropDestination | nullКуда бросок положил бы перетаскиваемые элементы сейчас, в сортируемом списке или дереве: например, окрасьте оверлей по папке, в которую попал бы драг в дереве. Вне всех целей — место слота, где их оставляет dropOutside: 'placeholder' (по умолчанию). null, когда бросок не положил бы их ни в какой список: над обычной целью броска, над списком, который их отклоняет, вне целей с dropOutside: 'cancel' или 'remove'. Пока решённый бросок анимируется — место, куда он приземляется.
Клавиатура
Перетаскивание с клавиатуры (keyboard провайдера): его клавиши и шаг свободных драгов.
{ start?: string[]; drop?: string[]; cancel?: string[] }Клавиши, которые берут (start), бросают и отменяют, — как KeyboardEvent.code ('Space', 'KeyW') или .key ('Enter', 'w'). По умолчанию: Space и Enter берут и бросают, Escape отменяет. Стрелки всегда двигают.
numberСколько пикселей проходит одно нажатие стрелки при свободном (не сортирующем) перетаскивании. По умолчанию 25.
Автоскролл
Как прокручиваются контейнеры, когда драг приближается к их краям (autoScroll провайдера).
numberЗона у края как доля размера контейнера. По умолчанию 0.2 (в пределах 24–96px).
numberМаксимальная скорость в px за кадр 16 мс.
numberСкорость в px за кадр, когда указатель на краю контейнера: медленнее внутри краевой зоны, быстрее за краем, вплоть до maxSpeed. По умолчанию 10.
Вибрация
Когда вибрируют драги касанием. Вибрируют только драги, начатые касанием; если браузер не умеет вибрировать (iOS Safari) или блокирует вибрацию (по странице ещё не было тапа), ничего не происходит.
HapticPatternДраг начинается после долгого нажатия. По умолчанию true.
HapticPatternДраг входит в цель (список, зону), которая его принимает. По умолчанию false.
HapticPatternБросок проходит. По умолчанию false.
HapticPatternБросок отклонён (целью или onBeforeDrop), и элементы улетают обратно. По умолчанию false.
Вибрация (navigator.vibrate): её длительность в мс или паттерн «вкл/выкл». true — вибрация по умолчанию (10 мс; [20, 40, 20] для refuse), false или 0 — без вибрации.
type HapticPattern = boolean | number | number[]Объявления
Тексты для скринридеров, по событиям. Верните undefined для встроенного текста, '' — чтобы не озвучивать ничего. context содержит позиции, которые озвучивают встроенные тексты сортируемых списков.
Текст произносит live-регион провайдера (перемещения — когда драг замирает на 500 мс); undefined оставляет событие следующему источнику (текстам интеграций, например позициям в сортируемых списках, затем текстам по умолчанию). Перемещение не озвучивается для цели, найденной при захвате: ничего ещё не сдвинулось.
(event: DnDEvent, context: AnnouncementContext) => string | undefinedДраг начался.
(event: DnDEvent, context: AnnouncementContext) => string | undefinedДраг перешёл на другую цель или на другое место в списке (когда он замирает на 500 мс).
(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', клавиша копирования).