The Tree component displays hierarchical data as an expandable list of folders and items. It supports single and multiple selection, drag-and-drop reordering, in-place renaming, a built-in filter bar, and per-node icons and styles. Node ids are used throughout the component to track selection, expanded state and events.

A node with a `children` array (even an empty one) is a **folder**. A node whose `children` is `null` or absent is a **leaf**. The Tree never saves changes for you. Renaming and expanding write back to `items`, but drag-and-drop only reorders the tree on screen and fires `onMove`, so persisting the new order (for example, to a database) is up to the view.

## Properties

| Name | Description | Property Type |
| --- | --- | --- |
| items | The tree's top-level nodes. Node ids must be unique across the whole tree, not just among siblings. Expanding or collapsing a folder writes that node's `expanded` flag here, and renaming a node writes its `label`. Drag-and-drop does not write here. A new `items` value with different nodes or order (for example, a re-query after persisting) replaces the on-screen order. See [TreeNode Properties](#treenode-properties) below. | array |
| selection | The ids of the selected nodes. Written by the component when the user selects, and writing it from a script selects those nodes (ids not in the tree are dropped). Holds at most one id unless `interaction.multiSelect` is on. | array |
| selectionData | The `data` of each selected node, in the same order as `selection` (`null` for nodes without data). Output only. | array |
| interaction | What the user is allowed to do with the tree. See [Interaction Properties](#interaction-properties) below. | object |
| filter | The built-in filter bar. See [Filter Properties](#filter-properties) below. | object |
| appearance | Row sizing, expand arrows and row state styles. See [Appearance Properties](#appearance-properties) below. | object |
| style | Style applied to the tree's root container. | style |

### TreeNode Properties

Each node in the `items` array (and within any node's `children`) supports the following properties.

| Name | Description | Property Type |
| --- | --- | --- |
| id | Identifier for the node, unique across the whole tree. Used by `selection`, `expanded` and every event. Numbers are treated as their string form. A node without an id is given a generated one, and a duplicate id gets a suffix. | string or number |
| label | The text shown for the node. | string |
| icon | The icon shown before the label, after the expand arrow. A `null` icon or an empty `path` uses [appearance.defaultNodeIcons](#appearancedefaultnodeicons). See [Icon Properties](#icon-properties) below. | object |
| disabled | Greys the node out and stops it from being selected, dragged or dropped into. A disabled folder disables everything under it. | boolean |
| draggable | Set to `false` to pin this node in place. Has no effect unless `interaction.dragEnabled` is on. Default is `true`. | boolean |
| droppable | Set to `false` so nothing can be dropped into this folder. Ignored on leaf nodes. Default is `true`. | boolean |
| editable | Set to `false` to stop this node from being renamed. Has no effect unless `interaction.editEnabled` is on. Default is `true`. | boolean |
| expanded | Whether this folder is open. Written by the component when the user expands or collapses it. Set it from a binding or script to open or close the folder. Leave it out to keep whatever state the user left the folder in. Ignored on leaf nodes. | boolean |
| data | A freeform payload (object, array, string, number, boolean or `null`), such as a database key. Returned in `selectionData` and in event payloads. | any |
| style | A per-node style, layered on top of every `appearance` style. | style |
| children | The node's child nodes. An array makes the node a folder, and `null` makes it a leaf. An empty array still makes a folder, so nodes can be dropped into it, but it shows no expand arrow and keeps the closed folder icon until it has children. | array |

Example `items` array:

```json
[
  {
    "id": "enterprise-1",
    "label": "Kanoa Industries",
    "expanded": true,
    "data": { "assetId": 9 },
    "children": [
      {
        "id": "site-1",
        "label": "Adelaide Hills",
        "data": { "assetId": 10 },
        "children": [
          {
            "id": "line-1",
            "label": "Line 1",
            "data": { "assetId": 101 },
            "children": null
          },
          {
            "id": "line-2",
            "label": "Line 2",
            "disabled": true,
            "data": { "assetId": 102 },
            "children": null
          }
        ]
      }
    ]
  }
]
```

### Icon Properties

Used by a node's `icon` and by each entry in `appearance.defaultIcons` and `appearance.defaultNodeIcons`.

| Name | Description | Property Type |
| --- | --- | --- |
| path | The icon path in `library/name` form, for example `material/folder`. Empty shows no icon. | string |
| color | The icon fill color. Empty inherits the text color. | color |
| style | Style applied to the icon's `svg` element. | style |

### Interaction Properties

| Name | Description | Property Type |
| --- | --- | --- |
| interactable | When `false`, nodes can't be selected or dragged. Folders can still be expanded and collapsed. Default is `true`. | boolean |
| branchNodeSelectable | When `false`, only leaf nodes can be selected. Clicking a folder does nothing beyond expanding it through its arrow. Default is `true`. | boolean |
| multiSelect | Allows selecting several nodes with Ctrl/Cmd-click and Shift-click. Default is `false`. | boolean |
| dragEnabled | Allows nodes to be dragged to reorder or re-parent them. Each drop fires `onMove`. Per-node `draggable`, `droppable` and `disabled` narrow this further. Default is `false`. | boolean |
| editEnabled | Allows renaming nodes in place. Press Enter on the focused node or double-click its label, then press Enter or click away to save, and Escape to cancel. Saving writes the node's `label` and fires `onEdit`. Per-node `editable` and `disabled` narrow this further. Default is `false`. | boolean |

### Filter Properties

The filter narrows the tree to nodes whose label contains the filter text, plus their ancestors. Folders open automatically while filtering and return to their previous state when the filter is cleared. Dragging is turned off while the filter has text.

| Name | Description | Property Type |
| --- | --- | --- |
| visible | Shows the built-in filter bar above the tree: the filter box and the buttons beside it. Hide it to drive `filter.text` from elsewhere, for example a Text Field. Default is `false`. | boolean |
| text | The current filter. Written by the built-in filter box as the user types. Set it from a binding or script to filter the tree. Empty shows every node. | string |
| caseSensitive | Match the label's case exactly. Default is `false`. | boolean |
| placeholder | Placeholder text in the built-in filter box. Default is `Filter Items...`. | string |
| clearSelectionButton | Shows a button beside the filter box that clears the selection and fires `onSelect`. Disabled while nothing is selected or the tree isn't interactable. Default is `true`. | boolean |
| expandButton | Shows a button beside the filter box that expands every folder, then collapses them all on the next click. Each folder it opens or closes writes its `expanded` flag and fires `onToggle`, as if it had been clicked. Default is `true`. | boolean |
| matchCount | The number of nodes whose label matches, across the whole tree. Ancestors shown only for context aren't counted. `0` when the filter is empty. Output only. | integer |
| style | Style applied to the built-in filter box. | style |
| containerStyle | Style applied to the filter bar, which holds the filter box and its buttons. | style |

### Appearance Properties

| Name | Description | Property Type |
| --- | --- | --- |
| rowHeight | The height of each row in pixels. Icons scale with it. Default is `24`. | number |
| indent | The horizontal indent per tree level, in pixels. Default is `12`. | number |
| showGuideLines | Draws guide lines from each open folder down through its contents, with a short stub to each leaf. Default is `true`. | boolean |
| defaultIcons | The expand/collapse arrow shown in front of every node, before the node's own icon. See [appearance.defaultIcons](#appearancedefaulticons) below. | object |
| defaultNodeIcons | The node icon used when a node doesn't set its own icon path. See [appearance.defaultNodeIcons](#appearancedefaultnodeicons) below. | object |
| selectedStyle | Style for selected rows. Without it, selected rows use the theme's call-to-action blue with white text. Anything set here overrides that. | style |
| unselectedStyle | Style for rows that aren't selected. | style |
| hoverStyle | Style layered on a row while the mouse is over it. Not applied to disabled rows. | style |
| dropTargetStyle | Style layered on a folder while nodes are being dragged over it. | style |
| disabledStyle | Style layered on disabled rows, including everything under a disabled folder. | style |

#### appearance.defaultIcons

The expand/collapse arrow shown in front of every node. Leave a path empty to show nothing, while keeping the space so labels stay aligned. Each entry takes the [Icon Properties](#icon-properties) above.

| Name | Description | Default Path |
| --- | --- | --- |
| collapsed | The arrow for a closed folder. | `material/arrow_right` |
| expanded | The arrow for an open folder. | `material/arrow_drop_down` |
| empty | Shown in the arrow position for leaf nodes and folders with no children. | Empty |

#### appearance.defaultNodeIcons

The node icon used when a node doesn't set its own icon path. A node's own icon color and style still apply. Leave a path empty to show no icon. Each entry takes the [Icon Properties](#icon-properties) above.

| Name | Description | Default Path |
| --- | --- | --- |
| collapsed | The icon for a closed folder. | `material/folder` |
| expanded | The icon for an open folder. | `material/folder_open` |
| empty | The icon for a leaf node. | `material/stop` |

## Component Events

### onSelect

Triggered when the selection changes, including when the clear selection button is used.

| Property | Type | Description |
| --- | --- | --- |
| event.selection | array | The ids of the selected nodes after this change, as strings. Mirrors the `selection` property. |
| event.selectionData | array | The `data` of each selected node, in the same order as `selection`. Mirrors the `selectionData` property. |

### onToggle

Triggered when the user expands or collapses a folder.

| Property | Type | Description |
| --- | --- | --- |
| event.id | string | The id of the folder. |
| event.expanded | boolean | `true` if the folder is now open, `false` if it was closed. |
| event.data | any | The folder's `data`, or `null` if it has none. |

### onDoubleClick

Triggered when the user double-clicks a folder or an item. Folders still expand or collapse as well.

| Property | Type | Description |
| --- | --- | --- |
| event.id | string | The id of the node that was double-clicked. |
| event.label | string | The label of the node that was double-clicked. |
| event.data | any | The node's `data`, or `null` if it has none. |

### onEdit

Triggered when the user renames a node and saves the new label. Requires `interaction.editEnabled`.

| Property | Type | Description |
| --- | --- | --- |
| event.id | string | The id of the renamed node. |
| event.label | string | The new label, already written to the node in `items`. |
| event.previousLabel | string | The label before the rename. |
| event.data | any | The node's `data`, or `null` if it has none. |

### onMove

Triggered when the user drops one or more nodes. Requires `interaction.dragEnabled`. The tree reorders itself on screen, but `items` is not changed. Use this event to persist the move.

| Property | Type | Description |
| --- | --- | --- |
| event.dragIds | array | The ids of the dropped nodes, in the order they now appear under the target parent. |
| event.dragNodes | array | The dropped nodes, one per id in `dragIds`. Each has `id`, `label`, `data`, and `fromParentId`, the id of the node's parent before the move (`null` if it was top-level). |
| event.parentId | string | The id of the folder the nodes were dropped into, or `null` for the top level. |
| event.parentData | any | The target folder's `data`, or `null` for the top level or a folder without data. |
| event.index | integer | The final position of the first dropped node among the target parent's children, counted after the move (0-based). |
| event.siblingIds | array | The target parent's full child order after the move. Writing each id's position as its sort order is enough to persist the move. |

## Common Scripting

Persist a drag-and-drop move by saving the new sibling order. In this example, `saveSortOrder` is a function in your project:

```python
# onMove event handler
for position, nodeId in enumerate(event.siblingIds):
    saveSortOrder(nodeId, event.parentId, position)
```

Read the data of the selected node:

```python
tree = self.getSibling("Tree")
if tree.props.selectionData:
    assetId = tree.props.selectionData[0]["assetId"]
```
