Tree
Rows that nest, open and close, such as the folders and files of a file browser, which the user can select and act on.
Documents
cv.pdf
Work
Photos
notes.txt
Selected: none. Open: docs.
<script>
import { $bind, $signal } from 'brumate/signals';
import Button from '@brumate/ui/Button.brm';
import Checkbox from '@brumate/ui/Checkbox.brm';
import Tree from '@brumate/ui/Tree.brm';
import TreeItem from '@brumate/ui/TreeItem.brm';
import type { Key } from '@brumate/ui/collection.ts';
import Icon from '../../src/components/Icon.brm';
interface File {
id: string;
name: string;
children?: File[];
}
const files: File[] = [
{
id: 'docs',
name: 'Documents',
children: [
{ id: 'cv', name: 'cv.pdf' },
{ id: 'work', name: 'Work', children: [{ id: 'plan', name: 'plan.md' }] },
],
},
{ id: 'photos', name: 'Photos', children: [{ id: 'beach', name: 'beach.jpg' }] },
{ id: 'notes', name: 'notes.txt' },
];
let picked = $signal<Key[]>([]);
let open = $signal<Key[]>(['docs']);
</script>
<Tree class="ui-tree" aria-label="Files" items=files item=file selectionMode="multiple" selectedKeys=$bind(picked) expandedKeys=$bind(open) />
<p>Selected: {picked.join(', ') || 'none'}. Open: {open.join(', ') || 'none'}.</p>
#template file(file: File) {
<TreeItem class="ui-treerow" textValue=(file.name)>
#if (file.children) {
<Button slot="chevron" class="ui-chevron"><Icon name="chevron" size=(14) /></Button>
} #else {
<span class="ui-chevron" aria-hidden="true"></span>
}
<Checkbox slot="selection" class="ui-box" aria-label=(`Select ${file.name}`)>
<span class="ui-mark" aria-hidden="true"><Icon name="check" size=(12) /><Icon name="minus" size=(12) /></span>
</Checkbox>
{file.name}
</TreeItem>
}
<Tree aria-label="Files" items=files item=file selectedKeys=$bind(picked) expandedKeys=$bind(open) selectionMode="multiple" />
#template file(file: File) {
<TreeItem textValue=(file.name)>
<Button slot="chevron">▸</Button>
<Checkbox slot="selection" aria-label=(`Select ${file.name}`) />
{file.name}
</TreeItem>
}
- A
Treetakesitems, each with anid, its key. An item withchildrenis a parent, even when the array is empty. It needsaria-labeloraria-labelledby. itemis a#templatethat renders aTreeItemfor an item. The Tree renders it for each item that shows, and gives the TreeItem its key and its place in the tree.expandedKeysholds the keys of the open items, andonExpandedChangehears of changes. The children of a closed item do not render.- Selection is as in a GridList:
selectionMode,selectionBehavior,selectedKeys,disabledKeysandonSelectionChange. rowHeightrenders only the rows in view, as in a Virtualizer, which gives it too.getTextValuereturns the text of an item for the letters that move to a row, for the rows out of view.onActiongets the key of a row that Enter acts on, or a press when nothing is selectable. Without it, and with nothing selectable, Enter and a press open and close a row with children.
Rows
- The Tree is a treegrid, and its rows are flat, as in React Aria. Each TreeItem is a row with one cell, with
aria-level,aria-posinsetandaria-setsize, andaria-expandedwhen it has children. The server renders them all, so the page is right before its code loads. - A
Buttonwithslot="chevron"opens and closes its row. It is not a tab stop, and its label says Expand or Collapse, with the name of the row. - A
Checkboxwithslot="selection"and novalueshows whether the row is selected, and toggles it. - A row shows
data-expanded,data-has-child-items,data-level,data-selected,data-disabled,data-pressed,data-hovered,data-focusedanddata-focus-visible. Its level is in the--tree-item-levelCSS variable too, to indent it:
[role='row'] {
padding-inline-start: calc((var(--tree-item-level) - 1) * 1.25rem);
}
Keyboard
- Tab stops at the tree once, at the row that had focus last, or else the first selected row, or the first row. Tab from inside the tree leaves it. When the row that has focus leaves the tree, such as when its parent closes from outside, the tree is a stop of Tab again.
- Up and Down move between the rows that show, Home and End go to the first and the last, and PageUp and PageDown move a page. Letters move to the row whose text starts with them.
textValuesets that text for a row whose content is more than text. - Right opens a closed row. Left closes an open row, or else moves to the row that holds it. Right to left, they turn around.
- On an open row, or a row with no children, Right and Left move through the controls in the row, and back to the row.
- Space selects the row that has focus, Shift extends a range, Ctrl+A or Cmd+A selects all, and Escape clears the selection.