Documentação/Guias
Sensores e arrastes programáticos
Como a entrada vira um arraste — os sensores de ponteiro e de teclado, restrições de ativação, canDrag, sensores seus e arrastes que você roteiriza com @vue-dnd-kit/actions.
Esta página foi traduzida por IA e pode conter imprecisões. Ler o original em inglês
Um sensor transforma a entrada em arrastes. Ele só diz ao provider "arraste este item a partir daqui", depois para onde a entrada se moveu, e se ela soltou ou cancelou. O provider faz o resto, do mesmo jeito para todo sensor: um arraste por vez, itens desativados recusados, restrições de ativação, ordenação, rolagem automática, anúncios, Escape e todo o drop.
Os sensores embutidos
| Sensor | Entrada |
|---|---|
pointerSensor | Mouse, toque e caneta, por Pointer Events. Ele captura o ponteiro, impede que a página selecione texto ou role sob um arraste por toque, e engole o clique que vem depois de um arraste. |
keyboardSensor | Space e Enter pegam e soltam, as setas movem, Escape cancela — qualquer arraste, arrastes por ponteiro também. |
Os dois rodam por padrão. A prop sensors do provider substitui a lista:
<DnDProvider :sensors="[pointerSensor, keyboardSensor, joystick.sensor]">
Para desligar os arrastes por teclado, prefira :keyboard="false" a deixar keyboardSensor de
fora: o sensor de teclado também é o que cancela qualquer arraste com Escape. As
teclas dele são redefinidas com keyboard.keys (veja
Acessibilidade).
Restrições de ativação
Uma pressão não arrasta na hora: ela espera a sua restrição de ativação, para que um clique continue sendo um clique e um deslizar do dedo ainda possa rolar a página. Troque as regras desta lista e pressione uma linha:
Carregando a demo…
<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import Inbox from './Inbox.vue';
</script>
<template>
<DnDProvider>
<Inbox />
</DnDProvider>
</template>Uma restrição tem três campos:
| Campo | O arraste começa… |
|---|---|
distance | Assim que o ponteiro se moveu essa quantidade de px. |
delay | Assim que a pressão durou essa quantidade de ms. Junto com distance, o que vier primeiro. |
tolerance | Com delay: quanto o ponteiro pode se mover enquanto isso. Além disso, a pressão desiste, para que a página possa rolar. Padrão de 5 px quando não há distance. |
Sem uma sua, cada tipo de ponteiro tem o seu padrão:
| Ponteiro | Padrão |
|---|---|
| Mouse | Depois de mover 4 px. Na hora quando pressionado em um handle, a menos que uma restrição de mouse esteja definida. |
| Caneta | Depois de mover 4 px. |
| Toque | Depois de uma pressão de 200 ms. Mover mais de 5 px antes disso faz desistir, e a página rola. |
Onde defini-las
Por tipo de ponteiro — mouse, touch, pen — no provider, em uma área de arraste ou no item.
Para cada tipo, vence a do próprio item, depois a da área dele, depois a do provider:
<DnDProvider :activation="{ mouse: { distance: 8 }, touch: { delay: 250, tolerance: 5 } }">
// Inbox.vue: the rules of every drag that starts inside the panel.
makeDragArea(panel, { activation: () => MODES[mode.value].activation });
// One item: a longer press by touch, the area's rule for the mouse.
makeSortableItem(el, { item: () => props.task, activation: { touch: { delay: 400, tolerance: 8 } } });
As opções são lidas quando uma pressão começa, então um getter ou um ref as troca ao vivo, como faz a demo. Arrastes por teclado começam na hora. Um sensor seu pede a sua própria restrição, ou a configurada para um tipo de ponteiro.
Um anel de toque longo
Enquanto uma pressão espera, useDnD().status é 'activating' e activation.progress vai de 0 a
1 — pelo tempo com delay, pela distância percorrida com distance. A demo desenha um anel onde
a pressão começou:
<script setup lang="ts">
const { status, activation } = useDnD();
</script>
<template>
<svg v-if="status === 'activating'" class="ring" viewBox="0 0 40 40">
<circle cx="20" cy="20" r="17" pathLength="1" :stroke-dasharray="`${activation.progress.value} 1`" />
</svg>
</template>
Dê ao anel pointer-events: none: ele fica sob o ponteiro, e o arraste testa o que está ali. Uma
pressão que desiste — moveu-se além da tolerance, ou foi solta cedo demais — leva status
direto de volta a 'idle'.
Handles começam na hora
Com um handle, só o handle inicia um arraste, e uma pressão do mouse nele arrasta na hora: pegar
uma alça não deixa nada a distinguir. Uma pressão solta onde começou continua sendo um clique,
então a alça pode abrir um menu no click (o ponteiro só é capturado quando se move). Um toque ainda espera o seu toque longo, a menos que o
handle não possa rolar a página. Dê a ele touch-action: none e ao item uma distância curta:
makeSortableItem(el, {
item: () => props.chat,
handle: '.grip',
// The grip has `touch-action: none`: a touch on it can't scroll, so it drags after 4 px.
activation: { touch: { distance: 4 } },
});
Uma restrição de mouse definida no item, na área dele ou no provider vale para os handles também.
Faça da alça um <button>, como faz a demo: ela recebe o foco, então um arraste por teclado
também começa por ela.
Só algumas pressões
canDrag é mais uma barreira em draggables e itens ordenáveis. Ele recebe o evento de entrada e
responde true ou false para esta pressão. Aqui, arrastes por ponteiro precisam de
Shift; o teclado arrasta como sempre:
Carregando a demo…
<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import Checklist from './Checklist.vue';
</script>
<template>
<DnDProvider>
<Checklist />
</DnDProvider>
</template>makeSortableItem(el, {
item: () => props.task,
// Never: the row is locked (and says so to screen readers with aria-disabled).
disabled: () => !!props.task.locked,
// Per press: pointer drags only with Shift held; the keyboard as usual.
canDrag: (event) => {
if (!props.shiftOnly || event instanceof KeyboardEvent || event.shiftKey) return true;
emit('refused');
return false;
},
});
| Opção | Consultada | Use para |
|---|---|---|
disabled | Ao vivo, e definida como aria-disabled | Itens que não podem se mover de jeito nenhum: trancados, somente leitura. |
canDrag | Uma vez por pressão, com o evento dela | Regras sobre a entrada: uma tecla segurada, um botão, um tipo de ponteiro. |
handle | Onde a pressão cai | Itens que iniciam um arraste só a partir de uma parte. |
canDrag é consultado antes da restrição de ativação, com o pointerdown ou o keydown. Um tipo
de ponteiro é mais uma regra que ele pode criar:
// Mouse and pen drag; a touch on the row always scrolls the page.
canDrag: (event) => !(event instanceof PointerEvent && event.pointerType === 'touch'),
Filhos interativos — botões, inputs, checkboxes, campos editáveis — nunca iniciam um arraste por
ponteiro, então continuam funcionando dentro de um draggable, como o checkbox e o menu de
responsável da demo. Com um handle, só o handle inicia um.
Um sensor seu
Um gamepad, um joystick na tela, comandos de voz: qualquer coisa que consiga apontar para um item e se mover pode conduzir um arraste. Este gamepad é um sensor. Segure A sobre um herói para pegá-lo, conduza com o direcional, A de novo solta, B cancela. O mouse e o teclado continuam funcionando ao lado dele:
Carregando a demo…
<script setup lang="ts">
import { onMounted, useTemplateRef } from 'vue';
import { DnDProvider, keyboardSensor, pointerSensor } from '@vue-dnd-kit/core';
import Party from './Party.vue';
import Gamepad from './Gamepad.vue';
import { createJoystick } from './joystick';
const stage = useTemplateRef<HTMLElement>('stage');
const joystick = createJoystick(() => stage.value);
// The gamepad drives drags next to the mouse, touch and the keyboard.
const sensors = [pointerSensor, keyboardSensor, joystick.sensor];
onMounted(() => {
const first = stage.value?.querySelector('li');
if (first) joystick.aim(first);
});
</script>
<template>
<DnDProvider :sensors="sensors">
<div ref="stage" class="arcade">
<Party />
<Gamepad :joystick="joystick" />
<span class="cursor" :style="{ left: `${joystick.cursor.value.x}px`, top: `${joystick.cursor.value.y}px` }" />
</div>
</DnDProvider>
</template>
<style scoped>
.arcade {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 32px 48px;
padding: 8px;
}
/* The gamepad's cursor: above everything, never what a hit test finds. */
.cursor {
position: absolute;
z-index: 1;
width: 18px;
height: 18px;
margin: -9px 0 0 -9px;
border: 2px solid #42b883;
border-radius: 50%;
background: color-mix(in srgb, #42b883 25%, transparent);
box-shadow: 0 0 0 4px color-mix(in srgb, #42b883 15%, transparent);
pointer-events: none;
}
</style>defineSensor dá ao sensor o seu nome e um setup que recebe o lado do provider, uma vez por
provider:
const sensor = defineSensor({
name: 'joystick',
// Once per provider, in the browser. The cleanup runs when the provider unmounts.
setup(ctx) {
context = ctx;
return () => {
context = null;
drag = null;
};
},
});
Uma pressão pede um arraste ao provider com activate(). O alvo é verificado como uma pressão
real: ele precisa ser um draggable deste provider, não desativado, e não um botão dentro de um:
/** A goes down: drops a running drag, or starts a hold over what is under the cursor. */
const press = () => {
if (drag?.state === 'dragging') return void drag.drop();
const at = point();
const target = document.elementFromPoint(at.x, at.y);
if (!target || !context) return;
drag = context.activate({
target, // checked like a real press: a draggable of this provider, not disabled
point: at,
type: 'pointer', // follows points: lists sort under it, containers auto-scroll
activation: { delay: 300 }, // `useDnD().activation.progress` runs meanwhile
onEnd: (ended) => {
if (drag === ended) drag = null;
},
}); // null: nothing to drag there
};
Depois o arraste se move com o direcional, e A ou B o encerram:
const move = (dx: number, dy: number) => {
cursor.value = { x: cursor.value.x + dx, y: cursor.value.y + dy }; // clamped in the demo
drag?.move(point()); // pending: checks the tolerance; dragging: moves
};
const release = () => {
if (drag?.state === 'pending') drag.cancel(); // A let go before the hold is over
};
const cancel = () => drag?.cancel();
Adicione o sensor aos sensors do provider, como faz a demo, ou rode-o enquanto um componente
existir com useSensor(joystick.sensor). Os pontos ficam em coordenadas da viewport: a demo
guarda o cursor nas coordenadas do palco, para que ele role com a página, e o converte em
point().
O que o sensor recebe
setup(context) recebe o lado do provider:
| Membro | O que é |
|---|---|
props | As props do provider (activation, keyboard…). Leia-as quando precisar: elas podem mudar. |
status | O status do arraste do provider, seja qual for o sensor que conduz o arraste. |
drag | O arraste atual do provider, pendente ou em andamento, seja qual for o sensor que o iniciou. null quando não há nenhum. |
activate(options) | Pede um arraste. Retorna um SensorDrag, ou null quando nenhum arraste pode começar. |
cancel() | Cancela o arraste ou a pressão pendente do provider. |
activate() recebe:
| Opção | Significado |
|---|---|
target | Onde a entrada caiu: um elemento dentro de um draggable, ou o id de um draggable. Um elemento é verificado como uma pressão real (handle, nenhum filho interativo); um id pula as verificações. |
point | Onde o arraste começa, em coordenadas da viewport. Padrão: o centro do draggable. |
type | 'pointer' (padrão): segue pontos, testa os alvos sob o ponteiro, rola automaticamente. 'keyboard': move por itens, revela os alvos, devolve o foco no fim. |
activation | Uma restrição ({ delay: 300 }), ou um tipo de ponteiro ('touch') para usar a configurada. Padrão: nenhuma, o arraste começa na hora. |
event | O evento de entrada, passado ao canDrag do item (sem ele, canDrag não é consultado). Um pointerType de toque faz dele um arraste por toque (vibração). |
onStart(drag) | O arraste começou, depois da ativação. |
onEnd(drag) | O arraste acabou: abortado, solto ou cancelado — por quem quer que seja. |
onResume(drag) | onBeforeDrop respondeu 'continue': o arraste continua, e move, drop e cancel voltam a valer. |
O SensorDrag que ele retorna:
| Membro | O que faz |
|---|---|
id, type | O id do item arrastado, e 'pointer' ou 'keyboard'. |
state | 'pending' (esperando a ativação), 'dragging', depois 'ended'. |
started | O arraste começou, mesmo que já tenha terminado desde então. |
move(point) | A entrada se moveu. Enquanto pendente, verifica a tolerância; durante o arraste, move o arraste. |
step(direction) | Arrastes por teclado: um passo de seta, 'up' | 'down' | 'left' | 'right'. |
place({ list, index }) | Coloca o item em uma posição de uma lista ordenável, como fazem os movimentos por teclado. Resolve false quando nenhuma lista o aceita ali. |
drop() | Solta. Resolve true quando o drop foi concluído, depois que ele pousou. |
cancel() | Cancela: o item volta ao lugar, ou uma pressão pendente é abandonada. |
Chamadas que não se aplicam mais — um movimento depois do fim, um segundo drop() — são
ignoradas. Tipos completos: Sensores.
setup dele com um contexto seu. Crie-o com
Object.create(context, { activate: { value: myActivate } }) em vez de um spread, para que os
getters ao vivo (status, drag, props) continuem funcionando.Arrastes programáticos
@vue-dnd-kit/actions move itens pelo código: botões "Mover para cima", menus, atalhos de
teclado, desfazer, tours de onboarding, testes. Ele é construído sobre a API pública de sensores, e
os arrastes dele são de verdade: anunciados e animados, passando por onBeforeDrop, onDrop,
e.result e preventDefault(), sob as regras do provider (um arraste por vez, itens desativados,
accept e group.put).
Carregando a demo…
<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import Queue from './Queue.vue';
</script>
<template>
<!-- @vue-dnd-kit/actions moves the rows from code: buttons, a shuffle, a scripted drag. -->
<DnDProvider>
<Queue />
</DnDProvider>
</template>npm i @vue-dnd-kit/actions
moveItem: uma chamada, um movimento
import { useDnDActions } from '@vue-dnd-kit/actions';
const { moveItem } = useDnDActions(); // in setup, under a provider
await moveItem('card-1', { index: 0 }); // to the top of its list
await moveItem('card-1', { list: 'done' }); // to the end of another list
await moveItem('card-1', { list: 'done', index: 2 }); // to a place in another list
listé oidde uma lista ordenável. Sem ele, o item fica na sua lista.indexé onde o item termina, contado sem o próprio item, e limitado ao tamanho da lista. Sem ele, o item vai para o fim.- Resolve
truequando o drop foi concluído,falsequando o item não está renderizado ou está desativado, nenhuma lista o aceita ali, ou o drop foi recusado. - As chamadas entram em fila: cada uma espera até o provider ficar ocioso, então um segundo clique rápido não se perde. O embaralhamento da demo são cinco chamadas seguidas.
- Ele roda como um arraste por teclado, então é anunciado e o foco fica no item.
- Só listas: posições em árvores também têm uma profundidade.
Veja Acessibilidade para uma linha com botões de mover.
startDrag: um arraste roteirizado
const { startDrag } = useDnDActions();
const drag = startDrag('card-1'); // null when it can't start now
if (drag) {
await drag.moveTo(document.querySelector('#trash')!); // onto an element
await drag.moveTo({ x: 400, y: 120 }); // to a point, in viewport coordinates
await drag.moveTo({ list: 'done', index: 2 }); // to a list position
await drag.drop(); // true when the drop went through; or drag.cancel()
}
moveTo resolve depois que o movimento foi renderizado, drop depois que o drop pousou. O "Play a
scripted drag" da demo desliza a última faixa para cima em pequenos passos de moveTo, depois a
solta no topo. startDrag(id, { from: { x, y } }) pega o item em um ponto em vez de no centro.
Passar um arraste para o teclado
startDrag(id, { type: 'keyboard' }) ergue o item como um arraste por teclado. O seu código pode
movê-lo com await drag.step('down'), ou deixá-lo para o usuário: o sensor de teclado conduz todo
arraste por teclado, seja quem for que o iniciou. Isso dá um item de menu "Mover":
Carregando a demo…
<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import Planner from './Planner.vue';
</script>
<template>
<!-- The ⋯ menus move rows with @vue-dnd-kit/actions. -->
<DnDProvider>
<Planner />
</DnDProvider>
</template>const { moveItem, startDrag } = useDnDActions();
// One move in one call: picked up, put there and dropped, announced and animated.
const toOther = () => moveItem(props.task.id, { list: props.other.id, index: 0 });
// A keyboard drag, handed to the arrow keys: Space or Enter drops, Escape cancels.
const withKeys = () => {
el.value?.focus();
startDrag(props.task.id, { type: 'keyboard' });
};
As setas movem a linha de posição em posição, ArrowLeft e ArrowRight
para a outra semana (as listas compartilham um group). Quando o arraste termina, o foco volta
para a linha. As dicas de teclas embaixo do quadro se acendem enquanto
useDnD().active.value?.sensor é 'keyboard'.
Fora de componentes
useDnDActions() roda um sensor, chamado actions, no provider acima. Para conduzir um provider a
partir de uma store ou de um harness de testes, ou sob um nome seu, crie as ações em um sensor que
você mesmo roda:
import { defineSensor } from '@vue-dnd-kit/core';
import { createDnDActions, type DnDActions } from '@vue-dnd-kit/actions';
export let tour: DnDActions | undefined;
export const tourSensor = defineSensor({
name: 'tour',
setup: (context) => void (tour = createDnDActions(context)),
});
<DnDProvider :sensors="[pointerSensor, keyboardSensor, tourSensor]">
Depois, await tour?.moveItem('card-1', { list: 'done' }) de qualquer lugar. Durante a
renderização no servidor nada é arrastado: moveItem resolve false e startDrag retorna null.