Documentação/Guias
O provider
DnDProvider controla todo arraste abaixo dele. Ele encontra o alvo, inicia arrastes, rola a página e conta ao useDnD e ao useDnDMonitor tudo o que acontece.
Esta página foi traduzida por IA e pode conter imprecisões. Ler o original em inglês
Um provider para o app
<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
</script>
<template>
<DnDProvider>
<RouterView />
</DnDProvider>
</template>
Coloque um DnDProvider lá no alto, no App.vue ou em um layout, e esqueça dele. Todo
composable (makeSortableList, makeDraggable, useDnD…) procura o provider mais próximo
acima do componente em que roda.
<DnDProvider> lança
No <DnDProvider> found above this component: o inject do Vue só enxerga ancestrais.O que ele controla
| Parte | O que o provider faz |
|---|---|
| Entrada | Transforma a entrada por ponteiro, toque e teclado em arrastes (sensors, activation), um arraste por vez. |
| Encontrar o alvo | Testa quais alvos de drop estão sob o arraste (collision) e avisa a eles. |
| O overlay | Renderiza a caixa que segue o ponteiro, teleportada para o body, e a faz pousar no drop. |
| A seleção | Um único conjunto de ids selecionados, compartilhado por todas as listas e draggables abaixo dele. |
| Rolagem automática | Rola os contêineres e a janela quando um arraste chega perto das bordas deles. |
| A live region | Anuncia aos leitores de tela os inícios de arraste, os movimentos, os drops e os cancelamentos. |
Ele não renderiza mais nada: a sua marcação fica como está.
Opções em resumo
Toda opção tem um padrão que funciona, então em geral o provider não recebe nenhuma. O provider
lê as props quando precisa delas: as demos abaixo trocam collision, activation e
autoScroll ao vivo.
| Prop | Padrão | O que define |
|---|---|---|
collision | pointerFirst | Como o alvo sob o arraste é escolhido. Abaixo. |
activation | mouse 4 px, toque 200 ms | Quando uma pressão vira arraste, por tipo de ponteiro. Abaixo. |
autoScroll | ligado | Rolagem nas bordas: { threshold, maxSpeed, acceleration } ou false. Abaixo. |
haptics | uma vibração curta ao pegar por toque | Vibrações para arrastes por toque, ou false. Abaixo. |
copyModifier | 'Alt' | Tecla que, segurada durante o arraste, transforma um movimento em cópia. |
modifiers | [] | Regras de movimento para todo arraste: veja restrições. |
animation / dropAnimation | ligado | Itens deslizando durante a ordenação / o overlay pousando: veja animações. |
animateChanges | desligado | Padrão para listas e árvores: animar mudanças nos dados que nenhum arraste fez. |
keyboard | Space / Enter, Escape | Teclas e passo do teclado; false desliga os arrastes por teclado. Veja acessibilidade. |
sensors | ponteiro + teclado | O que transforma a entrada em arrastes: veja sensores. |
overlayTo | 'body' | Para onde o overlay é teleportado. |
overlayFit | 'slot' | Tamanho do overlay: o da posição em que ele vai pousar, ou o da origem. Veja o overlay. |
overlayTransition | um crossfade | Como o overlay muda de um componente para outro durante o arraste: false, ou props de <Transition>. Veja alvos que mudam o overlay. |
overlayClass | — | Classes para o conteúdo do overlay ([data-dnd-overlay-content]), o elemento a animar como um todo: erguer quando o arraste começa, assentar quando ele pousa. Veja o overlay: erguer, inclinar, assentar. |
overlayPreviews | 5 | Multi-drag: quantos itens arrastados ganham um clone no início do arraste, ou 'all'. Veja o overlay padrão. |
announcements | embutidos | Textos para leitores de tela, ou false. |
screenReaderInstructions | embutido | O texto que todo draggable aponta com aria-describedby. |
defaultStyles | true | Injeta CSS de especificidade zero para os estados do arraste: veja estilização. |
A lista completa, com todas as formas, está na API do DnDProvider. O
provider também emite drag-start, drag-move, drag-over, drag-end, drag-cancel,
drag-resume e drag-settle, os mesmos eventos que o useDnDMonitor ouve.
Ler o arraste: useDnD()
Carregando a demo…
<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import Tasks from './Tasks.vue';
import StatePanel from './StatePanel.vue';
</script>
<template>
<!-- One provider: the list drags, the panel reads the drag. Neither imports the other. -->
<DnDProvider>
<div class="demo">
<Tasks />
<StatePanel />
</div>
</DnDProvider>
</template>
<style scoped>
.demo {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
justify-content: center;
gap: 24px;
}
</style>useDnD() retorna o arraste como estado reativo, em qualquer componente abaixo do provider:
import { useDnD } from '@vue-dnd-kit/core';
const { status, active, over, pointer, activation, isAutoScrolling, selection, cancel } = useDnD();
| Campo | O que contém |
|---|---|
status | 'idle', 'activating' (uma pressão esperando a ativação), 'dragging', 'dropping' (nenhum arraste em andamento, mas um overlay ainda está pousando, ou um onBeforeDrop assíncrono está decidindo), 'selecting' (um retângulo de seleção). |
active | O que está sendo arrastado: id, ids (todos os ids arrastados, o pego primeiro), items, data, type, element, rect, sensor. null quando parado. |
over | O alvo sob o arraste: id, data, element, rect e accepted. null sobre nada. |
pointer | O ponteiro em coordenadas da viewport, depois dos modificadores. |
activation.progress | De 0 a 1 enquanto uma pressão espera a ativação: desenhe um anel com ele. |
isAutoScrolling | A rolagem automática está movendo alguma coisa. |
selection | A seleção do provider: ids, has, add, delete, toggle, set, clear. Veja multi-drag. |
cancel() | Cancela o arraste em andamento (os itens voltam ao lugar), uma pressão carregando ou um retângulo de seleção. |
<script setup lang="ts">
import { computed } from 'vue';
import { useDnD } from '@vue-dnd-kit/core';
const { status, over } = useDnD();
const refused = computed(() => status.value === 'dragging' && over.value?.accepted === false);
</script>
<template>
<p v-if="refused" role="status">This list doesn't take that.</p>
</template>
Assim que um drop é decidido, active, over e pointer passam a ser null, mesmo enquanto o
overlay ainda está pousando (status vale 'dropping'). Uma UI derivada deles nunca mostra o
arraste antigo ao lado dos dados novos. Enquanto um onBeforeDrop assíncrono espera, o estado
fica como estava no momento de soltar.
O próximo arraste não espera um pouso: ele começa na hora, e status vale 'dragging'
enquanto o último overlay pousa ao redor dele. Uma pressão sobre um overlay que está pousando o
pega em pleno voo: o arraste continua de onde o overlay está. onDragSettle avisa quando o
overlay de um arraste chegou ao seu lugar.
Ouvir todo arraste: useDnDMonitor()
import { useDnDMonitor } from '@vue-dnd-kit/core';
useDnDMonitor({
onDragStart: ({ active }) => {},
onDragMove: ({ pointer, delta }) => {},
onDragOver: ({ over }) => {}, // the target changed
onBeforeDrop: (e) => 'accept', // or 'reject', 'continue', some ids; may be async
onDragEnd: ({ active, over, dropped, accepted, position }) => {},
onDragCancel: ({ active }) => {},
onDragResume: ({ active }) => {}, // onBeforeDrop answered 'continue'
onDragSettle: ({ active, over }) => {}, // its overlay has landed
});
Um monitor ouve todo arraste abaixo do seu provider, onde quer que ele comece: o painel acima registra os arrastes da lista sem importá-la. Ele para quando o componente que o chamou é desmontado.
| Evento | Quando |
|---|---|
onDragStart | Um arraste começa. |
onDragMove | O arraste se move. |
onDragOver | O alvo sob o arraste muda. |
onBeforeDrop | O arraste é solto. A resposta decide o drop: veja decidindo um drop. |
onDragEnd | O drop foi concluído. As listas ordenáveis já rodaram o onDrop delas e gravaram a nova ordem. dropped é false quando o arraste terminou fora de qualquer alvo. |
onDragCancel | Escape, um drop em um alvo que o recusa, ou um onBeforeDrop que respondeu 'reject'. |
onDragResume | onBeforeDrop respondeu 'continue': nada foi solto, e o arraste continua. |
onDragSettle | O overlay do arraste pousou (ou voltou ao lugar, ou foi pego pelo próximo arraste), com o evento que onDragEnd ou onDragCancel recebeu. Outros arrastes podem ter começado nesse meio-tempo. |
Todo evento traz active, over, pointer, delta (o deslocamento desde o início),
position (onde a caixa arrastada fica dentro de over, veja
arrastar e soltar) e, para itens de
uma lista ordenável, source. As formas completas estão na API de eventos.
onDragEnd é o lugar para o trabalho "depois do drop": salvar, limpar a seleção, analytics. O
onDrop de uma lista roda antes da gravação dela, então ainda vê a ordem antiga.Encontrar o alvo
Escolha uma função, depois arraste o alfinete ou o card sobre os alvos. Os selos mostram como a função os classifica neste quadro; o verde é o alvo.
Carregando a demo…
<script setup lang="ts">
import { computed, ref, shallowRef } from 'vue';
import {
DnDProvider,
closestCenter,
closestCorners,
pointerFirst,
pointerWithin,
rectIntersection,
type CollisionDetection,
type DnDEndEvent,
type Key,
} from '@vue-dnd-kit/core';
import Desk from './Desk.vue';
import { magnet } from './magnet';
const DETECTORS: Record<string, CollisionDetection> = {
pointerFirst,
pointerWithin,
rectIntersection,
closestCenter,
closestCorners,
magnet,
};
const names = Object.keys(DETECTORS);
const mode = ref('pointerFirst');
// The ranking of the current frame, best first: a wrapper around the chosen function
// records it, so the targets can show their rank.
const ranked = shallowRef<Key[]>([]);
const collision = computed<CollisionDetection>(() => {
const detect = DETECTORS[mode.value]!;
return (args) => (ranked.value = detect(args));
});
const result = ref('Drag the pin or the card over the targets');
const onEnd = ({ over }: DnDEndEvent) => {
ranked.value = [];
result.value = over ? `Dropped on ${over.id}` : 'Dropped on nothing';
};
const onCancel = () => {
ranked.value = [];
result.value = 'Cancelled';
};
</script>
<template>
<DnDProvider :collision="collision" @drag-end="onEnd" @drag-cancel="onCancel">
<div class="lab">
<div class="modes" role="radiogroup" aria-label="Collision detection">
<label v-for="name in names" :key="name" :class="{ on: mode === name }">
<input v-model="mode" type="radio" :value="name" />
{{ name }}
</label>
</div>
<code class="call"><DnDProvider :collision="{{ mode }}"></code>
<Desk :ranked="ranked" />
<p class="result" aria-live="polite">{{ result }}</p>
</div>
</DnDProvider>
</template>
<style scoped>
.lab {
display: flex;
flex-direction: column;
align-items: center;
gap: 14px;
}
.modes {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 2px;
max-width: 100%;
padding: 3px;
border-radius: 9px;
background: color-mix(in srgb, currentColor 7%, transparent);
}
.modes label {
padding: 5px 9px;
border-radius: 6px;
font: 12px/1.2 ui-monospace, Menlo, Consolas, monospace;
cursor: pointer;
}
.modes label.on {
background: Canvas;
box-shadow: 0 1px 3px rgb(0 0 0 / 0.25);
font-weight: 600;
}
.modes label:has(input:focus-visible) {
outline: 2px solid #42b883;
}
.modes input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.call {
max-width: 100%;
padding: 6px 10px;
overflow-x: auto;
border-radius: 8px;
background: color-mix(in srgb, currentColor 6%, transparent);
font: 11px/1.4 ui-monospace, Menlo, Consolas, monospace;
white-space: nowrap;
color: #42b883;
}
.result {
margin: 0;
font: 11px/1.4 ui-monospace, Menlo, Consolas, monospace;
opacity: 0.6;
}
</style>A cada quadro de um arraste, o provider passa para collision os alvos de drop em jogo, e ela
retorna os ids deles, do melhor para o pior. Cinco funções vêm com a biblioteca:
| Função | Classifica | Boa para |
|---|---|---|
pointerFirst | O que está sob o ponteiro; quando não há nada, o que a caixa arrastada sobrepõe. | O padrão: preciso sob o ponteiro, tolerante nos vãos. |
pointerWithin | Só o que está sob o ponteiro, o mais interno primeiro. | Alvos precisos: um vão não solta em lugar nenhum. |
rectIntersection | Todo alvo que a caixa arrastada sobrepõe: maior priority, depois o aninhamento mais profundo, depois a maior sobreposição. | Itens grandes sobre alvos pequenos, quando o ponteiro está na borda do item. |
closestCenter | Todo alvo, pela distância do centro da caixa até o centro dele. | Sempre alguém vence, mesmo longe: grades e listas de itens do mesmo tamanho. |
closestCorners | Todo alvo, pelas distâncias entre os quatro cantos. | Colunas lado a lado (kanban), alvos com alturas muito diferentes. |
Sob o ponteiro, o teste respeita o que o usuário vê: de alvos sobrepostos, só o de cima é
atingido (front, nunca back onde eles se sobrepõem), e de alvos aninhados o mais interno vem
primeiro (subfolder, depois folder).
Uma função sua
import type { CollisionDetection, Coordinates, Rect } from '@vue-dnd-kit/core';
/** How far the point is from the rect: 0 inside it. */
const gap = (rect: Rect, point: Coordinates) =>
Math.hypot(
Math.max(rect.left - point.x, 0, point.x - rect.right),
Math.max(rect.top - point.y, 0, point.y - rect.bottom),
);
/** What is under the pointer; in a gap, the nearest target within 40px. */
export const magnet: CollisionDetection = ({ hits, pointer, candidates }) => {
if (hits.length) return hits;
return candidates
.filter((candidate) => gap(candidate.rect, pointer) <= 40)
.sort((a, b) => gap(a.rect, pointer) - gap(b.rect, pointer))
.map((candidate) => candidate.id);
};
| Argumento | O que contém |
|---|---|
pointer | O ponteiro, depois dos modificadores. |
overlayRect | A caixa arrastada como é mostrada, null sem overlay. |
candidates | Todo alvo em jogo: id, rect (a parte visível dele, recortada pelos contêineres de rolagem), priority, depth (0 no topo, maior quando aninhado). |
hits | Os alvos sob o ponteiro, segundo o teste, do melhor para o pior. |
Retorne um array vazio para "nenhum alvo". Alvos desativados, alvos dentro dos itens arrastados
e alvos escondidos pelos seus contêineres de rolagem nunca chegam à função. Um alvo que recusa o
arraste continua competindo: sobre ele, over.accepted é false e o drop é recusado (um alvo
dentro de um item ordenável sai do caminho, para que a lista ordene sobre ele). A demo envolve a
função escolhida para registrar a classificação:
(args) => (ranked.value = detect(args)).
Quando uma pressão vira arraste
Uma pressão não arrasta na hora. activation define, por tipo de ponteiro (mouse, touch,
pen), o que ela espera: uma distance em px, um delay em ms (com uma tolerance além da qual
a pressão desiste, para que a página possa rolar), ou o que vier primeiro dos dois.
<DnDProvider :activation="{ mouse: { distance: 8 }, touch: { delay: 300, tolerance: 8 } }">
| Ponteiro | Padrão |
|---|---|
mouse, pen | { distance: 4 }: um clique continua sendo um clique. Uma pressão do mouse em um handle começa na hora, e continua sendo um clique se não se mover. |
touch | { delay: 200, tolerance: 5 }: um toque longo, para que as listas continuem rolando sob o dedo. |
Um tipo que você omite mantém o seu padrão. A activation do próprio draggable vence a de uma
área de arraste, que vence a do provider. Enquanto uma pressão espera,
useDnD().status é 'activating' e activation.progress vai de 0 a 1; Escape ou
useDnD().cancel() a cancela. Teste as restrições ao vivo, e desenhe um anel de toque longo a
partir do progresso, em sensores.
Rolagem automática
Arraste um canal em direção ao topo ou ao fim da lista. As faixas verdes são as zonas de borda: quanto mais fundo em uma delas, mais rápido a lista rola.
Carregando a demo…
<script setup lang="ts">
import { computed, ref } from 'vue';
import { DnDProvider } from '@vue-dnd-kit/core';
import Channels from './Channels.vue';
const OPTIONS = {
threshold: [0.1, 0.2, 0.4],
maxSpeed: [10, 25, 50],
acceleration: [4, 10, 25],
} as const;
const on = ref(true);
// The defaults: an edge zone of 20% of the container (24 to 96px), 10px a frame at the
// edge, up to 25px a frame past it.
const options = ref({ threshold: 0.2, maxSpeed: 25, acceleration: 10 });
const autoScroll = computed(() => (on.value ? options.value : false));
const call = computed(() =>
on.value
? `:auto-scroll="{ threshold: ${options.value.threshold}, maxSpeed: ${options.value.maxSpeed}, acceleration: ${options.value.acceleration} }"`
: ':auto-scroll="false"',
);
</script>
<template>
<DnDProvider :auto-scroll="autoScroll">
<div class="demo">
<div class="switches">
<label class="switch">
<input v-model="on" type="checkbox" />
autoScroll
</label>
<fieldset v-for="(values, name) in OPTIONS" :key="name" :disabled="!on">
<legend>{{ name }}</legend>
<label v-for="value in values" :key="value" :class="{ on: options[name] === value }">
<input v-model="options[name]" type="radio" :value="value" />
{{ value }}
</label>
</fieldset>
</div>
<code class="call">{{ call }}</code>
<Channels :threshold="on ? options.threshold : 0" />
</div>
</DnDProvider>
</template>
<style scoped>
.demo {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.switches {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px 14px;
}
fieldset {
display: inline-flex;
margin: 0;
padding: 3px;
border: 0;
border-radius: 9px;
background: color-mix(in srgb, currentColor 7%, transparent);
}
fieldset:disabled {
opacity: 0.4;
}
legend {
float: left;
align-self: center;
padding: 0 8px 0 6px;
font: 10px/1 ui-monospace, Menlo, Consolas, monospace;
opacity: 0.55;
}
fieldset label {
padding: 5px 9px;
border-radius: 6px;
font: 12px/1.2 ui-monospace, Menlo, Consolas, monospace;
cursor: pointer;
}
fieldset label.on {
background: Canvas;
box-shadow: 0 1px 3px rgb(0 0 0 / 0.25);
font-weight: 600;
}
fieldset label:has(input:focus-visible),
.switch:has(input:focus-visible) {
outline: 2px solid #42b883;
}
fieldset input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.switch {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 10px;
border-radius: 9px;
background: color-mix(in srgb, currentColor 7%, transparent);
font: 12px/1.2 ui-monospace, Menlo, Consolas, monospace;
cursor: pointer;
}
.switch input {
accent-color: #42b883;
}
.call {
max-width: 100%;
padding: 6px 10px;
overflow-x: auto;
border-radius: 8px;
background: color-mix(in srgb, currentColor 6%, transparent);
font: 11px/1.4 ui-monospace, Menlo, Consolas, monospace;
white-space: nowrap;
color: #42b883;
}
</style>Qualquer elemento com overflow: auto ou scroll rola sozinho, e a janela também. O provider
rola primeiro o contêiner sob o ponteiro, depois os ancestrais dele, depois a janela.
| Opção | Padrão | O que define |
|---|---|---|
threshold | 0.2 | A zona de borda, como fração do tamanho do contêiner, entre 24 e 96 px. |
acceleration | 10 | A velocidade bem na borda, em px por quadro. Ela cresce com o quadrado da profundidade dentro da zona, então o começo é lento e preciso. |
maxSpeed | 25 | A velocidade máxima. Além da borda (o ponteiro fora do contêiner), a velocidade continua crescendo até ela. |
:auto-scroll="false" desliga a rolagem. Uma direção só rola depois que o ponteiro se moveu para
aquele lado, então um item pego perto de uma borda não rola sozinho. Listas, árvores e
áreas de arraste recebem um autoScroll próprio para o seu
contêiner:
makeSortableList(list, { items: channels, autoScroll: { maxSpeed: 10 } });
Arrastes por teclado nunca rolam automaticamente: em vez disso, eles rolam o item até ficar
visível. isAutoScrolling (do useDnD() ou de uma área de arraste) é true enquanto algo rola.
Vibração e a tecla de cópia
Um arraste por toque vibra rapidamente quando começa. :haptics="false" desliga isso; um objeto
define cada vibração:
<DnDProvider :haptics="{ start: true, enter: 10, drop: 10, refuse: [20, 40, 20] }">
| Chave | Vibra quando | Padrão |
|---|---|---|
start | O toque longo vira arraste. | true (10 ms) |
enter | O arraste entra em um alvo que o aceita. | desligado |
drop | O drop é concluído. | desligado |
refuse | O drop é recusado e os itens voltam ao lugar. | desligado |
Cada uma recebe true (o padrão), uma duração em ms, um padrão liga/desliga, ou false. Só os
arrastes iniciados por toque vibram, e o Safari do iOS não consegue vibrar.
copyModifier é a tecla que, segurada durante um arraste entre listas, transforma o movimento em
cópia: 'Alt' por padrão, qualquer KeyboardEvent.key, ou null para nenhuma. A lista de onde os
itens vêm precisa de uma função clone; veja listas que compartilham itens.
Vários providers
A maioria dos apps precisa de exatamente um. Coloque a lista de desejos sob um provider próprio, e tente mover um livro de um lado para o outro: ele não consegue sair do seu provider, e o monitor externo deixa de ouvir a lista de desejos.
Carregando a demo…
<script setup lang="ts">
import { ref } from 'vue';
import { DnDProvider } from '@vue-dnd-kit/core';
import Shelf from './Shelf.vue';
import EventLog from './EventLog.vue';
const reading = ref([
{ id: 'dune', title: 'Dune', hue: 32 },
{ id: 'emma', title: 'Emma', hue: 330 },
{ id: 'ulysses', title: 'Ulysses', hue: 210 },
]);
const wishlist = ref([
{ id: 'beloved', title: 'Beloved', hue: 140 },
{ id: 'solaris', title: 'Solaris', hue: 260 },
]);
const separate = ref(false);
</script>
<template>
<DnDProvider>
<div class="demo">
<div class="modes" role="radiogroup" aria-label="Wishlist">
<label :class="{ on: !separate }"><input v-model="separate" type="radio" :value="false" />one provider</label>
<label :class="{ on: separate }"><input v-model="separate" type="radio" :value="true" />wishlist in its own</label>
</div>
<div class="columns">
<div class="column">
<Shelf id="reading" v-model="reading" title="Reading" />
<EventLog title="outer monitor" />
</div>
<!-- A provider inside a provider: its items belong to it, and drags never cross. -->
<DnDProvider v-if="separate">
<div class="column island">
<span class="tag"><DnDProvider></span>
<Shelf id="wishlist" v-model="wishlist" title="Wishlist" />
<EventLog title="inner monitor" />
</div>
</DnDProvider>
<div v-else class="column">
<Shelf id="wishlist" v-model="wishlist" title="Wishlist" />
</div>
</div>
</div>
</DnDProvider>
</template>
<style scoped>
.demo {
display: flex;
flex-direction: column;
align-items: center;
gap: 18px;
}
.modes {
display: inline-flex;
padding: 3px;
border-radius: 9px;
background: color-mix(in srgb, currentColor 7%, transparent);
}
.modes label {
padding: 5px 10px;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
}
.modes label.on {
background: Canvas;
box-shadow: 0 1px 3px rgb(0 0 0 / 0.25);
font-weight: 600;
}
.modes label:has(input:focus-visible) {
outline: 2px solid #42b883;
}
.modes input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.columns {
display: grid;
grid-template-columns: repeat(2, minmax(0, 230px));
gap: 16px;
max-width: 100%;
}
.column {
display: flex;
flex-direction: column;
gap: 12px;
min-width: 0;
}
.island {
position: relative;
margin: -10px;
padding: 10px;
border: 1.5px dashed color-mix(in srgb, #42b883 60%, transparent);
border-radius: 16px;
}
.tag {
position: absolute;
top: -9px;
right: 14px;
padding: 0 6px;
background: Canvas;
font: 10px/18px ui-monospace, Menlo, Consolas, monospace;
color: #42b883;
}
</style><DnDProvider>
<Shelf v-model="reading" />
<DnDProvider>
<Shelf v-model="wishlist" />
<EventLog /> <!-- hears the inner provider's drags only -->
</DnDProvider>
</DnDProvider>
- Os providers são independentes: um arraste nunca passa de um para outro.
- Providers aninhados são permitidos. Vence o dono mais próximo: um item pertence ao provider mais próximo acima dele, e o provider externo o ignora.
- Cada provider tem a sua própria seleção, overlay, live region e monitores.
Um segundo provider só faz sentido para uma parte da página que precisa das suas próprias configurações de provider (colisão, teclas do teclado, anúncios) e cujos itens nunca saem dela. Se os itens não conseguem se mover entre duas partes da página, verifique se eles não estão sob dois providers.
makeDragArea(board, { modifiers: [snapToGrid(20)], activation: { touch: { delay: 400 } } });
Um app montado em outro lugar, dentro de uma shadow root ou como um app Vue separado, pode se
juntar a um provider existente: passe useDnDContext() para provideDnDContext(app, context)
antes de montá-lo. O teste do que está sob o ponteiro e o overlay funcionam através de shadow roots.
SSR e Nuxt
O provider é seguro para SSR. Listas e itens são renderizados no servidor como qualquer outra
marcação. O overlay e a live region são teleportados e montados só no cliente, então nunca causam
um hydration mismatch. Os ids padrão vêm do useId() do Vue e batem entre servidor e cliente.
No Nuxt, o módulo registra o DnDProvider globalmente e importa os composables automaticamente:
veja instalação.