Документация/API

DnDProvider

Компонент, который отвечает за драг — ввод, оверлей, выделение, автоскролл и live-регион — для всего, что отрендерено внутри него.

Редактировать страницу

Страница переведена ИИ и может содержать неточности. Читать английский оригинал

const DnDProvider: Component<DnDProviderProps>;

Управляет драгами всего, что отрендерено внутри него: вводом, оверлеем, выделением, автоскроллом и объявлениями для скринридеров (см. DnDProviderProps).

App.vue
<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. См. руководство по провайдеру.

Большинство пропсов читается тогда, когда они нужны провайдеру: измените один, и следующий драг его использует.

Live

Загружаем демо…

<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>, который находится над всем остальным на странице. Выключите переключатель, и строки, которые вы перетаскиваете, исчезнут под диалогом. Руководство по оверлею объясняет, когда это нужно.

Live

Загружаем демо…

<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[]

Модификаторы для каждого драга, применяются первыми: модификаторы областей перетаскивания, перетаскиваемых элементов и списков идут после.

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

Вибрация для драгов касанием. По умолчанию — короткая, когда драг начинается после долгого нажатия; объект добавляет другие (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-startDnDEventДраг начался (после своей активации).
drag-moveDnDEventУказатель сдвинулся во время драга (не чаще раза за кадр).
drag-overDnDEventСменилась цель под драгом.
drag-endDnDEndEventБросок состоялся и был записан.
drag-cancelDnDEventДраг отменён или бросок отклонён.
drag-resumeDnDEventonBeforeDrop ответил 'continue': броска не было, и драг продолжается.
drag-settleDnDEndEvent или 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 | undefined

onBeforeDrop ответил 'continue': драг продолжается.

О чём объявление, помимо события: для собственных текстов (на другом языке, другими словами), которые говорят то же, что и встроенные.

AnnouncedPosition

В сортируемом списке или дереве: где находятся перетаскиваемые элементы. При старте — где их взяли; во время перемещения — где они приземлились бы (вне списков — нигде); в конце — где они приземлились или куда вернулись.

{ key: Key; label?: string; position: AnnouncedPosition }

Обмен местами: элемент, с которым обменялся перетаскиваемый, и где он сейчас.

number

Сколько элементов несёт драг.

boolean

Драг копирует (group.pull: 'clone', клавиша копирования).

Место в сортируемом списке или дереве (среди его видимых строк): index считается с 0, из length.

Key

Id списка или дерева.

string

label списка — название для людей.

number

Место, начиная с 0: встроенные тексты произносят index + 1.

number

Сколько мест в списке.