Tree View
TreeView shows a tree you can select from, expand and collapse, for example folders and files. It works with the mouse and with the keyboard. You give it a flat list of TreeNode objects, and it emits select with the key of the row the user picks.
Usage
<TreeView items="$nodes" selected="$selected" label="Files" (select)="onSelect" />
<div class="text-muted small mt-2">Selected: {$selected === null ? 'nothing yet' : $selected}</div>
<?php

use Viewi\Components\BaseComponent;
use Viewi\UI\Components\Navigation\TreeNode;

class FilesPage extends BaseComponent
{
 public array $nodes = [];
 public $selected = null;

 public function mounted()
 {
 // key, label, depth, parentKey, icon, badge
 $this->nodes = [
 new TreeNode('docs', 'Documents', 1, null, 'bi-folder', '3'),
 new TreeNode('reports', 'Reports', 2, 'docs', 'bi-folder', '2'),
 new TreeNode('q1', 'Q1.pdf', 3, 'reports', 'bi-file-earmark'),
 new TreeNode('q2', 'Q2.pdf', 3, 'reports', 'bi-file-earmark'),
 new TreeNode('notes', 'Notes.txt', 2, 'docs', 'bi-file-earmark'),
 new TreeNode('photos', 'Photos', 1, null, 'bi-folder', '2'),
 new TreeNode('beach', 'Beach.jpg', 2, 'photos', 'bi-file-earmark'),
 new TreeNode('city', 'City.jpg', 2, 'photos', 'bi-file-earmark'),
 new TreeNode('readme', 'Readme.md', 1, null, 'bi-file-earmark'),
 ];
 }

 public function onSelect($key)
 {
 $this->selected = $key;
 }
}
Building the list
items is a flat list in tree order: a node, then all of its children, then the next node. Each node has:
depth- its level,1for the top level. It sets the indentation.parentKey- the key of its parent,nullfor the top level.
A node has children when the node right after it in the list has its key as parentKey. Such a node gets a chevron that opens and closes it.
TreeView does not change selected by itself. Handle select and set the value you pass to selected, as in the example above.
Expand and collapse
- A small tree (
collapseOvernodes or fewer) starts fully expanded. A larger tree starts collapsed. SetcollapseOver="0"to always start expanded. - When
selectedchanges, all parents of the selected node are opened, so the highlighted row is never hidden inside a closed branch. - Once the user opens or closes a node, their choice is kept when
itemschanges. - Clicking the chevron opens or closes the node without selecting it.
Keyboard
The tree is one Tab stop: the row the user last focused, else the selected row, else the first one.
ArrowUpandArrowDownmove between visible rows.HomeandEndjump to the first and last row.ArrowRightopens a closed node, or moves to its first child when it is open.ArrowLeftcloses an open node, or moves to its parent.EnterorSpaceselects the row.
Row actions and rename
Use the actions slot to put buttons or a Dropdown Menu next to each row. The slot is rendered beside the row button, not inside it. It shows when the user hovers or focuses the row, and it is always visible on the selected row and on touch screens.
Use the edit slot to replace a row with your own content, for example a rename box. It is shown for the row whose key equals editingKey.
Both slots give you the row's TreeNode with data. Hover a row and click the pencil to rename it (press Enter to save, Escape to cancel), or the trash icon to delete it.
<TreeView items="$nodes" selected="$selected" editingKey="$editingKey" label="Files" (select)="onSelect">
 <slotContent name="actions" data="$node">
 <button type="button" class="btn btn-sm btn-link p-1" title="Rename" (click)="startRename($node)"><Icon name="bi-pencil" /></button>
 <button type="button" class="btn btn-sm btn-link p-1 text-danger" title="Delete" (click)="deleteNode($node)"><Icon name="bi-trash" /></button>
 </slotContent>
 <slotContent name="edit" data="$node">
 <input type="text" class="form-control form-control-sm" value="$node->label" aria-label="New name"
 (keydown)="onRenameKey($event)" />
 </slotContent>
</TreeView>
<?php

use Viewi\Components\BaseComponent;
use Viewi\Components\DOM\DomEvent;
use Viewi\UI\Components\Navigation\TreeNode;

class FilesPage extends BaseComponent
{
 public array $nodes = [];
 public $selected = null;
 public $editingKey = null;

 // mounted() and onSelect() as in the first example

 public function startRename(TreeNode $node)
 {
 $this->editingKey = $node->key;
 }

 public function deleteNode(TreeNode $node)
 {
 $this->removeNode($node->key);
 }

 public function onRenameKey(DomEvent $event)
 {
 if ($event->key === 'Enter') {
 $this->renameNode($this->editingKey, $event->target->value);
 $this->editingKey = null;
 } else if ($event->key === 'Escape') {
 $this->editingKey = null;
 }
 }

 public function renameNode($key, string $label)
 {
 $result = [];
 foreach ($this->nodes as $node) {
 if ($node->key === $key && $label !== '') {
 $result[] = new TreeNode($node->key, $label, $node->depth, $node->parentKey, $node->icon, $node->badge);
 } else {
 $result[] = $node;
 }
 }
 $this->nodes = $result;
 }

 // Removes the node and everything under it (the rows that follow it and are deeper).
 public function removeNode($key)
 {
 $result = [];
 $skipBelow = 0;
 foreach ($this->nodes as $node) {
 if ($skipBelow > 0 && $node->depth > $skipBelow) {
 continue;
 }
 $skipBelow = 0;
 if ($node->key === $key) {
 $skipBelow = $node->depth;
 continue;
 }
 $result[] = $node;
 }
 $this->nodes = $result;
 $this->selected = null;
 }
}
Drag and drop
Rows whose key is in draggableKeys can be dragged. The tree emits dragStart with the key when a drag starts and dragEnd when it ends, whether it was dropped or not.
With dropEnabled, every row except those in dropDisabledKeys accepts a drop. The row under the pointer is highlighted, and dropping emits drop with that row's key. A row that does not accept a drop shows the browser's "no drop" cursor and emits nothing.
TreeView does not move anything. Your component remembers what is being dragged (from dragStart, or from anything else on the page you made draggable) and updates items on drop.
In this example files can be dragged, and only folders accept a drop.
<TreeView items="$nodes" selected="$selected" label="Files" draggableKeys="$fileKeys" dropEnabled
 dropDisabledKeys="$fileKeys" (select)="onSelect" (dragStart)="onDragStart" (dragEnd)="onDragEnd"
 (drop)="onDrop" />
<div class="text-muted small mt-2">{$message === '' ? 'Drag a file onto a folder.' : $message}</div>
<?php

use Viewi\Components\BaseComponent;
use Viewi\UI\Components\Navigation\TreeNode;

class FilesPage extends BaseComponent
{
 public array $nodes = [];
 public $selected = null;
 public array $fileKeys = ['q1', 'q2', 'notes', 'beach', 'city', 'readme'];
 public $dragging = null;
 public string $message = '';

 // mounted() and onSelect() as in the first example

 public function onDragStart($key)
 {
 $this->dragging = $key;
 }

 public function onDragEnd()
 {
 $this->dragging = null;
 }

 // A file was dropped on a folder: move it to be the folder's first child.
 public function onDrop($folderKey)
 {
 if ($this->dragging === null) {
 return;
 }
 $moved = null;
 $rest = [];
 foreach ($this->nodes as $node) {
 if ($node->key === $this->dragging) {
 $moved = $node;
 } else {
 $rest[] = $node;
 }
 }
 if ($moved === null) {
 return;
 }
 $result = [];
 foreach ($rest as $node) {
 $result[] = $node;
 if ($node->key === $folderKey) {
 $result[] = new TreeNode($moved->key, $moved->label, $node->depth + 1, $node->key, $moved->icon);
 $this->message = $moved->label . ' moved to ' . $node->label;
 }
 }
 $this->nodes = $result;
 $this->dragging = null;
 }
}
Flat list and disabled rows
Set flat to list every item at one level, without indentation or chevrons. This is useful for search results, where the parents of a match may not be in the list. Tree Picker uses it this way.
Rows whose key is in disabledKeys are shown but can not be selected: clicking them or pressing Enter does not emit select.
<TreeView items="$matches" flat label="Search results" (select)="onSelect" />
<TreeView items="$nodes" disabledKeys="$here" label="Folders" (select)="onSelect" />
Properties
TreeView
items - list of TreeNode objects in tree order. Default: [].
selected - (optional) key of the highlighted row. Default: null.
label - (optional) accessible name (aria-label) of the tree. Default: Tree.
collapseOver - (optional) up to this many nodes the tree starts fully expanded. With more nodes it starts collapsed, except the path to selected. 0 means always start expanded. Default: 20.
indent - (optional) indentation per level, in rem. Default: 0.85.
flat - (optional) shows every item at one level, without indentation or chevrons. Arrow left and right do nothing. Default: false.
disabledKeys - (optional) keys of rows that are shown but can not be selected. Default: [].
editingKey - (optional) key of the row that shows the edit slot instead of its button. Default: null.
draggableKeys - (optional) keys of rows that can be dragged. Default: [].
dropEnabled - (optional) rows accept drops. Default: false.
dropDisabledKeys - (optional) keys of rows that never accept a drop, even with dropEnabled. Default: [].
TreeNode
TreeNode is a data class. Create it with new TreeNode($key, $label, $depth, $parentKey, $icon, $badge, $title).
key - unique key of the node. It is the value of select, drop and dragStart.
label - the text of the row.
depth - (optional) level of the node, 1 for the top level. Default: 1.
parentKey - (optional) key of the parent node, null for the top level. Default: null.
icon - (optional) icon shown before the label. Default: `` (empty), no icon.
badge - (optional) text shown in a badge at the end of the row, for example a count. Default: `` (empty), no badge.
title - (optional) tooltip of the row. Tree Picker also uses it as the full path of the node. Default: `` (empty).
Events
TreeView
(select) - happens when the user clicks a row or presses Enter or Space on it. The event value is the node's key. Disabled rows do not emit it.
(dragStart) - happens when the user starts dragging a row from draggableKeys. The event value is the node's key.
(dragEnd) - happens when a drag of a row ends, dropped or not.
(drop) - happens when something is dropped on a row that accepts drops. The event value is the target node's key.
Slots
TreeView
actions - content shown beside every row, for example buttons or a menu. data is the row's TreeNode.
edit - content shown instead of the row button when the node's key equals editingKey. data is the row's TreeNode.