# tp-tree-nav `tp-tree-nav` is a low-level, virtualized tree renderer for Lit. It renders what it is given and emits events; parents own state. You can wrap it directly or extend it to add opinionated behavior (selection, expansion, inline actions, empty states, etc.). ## Key ideas - **Multiple roots**: pass a `roots` array; each node may have `children`. - **Virtualized list**: uses `@lit-labs/virtualizer` to render a flat, indented list for large trees. - **Events only**: emits `node-click`, `node-context`, and `node-action` (for chevron toggle, inline actions, and context menu actions). Each event carries `originalEvent` so parents can cancel or coordinate. - **Opt-in actions**: provide `defaultActions` and per-node `actions`; node actions override defaults on the same `action` key. Use `beforeContextMenu(node, actions)` to modify or block the menu. - **Custom render**: `renderNode(node, meta)` lets you override row rendering (icons per node type, state-based styling). Common states like `expanded`/`selected` are parent-managed via `node.states` or managed mode. - **Helpers (pure)**: `getNodesWithState`, `clearState`, `applyStateIf` return new data; parents reassign `roots`. - **Default icons**: `chevron`, `folder`, `file` are available via `tp-icon`; pass string keys or custom icons. ## Quick example (wrap directly) ```js import './tp-tree-nav.js'; const tree = document.querySelector('tp-tree-nav'); tree.roots = [ { label: 'Project A', slug: 'project-a', states: ['expanded'], icon: 'folder', children: [ { label: 'main.js', slug: 'main-js', icon: 'file', states: [] }, { label: 'src', slug: 'src', icon: 'folder', states: ['expanded'], children: [ { label: 'index.js', slug: 'index-js', icon: 'file', states: [] }, ], }, ], }, ]; tree.defaultActions = [ { label: 'Rename', action: 'rename', icon: 'pencil' }, { label: 'Delete', action: 'delete', icon: 'delete' }, ]; tree.beforeContextMenu = (node, actions) => node.slug === 'project-a' ? actions.filter((a) => a.action !== 'delete') : actions; // simple toggle handler const targetPath = (path) => path.join('/'); tree.addEventListener('node-action', (e) => { if (e.detail.action !== 'toggle') return; const target = targetPath(e.detail.path); tree.roots = tree.applyStateIf('expanded', (_n, p) => targetPath(p) === target); }); ``` ## Two ways to use it ### A) Wrap directly (keep tp-tree-nav “dumb”) - Manage all state yourself; set `roots` and optional `defaultActions`/`actions`. - Listen to `node-action` (chevron/double-click toggles, inline actions, context actions) and `node-click`, mutate your data, then reassign `roots`. - Use `renderNode` for custom rows; `renderEmpty` for empty states. - Use helpers (`applyStateIf`, `clearState`, `getNodesWithState`) for immutable transforms/queries. ### B) Extend tp-tree-nav (add opinionated behavior) Leverage managed mode and hooks in a subclass: ```js import { TpTreeNav } from './tp-tree-nav.js'; class MyTree extends TpTreeNav { constructor() { super(); this.manageState = true; // base tracks expand/select this.autoExpandNew = false; // set true to auto-expand unseen paths this.selectionState = 'selected'; // state name for selection this.expandOnDoubleClick = true; // toggle expand via double-click this.multiSelect = false; // enable multi-select if needed this.showActions = true; // render inline actions when present this.renderEmpty = () => html`
No items
`; } set data(items) { this.items = items; // raw items with slug/label/icon/children/actions } _renderRow(item, meta) { const { node } = item; const selected = meta.states.includes(this.selectionState); return html`
this._onRowClick(item, e)} >
this._onChevronClick(item, e)} >
${node.label}
${node.actions?.length ? html`
${node.actions.map((action) => html` this._onInlineAction(item, action, e)} > `)}
` : null}
`; } } customElements.define('my-tree', MyTree); ``` ### Key props when extending - `items`: raw data (`slug`, `label`, `icon`, `children`, optional `actions`). - `manageState`: base manages expand/select when true. - `expandedPaths` / `selectedPaths`: arrays or Sets to seed/restore state. - `selectionState`: state name for selection (default `'selected'`). - `multiSelect`: allow multiple selections. - `expandOnDoubleClick`: toggle expansion on double-click (uses click detail). - `autoExpandNew`: auto-expand unseen nodes when first seen (default false). - `applyStates`: `(node, pathParts, states) => extraStates[]` to add custom states. - `applySelection`: override selection logic `(node, pathParts, states, originalEvent) => nextStates`. - `applyToggle`: override expand/collapse logic `(node, pathParts, states, originalEvent) => nextStates`. - `showActions`: render inline actions if provided. - `renderEmpty`: custom empty renderer. ### Events (both modes) - `node-click`: emitted on row click. - `node-action`: emitted for toggles (`action: 'toggle'`, source chevron/double-click), inline actions, and context menu actions. - `node-context`: before showing context menu; `preventDefault` to cancel. ### Helpers (both modes) - `TpTreeNav.buildTree(items, { expandedPaths, selectedPaths, selectionState, applyStates, knownPaths, autoExpandNew })` — pure helper used by managed mode; can be used externally. - `applyStateIf(state, predicate)`, `clearState(state)`, `getNodesWithState(state)` for immutable transforms/queries. ### Data shape - `slug` (unique per sibling), `label`, `icon` (string key or icon data), `children` (array), optional `actions` (`{ action, label?, icon?, tooltip? }`).