コンテンツへスキップ

ツリー

アルファ
ツリービューウィジェットは、ファイルシステムナビゲーターなど、子アイテムを表示または非表示にするために展開または折りたたむことができるアイテムの階層リストを表示します。

    ディレクトリ構造

  • コンポーネント
  • app.vue
  • nuxt.config.ts
vue
<script setup lang="ts">
import { TreeItem, TreeRoot } from 'radix-vue'
import { Icon } from '@iconify/vue'

const items = [
  {
    title: 'composables',
    icon: 'lucide:folder',
    children: [
      { title: 'useAuth.ts', icon: 'vscode-icons:file-type-typescript' },
      { title: 'useUser.ts', icon: 'vscode-icons:file-type-typescript' },
    ],
  },
  {
    title: 'components',
    icon: 'lucide:folder',
    children: [
      {
        title: 'Home',
        icon: 'lucide:folder',
        children: [
          { title: 'Card.vue', icon: 'vscode-icons:file-type-vue' },
          { title: 'Button.vue', icon: 'vscode-icons:file-type-vue' },
        ],
      },
    ],
  },
  { title: 'app.vue', icon: 'vscode-icons:file-type-vue' },
  { title: 'nuxt.config.ts', icon: 'vscode-icons:file-type-nuxt' },
]
</script>

<template>
  <TreeRoot
    v-slot="{ flattenItems }"
    class="list-none select-none w-56 bg-white text-blackA11 rounded-lg p-2 text-sm font-medium"
    :items="items"
    :get-key="(item) => item.title"
    :default-expanded="['components']"
  >
    <h2 class="font-semibold !text-base text-blackA11 px-2 pt-1">
      Directory Structure
    </h2>
    <TreeItem
      v-for="item in flattenItems"
      v-slot="{ isExpanded }"
      :key="item._id"
      :style="{ 'padding-left': `${item.level - 0.5}rem` }"
      v-bind="item.bind"
      class="flex items-center py-1 px-2 my-0.5 rounded outline-none focus:ring-grass8 focus:ring-2 data-[selected]:bg-grass4"
    >
      <template v-if="item.hasChildren">
        <Icon
          v-if="!isExpanded"
          icon="lucide:folder"
          class="h-4 w-4"
        />
        <Icon
          v-else
          icon="lucide:folder-open"
          class="h-4 w-4"
        />
      </template>
      <Icon
        v-else
        :icon="item.value.icon || 'lucide:file'"
        class="h-4 w-4"
      />
      <div class="pl-2">
        {{ item.value.title }}
      </div>
    </TreeItem>
  </TreeRoot>
</template>

機能

  • 制御可能または非制御可能。
  • フォーカスは完全に管理されています。
  • フルキーボードナビゲーション。
  • 右から左への方向をサポート。
  • 複数選択をサポート。
  • 異なる選択動作。

インストール

コマンドラインからコンポーネントをインストールします。

sh
$ npm add radix-vue

構成

すべての部品をインポートして組み立てる。

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

<template>
  <TreeRoot>
    <TreeItem />

    <!-- or with virtual -->
    <TreeVirtualizer>
      <TreeItem />
    </TreeVirtualizer>
  </TreeRoot>
</template>

APIリファレンス

ルート

ツリーのすべての部分を包含。

プロパティデフォルト
as
'ul'
AsTag | Component

このコンポーネントがレンダリングされる要素またはコンポーネント。`asChild`で上書きできます。

asChild
boolean

子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。

詳細はコンポジションガイドをご覧ください。

defaultExpanded
string[]

最初にレンダリングされたときの展開済みツリーの値。展開済みツリーの状態を制御する必要がない場合に使用します。

defaultValue
Record<string, any> | Record<string, any>[]

最初にレンダリングされたときのツリーの値。ツリーの状態を制御する必要がない場合に使用します。

dir
'ltr' | 'rtl'

該当する場合のリストボックスの読み取り方向。
省略した場合、グローバルに`ConfigProvider`から継承するか、LTR(左から右)の読み取りモードを想定します。

disabled
boolean

trueの場合、ユーザーがツリーと対話することを防ぎます。

expanded
string[]

展開済みアイテムの制御された値。`v-model`でバインドできます。

getKey*
(val: Record<string, any>) => string

この関数は各アイテムのインデックスを受け取り、そのアイテムの一意なキーを返す必要があります。

getChildren
(val: Record<string, any>) => Record<string, any>[] | undefined

この関数は各アイテムのインデックスを受け取り、そのアイテムの子のリストを返す必要があります。

items
Record<string, any>[]

アイテムのリスト

modelValue
Record<string, any> | Record<string, any>[]

ツリーの制御された値。`v-model`でバインドできます。

multiple
boolean

複数のオプションを選択できるかどうか。

propagateSelect
boolean

trueの場合、親を選択すると子孫も選択されます。

selectionBehavior
'toggle'
'replace' | 'toggle'

コレクションで複数選択をどのように動作させるか。

Emitペイロード
update:expanded
[val: string[]]
update:modelValue
[val: Record<string, any>]

値が変更されたときに呼び出されるイベントハンドラー。

スロット (デフォルト)ペイロード
flattenItems
FlattenedItem<Record<string, any>>[]
modelValue
Record<string, any> | Record<string, any>[]
expanded
string[]

アイテム

アイテムコンポーネント。

プロパティデフォルト
as
'li'
AsTag | Component

このコンポーネントがレンダリングされる要素またはコンポーネント。`asChild`で上書きできます。

asChild
boolean

子として渡された要素のデフォルトのレンダリング要素を変更し、それらのプロパティと動作をマージします。

詳細はコンポジションガイドをご覧ください。

level*
number

深さのレベル

value*
Record<string, any>

このアイテムに与えられる値

Emitペイロード
select
[event: SelectEvent<Record<string, any>>]

アイテムを選択したときに呼び出されるイベントハンドラー。
`event.preventDefault`を呼び出すことで、防止できます。

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

アイテムを選択したときに呼び出されるイベントハンドラー。
`event.preventDefault`を呼び出すことで、防止できます。

スロット (デフォルト)ペイロード
isExpanded
boolean
isSelected
boolean
isIndeterminate
boolean | undefined
handleToggle
handleSelect
データ属性
[data-indent]数値
[data-expanded]展開されている場合に存在
[data-selected]選択されている場合に存在

仮想化コンテナ

リストの仮想化を実現するための仮想コンテナ。

プロパティデフォルト
estimateSize
number

各アイテムの推定サイズ(px単位)

textContent
((item: Record<string, any>) => string)

タイプアヘッド機能を実現するための各アイテムのテキストコンテンツ

スロット (デフォルト)ペイロード
item
FlattenedItem<Record<string, any>>

複数アイテムの選択

Treeコンポーネントを使用すると、複数のアイテムを選択できます。単一の値ではなく値の配列を提供することで、これを有効にできます。

vue
<script setup lang="ts">
import { ref } from 'vue'
import { TreeRoot } from 'radix-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 { ref } from 'vue'
import { TreeItem, TreeRoot, TreeVirtualizer } from 'radix-vue'
</script>

<template>
  <TreeRoot :items>
    <!-- checkout https://radix-vue.com/components/tree.html#virtualizer -->
    <TreeVirtualizer
      v-slot="{ item }"
      :text-content="(opt) => opt.name"
    >
      <TreeItem v-bind="item.bind">
        {{ person.name }}
      </TreeItem>
    </TreeVirtualizer>
  </TreeRoot>
</template>

チェックボックス付き

一部のTreeコンポーネントでは、トグル/中間チェックボックスを表示したい場合があります。いくつかのプロパティとpreventDefaultイベントを使用することで、Treeコンポーネントの動作を変更できます。

親のチェックボックスで子孫を選択/選択解除したいので、propagateSelecttrueに設定します。次に、selectイベントをトリガーするチェックボックスを追加します。

vue
<script setup lang="ts">
import { ref } from 'vue'
import { TreeItem, TreeRoot } from 'radix-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 'radix-vue'

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`プロパティを提供することで、それをオーバーライドできます。

::: 注記 ノードに子がない場合、getChildrenは空の配列ではなくundefinedを返す必要があります。::

vue
<script setup lang="ts">
import { ref } from 'vue'
import { TreeRoot } from 'radix-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コンポーネントの場合、この例では、dndの処理のための主要なパッケージとしてpragmatic-drag-and-dropを使用します。

Stackblitzデモ

アクセシビリティ

ツリーWAI-ARIAデザインパターンに準拠。

キーボード操作

キー説明
Enter
TreeItemをハイライト表示すると、フォーカスされているアイテムを選択します。
下矢印
TreeItemにフォーカスがある場合、フォーカスを次のアイテムに移動します。
上矢印
TreeItemにフォーカスがある場合、フォーカスを前のアイテムに移動します。
右矢印
閉じられたTreeItem(ノード)にフォーカスがある場合、フォーカスを移動せずにノードを開きます。開いているノードの場合、フォーカスを最初の子ノードに移動します。最後のノードの場合、何も行いません。
左矢印
開いているTreeItem(ノード)にフォーカスがある場合、ノードを閉じます。最後のノードまたは閉じられたノードでもある子ノードにフォーカスがある場合、フォーカスを親ノードに移動します。最後のノードまたは閉じられたノードでもあるルートノードにフォーカスがある場合、何も行いません。
HomePageUp
フォーカスを最初のTreeItemに移動します。
EndPageDown
フォーカスを最後のTreeItemに移動します。