Tree
The Tree component provides a hierarchical view of data, similar to a file system explorer. It allows users to expand and collapse branches, select individual or multiple nodes, and traverse the hierarchy using keyboard navigation.
Features
Install
Install the component from your command line.
Anatomy
Import all parts and piece them together.
<script setup lang="ts">import * as tree from '@destyler/tree'import { normalizeProps, useMachine } from '@destyler/vue'import { computed, useId } from 'vue'import TreeNode from './Node.vue'
interface Node { id: string name: string children?: Node[]}const collection = tree.collection<Node>({ nodeToValue: node => node.id, nodeToString: node => node.name, rootNode: { id: 'ROOT', name: '', children: [ // ... ], },})const [state, send] = useMachine(tree.machine({ id: useId(), collection,}))const api = computed(() => tree.connect(state.value, send, normalizeProps))</script>
<template> <main> <div v-bind="api.getRootProps()"> <div v-bind="api.getTreeProps()"> <TreeNode v-for="(node, index) in api.collection.rootNode.children" :key="node.id" :node="node" :index-path="[index]" :api="api" /> </div> </div> </main></template>
<script setup lang="ts">import type { Api } from '@destyler/tree'import { computed } from 'vue'
interface Node { id: string name: string children?: Node[]}
interface Props { node: Node indexPath: number[] api: Api}
const props = defineProps<Props>()
const nodeProps = computed(() => ({ indexPath: props.indexPath, node: props.node,}))
const nodeState = computed(() => props.api.getNodeState(nodeProps.value))</script>
<template> <template v-if="nodeState.isBranch"> <div v-bind="api.getBranchProps(nodeProps)"> <div v-bind="api.getBranchControlProps(nodeProps)"> <span v-bind="api.getBranchIndicatorProps(nodeProps)"></span> <span v-bind="api.getBranchTextProps(nodeProps)"></span> </div> <div v-bind="api.getBranchContentProps(nodeProps)"> <Node v-for="(childNode, index) in node.children" :key="childNode.id" :node="childNode" :index-path="[...indexPath, index]" :api="api" /> </div> </div> </template> <template v-else> <div v-bind="api.getItemProps(nodeProps)"></div> </template></template>
import { normalizeProps, useMachine } from '@destyler/react'import * as tree from '@destyler/tree'import { useId } from 'react'import TreeNode from './Node.tsx'
interface Node { id: string name: string children?: Node[]}
const collection = tree.collection<Node>({ nodeToValue: node => node.id, nodeToString: node => node.name, rootNode: { id: 'ROOT', name: '', children: [ // ... ], },})
export default function Tree() { const [state, send] = useMachine(tree.machine({ id: useId(), collection, }))
const api = tree.connect(state, send, normalizeProps)
return ( <main > <div {...api.getRootProps()}> <div {...api.getTreeProps()}> {api.collection.rootNode.children?.map((node: any, index: any) => ( <TreeNode key={node.id} node={node} indexPath={[index]} api={api} /> ))} </div> </div> </main> )}
import type { Api } from '@destyler/tree'import { useMemo } from 'react'
interface Node { id: string name: string children?: Node[]}
interface TreeNodeProps { node: Node indexPath: number[] api: Api}
export default function TreeNode({ node, indexPath, api }: TreeNodeProps) { const nodeProps = useMemo(() => ({ indexPath, node, }), [indexPath, node])
const nodeState = useMemo(() => api.getNodeState(nodeProps), [api, nodeProps])
if (nodeState.isBranch) { return ( <div {...api.getBranchProps(nodeProps)}> <div {...api.getBranchControlProps(nodeProps)}> <span {...api.getBranchIndicatorProps(nodeProps)}></span> <span {...api.getBranchTextProps(nodeProps)}></span> </div> <div {...api.getBranchContentProps(nodeProps)}> {node.children?.map((childNode, index) => ( <Node key={childNode.id} node={childNode} indexPath={[...indexPath, index]} api={api} /> ))} </div> </div> ) }
return ( <div {...api.getItemProps(nodeProps)}></div> )}
<script lang="ts"> import * as tree from '@destyler/tree' import { normalizeProps, useMachine } from '@destyler/svelte' import TreeNode from './Node.svelte'
interface Node { id: string name: string children?: Node[] }
const collection = tree.collection<Node>({ nodeToValue: node => node.id, nodeToString: node => node.name, rootNode: { id: 'ROOT', name: '', children: [ // ... ], }, })
const [state, send] = useMachine(tree.machine({ id: crypto.randomUUID(), collection, }))
const api = $derived(tree.connect(state, send, normalizeProps))</script>
<main> <div {...api.getRootProps()}> <div {...api.getTreeProps()}> {#each api.collection.rootNode.children as node, index} <TreeNode {node} indexPath={[index]} {api} /> {/each} </div> </div></main>
<script lang="ts"> import type { Api } from '@destyler/tree'
interface Node { id: string name: string children?: Node[] }
export let node: Node export let indexPath: number[] export let api: Api
$: nodeProps = { indexPath, node, }
$: nodeState = api.getNodeState(nodeProps)</script>
{#if nodeState.isBranch} <div {...api.getBranchProps(nodeProps)}> <div {...api.getBranchControlProps(nodeProps)}> <span {...api.getBranchIndicatorProps(nodeProps)}></span> <span {...api.getBranchTextProps(nodeProps)}></span> </div> <div {...api.getBranchContentProps(nodeProps)}> {#each node.children || [] as childNode, index} <svelte:self node={childNode} indexPath={[...indexPath, index]} api={api} /> {/each} </div> </div>{:else} <div {...api.getItemProps(nodeProps)}></div>{/if}
import { normalizeProps, useMachine } from '@destyler/solid'import * as tree from '@destyler/tree'import { createMemo, createUniqueId } from 'solid-js'import TreeNode from './Node.tsx'
interface Node { id: string name: string children?: Node[]}
const collection = tree.collection<Node>({ nodeToValue: node => node.id, nodeToString: node => node.name, rootNode: { id: 'ROOT', name: '', children: [ // ... ], },})
export default function Tree() { const [state, send] = useMachine(tree.machine({ id: createUniqueId(), collection, }))
const api = createMemo(() => tree.connect(state, send, normalizeProps))
return ( <main> <div {...api().getRootProps()}> <div {...api().getTreeProps()}> {api().collection.rootNode.children?.map((node: any, index: any) => ( <TreeNode node={node} indexPath={[index]} api={api()} /> ))} </div> </div> </main> )}
import type { Api } from '@destyler/tree'import { createMemo } from 'solid-js'
interface Node { id: string name: string children?: Node[]}
interface TreeNodeProps { node: Node indexPath: number[] api: Api}
export default function TreeNode(props: TreeNodeProps) { const nodeProps = createMemo(() => ({ indexPath: props.indexPath, node: props.node, }))
const nodeState = createMemo(() => props.api.getNodeState(nodeProps()))
return ( <> {createMemo(() => nodeState().isBranch)() ? ( <div {...props.api.getBranchProps(nodeProps())}> <div {...props.api.getBranchControlProps(nodeProps())}> <span {...props.api.getBranchIndicatorProps(nodeProps())}></span> <span {...props.api.getBranchTextProps(nodeProps())}></span> </div> <div {...props.api.getBranchContentProps(nodeProps())}> {props.node.children?.map((childNode, index) => ( <TreeNode node={childNode} indexPath={[...props.indexPath, index]} api={props.api} /> ))} </div> </div> ) : ( <div {...props.api.getItemProps(nodeProps())}></div> )} </> )}
Expanding and Collapsing Nodes
By default, the tree view will expand or collapse when clicking the branch control.
To control the expanded state of the tree view,
use the api.expand
and api.collapse
methods.
api.expand(["node_modules/unocss"]) // expand a single nodeapi.expand() // expand all nodes
api.collapse(["node_modules/unocss"]) // collapse a single nodeapi.collapse() // collapse all nodes
Multiple selection
The tree view supports multiple selection.
To enable this, set the selectionMode
to multiple
.
const [state, send] = useMachine( tree.machine({ selectionMode: "multiple", }),)
Setting the default expanded nodes
To set the default expanded nodes, use the expandedValue
context property.
const [state, send] = useMachine( tree.machine({ expandedValue: ["node_modules/unocss"], }),)
Setting the default selected nodes
To set the default selected nodes, use the selectedValue
context property.
const [state, send] = useMachine( tree.machine({ selectedValue: ["node_modules/unocss"], }),)
Listening for selection
When a node is selected, the onSelectionChange
callback is invoked with the selected nodes.
const [state, send] = useMachine( tree.machine({ onSelectionChange(details) { console.log("selected nodes:", details) }, }),)
Listening for expanding and collapsing
When a node is expanded or collapsed, the onExpandedChange
callback is invoked with the expanded nodes.
const [state, send] = useMachine( tree.machine({ onExpandedChange(details) { console.log("expanded nodes:", details) }, }),)
Methods and Properties
Machine Context
The tree machine exposes the following context properties:
TreeCollection<T>
Partial<{ root: string; tree: string; label: string; node(value: string): string; }>
string[]
string[]
string
"single" | "multiple"
(details: ExpandedChangeDetails) => void
(details: SelectionChangeDetails) => void
(details: FocusChangeDetails) => void
boolean
boolean
"ltr" | "rtl"
string
() => ShadowRoot | Node | Document
Machine API
The tree api
exposes the following methods:
TreeCollection<V>
string[]
(value: string[]) => void
string[]
(value: string[]) => void
() => V[]
(value?: string[]) => void
(value?: string[]) => void
(value?: string[]) => void
(value?: string[]) => void
(value: string) => void
(value: string) => void
(value: string) => void
Data Attributes
Item
data-scope
data-part
data-path
data-value
data-focus
data-selected
data-disabled
data-depth
Item Text
data-scope
data-part
data-disabled
data-selected
data-focus
Item Indicator
data-scope
data-part
data-disabled
data-selected
data-focus
Branch
data-scope
data-part
data-depth
data-value
data-path
data-selected
data-state
data-disabled
Branch Indicator
data-scope
data-part
data-state
data-disabled
data-selected
data-focus
Branch Trigger
data-scope
data-part
data-disabled
data-state
data-value
Branch Control
data-scope
data-part
data-path
data-state
data-disabled
data-selected
data-focus
data-value
data-depth
Branch Text
data-scope
data-part
data-disabled
data-state
Branch Content
data-scope
data-part
data-state
data-depth
data-path
data-value
Branch Indent Guide
data-scope
data-part
data-depth
Accessibility
Keyboard Interaction
Tab
Enter
Space
ArrowDown
ArrowUp
ArrowRight
ArrowLeft
Home
End
a-z, A-Z
*
Shift + ArrowDown
Shift + ArrowUp
Ctrl + A