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
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:
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.
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
- 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) andright-panel(70% width). - 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. - 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).
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. Thefirstchild is positioned on the left, and thesecondis on the right.column: The resizer handle is horizontal. Thefirstchild is positioned on the top, and thesecondis 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.
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:
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:
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
dismissPreviewclass 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:
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.mdfile in your.agents/skills/react-zeugma/directory. - Copy the contents of the code block below and paste them into it.
---
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
| Prop | Type | Default | Description |
|---|---|---|---|
| controller | ZeugmaController | - | The layout controller returned by useZeugma() |
| renderPane | (paneId: string) => ReactNode | - | Callback that maps active pane IDs to custom pane structures |
| resizerSize | number | 4 | Thickness of the split resizer handles in pixels |
| snapThreshold | number | 8 | Pixel threshold to snap layout resizers to adjacent edges |
| locked | boolean | false | Disable all dragging and resizing |
| enableDragToDismiss | boolean | false | Enables dragging widgets out of boundaries to dismiss them |
| classNames | ZeugmaClassNames | - | 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>.
| Property | Type | Description |
|---|---|---|
| id | string | The tab's unique ID. |
| paneId | string | The ID of the pane containing this tab. |
| isActive | boolean | True if this tab is currently selected/active. |
| index | number | The tab's 0-indexed position in the pane tab bar. |
| metadata | Record<string, unknown> | undefined | Custom metadata values associated with this tab. |
| isDragging | boolean | True if this tab is actively being dragged. |
| isOver | boolean | True if another dragged tab/item is currently hovering over this tab. |
| onSelect | () => void | Callback to select/activate this tab. |
| onRemove | () => void | Callback to close/remove this tab. |
| isPoppedOut | boolean | [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. |
| remountOnPopout | boolean | undefined | [Experimental] If true, force-remounts the widget during popout/dock transitions instead of adopting the DOM node. |
Hooks & Contexts#
useZeugma Options
| Option | Type | Default | Description |
|---|---|---|---|
| initialLayout | TreeNode | null | null | The initial layout tree structure for the dashboard |
| locked | boolean | false | Initial lock state of the workspace |
ZeugmaController Methods & Queries
| Method / Query | Signature | Description |
|---|---|---|
| setLayout | (layout: TreeNode | null) => void | Replaces the entire layout tree with a new layout structure. |
| setLocked | (locked: boolean) => void | Locks or unlocks all resizing and dragging actions globally. |
| addTab | (tabId: string, targetPaneId?: string, metadata?: Record<string, unknown>) => void | Appends a tab into a target pane, or splits/creates a new pane if target is omitted. |
| removeTab | (tabId: string) => void | Removes a tab by its ID. Automatically collapses empty splits. |
| selectTab | (paneId: string, tabId: string) => void | Sets the active tab in the given pane. |
| removePane | (paneId: string) => void | Removes a pane and collapses the parent split. |
| splitPane | (targetId: string, direction: SplitDirection, type: 'left' | 'right' | 'top' | 'bottom', paneToAdd: string) => void | Splits 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) => void | Updates metadata associated with a tab. |
| updatePaneLock | (paneId: string, locked: boolean) => void | Toggles lock state for a specific pane. |
| mergeTab | (draggedTabId: string, targetPaneId: string) => void | Drags and drops a tab from its source pane to a target pane. |
| moveTab | (draggedTabId: string, targetTabId: string, position?: 'before' | 'after' | 'center') => void | Reorders a tab relative to another target tab, or swaps them if position is 'center'. |
| findPaneById | (paneId: string) => PaneNode | null | Queries a PaneNode by its unique ID. |
| findPaneContainingTab | (tabId: string) => PaneNode | null | Queries the parent PaneNode of a tab. |
| findTabById | (tabId: string) => TabDetails | null | Queries 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>.
| Property | Type | Description |
|---|---|---|
| id | string | The unique ID of the current pane. |
| tabIds | string[] | List of tab IDs docked inside the pane. |
| activeTabId | string | Currently active/selected tab ID. |
| isDragging | boolean | True if this pane is currently being dragged. |
| isFullscreen | boolean | True if this pane occupies the fullscreen/zoomed view. |
| toggleFullscreen | () => void | Toggles the pane to and from fullscreen mode. |
| remove | () => void | Removes the pane from the layout tree and collapses its parent split. |
| selectTab | (tabId: string) => void | Activates a specific tab in the pane. |
| removeTab | (tabId: string) => void | Closes a tab. If it was the last tab, removes the pane. |
| metadata | Record<string, unknown> | undefined | Metadata values associated with the active tab. |
| updateMetadata | (updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined) => void | Updates metadata for the active tab. |
| locked | boolean | True if this specific pane or the dashboard globally is locked. |
| tabsMetadata | Record<string, Record<string, unknown>> | undefined | Tab metadata mapping for all tabs inside this pane. |
| updateTabMetadata | (tabId: string, updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined) => void | Updates metadata for a specific tab in the pane. |
| isActiveTabPoppedOut | boolean | [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.
| Function | Signature | Description |
|---|---|---|
| splitPane | (tree: TreeNode, targetId: string, direction: SplitDirection, type: SplitType, paneToAdd: string | PaneNode) => TreeNode | Splits a pane and inserts another pane. |
| removePane | (tree: TreeNode, paneId: string) => TreeNode | null | Removes a pane and collapses the parent split. |
| addTab | (tree: TreeNode, targetPaneId: string, tabId: string, metadata?: Record<string, unknown>) => TreeNode | Appends a tab into a target pane. |
| removeTab | (tree: TreeNode, tabId: string) => TreeNode | null | Removes a tab; collapses empty panes. |
| selectTab | (tree: TreeNode, paneId: string, tabId: string) => TreeNode | Sets the active tab in the given pane. |
| mergeTab | (tree: TreeNode, draggedTabId: string, targetPaneId: string) => TreeNode | Moves a tab from its source pane to target pane. |
| moveTab | (tree: TreeNode, draggedTabId: string, targetTabId: string, position?: 'before' | 'after' | 'center') => TreeNode | Reorders a tab relative to another target tab, or swaps them if position is 'center'. |
| swapTabs | (tree: TreeNode, draggedTabId: string, targetTabId: string) => TreeNode | Swaps the positions and active states of two tabs. |
| movePaneTabs | (tree: TreeNode, draggedPaneId: string, targetTabId: string, position?: 'before' | 'after') => TreeNode | Moves all tabs from a dragged pane next to a target tab. |