backdrop
组件

树形组件

Alpha
树形视图控件展示了一个可展开或折叠以显示或隐藏其子项的分层列表,如文件系统导航器中的那样。

    Directory Structure

  • components
  • app.vue
  • nuxt.config.ts

功能特点

  • 可控或不可控.
  • 焦点完全受管理.
  • 完整的键盘导航.
  • 支持从右到左方向.
  • 支持多选.
  • 不同的选择行为.

安装

通过命令行安装组件。

sh
$ npm add reka-ui

结构

导入所有部件并组合在一起。

vue
<script setup>
import { TreeItem, TreeRoot, TreeVirtualizer } from 'reka-ui'
</script>

<template>
  <TreeRoot>
    <TreeItem />

    <!-- 或使用虚拟化 -->
    <TreeVirtualizer>
      <TreeItem />
    </TreeVirtualizer>
  </TreeRoot>
</template>

API 参考

Root

包含树的所有部分。

PropDefaultType
as
'ul'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

bubbleSelect
boolean

When true, selecting children will update the parent state.

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.
If omitted, inherits globally from ConfigProvider or assumes LTR (left-to-right) reading mode.

disabled
boolean

When true, prevents the user from interacting with tree

expanded
string[]

The controlled value of the expanded item. Can be binded with with v-model.

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>): string

This 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 v-model.

multiple
boolean

Whether multiple options can be selected or not.

propagateSelect
boolean

When true, selecting parent will select the descendants.

selectionBehavior
'toggle'
'replace' | 'toggle'

How multiple selection should behave in the collection.

EmitPayload
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

项目组件。

PropDefaultType
as
'li'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

level*
number

Level of depth

value*
Record<string, any>

Value given to this item

EmitPayload
select
[event: SelectEvent<Record<string, any>>]

Event handler called when the selecting item.
It can be prevented by calling event.preventDefault.

toggle
[event: ToggleEvent<Record<string, any>>]

Event handler called when the selecting item.
It can be prevented by calling event.preventDefault.

Slots (default)Payload
isExpanded
boolean
isSelected
boolean
isIndeterminate
boolean | undefined
handleToggle
(): void
handleSelect
(): void
Data AttributeValue
[data-indent]数字
[data-expanded]展开时存在
[data-selected]选中时存在

Virtualizer

虚拟化容器,用于实现列表虚拟化。

PropDefaultType
estimateSize
number

Estimated size (in px) of each item

overscan
number

Number 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" 即可启用此功能。

vue
<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>

虚拟列表

渲染长列表项目可能会拖慢应用速度,因此使用虚拟化可以显著提升性能。

更多关于虚拟化的通用信息,请参阅 虚拟化指南

vue
<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 事件的复选框。

vue
<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 中,

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

vue
<template>
  <TreeRoot
    :items="items"
    :get-key="(item) => item.title"
  >
    <Tree :tree-items="items" />
  </TreeRoot>
</template>

自定义子节点模式

默认情况下,<TreeRoot /> 期望你通过为每个节点传递 children 列表来提供节点的子节点列表。你可以通过提供 getChildren prop 来覆盖此行为。

info

如果节点没有任何子节点,getChildren 应返回 undefined 而不是空数组。

vue
<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 包。

Stackblitz 演示

无障碍性

遵循 Tree WAI-ARIA 设计模式

键盘交互

KeyDescription
Enter
当高亮显示在 TreeItem 上时,选择聚焦的项目。
ArrowDown
当焦点在 TreeItem 上时,将焦点移动到下一个项目。
ArrowUp
当焦点在 TreeItem 上时,将焦点移动到上一个项目。
ArrowRight
当焦点在已关闭的 TreeItem(节点)上时,打开节点但不移动焦点。在打开的节点上时,将焦点移动到第一个子节点。在结束节点上时,不执行任何操作。
ArrowLeft
当焦点在已打开的 TreeItem(节点)上时,关闭节点。当焦点在同时也是结束节点或已关闭节点的子节点上时,将焦点移动到其父节点。当焦点在同时也是结束节点或已关闭节点的根节点上时,不执行任何操作。
HomePageUp
将焦点移动到第一个 TreeItem
EndPageDown
将焦点移动到最后一个 TreeItem