本库的拖拽发生在页面之内。浏览器有它自己的拖放,可以跨越页面的边界:来自桌面的文件、来自另一个标签页的链接、来自另一个应用的文本,以及你的项被拖出到所有这些地方。本库也运行这些拖拽,把它们作为 provider 的拖拽:目标会亮起,列表会腾出位置,事件和屏幕阅读器文本都和平常一样。
Live
正在加载演示…
<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import Board from './Board.vue';
</script>
<template>
<DnDProvider>
<Board />
</DnDProvider>
</template>来自页面之外
目标用 external 接收这类拖拽,并说明它接收什么。没有它的目标会忽略这类拖拽。
Board.vue
const { items } = makeSortableList(grid, {
items: cards,
group: 'moodboard',
strategy: 'grid',
external: { files: 'image/*', links: true, text: true },
createItem: fromOutside,
});
external | 接收 |
|---|---|
files | 文件:true 表示任何文件,MIME 类型和扩展名按 <input accept> 的写法('image/*, .md'),或者一个逐个文件询问的函数。从不接收文件夹。 |
multiple | false:一次一个文件。拖入多个文件会被拒绝。 |
links | 链接(text/uri-list):来自另一个标签页的地址、来自某个页面的链接。 |
text | 纯文本:来自另一个应用的一段选中内容。 |
types | 其他 MIME 类型,写法与其他应用提供它们时完全一致。 |
makeDroppable 接受同样的选项:接收头像图片的放置区见拖放。
拖拽携带了什么
在放下之前,浏览器不会交出拖拽的数据。拖拽进行时,页面只能知道它携带了哪些种类的数据。active.external 和 e.external 说明已知的内容:
| 拖拽进行时 | 放置时 | |
|---|---|---|
types | 它提供的 MIME 类型:Files、text/uri-list…… | 相同 |
fileTypes | 每个文件一个 MIME 类型 | 相同 |
files | 空 | 目标接收的文件,以 File 的形式 |
urls、text、html | 空 | 它的链接、文本、HTML |
getData(type) | '' | 它提供的任意类型的数据 |
ready | false | true |
拖进列表
列表像为普通可拖拽元素那样为它腾出位置:通过 createItem,它会被询问两次:拖拽进行时,询问用来显示它将去往哪里的项;放置时,带着数据,询问要放进去的内容。返回一组项时,它们会全部插入到那个位置:每张图片一张卡片。
cards.ts
export const fromOutside = (active: Active): Card | Card[] | undefined => {
const { external } = active;
if (!external) return undefined;
// While it is held: a tile that says what is coming.
if (!external.ready) return { id: 'incoming', kind: 'incoming', label: incoming(external) };
// At the drop: a card each.
if (external.files.length) {
return external.files.map((file) => ({ id: uid(), kind: 'image', src: URL.createObjectURL(file), name: file.name }));
}
if (external.urls.length) return external.urls.map((url) => ({ id: uid(), kind: 'link', url, title: host(url) }));
const text = external.text.trim();
return text ? { id: uid(), kind: 'note', text } : undefined;
};
- 即将到来的图块和列表中的其他行一样,也是一行:列表渲染它,周围的行滑开为它让路,
onDrop拿到的是真正的项。 - 树也接收这类拖拽,一次一个节点:一个为每个文件带一个子节点的文件夹节点可以容纳多个文件。
- 放置事件带有包含数据的
e.external,在列表的onDrop中和在监听器中都是如此。
拖出页面
使用 native 时,用鼠标拖拽项就是浏览器自己的拖拽,所以它可以离开页面:拖到桌面上,拖进编辑器、聊天窗口、另一个标签页。
Tile.vue
makeSortableItem(el, {
item: () => props.card,
native: { data: () => toOutside(props.card) },
});
cards.ts
case 'link':
return { 'text/uri-list': card.url, 'text/plain': card.url };
case 'note':
return { 'text/plain': card.text };
data以 MIME 类型和文本的形式把项交给其他应用:text/plain、text/uri-list、text/html。在拖拽开始时读取。没有它时,native: true把label作为文本携带。- 在页面上,这次拖拽和其他拖拽一样工作:目标、腾出位置的列表、放置。拖拽的图像由浏览器绘制,代替浮层,元素留在原地(使用
overlay: false时transform保持为null)。 - 对该项的触摸拖拽和键盘拖拽仍是本库的拖拽:浏览器没有用于它们的拖拽。
effect(effectAllowed)规定它被放到的地方可以做什么:默认为'copyMove'。放到别处时,拖拽以droppedOutside结束,值为'move'、'copy'或'link',在onDragEnd中。移动会把这些项从它们的列表中拿走;复制则保留它们。
makeDraggable 以同样的方式接受 native。
在窗口之间
带 native 的项还会携带它自身:你的应用另一个窗口中同一分组的列表会接收它,就像它来自它们自己的某个列表一样。在第二个窗口中打开这个情绪板,然后把一张卡片拖过去。
- 拖拽进行时,另一个窗口已经拿到了这个项:它的列表会在相应位置显示它。
- 放置时它被放进去,而在这里它离开自己的列表(这次放置是移动)。按住 Ctrl(Mac 上为 Option)则改为复制。
- 带
createItem的列表也会为这类项被询问,此时active.external.group已设置,项在active.items[].item中:返回它们,或者返回带有那个窗口的 key 的副本。没有createItem时,它们按原样放进去。 - 项以 JSON 形式传递:日期会变成字符串,函数会被丢弃。
限制
- 数据只随放置到来,绝不会更早:拖拽进行时显示的项要设计成不依赖这些数据。
- 拖拽文件只适用于鼠标和触控板用户。也请在放置区旁边提供一个
<input type="file">。 - 页面上只有一个 provider 接收来自外部的拖拽:第一个拥有可接收它们的目标的 provider。
- 在 Firefox 中,一个项在拖拽期间被移进另一个列表、然后被放到另一个窗口中时,它不会得知这次放置:它在这里留在原来的位置。
- JSON 超过 16 KB 的项仍然可以去往另一个窗口,但那个窗口只在放置时才显示它。
API 见 makeDroppable 和 makeDraggable。