TreeView#

TreeView renders hierarchical data with expand/collapse toggles, indentation, and virtualization: only visible rows are rendered. Maps to UI Toolkit's TreeView.

Import#

import { TreeView } from "onejs-react"

Basic Usage#

Data is a plain nested array. Each node has data (whatever you want), optional children, and an optional numeric id. Rows are created by makeItem and populated by bindItem, which receives the node's data directly:

const project = [
    {
        id: 1, data: "Assets", children: [
            { id: 2, data: "Player.cs" },
            { id: 3, data: "Enemy.cs" },
        ],
    },
    { id: 4, data: "README.md" },
]

function FileTree() {
    return (
        <TreeView
            rootItems={project}
            fixedItemHeight={22}
            autoExpand
            makeItem={() => {
                const label = new CS.UnityEngine.UIElements.Label()
                // Stretch and center so the text sits mid-row
                label.style.flexGrow = 1
                label.style.unityTextAlign = CS.UnityEngine.TextAnchor.MiddleLeft
                return label
            }}
            bindItem={(element, index, data) => {
                element.text = data
            }}
            style={{ height: 300 }}
        />
    )
}

Ids and Updating Data#

Ids identify nodes across data updates: selection and expansion state are tracked by id. They are optional (omitted ids are auto-assigned), but provide stable ids whenever the tree's data changes over time, or state will not carry over.

To update the tree, pass a new rootItems reference. Mutating the existing array does nothing, the same rule as React state in general:

const [items, setItems] = useState(initialItems)

// Adds a node: new arrays along the changed path
setItems([...items, { id: 99, data: "New.txt" }])

Props#

Required Props#

PropTypeDescription
rootItemsTreeViewItem[]Nested data: { id?, data, children? }
makeItem() => VisualElementFactory for row content elements
bindItem(element, index, data) => voidPopulate a row; data is the node's data

Optional Props#

PropTypeDefaultDescription
unbindItem(element, index, data) => voidCalled when a row is recycled
destroyItem(element) => voidCalled when a row is destroyed
fixedItemHeightnumberFixed row height (fastest)
virtualizationMethod"FixedHeight", "DynamicHeight""FixedHeight"Virtualization strategy
autoExpandbooleanfalseExpand all items when data is set
selectionType"None", "Single", "Multiple""Single"Selection mode
onSelectionChange(items, ids) => voidSelected node data + ids
showBorderbooleanfalseBorder around the tree
showAlternatingRowBackgrounds"None", "ContentOnly", "All""None"Zebra striping

The row toggle and indentation come from UI Toolkit; makeItem returns only the row's content element. The imperative makeItem/bindItem pair exists for the same reason as ListView's: rows are recycled during scrolling, which beats React diffing for large trees.

Selection#

onSelectionChange hands you the selected nodes' data and their ids:

function SelectableTree({ items }) {
    const [current, setCurrent] = useState(null)

    return (
        <View>
            <Label text={current ? `Selected: ${current.name}` : "Nothing selected"} />
            <TreeView
                rootItems={items}
                fixedItemHeight={24}
                selectionType="Single"
                onSelectionChange={(datas, ids) => setCurrent(datas[0] ?? null)}
                makeItem={() => {
                    const label = new CS.UnityEngine.UIElements.Label()
                    label.style.flexGrow = 1
                    label.style.unityTextAlign = CS.UnityEngine.TextAnchor.MiddleLeft
                    return label
                }}
                bindItem={(element, index, data) => {
                    element.text = data.name
                }}
                style={{ height: 400 }}
            />
        </View>
    )
}

Expand, Collapse, and Selection by Id#

The underlying UI Toolkit methods are available through a ref:

function Controls() {
    const tree = useRef(null)

    return (
        <View>
            <View style={{ flexDirection: "row" }}>
                <Button text="Expand All" onClick={() => tree.current.ExpandAll()} />
                <Button text="Collapse All" onClick={() => tree.current.CollapseAll()} />
                <Button text="Jump" onClick={() => tree.current.SetSelectionById(3)} />
            </View>
            <TreeView ref={tree} rootItems={items} ... />
        </View>
    )
}

Useful methods: ExpandAll(), CollapseAll(), ExpandItem(id), CollapseItem(id), IsExpanded(id), SetSelectionById(id), GetTreeCount().

Multi-Element Rows#

Build richer rows (an icon, a name, a badge) in makeItem and reach the children by position in bindItem with element.ElementAt(i), which needs no setup. Q(name) also works after a one-time useExtensions(CS.UnityEngine.UIElements.UQueryExtensions) (OneJS 3.1.3+). See ListView: Complex Items for a full example; the pattern is identical.

Styling the Built-in Chrome#

The expand toggle, indentation, and row highlights come from UI Toolkit's default theme, which assumes a light-friendly palette (the default hover is a light grey that fights light text on dark UIs). Restyle them with a small stylesheet scoped under a class on your TreeView:

compileStyleSheet(`
.my-tree .unity-tree-view__item-toggle {
    margin-right: 6px;
}
.my-tree .unity-tree-view__item-toggle .unity-toggle__checkmark {
    background-color: rgba(0, 0, 0, 0);
    -unity-background-image-tint-color: #8b93a7;
}
.my-tree .unity-collection-view__item:hover {
    background-color: rgba(91, 156, 248, 0.12);
}
.my-tree .unity-collection-view__item--selected,
.my-tree .unity-collection-view__item--selected:hover {
    background-color: #2f4a7a;
}
`, "my-tree")
<TreeView className="my-tree" ... />

Rows are .unity-collection-view__item, with a --selected modifier and a :hover state; the expand toggle is .unity-tree-view__item-toggle, and the arrow inside it is .unity-toggle__checkmark (clear its background for a bare chevron). ListView rows share the same unity-collection-view__item classes, so the row rules work there too.

TreeView or ListView?#

Use TreeView when the data is hierarchical and users need to fold branches. For flat data, ListView is simpler and slightly cheaper. Both virtualize, so either handles thousands of rows.