树形组件
- composables
- components
- Home
- app.vue
- nuxt.config.ts
Directory Structure
功能特点
- 可控或不可控.
- 焦点完全受管理.
- 完整的键盘导航.
- 支持从右到左方向.
- 支持多选.
- 不同的选择行为.
安装
通过命令行安装组件。
$ npm add reka-ui结构
导入所有部件并组合在一起。
<script setup>
import { TreeItem, TreeRoot, TreeVirtualizer } from 'reka-ui'
</script>
<template>
<TreeRoot>
<TreeItem />
<!-- 或使用虚拟化 -->
<TreeVirtualizer>
<TreeItem />
</TreeVirtualizer>
</TreeRoot>
</template>API 参考
Root
包含树的所有部分。
| Prop | Default | Type |
|---|---|---|
as | 'ul' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
bubbleSelect | booleanWhen | |
defaultExpanded | string[]The value of the expanded tree when initially rendered. Use when you do not need to control the state of the expanded tree | |
defaultValue | Record<string, any> | Record<string, any>[]The value of the tree when initially rendered. Use when you do not need to control the state of the tree | |
dir | 'ltr' | 'rtl'The reading direction of the listbox when applicable. | |
disabled | booleanWhen | |
expanded | string[]The controlled value of the expanded item. Can be binded with with | |
getChildren | val.children | ((val: Record<string, any>) => Record<string, any>[])This function is passed the index of each item and should return a list of children for that item |
getKey* | (val: Record<string, any>): stringThis function is passed the index of each item and should return a unique key for that item | |
items | Record<string, any>[]List of items | |
modelValue | Record<string, any> | Record<string, any>[]The controlled value of the tree. Can be binded with with | |
multiple | booleanWhether multiple options can be selected or not. | |
propagateSelect | booleanWhen | |
selectionBehavior | 'toggle' | 'replace' | 'toggle'How multiple selection should behave in the collection. |
| Emit | Payload |
|---|---|
update:expanded | [val: string[]] |
update:modelValue | [val: Record<string, any> | Record<string, any>[]]Event handler called when the value of the toggle changes. |
| Slots (default) | Payload |
|---|---|
flattenItems | FlattenedItem<Record<string, any>>[] |
modelValue | Record<string, any> | Record<string, any>[] |
expanded | string[] |
Item
项目组件。
| Prop | Default | Type |
|---|---|---|
as | 'li' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
level* | numberLevel of depth | |
value* | Record<string, any>Value given to this item |
| Emit | Payload |
|---|---|
select | [event: SelectEvent<Record<string, any>>]Event handler called when the selecting item. |
toggle | [event: ToggleEvent<Record<string, any>>]Event handler called when the selecting item. |
| Slots (default) | Payload |
|---|---|
isExpanded | boolean |
isSelected | boolean |
isIndeterminate | boolean | undefined |
handleToggle | (): void |
handleSelect | (): void |
| Data Attribute | Value |
|---|---|
[data-indent] | 数字 |
[data-expanded] | 展开时存在 |
[data-selected] | 选中时存在 |
Virtualizer
虚拟化容器,用于实现列表虚拟化。
| Prop | Default | Type |
|---|---|---|
estimateSize | numberEstimated size (in px) of each item | |
overscan | numberNumber of items rendered outside the visible area | |
textContent | ((item: Record<string, any>) => string)Text content for each item to achieve type-ahead feature |
| Slots (default) | Payload |
|---|---|
item | FlattenedItem<Record<string, any>> |
virtualizer | Virtualizer<Element | Window, Element> |
virtualItem | VirtualItem |
示例
多选项目
Tree 组件允许你选择多个项目。你只需提供一个值数组而不是单个值,并设置 multiple="true" 即可启用此功能。
<script setup lang="ts">
import { TreeRoot } from 'reka-ui'
import { ref } from 'vue'
const people = [
{ id: 1, name: 'Durward Reynolds' },
{ id: 2, name: 'Kenton Towne' },
{ id: 3, name: 'Therese Wunsch' },
{ id: 4, name: 'Benedict Kessler' },
{ id: 5, name: 'Katelyn Rohan' },
]
const selectedPeople = ref([people[0], people[1]])
</script>
<template>
<TreeRoot
v-model="selectedPeople"
multiple
>
...
</TreeRoot>
</template>虚拟列表
渲染长列表项目可能会拖慢应用速度,因此使用虚拟化可以显著提升性能。
更多关于虚拟化的通用信息,请参阅 虚拟化指南。
<script setup lang="ts">
import { TreeItem, TreeRoot, TreeVirtualizer } from 'reka-ui'
import { ref } from 'vue'
</script>
<template>
<TreeRoot :items>
<TreeVirtualizer
v-slot="{ item }"
:text-content="(opt) => opt.name"
>
<TreeItem v-bind="item.bind">
{{ person.name }}
</TreeItem>
</TreeVirtualizer>
</TreeRoot>
</template>带复选框
某些 Tree 组件可能希望显示 已切换/不确定 的复选框。我们可以通过使用一些 prop 和 preventDefault 事件来改变 Tree 组件的行为。
我们将 propagateSelect 设置为 true,因为我们希望父复选框选择/取消选择其子项。然后,添加一个触发 select 事件的复选框。
<script setup lang="ts">
import { TreeItem, TreeRoot } from 'reka-ui'
import { ref } from 'vue'
</script>
<template>
<TreeRoot
v-slot="{ flattenItems }"
:items
multiple
propagate-select
>
<TreeItem
v-for="item in flattenItems"
:key="item._id"
v-bind="item.bind"
v-slot="{ handleSelect, isSelected, isIndeterminate }"
@select="(event) => {
if (event.detail.originalEvent.type === 'click')
event.preventDefault()
}"
@toggle="(event) => {
if (event.detail.originalEvent.type === 'keydown')
event.preventDefault()
}"
>
<Icon
v-if="item.hasChildren"
icon="radix-icons:chevron-down"
/>
<button
tabindex="-1"
@click.stop
@change="handleSelect"
>
<Icon
v-if="isSelected"
icon="radix-icons:check"
/>
<Icon
v-else-if="isIndeterminate"
icon="radix-icons:dash"
/>
<Icon
v-else
icon="radix-icons:box"
/>
</button>
<div class="pl-2">
{{ item.value.title }}
</div>
</TreeItem>
</TreeRoot>
</template>嵌套树节点
默认示例展示了扁平的树项目和节点,这使得 虚拟化 和自定义功能(如拖放)更容易实现。不过,你也可以构建具有嵌套 DOM 节点的树。
在 Tree.vue 中,
<script setup lang="ts">
import { TreeItem } from 'reka-ui'
interface TreeNode {
title: string
icon: string
children?: TreeNode[]
}
withDefaults(defineProps<{
treeItems: TreeNode[]
level?: number
}>(), { level: 0 })
</script>
<template>
<li
v-for=" tree in treeItems"
:key="tree.title"
>
<TreeItem
v-slot="{ isExpanded }"
as-child
:level="level"
:value="tree"
>
<button>…</button>
<ul v-if="isExpanded && tree.children">
<Tree
:tree-items="tree.children"
:level="level + 1"
/>
</ul>
</TreeItem>
</li>
</template>在 CustomTree.vue 中
<template>
<TreeRoot
:items="items"
:get-key="(item) => item.title"
>
<Tree :tree-items="items" />
</TreeRoot>
</template>自定义子节点模式
默认情况下,<TreeRoot /> 期望你通过为每个节点传递 children 列表来提供节点的子节点列表。你可以通过提供 getChildren prop 来覆盖此行为。
如果节点没有任何子节点,getChildren 应返回 undefined 而不是空数组。
<script setup lang="ts">
import { TreeRoot } from 'reka-ui'
import { ref } from 'vue'
interface FileNode {
title: string
icon: string
}
interface DirectoryNode {
title: string
icon: string
directories?: DirectoryNode[]
files?: FileNode[]
}
</script>
<template>
<TreeRoot
:items="items"
:get-key="(item) => item.title"
:get-children="(item) => (!item.files) ? item.directories : (!item.directories) ? item.files : [...item.directories, ...item.files]"
>
...
</TreeRoot>
</template>可拖拽/可排序树
对于更复杂的可拖拽 Tree 组件,在此示例中,我们将使用 pragmatic-drag-and-drop 作为处理拖放的 core 包。
无障碍性
键盘交互
| Key | Description |
|---|---|
Enter | 当高亮显示在 TreeItem 上时,选择聚焦的项目。 |
ArrowDown | 当焦点在 TreeItem 上时,将焦点移动到下一个项目。 |
ArrowUp | 当焦点在 TreeItem 上时,将焦点移动到上一个项目。 |
ArrowRight | 当焦点在已关闭的 TreeItem(节点)上时,打开节点但不移动焦点。在打开的节点上时,将焦点移动到第一个子节点。在结束节点上时,不执行任何操作。 |
ArrowLeft | 当焦点在已打开的 TreeItem(节点)上时,关闭节点。当焦点在同时也是结束节点或已关闭节点的子节点上时,将焦点移动到其父节点。当焦点在同时也是结束节点或已关闭节点的根节点上时,不执行任何操作。 |
HomePageUp | 将焦点移动到第一个 TreeItem |
EndPageDown | 将焦点移动到最后一个 TreeItem |
