Árbol
La conversión de datos planos a árbol y la propagación del marcado se resuelven en funciones puras de la capa core; el componente solo se ocupa del renderizado y del teclado.
Uso básico
<script setup>
import { ref } from 'vue'
const treeData = [
{ key: 'a', label: 'Frontend', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'Backend', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: 'Operaciones', disabled: true },
]
</script>
<template>
<!-- Uso básico -->
<LhTree :data="treeData" :default-expanded-keys="['a']" />
</template><script>
export default {
data() {
return {
treeData: [
{ key: 'a', label: 'Frontend', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'Backend', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: 'Operaciones', disabled: true },
],
}
},
}
</script>
<template>
<!-- Uso básico -->
<LhTree :data="treeData" :default-expanded-keys="['a']" />
</template>Marcado en cascada
Al marcar un nodo padre se marca en cascada a todos sus descendientes; cuando todos los hijos están marcados, el padre se marca automáticamente, y con una selección parcial se muestra el estado intermedio (indeterminado).
<script setup lang="ts">
const treeData = [
{ key: 'a', label: 'Frontend', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'Backend', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: 'Operaciones', disabled: true },
]
</script>
<template>
<!-- Marcado en cascada -->
<LhTree :data="treeData" show-checkbox :default-expanded-keys="['a', 'b']" />
</template><script>
export default {
data() {
return {
treeData: [
{ key: 'a', label: 'Frontend', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'Backend', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: 'Operaciones', disabled: true },
],
}
},
}
</script>
<template>
<!-- Marcado en cascada -->
<LhTree :data="treeData" show-checkbox :default-expanded-keys="['a', 'b']" />
</template>Carga diferida
Al activar lazy, expandir un nodo aún no cargado invoca la devolución de llamada load (resolve entrega los nodos secundarios y los expande; reject cancela); durante la carga, la flecha muestra un estado de rotación. Los nodos cargables deben declararse explícitamente con isLeaf: false (de lo contrario, los nodos sin children se deducen como hojas y no muestran flecha de expansión); los nodos secundarios descargados que sigan siendo ramas pendientes de carga también deben declarar isLeaf: false, mientras que las hojas reales declaran isLeaf: true.
<script setup>
import { ref } from 'vue'
const lazyData = ref([
{ key: 'lazy', label: 'Haz clic para cargar', isLeaf: false },
])
function loadNodes(node, resolve, reject) {
// Simula una petición asíncrona
setTimeout(() => {
if (node.key === 'lazy') {
resolve([
{ key: 'lazy-1', label: 'Nodo secundario 1', isLeaf: true },
{ key: 'lazy-2', label: 'Nodo secundario 2', isLeaf: true },
])
} else {
reject(new Error('nothing to load'))
}
}, 600)
}
</script>
<template>
<!-- Carga diferida -->
<LhTree :data="lazyData" lazy :load="loadNodes" />
</template><script>
export default {
data() {
return {
lazyData: [{ key: 'lazy', label: 'Haz clic para cargar', isLeaf: false }],
}
},
methods: {
// Simula una petición asíncrona
loadNodes(node, resolve, reject) {
setTimeout(() => {
if (node.key === 'lazy') {
resolve([
{ key: 'lazy-1', label: 'Nodo secundario 1', isLeaf: true },
{ key: 'lazy-2', label: 'Nodo secundario 2', isLeaf: true },
])
} else {
reject(new Error('nothing to load'))
}
}, 600)
},
},
}
</script>
<template>
<!-- Carga diferida -->
<LhTree :data="lazyData" lazy :load="loadNodes" />
</template>Independencia padre-hijo (checkStrictly)
Al activar checkStrictly, el marcado solo afecta al propio nodo: no hay cascada a descendientes, no hay retroceso a ancestros y no existe estado intermedio.
<script setup lang="ts">
const treeData = [
{ key: 'a', label: 'Frontend', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'Backend', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: 'Operaciones', disabled: true },
]
</script>
<template>
<!-- Independencia padre-hijo -->
<LhTree :data="treeData" show-checkbox check-strictly :default-expanded-keys="['a', 'b']" />
</template><script>
export default {
data() {
return {
treeData: [
{ key: 'a', label: 'Frontend', children: [
{ key: 'a-1', label: 'Vue' },
{ key: 'a-2', label: 'React' },
] },
{ key: 'b', label: 'Backend', children: [{ key: 'b-1', label: 'Node' }] },
{ key: 'c', label: 'Operaciones', disabled: true },
],
}
},
}
</script>
<template>
<!-- Independencia padre-hijo -->
<LhTree :data="treeData" show-checkbox check-strictly :default-expanded-keys="['a', 'b']" />
</template>Selección y claves controladas
multiple permite la selección múltiple; expand-on-click-node expande/contrae al hacer clic en un nodo; default-selected-keys / v-model:selected-keys controlan la selección; default-checked-keys / v-model:checked-keys controlan el marcado; default-expanded-keys / v-model:expanded-keys controlan la expansión; disabled deshabilita todo el árbol.
Seleccionados: [ "x" ] | Marcados: [ "x-1" ]
<script setup>
import { ref } from 'vue'
const selKeys = ref(['x'])
const chkKeys = ref(['x-1'])
const ctrlData = [
{ key: 'x', label: 'Diseño', children: [
{ key: 'x-1', label: 'Color' },
{ key: 'x-2', label: 'Tipografía' },
] },
{ key: 'y', label: 'Ingeniería' },
]
</script>
<template>
<!-- Selección y marcado controlados -->
<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">Seleccionados: {{ selKeys }} | Marcados: {{ chkKeys }}</p>
<!-- Deshabilitar todo el árbol -->
<LhTree :data="ctrlData" disabled />
</template><script>
export default {
data() {
return {
selKeys: ['x'],
chkKeys: ['x-1'],
ctrlData: [
{ key: 'x', label: 'Diseño', children: [
{ key: 'x-1', label: 'Color' },
{ key: 'x-2', label: 'Tipografía' },
] },
{ key: 'y', label: 'Ingeniería' },
],
}
},
}
</script>
<template>
<!-- Selección y marcado controlados -->
<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">Seleccionados: {{ selKeys }} | Marcados: {{ chkKeys }}</p>
<!-- Deshabilitar todo el árbol -->
<LhTree :data="ctrlData" disabled />
</template>API
LhTree Props
| Prop | Descripción | Tipo | Predeterminado |
|---|---|---|---|
| data | Datos del árbol | [LhTreeNode](#type-LhTreeNode)[] | — |
| showCheckbox | Muestra las casillas de marcado | boolean | false |
| multiple | Selección múltiple (al hacer clic en un nodo se conmuta la selección en lugar de reemplazarla) | boolean | false |
| expandOnClickNode | Expande/contrae al hacer clic en un nodo no hoja | boolean | true |
| defaultExpandedKeys | Claves expandidas por defecto (no controladas) | string[] | () => [] |
| expandedKeys | Claves expandidas controladas | string[] | — |
| defaultSelectedKeys | Claves seleccionadas por defecto (no controladas) | string[] | () => [] |
| selectedKeys | Claves seleccionadas controladas | string[] | — |
| defaultCheckedKeys | Claves marcadas por defecto (no controladas) | string[] | () => [] |
| checkedKeys | Claves marcadas controladas | string[] | — |
| checkStrictly | Marcado independiente entre padres e hijos (solo afecta al propio nodo, sin cascada ni estado intermedio) | boolean | false |
| lazy | Modo de carga diferida (al expandir un nodo no cargado se invoca load) | boolean | false |
| load | Devolución de llamada de carga diferida (resolve entrega los nodos secundarios y los expande; reject cancela) | (node: LhTreeNode, resolve: (children: LhTreeNode[]) => void, reject: (reason?: unknown) => void) => void | — |
| disabled | Deshabilitado por completo | boolean | false |
| sx | Propiedades de diseño rápido sx | LhSxProps | — |
Events
| Evento | Descripción | Parámetros |
|---|---|---|
| update:expandedKeys | Se dispara cuando cambia la lista de claves expandidas, para usarse con v-model:expanded-keys | (keys: string[]) |
| update:selectedKeys | Se dispara cuando cambia la lista de claves seleccionadas, para usarse con v-model:selected-keys | (keys: string[]) |
| update:checkedKeys | Se dispara cuando cambia la lista de claves marcadas, para usarse con v-model:checked-keys | (keys: string[]) |
| select | Se dispara cuando un nodo es seleccionado / se conmuta su selección | (key: string, node: LhTreeNode) |
| check | Se dispara tras un cambio de marcado | (keys: string[], node: LhTreeNode) |
| expand | Se dispara tras expandir / contrair un nodo | (key: string, expanded: boolean) |
| nodeLoad | Se dispara cuando regresan los nodos secundarios de una carga diferida | (node: LhTreeNode, children: LhTreeNode[]) |
Slots: node (con ámbito { node, level, expanded, selected }), empty.
LhTreeNode
| Campo | Descripción | Tipo |
|---|---|---|
| key | Clave única | string |
| label | Texto mostrado | string |
| children | Nodos secundarios | LhTreeNode[] |
| disabled | Deshabilitado | boolean |
| isLeaf | Si es un nodo hoja (si no se indica, se deduce a partir de children) | boolean |
Accesibilidad
role="tree"+role="treeitem", conaria-level/aria-expanded/aria-selected/aria-disabledexpuestos por completo- Las casillas usan
role="checkbox"+aria-checked(el estado intermedio esmixed) - Teclado (patrón tree de la APG): ↑/↓ se mueve entre elementos (saltando los deshabilitados, en bucle), → expande o entra al primer hijo, ← contrae o regresa al padre, Home/End van al primero/último, Enter/Space activan; roving tabindex
Definiciones de tipos
Este componente exporta las siguientes estructuras de tipos TypeScript (extraídas automáticamente de los fuentes packages/components/tree/src/types.ts y @lh-ui/share/core/*; abre cada entrada para ver la definición completa; consulta la tabla de Props de arriba para la descripción de los atributos):
LhTreeProps
export interface LhTreeProps {
/** Datos del árbol */
data: LhTreeNode[]
/** Muestra las casillas de marcado */
showCheckbox?: boolean
/** Selección múltiple (al hacer clic en un nodo se conmuta la selección en lugar de reemplazarla) */
multiple?: boolean
/** Expande/contrae al hacer clic en un nodo no hoja */
expandOnClickNode?: boolean
/** Claves expandidas por defecto (no controladas) */
defaultExpandedKeys?: string[]
/** Claves expandidas controladas */
expandedKeys?: string[]
/** Claves seleccionadas por defecto (no controladas) */
defaultSelectedKeys?: string[]
/** Claves seleccionadas controladas */
selectedKeys?: string[]
/** Claves marcadas por defecto (no controladas) */
defaultCheckedKeys?: string[]
/** Claves marcadas controladas */
checkedKeys?: string[]
/** Marcado independiente entre padres e hijos (solo afecta al propio nodo, sin cascada ni estado intermedio) */
checkStrictly?: boolean
/** Modo de carga diferida: al expandir un nodo no hoja aún no cargado se invoca load */
lazy?: boolean
/** Devolución de llamada de carga diferida: resolve entrega los nodos secundarios y los expande; reject (o una excepción) cancela */
load?: (
node: LhTreeNode,
resolve: (children: LhTreeNode[]) => void,
reject: (reason?: unknown) => void,
) => void
/** Deshabilitado por completo */
disabled?: boolean
/** Propiedades de diseño rápido sx */
sx?: LhSxProps
}LhFlatTreeNode (de @lh-ui/share/core/tree)
/** Nodo aplanado (para el renderizado) */
export interface LhFlatTreeNode {
node: LhTreeNode
key: string
level: number
parentKey: string | null
/** Cadena de ancestros (sin incluirse a sí mismo) */
ancestorKeys: string[]
/** Si es un nodo hoja */
isLeaf: boolean
}LhTreeNode (de @lh-ui/share/core/tree)
/** Datos del nodo del árbol */
export interface LhTreeNode {
/** Clave única */
key: string
/** Texto mostrado */
label: string
/** Nodos secundarios */
children?: LhTreeNode[]
/** Deshabilitado */
disabled?: boolean
/** Si es un nodo hoja (si no se indica, se deduce a partir de children) */
isLeaf?: boolean
}LhTreeCheckState (de @lh-ui/share/core/tree)
/** Estado del marcado */
export type LhTreeCheckState = 'checked' | 'indeterminate' | 'unchecked'