Documentation

Introduction#

React Zeugma is a recursive, drag-and-drop dashboard layout engine for React. It combines the tree-based, arbitrary splitting capabilities of react-mosaic with the declarative, state-driven API model of react-grid-layout, powered by the robust drag-and-drop primitives of @dnd-kit/core.

Interactive Playground

Resizer:6px
Snap:8px

Why Zeugma?

Most dashboard layout managers restrict you to a rigid grid where columns and rows are pre-calculated. Zeugma uses a binary tree layout, enabling users to split any panel vertically or horizontally to create highly complex, nested workspaces. Additionally, it features:

  • Headless (Style-Agnostic): No built-in styles. You have absolute control over containers, resizers, and previews using your own Tailwind or CSS classes.
  • Multi-Tab Pane Groups: Multiple widgets can be docked inside a single pane and switched using tabs.
  • Drag-to-Dismiss: Drag a tab or pane out of the dashboard boundary to remove or close it.
  • JSON Serialization: Save and restore layouts easily using a clean JSON-serializable tree structure.

Quick Start#

To get started, install the package using your package manager of choice:

Terminal
npm install react-zeugma

Here is a complete, minimal example showing how to initialize a layout tree with the useZeugma hook, render your panes, and mount the <Zeugma> component.

tsx
import { useZeugma, Zeugma, Pane, TreeNode } from 'react-zeugma'

// 1. Define the initial layout tree structure
const initialLayout: TreeNode = {
  type: 'split',
  direction: 'row',
  splitPercentage: 30,
  first: { type: 'pane', id: 'left-panel', tabIds: ['left-panel'], activeTabId: 'left-panel' },
  second: { type: 'pane', id: 'right-panel', tabIds: ['right-panel'], activeTabId: 'right-panel' },
}

// 2. Build your custom pane wrapper
function DashboardPane({ id }: { id: string }) {
  return (
    <Pane id={id}>
      <div className="flex flex-col h-full bg-zinc-900 border border-zinc-700 rounded-lg overflow-hidden">
        <Pane.DragHandle className="p-2 bg-zinc-800 cursor-grab text-zinc-300 font-semibold select-none">
          {id}
        </Pane.DragHandle>
        <Pane.Content className="flex-1 p-4 text-zinc-400">
          {(tab) => <div>Active Tab Content: {tab.id}</div>}
        </Pane.Content>
      </div>
    </Pane>
  )
}

// 3. Mount the layout controller and dashboard renderer
export default function DashboardApp() {
  const controller = useZeugma({ initialLayout })

  return (
    <div className="w-screen h-screen p-4 bg-zinc-950">
      <Zeugma 
        controller={controller} 
        renderPane={(paneId) => <DashboardPane id={paneId} />} 
        classNames={{
          resizer: 'bg-zinc-850 hover:bg-indigo-500 w-1 transition-colors',
          dropPreview: 'bg-indigo-500/10 border border-indigo-500 border-dashed rounded-lg'
        }}
      />
    </div>
  )

Step-by-Step Breakdown

  1. Define a Layout Tree: The layout is defined recursively. In this case, we split the workspace horizontally into a row containing left-panel (30% width) and right-panel (70% width).
  2. Build a Custom Pane Component: The <Pane> component sets up the drag boundary. We add a <Pane.DragHandle> to allow dragging, and a <Pane.Content> which uses a render function to display the active tab's content.
  3. Mount <Zeugma>: The root component manages the drag-and-drop context, handles layout calculation, and positions the split resizer bars between panes.

Tree-based Layouts#

React Zeugma models your dashboard layout as a recursive Binary Tree. Every node in the tree is either a SplitNode (which divides space horizontally or vertically between two children) or a PaneNode (which acts as a leaf container containing active tabs).

Split Direction
Pane-1Left Child
Pane-2Right Child
typescript
export type TreeNode = SplitNode | PaneNode

export interface SplitNode {
  type: 'split'
  direction: 'row' | 'column' // 'row' splits horizontally (left/right), 'column' splits vertically (top/bottom)
  splitPercentage: number // Percentage of the first child's size relative to the parent (5 to 95)
  first: TreeNode         // Left/Top child node
  second: TreeNode        // Right/Bottom child node
}

export interface PaneNode {
  type: 'pane'
  id: string              // Unique identifier for the pane
  tabIds: string[]        // List of tab IDs docked inside this pane
  activeTabId: string     // The currently selected tab ID
  locked?: boolean        // Optional lock to disable dragging this specific pane
  tabsMetadata?: Record<string, Record<string, unknown>> // Optional metadata per tab
}

Split Node Logic

The direction property determines how children are aligned:

  • row: The resizer handle is vertical. The first child is positioned on the left, and the second is on the right.
  • column: The resizer handle is horizontal. The first child is positioned on the top, and the second is on the bottom.

State & Controller#

The useZeugma hook manages the state of the dashboard layout. It returns a ZeugmaController instance containing the current layout state, locking status, and helper methods.

Tab Controller
Focus Tab:

Controlled vs. Uncontrolled Mode

You can run the layout engine in either controlled or uncontrolled mode depending on your state requirements:

Uncontrolled Mode

Pass initialLayout. The hook manages layout state internally. Ideal for simple dashboards or when utilizing built-in local storage persistence.

const controller = useZeugma({
  initialLayout: defaultLayout
})

Controlled Mode

Pass both layout and onChange. You are responsible for storing and updating the tree state. Useful for syncing layout with global state (Redux/Zustand) or database backends.

const [layout, setLayout] = useState(defaultLayout)
const controller = useZeugma({
  layout,
  onChange: setLayout
})

Pane Customization#

Panes represent the workspaces where tabs are rendered. Because React Zeugma is headless, you are responsible for rendering the pane chrome, borders, headers, and tabs. Zeugma provides helper subcomponents under the <Pane> namespace to simplify integration:

Headless Themes
Explorermodern
Codemodern

Pane Subcomponents

<Pane id="pane-id">

The root container of a panel. Establishes the drag-and-drop context boundaries and monitors drop hover intents.

<Pane.DragHandle>

Wraps the element that triggers panel dragging. Can be placed on the header, tab bar, or a specific drag icon. Adds pointer events and grab cursors automatically.

<Pane.Content>

Renders the active tab's content. Accepts a child render function (tab: TabDetails) => React.ReactNode which is evaluated dynamically when tabs are switched.

<Pane.Tabs>

Helper component to render and reorder the tabs. Accepts a renderTab prop to customize the tab buttons.

Advanced Features#

React Zeugma includes advanced features to build rich, professional workspaces, including layout persistence, drag-to-dismiss, and context-isolated APIs.

Layout Persistence#

React Zeugma provides automatic local storage persistence, but you can easily intercept state changes to sync layout trees to a remote database.

Example: Debounced Database Sync

When working in controlled mode, you can implement a debounced sync mechanism in a custom hook to avoid hitting your backend API on every intermediate pixel drag during resizing:

tsx
import { useState, useEffect } from 'react'
import { useZeugma, TreeNode } from 'react-zeugma'

export function PersistentDashboard({ userId }: { userId: string }) {
  const [layout, setLayout] = useState<TreeNode | null>(null)

  // 1. Fetch initial layout from DB on mount
  useEffect(() => {
    fetch(`/api/layouts/${userId}`)
      .then(res => res.json())
      .then(data => setLayout(data.layout))
  }, [userId])

  // 2. Debounce and save layout changes to DB
  useEffect(() => {
    if (!layout) return

    const timer = setTimeout(() => {
      fetch(`/api/layouts/${userId}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ layout })
      })
    }, 1000) // Save layout after 1 second of inactivity

    return () => clearTimeout(timer)
  }, [layout, userId])

  const controller = useZeugma({
    layout,
    onChange: setLayout
  })

  if (!layout) return <div>Loading workspace...</div>

  return <Zeugma controller={controller} renderPane={...} />
}

Drag-to-Dismiss#

When enableDragToDismiss is enabled, users can drag a tab or pane outside the outer boundaries of the dashboard to close it.

How it works:

  • Pointer Boundary Check: Zeugma tracks the cursor position relative to the root dashboard bounding client rect.
  • Trigger Distance: If the cursor exceeds the boundaries by more than dismissThreshold (in pixels), a dismissal intent is registered.
  • Visual Indicator: Zeugma applies the dismissPreview class to the background, allowing you to show a red overlay or close icon indicating that letting go will discard the panel.

Context APIs (usePaneContext)#

The usePaneContext hook provides access to the state and actions of the specific pane. This enables you to build custom pane headers, close buttons, and maximize toggles easily:

tsx
import { usePaneContext, Pane } from 'react-zeugma'
import { Maximize2, Minimize2, X } from 'lucide-react'

function CustomPaneHeader() {
  const { 
    id, 
    tabIds, 
    activeTabId, 
    selectTab, 
    remove, 
    toggleFullscreen, 
    isFullscreen 
  } = usePaneContext()

  return (
    <div className="flex items-center justify-between p-2 bg-zinc-800 text-white">
      {/* 1. Custom Tab Bar */}
      <div className="flex gap-1">
        {tabIds.map(tabId => (
          <button
            key={tabId}
            onClick={() => selectTab(tabId)}
            className={`px-2.5 py-1 text-xs ${tabId === activeTabId ? 'bg-indigo-600' : 'bg-zinc-700'}`}
          >
            {tabId}
          </button>
        ))}
      </div>

      {/* 2. Drag Handle */}
      <Pane.DragHandle className="flex-1 h-full cursor-grab" />

      {/* 3. Pane Controls */}
      <div className="flex gap-1">
        <button onClick={toggleFullscreen} title="Maximize">
          {isFullscreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
        </button>
        <button onClick={remove} title="Close Pane" className="hover:text-red-500">
          <X size={14} />
        </button>
      </div>
    </div>
  )
}

No Re-mounting (Keep-Alive) & Portals#

In traditional tree-based layout managers (such as react-mosaic), rearranging, splitting, or dragging panels changes the React component tree hierarchy, causing components to unmount and lose their state. React Zeugma prevents this by keeping components alive. It decouples the logical React tree from physical DOM rendering using an off-screen portal registry.

Every tab is mounted once into a stable, persistent DOM wrapper. When a tab becomes active, Zeugma projects its wrapper into the pane using appendChild(). When a tab is inactive or in-transit, it is stashed in a hidden container. This preserves iframe states, scroll positions, and input focus perfectly across all layout operations.

AI Integration (SKILL.md)#

React Zeugma provides a pre-configured SKILL.md file that helps AI coding assistants (like Gemini, Claude, or Copilot) understand the library's layout primitives and API structure. By adding this skill to your project's configuration, you can ask the AI to build or refactor complex layouts and custom pane configurations with high accuracy.

Setup Instructions

  • Create a SKILL.md file in your .agents/skills/react-zeugma/ directory.
  • Copy the contents of the code block below and paste them into it.
SKILL.md Configuration File
markdown
---
name: react-zeugma
description: Rules, types, component composition, and programmatic manipulation guidelines for integrating and using the react-zeugma dashboard layout engine.
---

# Skill: Using react-zeugma

`react-zeugma` is a recursive drag-and-drop dashboard layout engine for React. It manages tree-based pane splitting (similar to `react-mosaic`) and provides a declarative state-driven API (similar to `react-grid-layout`) built on `@dnd-kit/core`.

---

## 1. Core AI Rules & Constraints

- **Immutable State Rule**: Never mutate layout `TreeNode` objects directly in-place. You must treat them as immutable. Always use the pure utility functions exported by `react-zeugma/utils` to perform mutations and return fresh tree references.
- **Headless Styling Rule**: `react-zeugma` is 100% style-agnostic and applies no default CSS. You MUST specify class names in the `classNames` configuration on `<Zeugma>` (specifically for `resizer`, `dropPreview`, `tabDropPreview`, `paneDragPreview`, and `tabDragPreview`) or the layout features will be invisible/non-functional.
- **renderPane Placement Rule**: If using `<Zeugma>` with `children` (Context Provider Mode), you MUST pass the `renderPane` prop directly to `<PaneTree renderPane={renderPane} />`, and `renderPane` is forbidden on `<Zeugma>`. If using `<Zeugma>` in standalone mode (without `children`), you MUST pass `renderPane` directly to `<Zeugma renderPane={renderPane} />`.
- **DragHandle Placement**: Draggable panes require a child `<Pane.DragHandle>` component to define the interactive drag region.
- **Stable Layout During Drag Rule**: The logical `layout` object from the controller or context does not update in real-time during a drag session. It only updates once the drop action is committed. The active visual removals and split previews are managed by the library's internal rendering layout.

---

## 2. Layout Data Model (JSON Schema)

The dashboard layout is serialized as a recursive binary tree of `TreeNode` elements:

```ts
export type SplitDirection = 'row' | 'column'

export interface SplitNode {
  type: 'split'
  direction: SplitDirection
  first: TreeNode
  second: TreeNode
  splitPercentage: number // 0 to 100
}

export interface PaneNode {
  type: 'pane'
  id: string
  tabs: string[]
  activeTabId: string
  locked?: boolean
  tabsMetadata?: Record<string, Record<string, unknown>>
}

export type TreeNode = SplitNode | PaneNode

export interface TabDetails {
  id: string
  paneId: string
  isActive: boolean
  index: number
  metadata: Record<string, unknown> | undefined
  remountOnPopout?: boolean
}
```

- **`PaneNode` (Leaf)**: Holds active tabs and the selected tab ID.
- **`SplitNode` (Branch)**: Divides space into `first` and `second` sub-trees based on `splitPercentage` (percentage of the first child's size relative to the parent boundary).

---

## 3. Component Composition Rules

### Standalone Renderer

```tsx
import { useZeugma, Zeugma, Pane } from 'react-zeugma'

function Dashboard() {
  const controller = useZeugma({ initialLayout })
  return (
    <Zeugma
      controller={controller}
      renderPane={(id) => (
        <Pane id={id}>
          <Pane.DragHandle className="drag">Header</Pane.DragHandle>
          <Pane.Content>{(tab) => <div>{tab.id}</div>}</Pane.Content>
        </Pane>
      )}
    />
  )
}
```

### Context Provider Mode

If `<Zeugma>` wraps child components, it serves as a Context Provider. Use `<PaneTree>` (imported from `react-zeugma`) inside it to render the visual panels and pass the `renderPane` function directly to `<PaneTree>`:

```tsx
import { useZeugma, Zeugma, PaneTree, Pane } from 'react-zeugma'

function Dashboard() {
  const controller = useZeugma({ initialLayout })
  const renderPane = (id: string) => (
    <Pane id={id}>
      <Pane.DragHandle className="drag">Header</Pane.DragHandle>
      <Pane.Content>{(tab) => <div>{tab.id}</div>}</Pane.Content>
    </Pane>
  )
  return (
    <Zeugma controller={controller}>
      <div className="custom-wrapper">
        <PaneTree renderPane={renderPane} />
      </div>
    </Zeugma>
  )
}
```

## 4. API Reference and Configurations

### `<Zeugma>` Component Props

- `controller`: The layout state controller returned by `useZeugma(options)`.
- `persist?: boolean | ZeugmaPersistOptions`: Layout persistence configuration in localStorage.
  - `enabled?: boolean`: Whether layout persistence is enabled (defaults to true).
  - `key?: string`: The localStorage key (defaults to `'zeugma-layout'`).
- `renderPopoutWrapper?: (props: { tabId: string; document: Document; window: Window; children: React.ReactNode }) => React.ReactNode`: [Experimental] Optional custom wrapper to inject style managers or providers into popout windows.

### `useZeugma(options)`

Instantiates the dashboard state engine.

- `initialLayout?: TreeNode | null` (Initial uncontrolled tree)
- `layout?: TreeNode | null` (Controlled layout tree)
- `onChange?: (newLayout: TreeNode | null) => void` (Layout updates handler)
- `locked?: boolean` (Disable all resize/drag-and-drop operations)
- `fullscreenPaneId?: string | null` (Maximize target pane ID)
- `onFullscreenChange?: (paneId: string | null) => void` (Maximize toggle handler)

### `useZeugmaContext()`

Access actions and queries from parent `<Zeugma>` context:

```ts
const {
  layout,
  locked,
  setLocked,
  setLayout,
  removePane,
  addTab,
  selectTab,
  splitPane,
  findPaneById,
  findPaneContainingTab,
  findTabById,
  poppedOutTabIds,
  popoutTab,
  dockTab,
} = useZeugmaContext()
```

### `usePaneContext()`

Access details inside a child component of `<Pane>`:

```ts
const {
  id,
  tabs,
  activeTabId,
  isDragging,
  isFullscreen,
  toggleFullscreen,
  remove,
  selectTab,
  removeTab,
  updateMetadata,
  isActiveTabPoppedOut,
  popoutTab,
  dockTab,
} = usePaneContext()
```

---

## 5. Pure Tree Manipulation Utilities

Import these layout mutators/queries from `react-zeugma/utils`:

- **`splitPane(tree, targetId, direction, splitType, paneToAdd)`**: Splits `targetId` pane in a direction (`'row'` / `'column'`) and split type (`'left'` | `'right'` | `'top'` | `'bottom'`). `paneToAdd` can be a tab ID string or a full `PaneNode`. Returns the updated tree.
- **`removePane(tree, paneId)`**: Removes a pane and collapses the parent split. Returns the updated tree.
- **`addTab(tree, targetPaneId, tabId, metadata?)`**: Appends a tab to a target pane and sets it active. Returns the updated tree.
- **`removeTab(tree, tabId)`**: Removes a tab from its pane; collapses empty panes. Returns the updated tree.
- **`selectTab(tree, paneId, tabId)`**: Activates a tab inside a pane. Returns the updated tree.
- **`mergeTab(tree, draggedTabId, targetPaneId)`**: Moves a tab from its source pane to target pane. Returns the updated tree.
- **`moveTab(tree, draggedTabId, targetTabId, position?)`**: Moves a tab before/after target tab, or swaps them if position is `'center'`. Returns the updated tree.
- **`swapTabs(tree, draggedTabId, targetTabId)`**: Swaps the positions and active states of two tabs. Returns the updated tree.
- **`movePaneTabs(tree, draggedPaneId, targetTabId, position?)`**: Moves all tabs from a dragged pane before/after target tab. Returns the updated tree.
- **`findPaneById(tree, paneId)`**: Returns matching `PaneNode` or `null`.
- **`findPaneContainingTab(tree, tabId)`**: Returns parent `PaneNode` containing the tab, or `null`.
- **`findTabById(tree, tabId)`**: Returns `TabDetails` or `null`.
- **`computeLayout(tree)`**: Calculates absolute positions (`{ left, top, width, height }` as percentages) for all panes and splitters.

API Reference#

Complete API reference for React Zeugma components, hooks, contexts, and pure tree utilities.

Components & Props#

<Zeugma> Props

PropTypeDefaultDescription
controllerZeugmaController-The layout controller returned by useZeugma()
renderPane(paneId: string) => ReactNode-Callback that maps active pane IDs to custom pane structures
resizerSizenumber4Thickness of the split resizer handles in pixels
snapThresholdnumber8Pixel threshold to snap layout resizers to adjacent edges
lockedbooleanfalseDisable all dragging and resizing
enableDragToDismissbooleanfalseEnables dragging widgets out of boundaries to dismiss them
classNamesZeugmaClassNames-Custom CSS classes for dashboard elements
renderPopoutWrapper(props: { tabId: string; document: Document; window: Window; children: React.ReactNode }) => React.ReactNode-[Experimental] Custom wrapper to inject style managers / providers into popout windows

RenderTabProps

Properties passed to the renderTab callback in <Pane.Tabs>.

PropertyTypeDescription
idstringThe tab's unique ID.
paneIdstringThe ID of the pane containing this tab.
isActivebooleanTrue if this tab is currently selected/active.
indexnumberThe tab's 0-indexed position in the pane tab bar.
metadataRecord<string, unknown> | undefinedCustom metadata values associated with this tab.
isDraggingbooleanTrue if this tab is actively being dragged.
isOverbooleanTrue if another dragged tab/item is currently hovering over this tab.
onSelect() => voidCallback to select/activate this tab.
onRemove() => voidCallback to close/remove this tab.
isPoppedOutboolean[Experimental] True if this tab is open in a new popup window.
popout() => void[Experimental] Callback to popout this tab into a new window.
dock() => void[Experimental] Callback to dock this tab back to the dashboard.
remountOnPopoutboolean | undefined[Experimental] If true, force-remounts the widget during popout/dock transitions instead of adopting the DOM node.

Hooks & Contexts#

useZeugma Options

OptionTypeDefaultDescription
initialLayoutTreeNode | nullnullThe initial layout tree structure for the dashboard
lockedbooleanfalseInitial lock state of the workspace

ZeugmaController Methods & Queries

Method / QuerySignatureDescription
setLayout(layout: TreeNode | null) => voidReplaces the entire layout tree with a new layout structure.
setLocked(locked: boolean) => voidLocks or unlocks all resizing and dragging actions globally.
addTab(tabId: string, targetPaneId?: string, metadata?: Record<string, unknown>) => voidAppends a tab into a target pane, or splits/creates a new pane if target is omitted.
removeTab(tabId: string) => voidRemoves a tab by its ID. Automatically collapses empty splits.
selectTab(paneId: string, tabId: string) => voidSets the active tab in the given pane.
removePane(paneId: string) => voidRemoves a pane and collapses the parent split.
splitPane(targetId: string, direction: SplitDirection, type: 'left' | 'right' | 'top' | 'bottom', paneToAdd: string) => voidSplits a target pane in a given direction and adds a new pane.
updateMetadata(id: string, updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined) => voidUpdates metadata associated with a tab.
updatePaneLock(paneId: string, locked: boolean) => voidToggles lock state for a specific pane.
mergeTab(draggedTabId: string, targetPaneId: string) => voidDrags and drops a tab from its source pane to a target pane.
moveTab(draggedTabId: string, targetTabId: string, position?: 'before' | 'after' | 'center') => voidReorders a tab relative to another target tab, or swaps them if position is 'center'.
findPaneById(paneId: string) => PaneNode | nullQueries a PaneNode by its unique ID.
findPaneContainingTab(tabId: string) => PaneNode | nullQueries the parent PaneNode of a tab.
findTabById(tabId: string) => TabDetails | nullQueries details (paneId, index, metadata, isActive) of a tab.
popoutTab(tabId: string) => void[Experimental] Opens the specified tab in a new popup window.
dockTab(tabId: string) => void[Experimental] Docks the specified tab back into the dashboard grid layout.

usePaneContext() Properties

Returns PaneContextValue (extends PaneRenderProps). Available inside any child of <Pane>.

PropertyTypeDescription
idstringThe unique ID of the current pane.
tabIdsstring[]List of tab IDs docked inside the pane.
activeTabIdstringCurrently active/selected tab ID.
isDraggingbooleanTrue if this pane is currently being dragged.
isFullscreenbooleanTrue if this pane occupies the fullscreen/zoomed view.
toggleFullscreen() => voidToggles the pane to and from fullscreen mode.
remove() => voidRemoves the pane from the layout tree and collapses its parent split.
selectTab(tabId: string) => voidActivates a specific tab in the pane.
removeTab(tabId: string) => voidCloses a tab. If it was the last tab, removes the pane.
metadataRecord<string, unknown> | undefinedMetadata values associated with the active tab.
updateMetadata(updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined) => voidUpdates metadata for the active tab.
lockedbooleanTrue if this specific pane or the dashboard globally is locked.
tabsMetadataRecord<string, Record<string, unknown>> | undefinedTab metadata mapping for all tabs inside this pane.
updateTabMetadata(tabId: string, updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined) => voidUpdates metadata for a specific tab in the pane.
isActiveTabPoppedOutboolean[Experimental] True if the active tab in this pane is open in a new window.
popoutTab(tabId?: string) => void[Experimental] Pops out the active tab (or specific tab ID) into a new window.
dockTab(tabId?: string) => void[Experimental] Docks the active tab (or specific tab ID) back to the dashboard grid.

Tree Utilities#

Pure, immutable tree-manipulation utility functions imported from react-zeugma/utils. These return a fresh tree reference and are used to perform state updates in controlled mode.

FunctionSignatureDescription
splitPane(tree: TreeNode, targetId: string, direction: SplitDirection, type: SplitType, paneToAdd: string | PaneNode) => TreeNodeSplits a pane and inserts another pane.
removePane(tree: TreeNode, paneId: string) => TreeNode | nullRemoves a pane and collapses the parent split.
addTab(tree: TreeNode, targetPaneId: string, tabId: string, metadata?: Record<string, unknown>) => TreeNodeAppends a tab into a target pane.
removeTab(tree: TreeNode, tabId: string) => TreeNode | nullRemoves a tab; collapses empty panes.
selectTab(tree: TreeNode, paneId: string, tabId: string) => TreeNodeSets the active tab in the given pane.
mergeTab(tree: TreeNode, draggedTabId: string, targetPaneId: string) => TreeNodeMoves a tab from its source pane to target pane.
moveTab(tree: TreeNode, draggedTabId: string, targetTabId: string, position?: 'before' | 'after' | 'center') => TreeNodeReorders a tab relative to another target tab, or swaps them if position is 'center'.
swapTabs(tree: TreeNode, draggedTabId: string, targetTabId: string) => TreeNodeSwaps the positions and active states of two tabs.
movePaneTabs(tree: TreeNode, draggedPaneId: string, targetTabId: string, position?: 'before' | 'after') => TreeNodeMoves all tabs from a dragged pane next to a target tab.