Lifecycle Callbacks

Track structural and resize lifecycle events.

Lifecycle callbacks let your app observe workspace changes without taking over the reducer. Move tabs, split panels, close tabs, and watch the event log.

Resize callbacks are split between continuous updates and resize-end notifications, so you can update live UI during dragging while saving expensive work until the gesture finishes.

const structuralLayout: TileryInitialLayout<TabData> = {
  type: 'group',
  direction: 'horizontal',
  children: [
    {
      type: 'panel',
      id: 'editor',
      size: 62,
      tabs: [
        {
          id: 'main',
          data: {
            title: 'main.ts',
            body: 'Use the toolbar to trigger lifecycle callbacks.',
          },
        },
        {
          id: 'router',
          data: {
            title: 'router.ts',
            body: 'Activating, moving, splitting, and closing tabs updates the event stream.',
          },
        },
      ],
    },
    {
      type: 'panel',
      id: 'terminal',
      size: 38,
      tabs: [
        {
          id: 'console',
          data: {
            title: 'Console',
            body: 'Moved tabs can land here.',
          },
        },
      ],
    },
  ],
};

const tileryRef = useRef<TileryController | null>(null);
const tabCounterRef = useRef(0);
const splitCounterRef = useRef(0);
const [events, log, clearEvents] = useEventLog<{
  type: string;
  details: string;
}>(8);

const addTab = () => {
  const panel = tileryRef.current?.getPanel('editor');
  if (!panel) return;
  tabCounterRef.current += 1;
  panel.appendTab({
    id: `draft-${tabCounterRef.current}`,
    data: {
      title: `draft-${tabCounterRef.current}.ts`,
      body: 'New tabs fire onTabsOpen and usually onActiveTabChange.',
    },
  });
};

const activateNext = () => {
  const panel = tileryRef.current?.getPanel('editor');
  if (!panel || panel.tabs.length === 0) return;
  const activeIndex = panel.tabs.findIndex(
    (tab) => tab.id === panel.activeTab?.id,
  );
  const next = panel.tabs[(activeIndex + 1) % panel.tabs.length];
  if (next) panel.setActiveTab(next.id);
};

const moveActiveToTerminal = () => {
  const editor = tileryRef.current?.getPanel('editor');
  const terminal = tileryRef.current?.getPanel('terminal');
  const active = editor?.activeTab;
  if (!active || !terminal) return;
  active.moveTo({ panel: terminal.id, index: terminal.tabs.length });
};

const splitEditor = () => {
  const panel = tileryRef.current?.getPanel('editor');
  if (!panel) return;
  splitCounterRef.current += 1;
  panel.split('right', {
    size: 42,
    tabs: [
      {
        id: `split-${splitCounterRef.current}`,
        data: {
          title: `split-${splitCounterRef.current}.ts`,
          body: 'Panel splits emit open and split callbacks.',
        },
      },
    ],
  });
};

const closeActive = () => {
  tileryRef.current?.getPanel('editor')?.activeTab?.remove();
};

<Tilery<TabData>
  ref={tileryRef as React.Ref<TileryController>}
  initialLayout={structuralLayout}
  onActiveTabChange={(event) =>
    log({ type: 'onActiveTabChange', details: activeSummary(event) })
  }
  onTabsMove={(event) =>
    log({ type: 'onTabsMove', details: moveSummary(event) })
  }
  onPanelsOpen={(event) =>
    log({
      type: 'onPanelsOpen',
      details: panelSummary(event.panels.map((p) => p.id)),
    })
  }
  onPanelSplit={(event) =>
    log({
      type: 'onPanelSplit',
      details: `${event.splitPanelId} ${event.direction} -> ${event.createdPanelId}`,
    })
  }
  onTabsOpen={(event) =>
    log({
      type: 'onTabsOpen',
      details: tabSummary(event.tabs.map((tab) => tab.id)),
    })
  }
  onTabsClose={(event) =>
    log({
      type: 'onTabsClose',
      details: tabSummary(event.tabs.map((tab) => tab.id)),
    })
  }
  onPanelsClose={(event) =>
    log({
      type: 'onPanelsClose',
      details: panelSummary(event.panels.map((panel) => panel.id)),
    })
  }
  renderTabHeader={renderHeader}
  renderTabContent={renderContent}
/>
const resizeLayout: TileryInitialLayout<TabData> = {
  type: 'group',
  direction: 'horizontal',
  children: [
    {
      type: 'panel',
      id: 'navigator',
      size: 32,
      tabs: [
        {
          id: 'navigator-tab',
          data: {
            title: 'Navigator',
            body: 'Drag the divider to emit resize events.',
          },
        },
      ],
    },
    {
      type: 'panel',
      id: 'workspace',
      size: 68,
      tabs: [
        {
          id: 'workspace-tab',
          data: {
            title: 'Workspace',
            body: 'Resize callbacks report affected panels and sizes.',
          },
        },
      ],
    },
  ],
};

const [events, logResize, clearResizeEvents] = useEventLog<{
  type: string;
  details: string;
}>(6);

const logResizeEvent = useCallback(
  (type: string, event: TileryResizeEvent) => {
    logResize({ type, details: resizeSummary(event) });
  },
  [logResize],
);

<Tilery<TabData>
  initialLayout={resizeLayout}
  onResize={(event) => logResizeEvent('onResize', event)}
  onResizeEnd={(event) => logResizeEvent('onResizeEnd', event)}
  renderTabHeader={renderHeader}
  renderTabContent={renderContent}
/>

Related