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 nodesMultiple 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) => voidbooleanboolean"ltr" | "rtl"string() => ShadowRoot | Node | DocumentMachine API
The tree api exposes the following methods:
TreeCollection<V>string[](value: string[]) => voidstring[](value: string[]) => void() => V[](value?: string[]) => void(value?: string[]) => void(value?: string[]) => void(value?: string[]) => void(value: string) => void(value: string) => void(value: string) => voidData Attributes
Item
data-scopedata-partdata-pathdata-valuedata-focusdata-selecteddata-disableddata-depthItem Text
data-scopedata-partdata-disableddata-selecteddata-focusItem Indicator
data-scopedata-partdata-disableddata-selecteddata-focusBranch
data-scopedata-partdata-depthdata-branchdata-valuedata-pathdata-selecteddata-statedata-disabledBranch Indicator
data-scopedata-partdata-statedata-disableddata-selecteddata-focusBranch Trigger
data-scopedata-partdata-disableddata-statedata-valueBranch Control
data-scopedata-partdata-pathdata-statedata-disableddata-selecteddata-focusdata-valuedata-depthBranch Text
data-scopedata-partdata-disableddata-stateBranch Content
data-scopedata-partdata-statedata-depthdata-pathdata-valueBranch Indent Guide
data-scopedata-partdata-depthAccessibility
Keyboard Interaction
TabEnterSpaceArrowDownArrowUpArrowRightArrowLeftHomeEnda-z, A-Z*Shift + ArrowDownShift + ArrowUpCtrl + A