Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit65083fe8f2

README.md

edui.widgets.tree (module)

The editor hierarchy tree primitive — edui.tree{...}.

The tree is generic over its nodes. It never reads an entity field: a caller supplies the accessors and owns the state, and the tree only reports clicks. That is what lets one primitive back the Entities hierarchy, an asset folder listing, and a scene-layer list with no branch per caller.

  • Accessors: idOf(node), childrenOf(node), labelOf(node) — defaulting to node.id, node.children, and node.label/node.name.
  • Expand state is the caller's: pass an expanded set ({ [id] = true }) or an isExpanded(node, id) predicate, and handle onToggle(node, id).
  • Selection is the caller's: pass selected (an id, or an { [id] = true } set for multi-selection) and handle onSelect(node, id).
  • renderRow(node, row, selected) replaces the default label with your own content (badges, per-row icon buttons); return one widget or a list.

Flattening

flatten{ roots, ... } -> { { node, id, depth, hasChildren, expanded, parent } } is exported separately (edui.widgets.flattenTree) and is pure — no ui.* calls and no app — so display order is testable without a rendered frame. A node's children appear only while that node is expanded, so a collapsed tree costs nothing to build. A node that appears on its own ancestry is reported and skipped instead of recursing forever.

The window

windowBounds(total, offset, viewport, rowH, overscan?, quantum?) -> first, last (edui.widgets.windowBounds) is the pure arithmetic behind the windowed shape: the rows in view plus an overscan on each side, the start snapped to a multiple of the quantum so a small scroll moves nothing. first is the 0-based index of the band's first row and last the 1-based index of its last, so the rows are first + 1 through last.

Row rendering

Rows are uniform-height, which is exactly the shape scrollArea's virtual prop wants — the renderer then draws only the band in view. Leaf rows still reserve the disclosure-chevron slot so every label at a given depth starts on the same column.

Row keys are stable ("<key>:<id>"), so the callback ids survive a rebuild and anything the renderer tracks by id (focus, ui.widgetState) stays put.

The disclosure chevron and the select target are siblings, never nested — a click lands on exactly one, so opening a node cannot also select it. The select target grows to fill the rest of the row, which is what keeps the whole row clickable rather than just the label. The selection background sits on the non-clickable row container so it spans the indent and chevron too.

Note that virtual is render-side windowing: the row widgets are still built for every visible node. Construction-side windowing (building only the scrolled band) needs the virtualTotal/virtualFirst scrollArea props, which are not on this branch.