Skip to main content

Tree views

A tree in a container, fed by a provider. You supply nodes; the host owns rendering, selection, and expansion.

import { function defineMod<const TMod extends ModDefinition>(definition: TMod & ValidateModHooks<TMod>): Mod
The entry point of the SDK. A mod's module default-exports one `defineMod` call and nothing else. The returned value is data. It registers nothing, starts nothing, and touches no host state. The host folds it twice. At build time it runs the module in a throwaway sandbox and reads the value to derive the serializable manifest. At runtime it folds the same value again to wire your handlers into their lanes: ext-host for commands, tools, effects, and hooks; the sandboxed renderer for views and widgets. That double fold is why the module must be pure. It is re-executed to produce the manifest, so anything at the top level, a file read, a network call, a timer, a `console` line, runs during a build with no host attached. Put behavior inside handlers: `run`, `activate`, `handle`. The `ValidateModHooks` constraint makes the call fail to typecheck when a `hook.beforeAction` targets an action that is neither a known host action nor `command:` plus one of this mod's own command ids.
@example```ts export default defineMod({ metadata: { displayName: 'Review', description: 'Review helpers for the current change.', }, commands: [ command({ id: 'review.open', title: 'Review: Open', run: openPanel }), slashCommand({ id: 'review', description: 'Review this change', run: review }), ], ai: { tools: [reviewTool], }, }); ```@categoryAuthoring
defineMod
, function treeView<TElement extends object, TContextValue extends string = string>(input: TreeViewInput<TElement, TContextValue>): TreeViewController<TElement, TContextValue>
Declare a panel whose content is a tree the host renders. You supply a data provider, `getChildren` and `getTreeItem`, and the host draws the rows, handles selection, and places any inline actions. Reach for this instead of {@link view } whenever the content is a hierarchy, because you get the workbench's own tree behaviour rather than a webview you have to style yourself. This is the one authoring constructor that returns a live object rather than plain declaration data. Hold on to the controller it gives you and call `refresh()` after your data changes: with no arguments, listeners receive `null`, meaning reload everything; with elements, they receive exactly those elements.
@exampleA tree of open tasks that refreshes when the list changes ```ts const tasks = treeView<Task>({ id: 'tasks.tree', title: 'Tasks', container: 'left', provider: { getChildren: parent => (parent === null ? store.roots() : parent.children), getTreeItem: task => ({ id: task.id, label: task.title }), }, }); store.onChange(task => tasks.refresh(task)); ```@categoryAuthoring
treeView
} from '@modular/sdk';
interface Chapter { readonly Chapter.id: stringid: string; readonly Chapter.heading: stringheading: string; } export default
defineMod<{
    readonly metadata: {
        readonly displayName: "Panels";
        readonly description: "Contributed surfaces";
    };
    readonly views: readonly [TreeViewController<Chapter, string>];
}>(definition: {
    readonly metadata: {
        readonly displayName: "Panels";
        readonly description: "Contributed surfaces";
    };
    readonly views: readonly [TreeViewController<Chapter, string>];
}): Mod
The entry point of the SDK. A mod's module default-exports one `defineMod` call and nothing else. The returned value is data. It registers nothing, starts nothing, and touches no host state. The host folds it twice. At build time it runs the module in a throwaway sandbox and reads the value to derive the serializable manifest. At runtime it folds the same value again to wire your handlers into their lanes: ext-host for commands, tools, effects, and hooks; the sandboxed renderer for views and widgets. That double fold is why the module must be pure. It is re-executed to produce the manifest, so anything at the top level, a file read, a network call, a timer, a `console` line, runs during a build with no host attached. Put behavior inside handlers: `run`, `activate`, `handle`. The `ValidateModHooks` constraint makes the call fail to typecheck when a `hook.beforeAction` targets an action that is neither a known host action nor `command:` plus one of this mod's own command ids.
@example```ts export default defineMod({ metadata: { displayName: 'Review', description: 'Review helpers for the current change.', }, commands: [ command({ id: 'review.open', title: 'Review: Open', run: openPanel }), slashCommand({ id: 'review', description: 'Review this change', run: review }), ], ai: { tools: [reviewTool], }, }); ```@categoryAuthoring
defineMod
({
metadata: {
    readonly displayName: "Panels";
    readonly description: "Contributed surfaces";
}
metadata
: { displayName: "Panels"displayName: 'Panels', description: "Contributed surfaces"description: 'Contributed surfaces' },
views: readonly [TreeViewController<Chapter, string>]views: [ treeView<Chapter, string>(input: TreeViewInput<Chapter, string>): TreeViewController<Chapter, string>
Declare a panel whose content is a tree the host renders. You supply a data provider, `getChildren` and `getTreeItem`, and the host draws the rows, handles selection, and places any inline actions. Reach for this instead of {@link view } whenever the content is a hierarchy, because you get the workbench's own tree behaviour rather than a webview you have to style yourself. This is the one authoring constructor that returns a live object rather than plain declaration data. Hold on to the controller it gives you and call `refresh()` after your data changes: with no arguments, listeners receive `null`, meaning reload everything; with elements, they receive exactly those elements.
@exampleA tree of open tasks that refreshes when the list changes ```ts const tasks = treeView<Task>({ id: 'tasks.tree', title: 'Tasks', container: 'left', provider: { getChildren: parent => (parent === null ? store.roots() : parent.children), getTreeItem: task => ({ id: task.id, label: task.title }), }, }); store.onChange(task => tasks.refresh(task)); ```@categoryAuthoring
treeView
<Chapter>({
id: stringid: 'panels.chapters', title: stringtitle: 'Chapters', container?: "left" | "right" | "bottom" | "anywhere" | undefinedcontainer: 'left', provider: TreeDataProvider<Chapter, string>provider: { TreeDataProvider<Chapter, string>.getChildren(parent: Chapter | null, ctx: TreeContext<Chapter>): readonly Chapter[] | Promise<readonly Chapter[]>getChildren: parent: Chapter | nullparent => (parent: Chapter | nullparent === null ? function loadChapters(): readonly Chapter[]loadChapters() : []), TreeDataProvider<Chapter, string>.getTreeItem(element: Chapter, ctx: TreeContext<Chapter>): TreeItem<string> | Promise<TreeItem<string>>getTreeItem: chapter: Chapterchapter => ({ TreeItem<string>.id: stringid: chapter: Chapterchapter.Chapter.id: stringid, TreeItem<string>.label: stringlabel: chapter: Chapterchapter.Chapter.heading: stringheading }), }, }), ], }); declare function function loadChapters(): readonly Chapter[]loadChapters(): readonly Chapter[];

A tree view is generic over its element type, so getTreeItem receives the same Chapter that getChildren returned rather than an opaque node.

Members

TreeViewid, title, container, icon, provider, inlineActions, when, kind, plus onDidChangeTreeData and onSelect.

MemberWhat it decides
providerwhere the nodes come from
onDidChangeTreeDatahow you tell the host the nodes changed
onSelectwhat happens when a person picks one
containerwhere the tree is mounted
inlineActionsthe buttons on a row
whenwhether the tree appears at all

Push, do not poll

onDidChangeTreeData exists because the host does not watch your data. If you mutate what provider returns without firing it, the tree keeps showing the old nodes and nothing reports an error.