Programmatic Control
Use the Tilery controller plus panel and tab objects for imperative workflows.
Programmatic control is for workflows that start outside a drag gesture, such as opening a resource from search or splitting a panel from a toolbar. Use the controls here to append tabs, split panels, and reopen an existing resource tab.
The controller gives app-level operations, while panel and tab objects are convenient when your renderer already knows which part of the workspace it is acting on.
const panelApiLayout: TileryInitialLayout<TabData> = {
type: 'panel',
id: 'main',
tabs: [
{
id: 'welcome',
data: {
title: 'Welcome',
body: 'Panel objects can append tabs, split panels, and remove panels.',
},
},
],
};
const tileryRef = useRef<TileryController | null>(null);
const counterRef = useRef(0);
const getFirstPanel = () => tileryRef.current?.getPanels()[0] ?? null;
const addTab = () => {
const panel = getFirstPanel();
if (!panel) return;
counterRef.current += 1;
panel.appendTab({
id: `tab-${counterRef.current}`,
data: {
title: `Tab ${counterRef.current}`,
body: 'This tab was appended through a panel object.',
},
});
};
const splitRight = () => {
const panel = getFirstPanel();
if (!panel) return;
counterRef.current += 1;
panel.split('right', {
size: 48,
tabs: [
{
id: `split-${counterRef.current}`,
data: {
title: `Split ${counterRef.current}`,
body: 'This panel was created through panel.split().',
},
},
],
});
};
const removeActive = () => {
getFirstPanel()?.activeTab?.remove();
};
<Tilery<TabData>
ref={tileryRef as Ref<TileryController>}
initialLayout={panelApiLayout}
renderTabHeader={renderHeader}
renderTabContent={renderContent}
/>const tabApiLayout: TileryInitialLayout<TabData> = {
type: 'group',
direction: 'horizontal',
children: [
{
type: 'panel',
id: 'editor',
size: 58,
tabs: [
{
id: 'main-ts',
data: {
title: 'main.ts',
body: 'Tab objects can move, activate, remove, and update data.',
},
},
{
id: 'search',
data: {
title: 'Search',
body: 'This tab is activated if it already exists.',
},
},
],
},
{
type: 'panel',
id: 'terminal',
size: 42,
tabs: [
{
id: 'shell',
data: {
title: 'Shell',
body: 'Programmatic moves can target another panel by id.',
},
},
],
},
],
};
const tileryRef = useRef<TileryController | null>(null);
const renameCounterRef = useRef(0);
const getActiveTab = (): TileryTab<TabData> | null => {
const tab =
tileryRef.current?.getPanels().find((panel) => panel.activeTab)
?.activeTab ?? null;
return tab as TileryTab<TabData> | null;
};
const renameActive = () => {
const tab = getActiveTab();
if (!tab) return;
renameCounterRef.current += 1;
tab.setData({
...tab.data,
title: `${tab.data.title} ${renameCounterRef.current}`,
});
};
const moveActiveToTerminal = () => {
const tab = getActiveTab();
const terminal = tileryRef.current?.getPanel('terminal');
if (!tab || !terminal || tab.panel.id === terminal.id) return;
tab.moveTo({ panel: terminal.id, index: terminal.tabs.length });
};
const activateSearch = () => {
tileryRef.current?.getTab('search')?.activate();
};
<Tilery<TabData>
ref={tileryRef as Ref<TileryController>}
initialLayout={tabApiLayout}
renderTabHeader={renderHeader}
renderTabContent={renderContent}
/>const workflowApiLayout: TileryInitialLayout<TabData> = {
type: 'group',
direction: 'horizontal',
children: [
{
type: 'panel',
id: 'workspace',
size: 64,
activeTabId: 'readme',
tabs: [
{
id: 'readme',
data: {
title: 'README.md',
body: 'openOrActivateTab() opens a missing tab once, then activates it on later calls.',
},
},
{
id: 'scratch',
data: {
title: 'Scratch',
body: 'changeTabId() can replace a temporary tab id with an app-level id.',
},
},
],
},
{
type: 'panel',
id: 'inspector',
size: 36,
tabs: [
{
id: 'outline',
closable: false,
data: {
title: 'Outline',
body: 'New workflow tabs are inserted near related tabs instead of appended blindly.',
},
},
],
},
],
};
const tileryRef = useRef<TileryController | null>(null);
const openOrActivateSettings = () => {
tileryRef.current?.openOrActivateTab(
{
id: 'settings',
data: {
title: 'Settings',
body: 'This tab is created only once. Press the button again and Tilery activates the existing tab.',
},
},
{ afterTab: 'readme' },
);
};
const openOrActivatePreview = () => {
tileryRef.current?.openOrActivateTab(
{
id: 'preview',
data: {
title: 'Preview',
body: 'A stable id lets the app avoid duplicate tabs for the same resource.',
},
},
{ panel: 'workspace', index: 1 },
);
};
const toggleScratchId = () => {
const tilery = tileryRef.current;
if (!tilery) return;
const oldId = tilery.getTab('scratch') ? 'scratch' : 'scratch-renamed';
const newId = oldId === 'scratch' ? 'scratch-renamed' : 'scratch';
tilery.changeTabId(oldId, newId)?.activate();
};
<Tilery<TabData>
ref={tileryRef as Ref<TileryController>}
initialLayout={workflowApiLayout}
renderTabHeader={renderHeader}
renderTabContent={renderContent}
/>Related
- Programmatic Control — Choose the right controller, panel, or tab method.
- Events & Callbacks — React to the structural changes made here.