List and tree data
State for the items of a collection that an app changes: the items, a selection, and the changes that add, move and remove them. They follow useListData and useTreeData of React Stately.
Documents
cv.pdf
notes.txt
<script>
import { $bind, $signal } from 'brumate/signals';
import Button from '@brumate/ui/Button.brm';
import Tree from '@brumate/ui/Tree.brm';
import TreeItem from '@brumate/ui/TreeItem.brm';
import { treeData } from '@brumate/ui/data.brm.ts';
import type { TreeDataNode } from '@brumate/ui/data.ts';
import Icon from '../../src/components/Icon.brm';
interface File {
id: string;
name: string;
children?: File[];
}
const { items, selectedKeys, append, remove, removeSelectedItems } = treeData<File>({
initialItems: [
{ id: 'docs', name: 'Documents', children: [{ id: 'cv', name: 'cv.pdf' }] },
{ id: 'notes', name: 'notes.txt' },
],
});
let count = $signal(0);
function add() {
count++;
append('docs', { id: `new-${count}`, name: `untitled-${count}.md` });
}
</script>
<Tree class="ui-tree" aria-label="Files" items=items item=file selectionMode="multiple" selectedKeys=$bind(selectedKeys) expandedKeys=(['docs']) />
<p>
<Button class="ui-button" onPress=add>Add to Documents</Button>
<Button class="ui-button" onPress=removeSelectedItems>Remove selected</Button>
</p>
#template file(node: TreeDataNode<File>) {
<TreeItem class="ui-treerow" textValue=(node.value.name)>
#if (node.children) {
<Button slot="chevron" class="ui-chevron"><Icon name="chevron" size=(14) /></Button>
} #else {
<span class="ui-chevron" aria-hidden="true"></span>
}
{node.value.name}
<Button class="ui-button ui-row-action" aria-label=(`Remove ${node.value.name}`) onPress=(() => remove(node.id))>×</Button>
</TreeItem>
}
<script>
import { $bind } from 'brumate/signals';
import { treeData } from '@brumate/ui/data.brm.ts';
const { items, selectedKeys, append, removeSelectedItems } = treeData<File>({
initialItems: files,
});
</script>
<Tree aria-label="Files" items=items item=file selectionMode="multiple" selectedKeys=$bind(selectedKeys) />
<Button onPress=(() => append('docs', { id: 'new', name: 'new.md' }))>Add</Button>
<Button onPress=removeSelectedItems>Remove selected</Button>
Both are reactive modules: a component calls one at the top level of its script and destructures what it needs. The items and the selection are signals of the component, so the page state holds them and the server renders them.
listData
const { items, selectedKeys, append, remove } = listData<Pet>(
{ initialItems: pets, initialSelectedKeys: [], initialFilterText: '' },
(pet) => pet.name,
(pet, text) => pet.name.includes(text),
);
- The first argument holds the values to start with:
initialItems,initialSelectedKeysandinitialFilterText. getKey, the second, gives the key of an item. It is the item'sid, or else itskey, without it.filter, the third, says whether an item shows for the text of the filter. All items show without it.- The functions are arguments of their own, not options, because the page state holds the options, and it cannot hold a function.
It gives:
items, the items that the filter keeps,selectedKeysandfilterText.getItem(key).insert(index, ...items),insertBefore(key, ...items),insertAfter(key, ...items),append(...items)andprepend(...items).remove(...keys)andremoveSelectedItems(). A removed item leaves the selection.move(key, toIndex), andmoveBefore(key, keys)andmoveAfter(key, keys), which move the items ofkeystogether, in their order, next to the item ofkey.update(key, item).setSelectedKeys(keys),addKeysToSelection(keys),removeKeysFromSelection(keys)andsetFilterText(text).
treeData
const { items, append, move, remove } = treeData<File>(
{ initialItems: files, initialSelectedKeys: [] },
(file) => file.path,
(file) => file.files,
);
getKeyandgetChildrenare the second and third arguments. Without them, an item's key is itsidorkey, and its children are itschildren.itemsare nodes, which a Tree takes: each has itsid, the key of its parent asparentKey, the item asvalue, and itschildrenas nodes, or null for an item without children.
It gives:
itemsandselectedKeys.getItem(key), the node of a key anywhere in the tree.insert(parentKey, index, ...items),insertBefore(key, ...items),insertAfter(key, ...items),append(parentKey, ...items)andprepend(parentKey, ...items). AparentKeyof null is the top of the tree.remove(...keys)andremoveSelectedItems(). A removed node takes its children with it, and they leave the selection.move(key, toParentKey, index),moveBefore(key, keys)andmoveAfter(key, keys). A node cannot move into itself or a node inside it.update(key, item), which builds the node again from the item, with its children.setSelectedKeys(keys).
Differences from React Stately
- A selection is an array of keys, as the parts take it, not a
Set, and there is no'all'. - A node of tree data has an
id, not akey, as the items of a Tree have. getKey,filterandgetChildrenare arguments of their own, as above.