npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@object-ui/plugin-tree

v18.3.0

Published

Tree / tree-grid visualization plugin for ObjectUI

Readme

@object-ui/plugin-tree

Tree / tree-grid view plugin for Object UI.

Renders a self-referencing object as an indented, expand/collapse tree-grid — the right view for hierarchies of unbounded depth such as business unit / org chart, category trees, menu trees, BOMs, or nested comments. (Grouping handles fixed-depth hierarchies; a tree handles arbitrary depth.)

It registers one component type via the ComponentRegistry:

  • object-tree — the object-bound renderer, and the one an author selects

The bare tree registry key (view:tree), a second key on the same renderer, is RETIRED (objectui#10859 batch 8): objectui validate refused it at type, and nothing authored it. tree survives as a host-composition VIEW type (see below), which ObjectView and ListView compose into an object-tree node.

Usage

Author an object-tree node. ObjectQLComponentSchema narrows on type, so each key is checked against ObjectTreeSchema, which mirrors the object-tree row of @objectstack/spec (17.5.0 and later):

import type { ObjectQLComponentSchema } from '@object-ui/types';

const schema: ObjectQLComponentSchema = {
  type: 'object-tree',
  objectName: 'business_unit',
  tree: {
    parentField: 'parent',        // single-parent pointer (auto-detected if omitted)
    labelField: 'name',           // indented first column
    fields: ['manager'],          // additional flat columns
    defaultExpandedDepth: 1,      // 0 = roots only; omit = expand all
  },
  filter: [{ field: 'status', operator: 'equals', value: 'active' }],
  navigation: { mode: 'drawer' },
};

Keys

| Key | Description | | --- | --- | | objectName | The object to query. One of data, staticData and objectName is the record source, read in that order; objectui validate refuses a node with none of the three. | | data | A { provider, … } configuration, read first. { provider: 'value', items } draws those records and { provider: 'object', object } queries that object. The api and schema providers draw no rows on the tree. A bare array is not a record source (objectui#8348): inline rows go under staticData. | | staticData | Inline records, read second: a data configuration wins, and a tree carrying both staticData and objectName draws these rows and never queries the object. | | tree | The field configuration — the spec's TreeConfig (table below). | | filter | Rule array [{ field, operator, value }], lowered to $filter and applied to inline rows as well. Context tokens such as {current_user_id} are resolved first. A record whose parent the filter removed is drawn as a root. | | navigation | What a row click opens (below). |

The node's dataSource binding is not a record source on this block: the registration is not wrapped in ElementDataSourceGate, so nothing lands a binding's object on objectName, and objectui validate does not count it.

The tree block

| Key | Default | Description | | --- | --- | --- | | parentField | auto-detected | Field holding the parent reference. When omitted, the renderer picks the object's tree field (or a lookup/master_detail that references the same object). | | labelField | name | Field rendered indented in the first column. | | fields | [] | Additional fields rendered as flat columns after the label. | | defaultExpandedDepth | unset | Initial expansion depth. 0 = roots only; unset = expand everything. |

Records whose parent is missing (or points outside the result set) are kept as roots, so nothing is silently dropped.

⚠️ The same four keys written flat on the node are still read, and a flat key outranks the block's member of the same name. They are the form a host composes; the spec row declares only the tree block, so author that.

navigation

navigation takes the spec's NavigationConfig ({ mode, size, width, openNewTab, preventNavigation }), the block a list view declares. On a tree no parent view navigates for:

  • Absent: a click opens nothing. This renderer supplies no drawer default.
  • drawer, and a block without mode (it takes the spec's drawer default), open the row's record in the drawer.
  • new_window opens /{objectName}/record/{id} in a new tab; none opens nothing.
  • page opens a record page through the record navigator the host publishes (the console publishes one on its custom pages, record pages and list views). The object is data.object when data is the object provider, so it wins when both are written, and the tree's objectName otherwise. Under a host that publishes none, such as an embedded renderer, or on a tree that names neither (inline rows with no objectName), there is no record page to open and the click opens nothing.
  • preventNavigation: true opens nothing whatever the mode. openNewTab: true opens the record page in a new tab and outranks every mode except none.
  • size picks the overlay width bucket; the deprecated width wins over it.

A click handler from a parent view outranks the whole key.

Expansion: the seed and the user's answer

defaultExpandedDepth seeds expansion; it does not own it. The seeded set is derived from the forest during render rather than mirrored into component state, so a tree that expands by default is painted expanded in the first commit that has rows — there is no frame in which the forest is drawn collapsed (objectui#8666).

When the record set changes — a refetch, a filter, a host that reallocates the rows — the seed is recomputed for the new forest. A node the user opened or closed by clicking its chevron, and which is still in the forest, keeps the user's answer; every other node, a genuinely new one included, takes the seed. Expansion is per-mount session state: it is not addressable and is not persisted.

The tree view type is host composition, not authoring

tree is not an authorable view type. Neither ObjectViewSchema.defaultViewType nor NamedListView.type admits it — both are the same seven-value union that stops at map — so no authored document selects a tree view, and ObjectViewSchema declares no views member at all. The tree branch runs only when a host composes ObjectView with a views prop, whose entries carry id and label and are typed ViewType. That was ruled deliberate on objectui#5321 (2026-08-20): tree and chart are recorded as host-composition-only surfaces rather than added to the authored unions.

The live consumer is the console: it passes stored view records to ObjectView as views, and its create-view dialog offers tree among the types a console user can create. To render a tree from authored metadata, write the object-tree node above.

The host config has a type — TreeViewConfig

Host config is not untyped config. A block a host stores and re-writes is a contract, so the per-view tree block is exported from @object-ui/types and the renderer imports it rather than keeping a private copy (objectui#8253, ruled 2026-09-07).

⚠️ It is not the single declaration of that shape, and saying so was itself the defect objectui#8841 fixed. @objectstack/spec owns this block — it declares it as TreeConfig and hangs it on ListView.tree — and @object-ui/types already publishes it a second way, derived, as ListViewSchema['tree']. TreeViewConfig is now a derivation of the protocol's block rather than a copy of it — in packages/types/src/views.ts it is a one-line alias of NonNullable<ListView['tree']>, taken from @objectstack/spec/ui. So the accurate claim is the narrower one: this is the name a host writes against, and it tracks the protocol by construction rather than by anyone remembering to update it.

import type { TreeViewConfig } from '@object-ui/types';

// The block a host writes as `tree` on a `views` entry, or as `options.tree`
// on a stored view record. Every key is optional; a host writes the subset it
// means.
const treeConfig: TreeViewConfig = {
  parentField: 'parent',        // single-parent pointer (auto-detected if omitted)
  labelField: 'name',           // indented first column
  fields: ['name', 'manager'],  // additional flat columns
  defaultExpandedDepth: 1,      // 0 = roots only; omit = expand all
};

Annotating the block is what turns a typo into a diagnostic: parentFeild used to be stored, read by nobody and reported by nothing, because the views entry admits any key. Against this type it is a compile error.

⛔ titleField is not part of this block. objectui#8253 declared it as a legacy second rung for labelField; @objectstack/[email protected] refuses tree.titleField by name (TreeConfigSchema is strict since spec #15469), so declaring it published a key the protocol rejects — an author who followed this type was refused at publish. objectui#8841 removed it.

The renderers still tolerate a titleField already stored on a view record: plugin-view, plugin-list and the console's own composition each fall back to it when labelField is absent, so nothing that renders today stops rendering. Those reads are untyped tolerance awaiting a follow-up, ⛔ not a declaration — write labelField, which is the protocol's spelling and wins wherever both are present.

⛔ This does not make tree an authorable view type. objectui#5321 is unchanged: the block is written by a host, never by a document author, and the authored node is the object-tree schema at the top of this file.

License

MIT — see LICENSE.