Tree ツリー
フラットデータからツリーへの変換とチェック伝播はすべて core 層の純関数で行われ、コンポーネントはレンダリングとキーボード操作だけを担います。
基本的な使い方
<script setup>
import { ref } from 'vue'
const treeData = [
{ key: 'a', label: 'フロントエンド', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'バックエンド', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: '運用保守', disabled: true },
]
</script>
<template>
<!-- 基本的な使い方 -->
<LhTree :data="treeData" :default-expanded-keys="['a']" />
</template><script>
export default {
data() {
return {
treeData: [
{ key: 'a', label: 'フロントエンド', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'バックエンド', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: '運用保守', disabled: true },
],
}
},
}
</script>
<template>
<!-- 基本的な使い方 -->
<LhTree :data="treeData" :default-expanded-keys="['a']" />
</template>チェック可能(カスケード)
親にチェックを付けるとすべての子孫へカスケード伝播します。子がすべて選択されると親も自動的に選択され、一部選択時は中間状態(indeterminate)で表示されます。
<script setup lang="ts">
const treeData = [
{ key: 'a', label: 'フロントエンド', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'バックエンド', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: '運用保守', disabled: true },
]
</script>
<template>
<!-- チェックのカスケード -->
<LhTree :data="treeData" show-checkbox :default-expanded-keys="['a', 'b']" />
</template><script>
export default {
data() {
return {
treeData: [
{ key: 'a', label: 'フロントエンド', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'バックエンド', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: '運用保守', disabled: true },
],
}
},
}
</script>
<template>
<!-- チェックのカスケード -->
<LhTree :data="treeData" show-checkbox :default-expanded-keys="['a', 'b']" />
</template>遅延読み込み
lazy を有効にすると、未読み込みのノードを展開する際に load コールバックが呼ばれます(resolve が子ノードを登録して展開、reject はキャンセル)。読み込み中は矢印が回転表示になります。 読み込み可能なノードは isLeaf: false を明示的に宣言してください(そうしないと、children を持たないノードは葉と推論され、展開矢印が表示されません)。 読み込まれた子ノードがさらに読み込み待ちのブランチである場合も同様に isLeaf: false を宣言し、本当の葉には isLeaf: true を宣言します。
<script setup>
import { ref } from 'vue'
const lazyData = ref([
{ key: 'lazy', label: 'クリックして読み込み', isLeaf: false },
])
function loadNodes(node, resolve, reject) {
// 非同期リクエストをシミュレート
setTimeout(() => {
if (node.key === 'lazy') {
resolve([
{ key: 'lazy-1', label: '子ノード 1', isLeaf: true },
{ key: 'lazy-2', label: '子ノード 2', isLeaf: true },
])
} else {
reject(new Error('nothing to load'))
}
}, 600)
}
</script>
<template>
<!-- 遅延読み込み -->
<LhTree :data="lazyData" lazy :load="loadNodes" />
</template><script>
export default {
data() {
return {
lazyData: [{ key: 'lazy', label: 'クリックして読み込み', isLeaf: false }],
}
},
methods: {
// 非同期リクエストをシミュレート
loadNodes(node, resolve, reject) {
setTimeout(() => {
if (node.key === 'lazy') {
resolve([
{ key: 'lazy-1', label: '子ノード 1', isLeaf: true },
{ key: 'lazy-2', label: '子ノード 2', isLeaf: true },
])
} else {
reject(new Error('nothing to load'))
}
}, 600)
},
},
}
</script>
<template>
<!-- 遅延読み込み -->
<LhTree :data="lazyData" lazy :load="loadNodes" />
</template>親子の非連動(checkStrictly)
checkStrictly を有効にすると、チェックはノード自身にだけ作用します。子孫へのカスケード、祖先の遡及、中間状態はすべてありません。
<script setup lang="ts">
const treeData = [
{ key: 'a', label: 'フロントエンド', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'バックエンド', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: '運用保守', disabled: true },
]
</script>
<template>
<!-- 親子の非連動 -->
<LhTree :data="treeData" show-checkbox check-strictly :default-expanded-keys="['a', 'b']" />
</template><script>
export default {
data() {
return {
treeData: [
{ key: 'a', label: 'フロントエンド', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'バックエンド', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: '運用保守', disabled: true },
],
}
},
}
</script>
<template>
<!-- 親子の非連動 -->
<LhTree :data="treeData" show-checkbox check-strictly :default-expanded-keys="['a', 'b']" />
</template>選択と制御可能なキー
multiple で複数選択を許可します;expand-on-click-node はノードのクリックで展開/折りたたみを行います;default-selected-keys / v-model:selected-keys で選択を制御します;default-checked-keys / v-model:checked-keys でチェックを制御します;default-expanded-keys / v-model:expanded-keys で展開を制御します;disabled でツリー全体を無効化します。
選択:[ "x" ] | チェック:[ "x-1" ]
<script setup>
import { ref } from 'vue'
const selKeys = ref(['x'])
const chkKeys = ref(['x-1'])
const ctrlData = [
{ key: 'x', label: 'デザイン', children: [
{ key: 'x-1', label: '色彩' },
{ key: 'x-2', label: 'フォント' },
] },
{ key: 'y', label: 'エンジニアリング' },
]
</script>
<template>
<!-- 制御可能な選択とチェック -->
<LhTree
v-model:selected-keys="selKeys"
v-model:checked-keys="chkKeys"
:data="ctrlData"
multiple
expand-on-click-node
:default-expanded-keys="['x']"
:default-selected-keys="['x']"
:default-checked-keys="['x-1']"
/>
<p class="demo-tree-log">選択:{{ selKeys }} | チェック:{{ chkKeys }}</p>
<!-- ツリー全体を無効化 -->
<LhTree :data="ctrlData" disabled />
</template><script>
export default {
data() {
return {
selKeys: ['x'],
chkKeys: ['x-1'],
ctrlData: [
{ key: 'x', label: 'デザイン', children: [
{ key: 'x-1', label: '色彩' },
{ key: 'x-2', label: 'フォント' },
] },
{ key: 'y', label: 'エンジニアリング' },
],
}
},
}
</script>
<template>
<!-- 制御可能な選択とチェック -->
<LhTree
v-model:selected-keys="selKeys"
v-model:checked-keys="chkKeys"
:data="ctrlData"
multiple
expand-on-click-node
:default-expanded-keys="['x']"
:default-selected-keys="['x']"
:default-checked-keys="['x-1']"
/>
<p class="demo-tree-log">選択:{{ selKeys }} | チェック:{{ chkKeys }}</p>
<!-- ツリー全体を無効化 -->
<LhTree :data="ctrlData" disabled />
</template>API
LhTree Props
| プロパティ | 説明 | 型 | デフォルト値 |
|---|---|---|---|
| data | ツリーデータ | [LhTreeNode](#type-LhTreeNode)[] | — |
| showCheckbox | チェックボックスを表示する | boolean | false |
| multiple | 複数選択(ノードのクリックで選択を置き換えず切り替える) | boolean | false |
| expandOnClickNode | 葉ではないノードのクリックで展開/折りたたみを行う | boolean | true |
| defaultExpandedKeys | デフォルトで展開されるキー(非制御) | string[] | () => [] |
| expandedKeys | 制御される展開キー | string[] | — |
| defaultSelectedKeys | デフォルトで選択されるキー(非制御) | string[] | () => [] |
| selectedKeys | 制御される選択キー | string[] | — |
| defaultCheckedKeys | デフォルトでチェックされるキー(非制御) | string[] | () => [] |
| checkedKeys | 制御されるチェックキー | string[] | — |
| checkStrictly | 親子のチェックを連動させない(チェックは自身にのみ作用し、カスケードと中間状態なし) | boolean | false |
| lazy | 遅延読み込みモード(未読み込みノードの展開時に load を呼ぶ) | boolean | false |
| load | 遅延読み込みコールバック(resolve が子ノードを登録して展開、reject はキャンセル) | (node: LhTreeNode, resolve: (children: LhTreeNode[]) => void, reject: (reason?: unknown) => void) => void | — |
| disabled | 全体を無効化 | boolean | false |
| sx | sx クイックレイアウトプロパティ | LhSxProps | — |
Events
| イベント名 | 説明 | コールバック引数 |
|---|---|---|
| update:expandedKeys | 展開キーリストが変化した時に発火。v-model:expanded-keys と組み合わせて使用 | (keys: string[]) |
| update:selectedKeys | 選択キーリストが変化した時に発火。v-model:selected-keys と組み合わせて使用 | (keys: string[]) |
| update:checkedKeys | チェックキーリストが変化した時に発火。v-model:checked-keys と組み合わせて使用 | (keys: string[]) |
| select | ノードがクリックで選択/選択切り替えされた後に発火 | (key: string, node: LhTreeNode) |
| check | チェックの変化後に発火 | (keys: string[], node: LhTreeNode) |
| expand | ノードの展開/折りたたみ後に発火 | (key: string, expanded: boolean) |
| nodeLoad | 遅延読み込みの子ノードが返された後に発火 | (node: LhTreeNode, children: LhTreeNode[]) |
スロット:node(スコープ { node, level, expanded, selected })、empty。
LhTreeNode
| フィールド | 説明 | 型 |
|---|---|---|
| key | 一意キー | string |
| label | 表示テキスト | string |
| children | 子ノード | LhTreeNode[] |
| disabled | 無効化 | boolean |
| isLeaf | 葉かどうか(未指定時は children から推論) | boolean |
アクセシビリティ
role="tree"+role="treeitem"で、aria-level/aria-expanded/aria-selected/aria-disabledをすべて公開します- チェックボックスは
role="checkbox"+aria-checked(中間状態はmixed)です - キーボード操作(APG tree パターン):↑/↓ で項目間を移動(無効化をスキップ、ループ)、→ で展開または最初の子へ、← で折りたたみまたは親へ戻る、Home/End で先頭・末尾、Enter/Space でアクティブ化。roving tabindex を採用
型定義
本コンポーネントが外部にエクスポートする TypeScript の型構造は以下の通りです(packages/components/tree/src/types.ts と @lh-ui/share/core/* のソースから自動抽出。各エントリーをクリックすると完全な定義を表示します。プロパティの説明は上記の Props 表を参照してください):
LhTreeProps
export interface LhTreeProps {
/** ツリーデータ */
data: LhTreeNode[]
/** チェックボックスを表示する */
showCheckbox?: boolean
/** 複数選択(ノードのクリックで選択を置き換えず切り替える) */
multiple?: boolean
/** 葉ではないノードのクリックで展開/折りたたみを行う */
expandOnClickNode?: boolean
/** デフォルトで展開されるキー(非制御) */
defaultExpandedKeys?: string[]
/** 制御される展開キー */
expandedKeys?: string[]
/** デフォルトで選択されるキー(非制御) */
defaultSelectedKeys?: string[]
/** 制御される選択キー */
selectedKeys?: string[]
/** デフォルトでチェックされるキー(非制御) */
defaultCheckedKeys?: string[]
/** 制御されるチェックキー */
checkedKeys?: string[]
/** 親子のチェックを連動させない(チェックはノード自身にのみ作用し、カスケードと中間状態なし) */
checkStrictly?: boolean
/** 遅延読み込みモード:未読み込みの非葉ノードの展開時に load を呼ぶ */
lazy?: boolean
/** 遅延読み込みコールバック:resolve が子ノードを登録して展開、reject(または例外)はキャンセル */
load?: (
node: LhTreeNode,
resolve: (children: LhTreeNode[]) => void,
reject: (reason?: unknown) => void,
) => void
/** 全体を無効化 */
disabled?: boolean
/** sx クイックレイアウトプロパティ */
sx?: LhSxProps
}LhFlatTreeNode (@lh-ui/share/core/tree より)
/** フラット化されたノード(レンダリング用) */
export interface LhFlatTreeNode {
node: LhTreeNode
key: string
level: number
parentKey: string | null
/** 祖先チェーン(自身を含まない) */
ancestorKeys: string[]
/** 葉かどうか */
isLeaf: boolean
}LhTreeNode (@lh-ui/share/core/tree より)
/** ツリーのノードデータ */
export interface LhTreeNode {
/** 一意キー */
key: string
/** 表示テキスト */
label: string
/** 子ノード */
children?: LhTreeNode[]
/** 無効化 */
disabled?: boolean
/** 葉かどうか(未指定時は children から推論) */
isLeaf?: boolean
}LhTreeCheckState (@lh-ui/share/core/tree より)
/** チェック状態 */
export type LhTreeCheckState = 'checked' | 'indeterminate' | 'unchecked'