Render a tiling layout in React — in one paste.
@n-uf/hypr-tiling is a controlled tiling renderer: you own a layout tree in state, and the component drags, drops, resizes, groups, and keyboard-controls its panes at runtime. Paste the block below and you have a working, resizable layout — the guides take it from there. These docs cover only the public API; contributors working on the library start from CONTRIBUTING.md.
import { useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// The golden path: a controlled TilingRenderer. You own the layout tree in
// state; the renderer reports every user edit (drag, resize, group…) through
// onLayoutChange, and you apply it straight back. renderTile paints each pane.
const tiles: TilingTile[] = [
{ id: "editor", title: "Editor" },
{ id: "preview", title: "Preview" },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "left", tileId: "editor" },
second: { kind: "leaf", id: "right", tileId: "preview" },
};
export function Quickstart(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={setLayout}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<div style={{ padding: 12, fontFamily: "system-ui", fontSize: 13 }}>
{tile.title}
</div>
)}
/>
);
}
Quickstart
Three steps to the layout above. Every step is runnable; there are no concept detours here — those come after, only as needed.
Install
Add the package and its React 19 peers.
pnpm add @n-uf/hypr-tiling react react-domAdd the Tailwind content glob
The library ships utility classes in its dist, not CSS. Add that directory to your Tailwind content globs or the pane, divider, and drag-ghost classes get purged.
// tailwind.config.{js,ts}
export default {
content: [
"./src/**/*.{ts,tsx}",
// Required: hypr-tiling ships utility classes in its dist output.
// Without this glob Tailwind purges the pane / divider / ghost classes.
"./node_modules/@n-uf/hypr-tiling/dist/**/*.{js,mjs,cjs}",
],
};Render a controlled TilingRenderer
Own the layout tree in state, pass a config, and apply every edit the renderer reports through onLayoutChange. This is the exact source behind the live panes above — drag a header to rearrange, drag the divider to resize.
import { useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// The golden path: a controlled TilingRenderer. You own the layout tree in
// state; the renderer reports every user edit (drag, resize, group…) through
// onLayoutChange, and you apply it straight back. renderTile paints each pane.
const tiles: TilingTile[] = [
{ id: "editor", title: "Editor" },
{ id: "preview", title: "Preview" },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "left", tileId: "editor" },
second: { kind: "leaf", id: "right", tileId: "preview" },
};
export function Quickstart(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={setLayout}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<div style={{ padding: 12, fontFamily: "system-ui", fontSize: 13 }}>
{tile.title}
</div>
)}
/>
);
}
Next: paint your own pane content in Render your own content, then skim Concepts when you want the mental model.
How do I…
Each recipe is an outcome you want, a complete snippet that compiles against the current public API, the two or three knobs that matter, and where to look next.
Define the initial layout
Describe the starting arrangement as a plain, serialisable tree you own in state — a leaf holds one tile, a split divides space by a ratio, a group stacks leaves behind a tab strip.
import type { TilingLayoutNode, TilingTile } from "@n-uf/hypr-tiling";
// A layout is a plain, serialisable tree you own in state. Three node kinds:
// • leaf — holds one tile (by tileId)
// • split — divides space along an axis (horizontal | vertical) by a ratio
// • group — stacks several leaves behind a tab strip (one active at a time)
// Every node needs a stable `id`. A leaf's `tileId` points into your tiles.
export const tiles: TilingTile[] = [
{ id: "files", title: "Files" },
{ id: "editor", title: "Editor" },
{ id: "terminal", title: "Terminal" },
{ id: "problems", title: "Problems" },
];
// Files on the left; a stacked editor over a tabbed group (terminal + problems)
// on the right. This whole object is JSON-serialisable — persist and restore it.
export const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.25,
first: { kind: "leaf", id: "files-pane", tileId: "files" },
second: {
kind: "split",
id: "main",
axis: "vertical",
ratio: 0.7,
first: { kind: "leaf", id: "editor-pane", tileId: "editor" },
second: {
kind: "group",
id: "bottom-group",
activeMemberId: "terminal-pane",
members: [
{ kind: "leaf", id: "terminal-pane", tileId: "terminal" },
{ kind: "leaf", id: "problems-pane", tileId: "problems" },
],
},
},
};
Every node needs a stable id; a leaf’s tileId points into your tiles. Splits carry an axis ("horizontal" / "vertical") and a ratio in [0,1]; groups carry an activeMemberId. The whole object is JSON — persist and restore it verbatim.
Render your own content in a pane
Take over what each pane draws with a renderTile callback while the renderer keeps owning layout, resize, and drag.
import { useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// renderTile paints the BODY and chrome of every pane — it's how you render your
// own content. The renderer still owns layout, splits, resize and drag; your job
// is to draw one pane and forward its interaction handles:
// • root must be `article[data-leaf-id]` (the renderer resolves the drag source
// from it) and wire onFocus / onPointerMove / onPointerLeave
// • the header wires onHandlePointerDown (this is the drag-pickup handle)
// Everything else in TilingRenderTileProps (isFocused, isMaximized, the tile
// payload, …) is presentation state you style from.
const tiles: TilingTile[] = [
{ id: "chart", title: "Revenue", content: <strong>$1.2M</strong> },
{ id: "log", title: "Activity", content: <span>3 new events</span> },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "a", tileId: "chart" },
second: { kind: "leaf", id: "b", tileId: "log" },
};
function Pane(args: TilingRenderTileProps): ReactElement {
return (
<article
data-leaf-id={args.leafId}
tabIndex={-1}
onFocus={args.onFocus}
onPointerMove={args.onPointerMove}
onPointerLeave={args.onPointerLeave}
style={{
display: "flex",
flexDirection: "column",
height: "100%",
border: args.isFocused ? "1px solid #fbbf24" : "1px solid #ffffff1a",
borderRadius: 8,
background: "#121316",
overflow: "hidden",
}}
>
<header
onPointerDown={args.onHandlePointerDown}
style={{ cursor: "grab", padding: "6px 10px", fontSize: 11, color: "#d6d3d1" }}
>
{args.tile.title}
</header>
<div style={{ padding: 10, fontSize: 13, color: "#e7e5e4" }}>
{args.tile.content}
</div>
</article>
);
}
export function RenderTileExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={setLayout}
renderTile={(args: TilingRenderTileProps): ReactNode => <Pane {...args} />}
/>
);
}
Root the pane on article[data-leaf-id] and forward onFocus, onPointerMove, and onPointerLeave; put onHandlePointerDown on your drag handle (the header). Everything else on TilingRenderTileProps (isFocused, isMaximized, the tile payload) is presentation state you style from. renderTile is a full-pane render prop, not a content slot — to draw your own header, buttons, and frame, see Render your own pane frame & header.
Render your own pane frame & header (full custom look-and-feel)
Own the ENTIRE pane, not just its body: draw your own frame, header, and controls, and wire them to the renderer’s drag, maximize, focus, and grouping handlers. Same generic renderTile prop as above — it returns the whole pane and hands you every interaction handle and state flag — no extra host-only prop is involved.
import { useState, type CSSProperties, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
useTilingTheme,
isMultiSelectModifierActive,
TilingPaneRoot,
TilingDragHandle,
TilingPaneAction,
TilingPaneBody,
type TilingLayoutNode,
type TilingTile,
type TilingRenderTileProps,
type TilingTheme,
} from "@n-uf/hypr-tiling";
// renderTile is a FULL-PANE render prop, not a content slot: it returns the
// WHOLE pane — frame, header, body — and receives every interaction handle +
// state flag the renderer computes. That means you can own the ENTIRE pane
// look-and-feel (your own frame, header, buttons, focus/drag affordances), not
// just `tile.content`, while the renderer keeps running layout, resize, drag,
// grouping, focus and keyboard control underneath. Grouping gestures to wire:
// a title-bar group icon (onToggleMultiSelect / onGroupMultiSelection /
// onClearMultiSelection / group.ungroup), Alt/Opt+click, and drag a header
// onto a title bar (groupDropTargetRef). Alt+G stays on the library keymap.
//
// There are two ways to write a custom pane, shown side by side below:
//
// • THE EASY PATH — the optional helper primitives (TilingPaneRoot,
// TilingDragHandle, TilingPaneAction, TilingPaneBody) encode the wiring
// rules for you, so you only bring styling + content.
// • THE RAW PATH — plain DOM wired by hand, the full escape hatch, for when
// you need control the primitives don't give.
//
// The contract every custom pane must honor (the primitives encode all of it):
// • root is `article[data-leaf-id={leafId}]` — the renderer resolves the drag
// source from it (`closest("article[data-leaf-id]")`); wire onFocus,
// onPointerMove and onPointerLeave on it too.
// • your drag handle (typically the header) wires onHandlePointerDown with
// `touch-action: none`.
// • header action buttons stopPropagation on pointerDown + click so they don't
// start a drag or steal focus.
// • render the body only when paneBodyRenderMode === "render-content" (the
// drag ghost reuses the same render path, so this keeps the ghost body in
// sync and never empty).
const tiles: TilingTile[] = [
{ id: "editor", title: "editor", accent: "sky", content: <code>main.tsx</code> },
{ id: "preview", title: "preview", accent: "emerald", content: <span>rendered output</span> },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "a", tileId: "editor" },
second: { kind: "leaf", id: "b", tileId: "preview" },
};
// THE EASY PATH — the same custom pane, built with the helper primitives. Each
// primitive owns one wiring rule; you supply only className/style and content.
function PrimitivesPane(args: TilingRenderTileProps): ReactElement {
const theme: TilingTheme = useTilingTheme();
return (
<TilingPaneRoot
pane={args}
className={args.isFocused ? theme.resolveFocusFrame(args.tile.accent) : ""}
style={{
display: "flex",
flexDirection: "column",
height: "100%",
borderRadius: 10,
background: "#101114",
overflow: "hidden",
boxShadow: "0 18px 40px -30px rgba(0,0,0,0.9)",
}}
>
<TilingDragHandle
pane={args}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: 8,
padding: "7px 10px",
cursor: "grab",
borderBottom: "1px solid #ffffff14",
}}
>
<span
className={theme.resolveAccentText(args.tile.accent)}
style={{ fontFamily: "ui-monospace, monospace", fontSize: 11, textTransform: "uppercase", letterSpacing: "0.14em" }}
>
{args.tile.title}
</span>
<span style={{ display: "flex", gap: 6 }}>
{args.isMultiSelected && args.canGroupMultiSelection ? (
<TilingPaneAction
onClick={(): void => args.onGroupMultiSelection(args.leafId)}
style={{ fontSize: 10, padding: "2px 6px", borderRadius: 6, border: "1px solid #fbbf2470", color: "#fde68a", background: "transparent" }}
>
group
</TilingPaneAction>
) : null}
{args.isMaximizeEnabled ? (
<TilingPaneAction
onClick={(): void => args.onToggleMaximize()}
aria-label={args.isMaximized ? "restore pane" : "maximize pane"}
style={{ fontSize: 12, lineHeight: 1, padding: "2px 6px", borderRadius: 6, border: "1px solid #ffffff1f", color: "#d6d3d1", background: "transparent" }}
>
{args.isMaximized ? "\u2715" : "\u2922"}
</TilingPaneAction>
) : null}
</span>
</TilingDragHandle>
<TilingPaneBody pane={args} style={{ flex: 1, minHeight: 0, overflow: "auto", padding: 12, fontSize: 13, color: "#e7e5e4" }}>
{args.tile.content}
</TilingPaneBody>
</TilingPaneRoot>
);
}
// THE RAW PATH — the identical pane wired by hand: article[data-leaf-id],
// header onPointerDown, buttons that stopPropagation, body render-mode gate.
// Only the public TilingRenderTileProps handles + flags and theme tokens are
// used — no debug/observability fields exist on the contract.
function RawPane(args: TilingRenderTileProps): ReactElement {
const theme: TilingTheme = useTilingTheme();
const frameStyle: CSSProperties = {
display: "flex",
flexDirection: "column",
height: "100%",
borderRadius: 10,
background: "#101114",
overflow: "hidden",
outline: "none",
boxShadow: "0 18px 40px -30px rgba(0,0,0,0.9)",
};
return (
<article
data-leaf-id={args.leafId}
tabIndex={-1}
onFocus={args.onFocus}
onPointerMove={args.onPointerMove}
onPointerLeave={args.onPointerLeave}
style={frameStyle}
// Focus frame comes from the active theme + this pane's accent — a custom
// frame still reads as part of the themed surface.
className={args.isFocused ? theme.resolveFocusFrame(args.tile.accent) : ""}
>
<header
onPointerDown={args.onHandlePointerDown}
onClick={(event): void => {
// Alt/Opt+click toggles this pane's multi-selection without focusing.
if (args.isMultiSelectGroupingEnabled && isMultiSelectModifierActive(event)) {
event.stopPropagation();
event.preventDefault();
args.onToggleMultiSelect();
}
}}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: 8,
padding: "7px 10px",
cursor: "grab",
touchAction: "none",
borderBottom: "1px solid #ffffff14",
}}
>
<span
className={theme.resolveAccentText(args.tile.accent)}
style={{ fontFamily: "ui-monospace, monospace", fontSize: 11, textTransform: "uppercase", letterSpacing: "0.14em" }}
>
{args.tile.title}
</span>
<span style={{ display: "flex", gap: 6 }}>
{args.isMultiSelected && args.canGroupMultiSelection ? (
<button
type="button"
onPointerDown={(e): void => e.stopPropagation()}
onClick={(e): void => {
e.stopPropagation();
args.onGroupMultiSelection(args.leafId);
}}
style={{ fontSize: 10, padding: "2px 6px", borderRadius: 6, border: "1px solid #fbbf2470", color: "#fde68a", background: "transparent" }}
>
group
</button>
) : null}
{args.isMaximizeEnabled ? (
<button
type="button"
onPointerDown={(e): void => e.stopPropagation()}
onClick={(e): void => {
e.stopPropagation();
args.onToggleMaximize();
}}
aria-label={args.isMaximized ? "restore pane" : "maximize pane"}
style={{ fontSize: 12, lineHeight: 1, padding: "2px 6px", borderRadius: 6, border: "1px solid #ffffff1f", color: "#d6d3d1", background: "transparent" }}
>
{args.isMaximized ? "\u2715" : "\u2922"}
</button>
) : null}
</span>
</header>
<div style={{ flex: 1, minHeight: 0, overflow: "auto", padding: 12, fontSize: 13, color: "#e7e5e4" }}>
{args.paneBodyRenderMode === "render-content" ? args.tile.content : null}
</div>
</article>
);
}
export function CustomChromeExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
themeId="mosaic"
onLayoutChange={setLayout}
// The left pane uses the helper primitives (easy path); the right pane is
// wired by hand (raw path). Both are fully interactive — drag, resize,
// focus, maximize, group.
renderTile={(args: TilingRenderTileProps): ReactNode =>
args.leafId === "a" ? <PrimitivesPane {...args} /> : <RawPane {...args} />
}
/>
);
}
Easiest path: the optional helper primitives. TilingPaneRoot spreads data-leaf-id + the focus/hover handlers, TilingDragHandle wires the drag pickup with touch-action: none and the Alt/Opt+click group toggle, TilingPaneAction is a header button that stops propagation so it never starts a drag or steals focus, and TilingPaneBody renders its children only in "render-content" mode. They encode the wiring rules so a custom pane can’t get them wrong; the raw renderTile args remain the full escape hatch. By hand: root on article[data-leaf-id] (the renderer resolves the drag source from it) and forward onFocus, onPointerMove, onPointerLeave. Put onHandlePointerDown on your header, expose onToggleMaximize, and add grouping via a title-bar group icon, Alt/Opt+click (onToggleMultiSelect + onGroupMultiSelection, gate the click with isMultiSelectModifierActive), or drag a header onto a title bar (groupDropTargetRef). Style from the state flags (isFocused, isMaximized, isDragSource) and compose with theme tokens from useTilingTheme() (resolveAccentText, resolveFocusFrame). Render the body only when paneBodyRenderMode is "render-content" so the drag ghost mirrors your pane. The ghost / cursor / cancel overlays mount on the overlay portal container (default document.body, see overlayPortalContainer) so position: fixed stays window-relative and the ghost stays outside the React root’s event-delegation scope. The homepage’s own tiles are built exactly this way — and the “panes” switch on the homepage flips them to a light minimalist variant built with these primitives.
Theme & color panes
Restyle the whole surface with a built-in theme, or give an individual pane its own accent — switching is live, with no remount.
import { useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
TilingThemeProvider,
useTilingTheme,
resolveTilingTheme,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingThemeId,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// Two ways to theme:
// 1. Pick a built-in theme with the `themeId` prop ("neon-terminal" |
// "clean-flat" | "mosaic"). Switching is live — no remount.
// 2. Give a pane its own per-tile accent via `tile.accent` (one of eight hues).
// Inside a pane you can read the active theme with useTilingTheme() — wrap a
// subtree in TilingThemeProvider to supply it (resolveTilingTheme maps an id to
// the token object).
const tiles: TilingTile[] = [
{ id: "a", title: "Signals", accent: "emerald" },
{ id: "b", title: "Alerts", accent: "rose" },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "l", tileId: "a" },
second: { kind: "leaf", id: "r", tileId: "b" },
};
// A pane body that reads the live theme id from context.
function ThemedBody({ title }: { title: string }): ReactElement {
const theme = useTilingTheme();
return (
<div style={{ padding: 12, fontSize: 13 }}>
{title} · theme: {theme.id}
</div>
);
}
export function ThemingExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
const [themeId, setThemeId] = useState<TilingThemeId>("neon-terminal");
return (
<TilingThemeProvider theme={resolveTilingTheme(themeId)}>
<button type="button" onClick={(): void => setThemeId("clean-flat")}>
Use clean-flat
</button>
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
themeId={themeId}
onLayoutChange={setLayout}
onThemeChange={setThemeId}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<ThemedBody title={tile.title} />
)}
/>
</TilingThemeProvider>
);
}
Set themeId ("neon-terminal" / "clean-flat" / "mosaic") for the whole renderer; set tile.accent for one pane. useTilingTheme() reads the active theme inside a pane; resolveTilingTheme maps an id to its token object for TilingThemeProvider. Theme class tokens stay literal strings (Tailwind JIT) — that is not a portal constraint. Host CSS variables, data-theme, or a scoped dark class on a wrapper must pass that wrapper as overlayPortalContainer (element or thunk; default document.body) so the ghost inherits them. The container must not sit under a transform / filter / backdrop-filter / contain: paint ancestor; overlay z-indexes 220 / 230 are relative to that stacking context.
Choose which interactions are allowed
Turn interactions on or off — everything is enabled by default, so you subtract what you don’t want through one prop.
import { useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
TILING_DASHBOARD_PRESET,
resolveInteractionCapabilities,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingInteractionCapabilities,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// Every interaction (drag, resize, keyboard, grouping, maximize) is ON by
// default. Narrow behavior through the single `interaction` prop: pass a partial
// TilingInteractionCapabilities, or start from a preset and override. Here we
// take TILING_DASHBOARD_PRESET but disable grouping and lock resizing to the
// horizontal axis. resolveInteractionCapabilities() materializes the fully
// resolved shape if you need to read effective values yourself.
const interaction: TilingInteractionCapabilities = {
...TILING_DASHBOARD_PRESET,
grouping: false,
resize: "horizontal",
};
// Effective, fully-defaulted capabilities — handy for driving your own UI.
const resolved = resolveInteractionCapabilities(interaction);
export const isGroupingEnabled: boolean = resolved.grouping.enable;
const tiles: TilingTile[] = [
{ id: "a", title: "Board" },
{ id: "b", title: "Detail" },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.6,
first: { kind: "leaf", id: "l", tileId: "a" },
second: { kind: "leaf", id: "r", tileId: "b" },
};
export function CapabilitiesExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
interaction={interaction}
onLayoutChange={setLayout}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<div style={{ padding: 12, fontSize: 13 }}>{tile.title}</div>
)}
/>
);
}
Pass a partial TilingInteractionCapabilities to the interaction prop, or spread a preset like TILING_DASHBOARD_PRESET and override. resolveInteractionCapabilities returns the fully-defaulted shape when you need to read effective values. Every interaction is on by default; the lone opt-in is the tab strip’s dev-only content toggle (paneSwitching.showContentToggle), off unless you ask for it.
Save & restore a layout
Persist the arrangement across reloads. Because the tree is plain JSON you own, this is just save on change and load on mount.
import { useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// Because YOU own the tree and it's plain JSON, persistence is just save/load.
// onLayoutChange fires on every user edit — write it to storage there. On mount,
// read it back (falling back to a default). No library-specific serializer.
const STORAGE_KEY = "my-app.layout";
const tiles: TilingTile[] = [
{ id: "a", title: "Nav" },
{ id: "b", title: "Content" },
];
const defaultLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.3,
first: { kind: "leaf", id: "l", tileId: "a" },
second: { kind: "leaf", id: "r", tileId: "b" },
};
function loadLayout(): TilingLayoutNode {
const raw = typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE_KEY) : null;
if (raw == null) {
return defaultLayout;
}
// In real code, validate the parsed shape before trusting it.
return JSON.parse(raw) as TilingLayoutNode;
}
export function SaveRestoreExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(loadLayout);
const persist = (next: TilingLayoutNode): void => {
setLayout(next);
localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
};
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={persist}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<div style={{ padding: 12, fontSize: 13 }}>{tile.title}</div>
)}
/>
);
}
Write the layout to storage inside onLayoutChange and read it back in your useState initializer with a default fallback. Validate the parsed shape before trusting it in production. No library-specific serializer is involved.
Run multiple workspaces over one tile pool
Switch between several saved arrangements that share the same tiles — like a tiling window manager — without remounting panes that exist in both workspaces.
import { useState, type ReactElement, type ReactNode } from "react";
import {
DEFAULT_TILING_LAYOUT_CONFIG,
TilingRenderer,
TilingWorkspaceSwipeScope,
WORKSPACE_KEY_BINDINGS,
useTilingWorkspaceSetController,
useTilingWorkspaceTabs,
useWorkspaceSwipe,
workspaceSetOfLayout,
type TilingLayoutNode,
type TilingRenderTileProps,
type TilingTile,
type TilingWorkspace,
type TilingWorkspaceSet,
type TilingWorkspaceSetCommitReason,
type TilingWorkspaceSwitchEvent,
type UseTilingWorkspaceTabsResult,
} from "@n-uf/hypr-tiling";
// Several layout trees over one tile pool: the set controller owns the
// persisted document + in-flight tree overlays; the tab strip is the native
// drop target; swipe / keymap / spring-load ride the same TilingCommand path.
const tiles: TilingTile[] = [
{ id: "a", title: "Board" },
{ id: "b", title: "Detail" },
];
const seedLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.55,
first: { kind: "leaf", id: "l", tileId: "a" },
second: { kind: "leaf", id: "r", tileId: "b" },
};
function WorkspaceTabStrip({
tabs,
onCreate,
}: {
readonly tabs: UseTilingWorkspaceTabsResult;
readonly onCreate: () => void;
}): ReactElement {
const swipe = useWorkspaceSwipe();
return (
<div {...tabs.tablistProps} data-swipe-progress={String(swipe.progress)}>
{tabs.tabs.map((tab) => (
<button key={tab.workspace.id} {...tab.tabProps}>
{tab.workspace.name}
</button>
))}
<button type="button" onClick={onCreate}>
+
</button>
</div>
);
}
export function WorkspacesExample(): ReactElement {
const [persisted, setPersisted] = useState<TilingWorkspaceSet>(() =>
workspaceSetOfLayout(seedLayout),
);
const ctl = useTilingWorkspaceSetController({
value: persisted,
onCommit: (
next: TilingWorkspaceSet,
_reason: TilingWorkspaceSetCommitReason,
): void => {
setPersisted(next);
},
mintWorkspaceId: (): string => crypto.randomUUID(),
nextWorkspaceName: (existing: ReadonlyArray<TilingWorkspace>): string =>
`Workspace ${existing.length + 1}`,
});
const tabs = useTilingWorkspaceTabs({
workspaces: ctl.set,
onWorkspacesChange: ctl.onWorkspacesChange,
});
return (
<TilingWorkspaceSwipeScope>
<WorkspaceTabStrip
tabs={tabs}
onCreate={(): void => {
ctl.create();
}}
/>
<TilingRenderer
workspaces={ctl.set}
onWorkspacesChange={ctl.onWorkspacesChange}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
paneIdentity="stable"
interaction={{
keyBindings: { bindings: [...WORKSPACE_KEY_BINDINGS] },
workspaces: {
followMovedLeaf: true,
switch: {
transition: "slide",
wheelSwipe: { modifier: "meta" },
touchSwipe: true,
},
springLoad: { dwellMs: 500 },
},
}}
onWorkspaceSwitch={(_event: TilingWorkspaceSwitchEvent): void => {
/* via: key | command | swipe | spring-load | reveal | tab-drop */
}}
onMoveLeaf={(): void => {
/* do not switch from here — followMovedLeaf already switched */
}}
{...tabs.rendererProps}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<div style={{ padding: 12, fontSize: 13 }}>{tile.title}</div>
)}
/>
</TilingWorkspaceSwipeScope>
);
}
Pass workspaces + onWorkspacesChange instead of layout + onLayoutChange. tiles is the whole pool (every tile any workspace seats). Pair with useTilingWorkspaceTabs for tablist ARIA, keyboard, and native tab drop targets (tabs.rendererProps). Spread WORKSPACE_KEY_BINDINGS into interaction.keyBindings.bindings for default chords; dispatch switch-workspace, cycle-workspace, move-leaf-to-workspace, and reveal-tile through a TilingCommandHandle ref. Opt into trackpad swipe, slide/fade transition, and spring-loaded tab drop via interaction.workspaces (switch.wheelSwipe (optional modifier: "meta"; sequence-start gating + whole-window horizontal lock apply regardless), switch.transition, springLoad). followMovedLeaf: true moves and switches in one onWorkspacesChange (onWorkspaceSwitch via: "tab-drop") — do not switch again from onMoveLeaf. For a persisted document use useTilingWorkspaceSetController (value / onCommit, debounced tree edits). Set-wide orphan handling: orphanTiles + onIntegrityIssues.
Trigger actions from your own buttons
Drive the layout from a toolbar, menu, or any control you build — split, group, resize, maximize — without touching the tree by hand.
import { useRef, useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingCommandHandle,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// Drive the layout from YOUR OWN buttons. Take the renderer's imperative handle
// with a ref, then dispatch typed TilingCommands — the same command set the
// keyboard and drag layers use. Great for toolbars, context menus, or a
// "reset layout" button. A command targeting a disabled capability is a safe
// no-op, so you never have to guard the happy path.
const tiles: TilingTile[] = [
{ id: "a", title: "Left" },
{ id: "b", title: "Right" },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "left", tileId: "a" },
second: { kind: "leaf", id: "right", tileId: "b" },
};
export function CommandsExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
const handle = useRef<TilingCommandHandle>(null);
return (
<div style={{ display: "flex", flexDirection: "column", gap: 8, height: "100%" }}>
<div style={{ display: "flex", gap: 6 }}>
<button
type="button"
onClick={(): void =>
handle.current?.dispatch({ kind: "set-split-ratio", splitId: "root", ratio: 0.7 })
}
>
Widen left
</button>
<button
type="button"
onClick={(): void => handle.current?.dispatch({ kind: "toggle-split-axis", splitId: "root" })}
>
Flip axis
</button>
<button
type="button"
onClick={(): void =>
handle.current?.dispatch({ kind: "group-leaves", leafIds: ["left", "right"] })
}
>
Group both
</button>
</div>
<div style={{ flex: 1, minHeight: 0 }}>
<TilingRenderer
ref={handle}
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={setLayout}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<div style={{ padding: 12, fontSize: 13 }}>{tile.title}</div>
)}
/>
</div>
</div>
);
}
Take the renderer’s TilingCommandHandle with a ref and call dispatch with a typed TilingCommand (set-split-ratio, toggle-split-axis, group-leaves, …). A command targeting a disabled capability is a safe no-op, so you never have to guard the happy path.
Build a command bar / keyboard shortcuts
advancedWire your own command bar or key bindings that only surface — and only fire — when the target command would actually do something.
import { useRef, useState, type KeyboardEvent, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
resolveInteractionCapabilities,
isCommandEnabled,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingCommand,
type TilingCommandGates,
type TilingCommandHandle,
type TilingInteractionCapabilities,
type TilingLayoutNode,
type TilingTile,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// ADVANCED: build your own command bar / keyboard shortcuts. isCommandEnabled is
// the gate the renderer uses for its own shortcut chips — pass it a TilingCommand
// plus the gates derived from your resolved capabilities and it tells you whether
// the command would do anything. Use it to hide dead buttons and to keep a
// keyboard binding browser-graceful (only preventDefault when the command runs).
// Map resolved capabilities → the gate flags isCommandEnabled reads.
function gatesFor(interaction?: TilingInteractionCapabilities): TilingCommandGates {
const caps = resolveInteractionCapabilities(interaction);
return {
maximizeEnabled: caps.maximize.enable,
paneSwitchingEnabled: caps.paneSwitching.enable,
focusEnabled: caps.focus,
rearrangeEnabled: caps.rearrange,
sizingEnabled: caps.paneTitleBarControls.sizing,
acquireSpaceEnabled: caps.paneTitleBarControls.acquireSpace,
collapseEnabled: caps.paneTitleBarControls.collapse,
resizeEnabled: caps.resize !== "none",
layoutEnabled: caps.masterLayout,
groupingEnabled: caps.grouping.enable,
workspacesEnabled: caps.workspaces.enable,
};
}
interface BarEntry {
readonly label: string;
readonly key: string;
readonly command: TilingCommand;
}
const ENTRIES: ReadonlyArray<BarEntry> = [
{ label: "Next pane", key: "]", command: { kind: "focus-cycle", direction: "next" } },
{ label: "Maximize", key: "m", command: { kind: "toggle-maximize" } },
{ label: "Master layout", key: "l", command: { kind: "cycle-layout-mode" } },
];
const tiles: TilingTile[] = [
{ id: "a", title: "One" },
{ id: "b", title: "Two" },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "l", tileId: "a" },
second: { kind: "leaf", id: "r", tileId: "b" },
};
export function CommandBarExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
const handle = useRef<TilingCommandHandle>(null);
const gates: TilingCommandGates = gatesFor();
// Only surface commands that are actually enabled right now.
const available: ReadonlyArray<BarEntry> = ENTRIES.filter((entry): boolean =>
isCommandEnabled(entry.command, gates),
);
const onKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {
const match = available.find((entry): boolean => entry.key === event.key);
if (match != null) {
event.preventDefault();
handle.current?.dispatch(match.command);
}
};
return (
<div
onKeyDown={onKeyDown}
tabIndex={0}
style={{ display: "flex", flexDirection: "column", gap: 8, height: "100%", outline: "none" }}
>
<div style={{ display: "flex", gap: 6 }}>
{available.map((entry): ReactElement => (
<button
key={entry.label}
type="button"
title={`shortcut: ${entry.key}`}
onClick={(): void => handle.current?.dispatch(entry.command)}
>
{entry.label}
</button>
))}
</div>
<div style={{ flex: 1, minHeight: 0 }}>
<TilingRenderer
ref={handle}
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={setLayout}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<div style={{ padding: 12, fontSize: 13 }}>{tile.title}</div>
)}
/>
</div>
</div>
);
}
Derive TilingCommandGates from resolveInteractionCapabilities, then call isCommandEnabled(command, gates) to hide dead controls and keep a keyboard binding browser-graceful (only preventDefault when the command runs). This is the same gate the renderer uses for its built-in shortcut chips.
Group, split & maximize panes
Fold panes into a tabbed group, split space, or maximize a pane — as built-in gestures and from your own code.
import { useRef, useState, type ReactElement, type ReactNode } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
type TilingCommandHandle,
type TilingRenderTileProps,
} from "@n-uf/hypr-tiling";
// Grouping, splitting and maximizing are built-in interactions (Alt/Opt+G to
// group a selection, Alt+Enter to maximize, drag a header to split), but you can
// also drive them from code with commands:
// • group-leaves — fold leaves into one tabbed group
// • insert-adjacent — split: drop one leaf beside another
// • toggle-maximize — maximize / restore a leaf
const tiles: TilingTile[] = [
{ id: "a", title: "A" },
{ id: "b", title: "B" },
{ id: "c", title: "C" },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.34,
first: { kind: "leaf", id: "a", tileId: "a" },
second: {
kind: "split",
id: "rest",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "b", tileId: "b" },
second: { kind: "leaf", id: "c", tileId: "c" },
},
};
export function GroupSplitMaximizeExample(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
const [maximizedLeafId, setMaximizedLeafId] = useState<string | null>(null);
const handle = useRef<TilingCommandHandle>(null);
return (
<div style={{ display: "flex", flexDirection: "column", gap: 8, height: "100%" }}>
<div style={{ display: "flex", gap: 6 }}>
<button
type="button"
onClick={(): void =>
handle.current?.dispatch({ kind: "group-leaves", leafIds: ["b", "c"] })
}
>
Group B + C
</button>
<button
type="button"
onClick={(): void => handle.current?.dispatch({ kind: "toggle-maximize", leafId: "a" })}
>
Maximize A
</button>
</div>
<div style={{ flex: 1, minHeight: 0 }}>
<TilingRenderer
ref={handle}
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={setLayout}
maximizedLeafId={maximizedLeafId}
onMaximizedLeafChange={setMaximizedLeafId}
renderTile={({ tile }: TilingRenderTileProps): ReactNode => (
<div style={{ padding: 12, fontSize: 13 }}>{tile.title}</div>
)}
/>
</div>
</div>
);
}
Users can group with the title-bar group icon, Alt/Opt+click, Alt/Opt+G, or by dragging a header onto a title bar; drag a header to split, and Alt+Enter to maximize out of the box. From code, dispatch group-leaves, insert-adjacent, and toggle-maximize. For custom multi-select affordances, isMultiSelectModifierActive reports whether the platform modifier is held.
Concepts
Three ideas unblock everything above. That is deliberately all — there is no architecture here.
leaf (one tile), split (two children divided by a ratio along an axis), and group (leaves stacked behind a tab strip). It is plain, serialisable data held in your state.TilingRenderer is controlled. It performs drag, resize, grouping, focus, and keyboard control, then reports the resulting tree through onLayoutChange — it never mutates state behind your back. You apply the edit (or persist / diff / veto it).interaction prop (TilingInteractionCapabilities) subtracts or reshapes behavior; presets are just pre-filled partials. You configure by turning things off, not wiring things on — the one opt-in is the dev-only pane-content toggle, off by default.Examples gallery
Complete, controlled TilingRenderer apps. Each file is runnable as-is and type-checked against the public API — copy one and start editing. Neither passes renderTile, so each uses the renderer’s default pane chrome and is fully interactive: drag a header, drag a divider, Alt/Opt-click headers then group.
Metrics dashboard
A master-stack of accented metric panes on the clean-flat theme.
import { useState, type ReactElement } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
} from "@n-uf/hypr-tiling";
// A whole runnable dashboard: four metric/detail panes in a master-stack shape,
// each with its own accent and content. Copy this file wholesale — it's a
// complete, controlled TilingRenderer app.
//
// No `renderTile` is passed, so the renderer supplies its DEFAULT pane chrome —
// a themed frame + header that is fully interactive out of the box: drag a header
// to rearrange, drag a divider to resize, Alt/Opt+click headers then "Group" to
// tab them, and maximize. Each pane paints its `tile.content` in the body (the
// dev-only content toggle is off by the library default, so content shows at
// rest). Reach for a `renderTile` callback only when you want to own the pane
// frame yourself — see the "custom look-and-feel" guide.
interface Metric {
readonly label: string;
readonly value: string;
}
function MetricCard({ metric }: { metric: Metric }): ReactElement {
return (
<div style={{ padding: 14, fontFamily: "system-ui" }}>
<div style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: 1, color: "#a8a29e" }}>
{metric.label}
</div>
<div style={{ fontSize: 26, fontWeight: 600, color: "#fafaf9" }}>{metric.value}</div>
</div>
);
}
const tiles: TilingTile[] = [
{ id: "revenue", title: "Revenue", accent: "emerald", content: <MetricCard metric={{ label: "MRR", value: "$48.2k" }} /> },
{ id: "users", title: "Active users", accent: "sky", content: <MetricCard metric={{ label: "DAU", value: "12,904" }} /> },
{ id: "errors", title: "Error rate", accent: "rose", content: <MetricCard metric={{ label: "5xx / min", value: "0.03%" }} /> },
{ id: "latency", title: "Latency", accent: "amber", content: <MetricCard metric={{ label: "p95", value: "142 ms" }} /> },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "horizontal",
ratio: 0.5,
first: {
kind: "split",
id: "left",
axis: "vertical",
ratio: 0.5,
first: { kind: "leaf", id: "revenue-pane", tileId: "revenue" },
second: { kind: "leaf", id: "users-pane", tileId: "users" },
},
second: {
kind: "split",
id: "right",
axis: "vertical",
ratio: 0.5,
first: { kind: "leaf", id: "errors-pane", tileId: "errors" },
second: { kind: "leaf", id: "latency-pane", tileId: "latency" },
},
};
export function DashboardApp(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
themeId="clean-flat"
onLayoutChange={setLayout}
/>
);
}
Terminal grid
Monospace shell / logs / htop panes on the neon-terminal theme — the Hyprland homage, in the terminal.
$ pnpm dev ▸ ready in 312ms $ _
[info] listening :3000 [info] compiled ok
cpu 12% mem 3.1G / 16G load 0.42
import { useState, type ReactElement } from "react";
import {
TilingRenderer,
DEFAULT_TILING_LAYOUT_CONFIG,
type TilingLayoutNode,
type TilingTile,
} from "@n-uf/hypr-tiling";
// A whole runnable "terminal grid" — the Hyprland homage made literal: several
// monospace shell panes a user splits, stacks and rearranges at runtime. Copy
// this file wholesale. No `renderTile` is passed, so the renderer's default pane
// chrome (fully interactive: drag / resize / group / maximize) frames each
// terminal and paints its `tile.content` body at rest.
function Terminal({ lines }: { lines: ReadonlyArray<string> }): ReactElement {
return (
<pre
style={{
margin: 0,
padding: 12,
height: "100%",
overflow: "auto",
fontFamily: "ui-monospace, monospace",
fontSize: 12,
lineHeight: 1.6,
color: "#a7f3d0",
background: "#0a0b0d",
}}
>
{lines.join("\n")}
</pre>
);
}
const tiles: TilingTile[] = [
{ id: "shell", title: "zsh", content: <Terminal lines={["$ pnpm dev", "▸ ready in 312ms", "$ _"]} /> },
{ id: "logs", title: "logs", content: <Terminal lines={["[info] listening :3000", "[info] compiled ok"]} /> },
{ id: "top", title: "htop", content: <Terminal lines={["cpu 12%", "mem 3.1G / 16G", "load 0.42"]} /> },
];
const initialLayout: TilingLayoutNode = {
kind: "split",
id: "root",
axis: "vertical",
ratio: 0.6,
first: {
kind: "split",
id: "top-row",
axis: "horizontal",
ratio: 0.5,
first: { kind: "leaf", id: "shell-pane", tileId: "shell" },
second: { kind: "leaf", id: "logs-pane", tileId: "logs" },
},
second: { kind: "leaf", id: "top-pane", tileId: "top" },
};
export function TerminalGridApp(): ReactElement {
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
themeId="neon-terminal"
onLayoutChange={setLayout}
/>
);
}
Changelog
Newest first, including Unreleased. Calendar versioning (YY.M.R) cannot signal a SemVer major — breaking changes are flagged in the notes below, not by the version number. This page renders packages/hypr-tiling/CHANGELOG.md directly.
Unreleased
Nothing pending.
26.9.10
2026-09-24Tab strips — rails and pills
Tabs are content-width pills in a rounded rail (2px pad / gap, 1px railBorderColor, radius + 2). They never stretch; they shrink only on overflow (min 56px, ellipsis, title tooltip). The strip row keeps a hairline bottom border and the strip background. Active chrome is a pill fill, not the old inset 0 2px 0 accent top edge — accent is the :focus-visible outline. Group-strip defaults use a quiet pill (tabActiveBackground: rgba(255,255,255,0.08)); pane-strip defaults (TILING_PANE_TAB_STRIP_THEME_DEFAULTS) use a solid pill (rgb(252, 211, 77) / rgb(12, 13, 16)). Additive tokens: railBackground, railBorderColor, nestedTabActiveBackground, nestedTabActiveColor. When a group member is maximized under a visible pane strip, the group row is the nested tier (data-nested="true", --hpt-tab-strip-nested, height − 4, ~18px indent, └ tick, nested active tokens).
Maximize
maximize.keepGroupTabStrip(defaulttrue) keeps the group tab strip when a group member is maximized: the group is painted at viewport size with a render-timeactiveMemberIdoverride (layout is not mutated). This is a behaviour change for grouped leaves — strip height is subtracted from the maximized member. Setfalsefor the previous leaf-only maximize. The top-level pane strip's group tab now labels the maximized member, not the stored active member.
Pane tab strip
paneSwitching.showTabStripacceptstrue/false/"maximized"."maximized"paints the top-level strip only while a pane is maximized.paneSwitching.tabStrip(placement,height,theme,renderTabLabel) themes that strip with the group-strip token vocabulary (--hpt-group-tab-*, includingtabHoverColor). Default placement"top", height and theme match the group strip."bottom"mounts the strip after the viewport. Root classhpt-pane-tab-strip(plus sharedhpt-tab-strip).- Lab chrome (wordmark / pickers / content toggle) renders only when
showTabStrip === trueand those lab hooks are present.
Tab groups — built-in strip contrast
- Inactive group tabs no longer paint at
opacity: 0.55. Label color istabColorat full opacity; hover uses the newtabHoverColortoken (--hpt-group-tab-hover-color). DefaulttabColorstaysrgb(148, 163, 184)onrgba(0, 0, 0, 0.45). TilingGroupTabStripTheme.tabHoverColoris additive; omitted values keep the defaultrgb(226, 232, 240).
Docs
- Home: the hero slot is a tab group, hypr-tiling | Proof | Scenarios, with the hero active on first paint; the four original tiles keep their places and widths. Proof facts (suite and test counts, gzip sizes, dependencies, license, version) are generated from the package. Scenarios run through the public command dispatch and the workspace-set controller.
26.9.9
2026-09-24Tab groups — built-in strip is the canonical rendering
showGroupTabStrip already defaults to true (no default flip). Hosts that pass false keep the strip hidden. New groupTabStrip options are additive.
groupTabStrip.placement"top"(default, above the pane header) or"bottom"(under the pane body).heightdefault28(px). Both placements subtract that height from the member content box.showEject(defaulttrue) ejects the active member (remove-from-group).showUngroup(defaulttrue) ungroups. Both sit at the right end of the strip.theme(TilingGroupTabStripTheme) is optional CSS values —background,borderColor,tabColor,tabActiveColor,tabBackground,tabActiveBackground,accent,fontFamily,fontSize,letterSpacing,radius,gap,paddingX,controlColor,controlHoverColor— resolved over the dark/amber default and applied as inline styles plus--hpt-group-tab-*custom properties (same partial-token path asdragChrome).renderTabLabel(member)overrides the tile-title label.memberis{ id, tileId, title, active }.- The strip is a
role="tablist"ofrole="tab"buttons (aria-selected, Left/Right/Home/End). Labels ellipsize with atitletooltip. The active-indicator transition is disabled underprefers-reduced-motion. - A drop on a built-in tab inserts at that tab's index (
TilingDropIntentState.memberInsertIndex). A drop past the last tab, and every hostgroupDropTargetRefhit, still appends.
Multi-select grouping
- Pane args gain
multiSelectionCount: number— how many leaves are multi-selected in the active workspace (0when none). Every pane of that workspace reports the same count, including panes that are not themselves selected, so a host can show Cancel when only one tile is selected. Inactive retained panes and the drag-ghost / drag-cancel surfaces report0.
Drop preview
TilingLeafPreviewModegains"group-merge". While a drag hovers a group-merge target (built-in group tab strip, or a hostgroupDropTargetRefhit withfallbackReason: "host-group-drop-target"), the target pane'spreview.modeis"group-merge"(role: "drop-target-result-shadow"). The drag source and every other pane keeppreview: nullfor that intent. Live drag mode still suppresses the per-tile preview.
26.9.8
2026-09-24Docs
- README, CHANGELOG intro and the docs home state the calendar-versioning contract: active development, releases may break compatibility, pin exact versions.
Multi-select grouping
- Pane args gain
onClearMultiSelection()— clears the whole selection from host chrome (same path as Escape).
Tab groups — host drop target
Additive. Hosts that hide grouping.showGroupTabStrip and paint their own chips can still offer drag-to-group.
TilingRenderTileProps.groupDropTargetRef: React.RefCallback<HTMLElement | null>— attach it to the element (title bar, chip strip, or both; the same callback may be attached to several elements) that should accept "drop here to group with this pane". Inert on the drag-ghost and drag-cancel surfaces.- While
grouping.enableis on, a pointer over a registered element resolvesgroup-mergeon the same commit path as the built-in strip (deriveCandidateTree/ projected layout). A loose leaf becomes a new group{target, source}with the source active; an existing group appends the source and makes it active. One commit; pane identity is preserved underpaneIdentity: "stable". Works withshowGroupTabStripeithertrueorfalse. - Precedence: built-in group tab strip, then the host element (it wins over the centre swap and over any edge band the element covers), then uncovered edge bands (
edge-insert), then the pane centre (swap). The drag source's own target, and a group that already contains the source, fall through to that body partition. - Inserting at a hovered chip index is not part of this change; a group target appends.
26.9.7
2026-09-24Workspaces — reset to defaults
Additive. No default key bindings; hosts opt in.
resetWorkspaceLayout(set, defaults, workspaceId?)— replace one workspace'slayoutandnamefrom the seed workspace of the same id (workspaceIdomitted →set.activeId). Missing seed id →layout: null, name kept.activeIdunchanged. Same-reference when nothing changes.resetWorkspaceSet(set, defaults)— restore the seed set, keepingset.activeIdwhen that id exists in the seed. Same-reference when already equal.workspaceSetEquals(a, b)— structural equality over ids, names,activeId, and persisted layout trees.- Commands
{ kind: "reset-workspace"; workspaceId? }and{ kind: "reset-workspaces" }, gated byworkspacesEnabledand the rendererworkspaceDefaultsseed (no-op without the seed). Emit throughonWorkspacesChange; never fireonWorkspaceSwitch. TilingRendererset-mode propworkspaceDefaults?: TilingWorkspaceSet.- Controller
defaultsoption,reset("workspace" | "all", workspaceId?), andatDefaults(truewhenreset("all")would change nothing, whichever workspace is active).
Docs
- Workspace navigation (26.9.3–26.9.6) documented across the package README, root README, agent records, and docs site: set ops, set controller, renderer set-mode, tab strip, commands/keymap, swipe gates, transitions, and switch
viaincluding"tab-drop". Hosts must not switch again fromonMoveLeaf. Changelog restores the missing## 26.9.4heading so 26.9.5 and 26.9.4 stay distinct.
26.9.6
2026-09-24Drag-source mount identity across a tab drop. Additive; no default changes.
- Fixed: a dragged pane no longer remounts when its drag has no in-tree candidate. Under
paneIdentity: "stable"the live-drag display tree lifts the source out whenever the pointer is over nothing or over an external target (a workspace tab); the pool used to drop the pane's entry there, so the release — in-tree, external claim, or a move into another workspace — remounted it and lost in-pane local state. The source now stays parked in the pool throughdraggingandsettling(last render props, transient flags cleared, likeinactiveWorkspaces: "keep-mounted"retention) and the release reseats the same mount. - Tab drop honours `interaction.workspaces.followMovedLeaf`. A plain (non-spring-loaded) release on a workspace tab with
followMovedLeaf: truemoves the leaf AND switchesactiveIdin ONEonWorkspacesChange, the same edgemove-leaf-to-workspacetakes, and firesonWorkspaceSwitch({ via: "tab-drop" })+ focus memory for the moved leaf. Hosts that switched fromonMoveLeafshould stop: a second commit paints the leaf seated only in an inactive workspace first, which underinactiveWorkspaces: "unmount"is itself a remount.followMovedLeaf: false(default) is unchanged — the set is reported moved, not switched. - `TilingWorkspaceSwitchVia` gains `"tab-drop"` (additive union member; exhaustive
switches over the union need a new arm).
26.9.5
2026-09-23Three desktop disambiguation gates on the workspace wheel-swipe FSM. Hosts on 26.9.4 semantics keep modifier: null (gate 1 off) and pick up gates 2–3, which only remove false-positive arms. Touch swipe is unchanged except that it ignores modifier.
- breakingBREAKING (type): `WheelInputSample` and the swipe `WHEEL` event require `metaKey` / `altKey` / `shiftKey`. Custom
WheelTouchInputPortimplementations must stamp the DOM modifier flags (the built-increateDomWheelTouchPortdoes).ctrlKeyis still the pinch-zoom reject. - `TilingWorkspaceSwipeConfig.modifier: "meta" | "alt" | "shift" | null` — default
null(no modifier required). When set, a wheel run arms only while that key is held on the arming samples; oncetracking, releasing it does not cancel or convert the gesture to scroll.ctrlKeystill rejects regardless. Touch ignoresmodifier.overscroll-behavior-x: containstill applies; the portpreventDefaults only whiletracking, so a wheel without the modifier scrolls normally. - Behaviour change (tightening): sequence-start gating. A wheel run is the samples with gaps
< wheelIdleMs. Only the start of a fresh run may arm: if the first sample(s) fail (vertical-dominant, modifier missing, chain can still scroll, …), the run is claimed by scroll (idle.runClaimedByScroll+lastWheelTs) and later samples of that run cannot arm — even if they become horizontal. Afterlockoutexpiry, a momentum tail that continues the committed run still cannot arm. - Behaviour change (tightening): whole-window horizontal lock.
|dx| > 2·|dy|still applies per sample at arming; the same ratio must also hold on the accumulatedarmedwindow (|travelDy| ≤ |travelPx| / 2) until travel reachesthresholdPx. A gradually diagonal path is rejected and the run claimed by scroll. Oncetracking, vertical drift is still tolerated. - `switch.wheelSwipe: { modifier: "meta" }` resolves into
switch.swipe.modifierthrough the existing partial-config path (resolveWorkspaceSwipeConfig).
26.9.4
2026-09-23Engine-owned workspace navigation on top of the 26.9.3 workspace set: tile-keyed set ops and revealTile, typed workspace commands and an opt-in keymap fragment, a debounced useTilingWorkspaceSetController for persisted sets, pool-aware set rendering with explicit orphan policy, trackpad/touch swipe and slide/fade switch transitions, and spring-loaded tab drop with drag REARM. The set-mode wrapper dispatches every gesture through TilingCommand and reports the next TilingWorkspaceSet on onWorkspacesChange. DashAI (starpay-app/packages/dashai) is the first consumer cut-over target (S4/S5 after publish). Three breaking changes — read the first three bullets.
- breakingBREAKING (type): `TilingCommand` gains four kinds — exhaustive switches with a `never` guard fail typecheck:
switch-workspace(workspaceIdor 1-basedindex),cycle-workspace,move-leaf-to-workspace(workspaceIdor neighbourdirection; omittedleafId→ focused leaf;followdefaults frominteraction.workspaces.followMovedLeaf),reveal-tile. - breakingBREAKING (type): `TilingCommandGates` gains `workspacesEnabled`:
falsein single-layout mode and wheninteraction.workspaces.enableisfalse; those commands are then a no-op. - breakingBREAKING (behaviour): set-mode layout healing is per active tree, not set-wide force-seat. In set mode
tilesis the whole pool, but coverage is checked per tree: the active workspace's layout is healed against the tiles *it* seats only. Pre-26.9.4 the wrapper reused the single-layout heal, so a pool tile shown only in another workspace was force-seated into the active tree on the next integrity settle and a seat for an unknown tile was pruned. Single-layout mode is unchanged. Hosts that relied on the implicit prune must opt intoorphanTiles: "seat-in-active"(see pool renderer bullets).
Engine set ops
- `TilingWorkspacePlacement` `region` —
{ kind: "region"; region: "start" | "end" }seats the leafleftof the reading-order first leaf /rightof the last, viainsertLeafInto'sadjacentpath. A group-member neighbour seats beside the group node, not inside it. Anulldestination becomes the bare leaf.TILING_DEFAULT_WORKSPACE_PLACEMENTstays root-second. - `moveTileToWorkspace(set, tileId, to, placement?, options?)` — tile-keyed
moveLeafToWorkspace. A tile seated nowhere is seated only whenoptions.leaf.idis given (the engine never invents leaf ids); otherwise the set is unchanged. - `MoveTileToWorkspaceOptions` —
{ leaf?: { id } }mint id formoveTileToWorkspacewhen the tile has no seat. - `showTileInWorkspace(set, tileId, to, placement?)` — tile-keyed
showInWorkspace. Unchanged when the tile is unknown, already shown into, ortois unknown. - `hideTileFromWorkspace(set, tileId, from)` — tile-keyed hide; returns
{ set, orphaned }.orphanedistruewhen the tile now has no seat anywhere. - `TilingHideTileResult` —
{ set, orphaned }return ofhideTileFromWorkspace. - `removeTile(set, tileId)` — drop every seat of the tile. Unchanged when the tile is seated nowhere.
- `revealTile(set, tileId, prefer?)` — pick
preferwhen it shows the tile, else the first workspace in tab order that does; switchactiveIdand activate a group member tab as needed. Returnsnullwhen the tile is seated nowhere; same set reference whenchangedis"none". - `TilingRevealTileResult` / `TilingRevealTileChanged` —
{ set, workspaceId, leafId, changed: "none" | "tab" | "workspace" | "both" }return ofrevealTile.
Workspace commands & keymap
- `interaction.workspaces?: boolean | { enable?; followMovedLeaf?; switch?; springLoad? }` — default
enable: true,followMovedLeaf: false. Resolved asResolvedTilingWorkspacesCapability(includesswitchandspringLoad; see swipe / transition / spring-load sections). - `WORKSPACE_KEY_BINDINGS` — opt-in chord→command fragment (
Alt+Arrowcycle,Alt+1..9switch by index,Alt+Shift+Arrowmove+follow). Not merged intoTILING_KEYMAP_DEFAULTS; hosts spread it intointeraction.keyBindings.bindings. - Set-mode wrapper dispatches the four workspace commands through the existing
TilingCommandHandle/dispatchCommandpath and reports the next set ononWorkspacesChange. - `reveal-tile` dispatches engine
revealTile(set, tileId, activeId)(prefers the active workspace when it shows the tile), updates the wrapper's focus memory for the revealed leaf, and firesonWorkspaceSwitch({ via: "reveal" })whenactiveIdchanged. - `onWorkspaceSwitch?: (event: { from; to; via: "tab" | "key" | "command" | "swipe" | "spring-load" | "reveal" }) => void` — fired beside
onWorkspacesChangewhenactiveIdchanges through the renderer."tab"is reserved (not emitted yet); a host tab strip that callsswitch-workspaceis reported as"command".
Set controller
- `useTilingWorkspaceSetController` — host-agnostic session over a persisted
TilingWorkspaceSet: tree edits (divider, rearrange, tab activation, in-tree drop) coalesce fortreeDebounceMs(default 400) and commit once with reason"tree";create/rename/remove/switchcommit immediately as"lifecycle";moveTileas"move";revealas"reveal"whenchanged !== "none". Every mutation folds pending trees first so a lifecycle commit never drops an in-flight tree edit.readOnlykeeps switches local and commits nothing. Pending trees flush synchronously on unmount. Replaces thelocalTrees/pendingRef/lastPersistedRefblock a set host (DashAIdashboard-renderer.tsx) keeps by hand. - `TilingWorkspaceSetCommitReason` —
"tree" | "lifecycle" | "move" | "reveal". - `foldPendingTrees` / `viewedWorkspaceSet` / `diffWorkspaceTreeLayouts` / `adoptIncomingWorkspaceTrees` / `classifyIncomingWorkspaceSet` — framework-free helpers the hook uses (also on
./engine).
Pool-aware set renderer
- Set mode `tiles` is the whole pool — pass every tile any workspace seats; active-tree healing never force-seats a tile seated only elsewhere (see breaking bullet above).
- `orphanTiles?: "seat-in-active" | "ignore" | "report"` (set mode, default
"report") — a pool tile no workspace seats / a seat whose tile is not in the pool."report"leaves the set untouched and callsonIntegrityIssues;"seat-in-active"runsrepairWorkspaceSet(orphans seatedTILING_DEFAULT_WORKSPACE_PLACEMENTin the active tree, unknown seats pruned) once per set identity and emits the repaired set ononWorkspacesChange, then reports what it repaired;"ignore"does neither. - `mintLeafId?: (tileId) => string` (set mode) — leaf id for a seat minted by
"seat-in-active"; default `leaf:${tileId}`. - `onIntegrityIssues?: (issues: ReadonlyArray<TilingWorkspaceSetIssue>) => void` (set mode) — fires when the issue fingerprint (
kind|workspaceId|leafId|tileIdrows) changes; an empty list fires once when the last issue clears. Not called under"ignore". - `inactiveWorkspaces?: "unmount" | "keep-mounted"` (set mode, default
"unmount") —"keep-mounted"keeps a tile-keyed stable pane (paneIdentity: "stable") whose tile is seated only in an inactive workspace parked in the hidden pool (display: none) instead of unmounting it. Costs: retained DOM + React subtree; retained only after first paint; empty active workspace still unmounts everything; no-op underpaneIdentity: "slot". - `TilingRenderTileProps.workspaceId: string` / `seatCount: number` — the workspace the pane is painted in and how many workspaces seat the tile. Single-layout mode reports
TILING_MAIN_WORKSPACE_ID/1. A retained pane keeps the workspace id of its last live paint. - `TilingOrphanTilePolicy` / `TilingInactiveWorkspacesMode` — the two prop unions (
.and./engine).
Swipe navigation
- `interaction.workspaces.switch?: { wheelSwipe?: boolean | Partial<TilingWorkspaceSwipeConfig>; touchSwipe?: boolean; transition?: "none" | "slide" | "fade" }` — opt-in swipe (both default
false) and switch transition (default"none"). Resolved asResolvedTilingWorkspaceSwitchCapability(resolveWorkspaceSwitchCapabilityon./engine). When swipe is enabled the set-mode root carriesoverscroll-behavior-x: containandtouch-action: pan-yfor touch. A committed swipe dispatchescycle-workspaceand firesonWorkspaceSwitch({ via: "swipe" }). - `useWorkspaceSwipe(): { progress, target, phase }` + `TilingWorkspaceSwipeScope` — headless read of the gesture (
progress-1..1, negative = previous). Live when the hook and the set-mode renderer share a scope. TypesTilingWorkspaceSwipeSnapshot,TilingWorkspaceSwipePhase,TilingWorkspaceSwipeTarget;TILING_WORKSPACE_SWIPE_DEFAULTSon.. - `workspaceSwipeReducer(state, event, config?)` (
./engine) — pure swipe FSMidle → armed → tracking → settling → lockout → idle. Commits when|progress| ≥ commitFractionor peak velocity≥ commitVelocityPxMs;settling(commit)carriescycle-workspace. HelpersresolveSwipeArming,swipeProgressOfTravel,shouldCommitSwipe,swipeCommitCommand,hasSwipeNeighbour,swipeTargetOfTravel,workspaceSwipeSnapshot,resolveWorkspaceSwipeConfig; constantsTILING_WORKSPACE_SWIPE_INITIAL_STATE/_EMPTY_CONTEXT/_IDLE_SNAPSHOT. - Swipe settle waits for the switch transition — with
switch.transitionnot"none", the swipe FSM stayssettlinguntilWorkspaceTransitionStagefinishes (commit or cancel);SETTLE_DONEthen moves tolockout/idle. With"none", reduced motion, or no mounted stage the settle is immediate.settleGateonUseWorkspaceSwipeDriverParams(wrapper implements it over the stage'sonSettled). - Ports —
WheelTouchInputPort/WheelTouchInputListener/WheelInputSample/TouchInputSampleandScrollChainPort(./engine); DOM adapterscreateDomWheelTouchPort,normaliseWheelDelta,createDomScrollChainPort,canElementScrollFurther(.).
Switch transition
- `TilingWorkspaceTransitionMode` —
"none" | "slide" | "fade". Default"none". - `TilingWorkspaceTransitionDirection` —
"prev" | "next". - `TilingWorkspaceTransitionConfig` / `DEFAULT_WORKSPACE_TRANSITION_CONFIG` —
{ mode: "none", durationMs: 200, easing }. - `resolveTransitionMode(requested, { reducedMotion, degraded })` — reduced motion →
"none"; degraded canvas capture falls"slide"→"fade". - `transitionTransform(progress, direction, mode)` — compositor-friendly
translateX/ opacity for outgoing clone and live incoming tree. - `captureViewClone(root)` — viewport
cloneNode(true); copies<canvas>viadrawImage; strips hit-testdata-*;degraded: trueon tainted / oversized canvases (16 M pixel budget). - `WorkspaceTransitionStage` / `useWorkspaceTransition` — stage +
begin({ direction, mode })/scrub(progress)/finish("commit" | "cancel"). The set-mode wrapper mounts the stage inside the viewport and drives it:beginbeforeonWorkspacesChangefor renderer-driven switches and before a host-drivenactiveIdchange; tracking swipe scrubsprogress; short release settles withfinish("cancel"); committed swipe keeps its capture.cycle-workspacekeeps cycle direction across wrap-around; other switches deriveprev/nextfrom tab order. Hosts that manually droveuseWorkspaceTransitionshould useinteraction.workspaces.switch.transitioninstead. - `theme.workspaceTransition?: { durationMs; easing }` — optional motion tokens;
resolveWorkspaceTransitionfills 200 ms + drag-hop easing.
Spring-loaded tab drop
- `springLoadReducer(state, event, config?)` (
./engine) — dwell FSMidle → dwelling → firedover workspace-tab hover;TICKatdwellMsfires once withintent: { kind: "commit-and-rearm", leafId, workspaceId, placement }. HelpersspringLoadFireAt,isWorkspaceTabDragHover; constantsTILING_SPRING_LOAD_INITIAL_STATE; typesTilingSpringLoadState/TilingSpringLoadEvent/TilingSpringLoadIntent/TilingSpringLoadPhase. - `TILING_SPRING_LOAD_DEFAULTS` / `TilingSpringLoadConfig` —
{ dwellMs: 500 };resolveSpringLoadConfig(partial?). `TilingSpringLoadCapability` —Partial<TilingSpringLoadConfig> | false;resolveSpringLoadCapability(value)→ config ornull. - `interaction.workspaces.springLoad?: TilingSpringLoadCapability | null` (set mode, default off) — while a pane drag reports
kind: "workspace-tab"external hover over a non-active workspace, the wrapper runsspringLoadReducerand arms a dwell timer. On fire: claimed external commit, oneonWorkspacesChangewithmoveLeafToWorkspace+switchWorkspace(with transition when configured),onMoveLeaf+onWorkspaceSwitch({ via: "spring-load" }), then dragREARMon the leaf's new seat under the held pointer. Active workspace tab never dwells; release / Escape mid-dwell runs ordinary tab drop / cancel. - Drag machine `REARM` event (
DragRearmEvent,./engine) — starts a drag fromidlewithoutpointerdownat the current pointer on a new leaf seat after spring-load commit. Accepted only inidle(canRearmDrag).rearmPointerAnchorOffset(footprint, client)— default grab offset.
26.9.3
2026-09-22Engine-native workspace set (_agent/workspace-set-concept.md, library half H1–H4), plus the titlebar-only pane collapse that was pending as "Unreleased", plus the React-free ./engine entry fix. Additive minor for the workspace set; the collapse work carries two default-only breakages — read its first two bullets.
Note
26.10.0 was published under a mis-numbered calendar version (month 10 ≠ September) and was unpublished from npm; 26.9.3 supersedes it with identical workspace-set API plus the React-free ./engine entry. (YY.M.R: M is the calendar month of the release — September is 9.)
Workspace set
- `TilingWorkspaceSet` —
{ workspaces: [{ id, name, layout }], activeId }over one tile pool. Pureset → setops:createWorkspace,renameWorkspace,deleteWorkspace(refuses the last; returnsremovedTileIdsshown only there),switchWorkspace,cycleWorkspace,setWorkspaceLayout,moveLeafToWorkspace(set, leafId, to, placement?),showInWorkspace,hideFromWorkspace,workspaceSetOfLayout,activeWorkspace,findWorkspaceById,queryWorkspaceSet,normalizeWorkspaceName. LimitsTILING_WORKSPACES_MAX(12) andTILING_WORKSPACE_NAME_MAX_CHARS(40) are enforced at create / rename. - Integrity —
workspaceSetIssues(set, { expectedTileIds? })walks the invariants (unique non-empty workspace ids, valid names,activeIdexists, every tree structurally valid, one leaf id ↔ one tile id across the set, no orphan / unknown tile againstexpectedTileIds);repairWorkspaceSetfixes deterministically and returns the reasons. - `TilingWorkspacePlacement` +
insertLeafInto/extractLeafNode(engine) — the seat-and-extract halves the movers share, now public so the set ops reuse them instead of duplicating tree code. - `TilingRenderer` workspace-set mode —
workspaces+onWorkspacesChange(TilingRendererWorkspaceSetProps) as the alternative tolayout+onLayoutChange;TilingRendererPropsis unchanged and the mode-independent surface isTilingRendererCommonProps. The active tree is painted; a switch does not remount a pane whose leaf exists in both workspaces. Uncontrolled focus and maximize are remembered per workspace; a drag in flight is cancelled when its source leaf leaves the controlled tree (workspace switch).renderEmptyWorkspace(workspace)fills anulllayout.onMoveLeaf(leafId, from, to)reports a tab drop. - Native workspace-tab drop target —
TilingExternalDragHoveris now the unionTilingExternalDropHover | TilingWorkspaceTabDragHover(a bare{ targetId, point }literal is still the former). Akind: "workspace-tab"hover settles in set mode bymoveLeafToWorkspacethrough the 26.9.2 claim-before-settle path — no host hit-test, no host claim.resolveWorkspaceTabHover(targets, point)is the pure hit-test;TilingGhostChipContext.workspaceIdnames the destination for the chip. - `resolveExternalDragHover` / `onExternalDragHoverChange` renderer props — engine-driven hover resolution (called per processed drag frame and synchronously at release) as the alternative to feeding
externalDragHoverfrom host state. - `onExternalDrop` now receives the full hover as a 4th argument and may return
falseto decline the claim (the release then cancels with the fly-back). Existing 3-argumentvoidhosts are unaffected. - Headless `useTilingWorkspaceTabs` / `TilingWorkspaceTabs` —
tablist/tab/tabpanelprops, roving focus, Arrow / Home / End / Enter / Space / F2 / Delete (resolveWorkspaceTabKeyis the pure model),onRenameRequest/onCloseRequestaffordances withtab.rename/tab.close/createcommitting through the engine ops, andrendererPropsthat make every tab a native drop target. Renders nothing; classes come only from the new theme tokens. - Theme tokens
TilingTheme.workspaceTabs,workspaceTab,workspaceTabActive,workspaceTabDropTarget(all optional strings).
Pane collapse (previously "Unreleased")
- breakingBREAKING (default only): `resizeFloor` now defaults to `"chrome"`. An interactive resize can size a pane out to its collapsed titlebar/chrome extent with no opt-in. The old content floor remains as
resizeFloor: "body"onTilingLeafNode(per-leaf) orTilingLayoutConfig(library-wide). - breakingBREAKING (default only): a collapsed pane's body stays mounted.
TilingLayoutConfig.collapseBodyModeis"keep-mounted"|"unmount"(default"keep-mounted"). The prior unmount-on-collapse behavior iscollapseBodyMode: "unmount". - Titlebar-only pane collapse (`paneTitleBarControls.collapse`, opt-in). Collapse pins the pane to its chrome extent on the split axis; the body is hidden (
display: none). Collapsed panes stay draggable. Axis is recorded so a drag re-parent reasserts the pin on the new split. Both-collapsed siblings stay collapsed; leftover axis space is an intentional split slack void (drag or nudge the divider to expand both). - `onPaneCollapsedChange` —
{ leafId, collapsed }after the layout edit, so a host can retitle or react without parsing the tree. - `minBBoxPx` — leaf-scoped resize floor that travels with the pane.
- `TilingCommandGates.collapseEnabled` gates
toggle-collapse/set-collapsed. Non-interactivetitleBarContentno longer swallows titlebar drag.
Fixed
- `./engine` entry no longer pulls the React renderer chunk; safe to import from server / react-server layers (Next route handlers). In 26.10.0 (the mis-numbered first publish of this release)
dist/engine.mjsre-exported from the code-split chunk that also backs.(the whole renderer —createContext, hooks, theme), andengine.tsitself re-exportedaccentHue,BASELINE_DRAG_HOP_DURATION_MS, andINSTANT_DRAG_DURATION_MSfromreact/. Importing@n-uf/hypr-tiling/enginefrom a Next.js route handler therefore evaluated React'sreact-serverbuild and failed withTypeError: createContext is not a function. The three demoted symbols now live in the engine layer (engine/accent-hues.ts,engine/drag-timing.ts; same names, same values,.exports unchanged), andengine.tsis built as its own tsup configuration with code splitting off, sodist/engine.{mjs,cjs}is a standalone bundle with noreact/react-domimport and no chunk shared with././devtools. Guarded by__tests__/engine-entry-react-free.test.ts(pnpm test:dist, run in CI andprepublishOnly) and by theengine ↛ reactlayering guardrail, which now also scans theengine.tsentry file. No public API change (all three API reports identical).
26.9.2
2026-09-21Compact drag ghost + external-drop claim (26.9.x standalone subset of the WorkspaceSet drag scope). Additive; default drag behaviour is unchanged.
- `TilingRendererProps.dragGhostMode` —
"footprint"(default, today's tile-sized ghost) |"compact"(always the cursor-anchored chip) |"auto"(chip whileexternalDragHoveris set, footprint otherwise). Footprint ↔ compact transitions over ~140 ms scale/opacity;prefers-reduced-motionis instant. SameoverlayPortalContaineras the footprint ghost. - `TilingRendererProps.externalDragHover` — host-reported
{ targetId, point }while the pointer is over chrome outside the tiling tree. The engine does not hit-test host chrome. - `TilingRendererProps.onExternalDrop` —
(leafId, targetId, point)fired on release while hover is set, before the FSM settles. Marks the dragclaimedand skipsDragCancelOverlay. Absent → existing cancel + fly-back. - Theme slot `TilingTheme.ghostChip` —
(ctx: { leafId, title?, point, targetId? }) => ReactNode. Host paints the chip contents; default is a minimal neutral chip (title / leaf id).
26.9.1
Patch release. The drag overlays (ghost, custom cursor, cancel fly-back) still default to a document.body portal so position: fixed stays window-relative, but a host can now redirect that portal:
- `TilingRendererProps.overlayPortalContainer` —
HTMLElement | null | (() => HTMLElement | null). The thunk is evaluated every render so a late-mounted container (ref / callback) is picked up without remounting the renderer.null/ omitted falls back todocument.body. BothDragPaneOverlayandDragCursorOverlay(and the cancel fly-back) use the same resolved container. - Containing-block caveat: the container must not sit under an ancestor with
transform/filter/backdrop-filter/perspective/contain: paint(orlayout/strict/content) /will-changeof those. The overlays still use window-relative client coordinates; a containing-block ancestor reintroduces ghost↔seat drift. - Use this when host theme tokens (CSS variables on a scoped root) must inherit into the ghost. Do not add a second scoping mechanism. Overlay z-indexes (ghost 220, cursor 230, cancel 219) are relative to the container's stacking context, not the document.
- The body default exists for two independent reasons: containing-block immunity (
position: fixedstays window-relative) AND the ghost staying outside the React root's event-delegation scope. Redirecting the DOM mount does not change the delegation invariant — React still walks fibers, and measurement selectors stay root/viewport-scoped.
26.9.0
Feature release (calendar-aligned YY.M.R; 26.9 = September, .0 = first release of the month). One breaking theme-shape change — read the first bullet.
- breakingBREAKING (theme shape): `paneShell.dragSourceOpacity` removed. The picked-up source pane's dim is now
dragChrome.sourcePane(see below) and is applied by the RENDERER to the whole leaf wrapper — title AND content, one opacity — for the default tile and a customrenderTilealike. Custom panes that applied their ownisDragSourcefade should drop it (it now double-dims). A consumer theme object that still spellspaneShell.dragSourceOpacityfails typecheck (excess property); delete the line or move the value todragChrome: { sourcePane }. - New theme slot `dragChrome` (`TilingThemeDragChromeTokens`, optional + partial). Every renderer-painted drag surface reads theme tokens, so a host can make the drag state look exactly like its at-rest pane. Tokens:
ghostLifted/ghostSeated/ghostTransition(ghost WRAPPER elevation / scale / opacity / tint delta — outside theghost.surfaceshell),cancelFlyBack,sourceReservation(the content-less seat's full surface — radius / border / background / shadow),sourcePane(whole-pane source dim),dropTarget(drop-target leaf highlight, default none),dropIntentLayer(default tile's edge/center hint frame radius + border width),cursorBadge+cursorBadgeValid/cursorBadgeInvalid/cursorBadgeNeutral(the pointer-pinned drop-validity badge shape + tones), andresolveSeatFrame(accent)(the seat's hop-in frame; default =resolveFocusFrame).resolveDragChrome(theme)fills omitted tokens with a PANE-SHELL-INHERITING default: the seat wearspaneShell.surface, the ghost wrapper carries a small neutral elevation +opacity-95, no drop-target highlight. A theme that passes nothing never shows a look it did not author. - Default drag look changed for `clean-flat` / `mosaic` / consumer themes. The renderer no longer hardcodes the neon look in the drag path: no
rounded-xlseat, noscale-[1.01]+ slate-950 drop shadow on the ghost wrapper, no cyan/rose/slate cursor badge unless the theme asks. The built-inneon-terminaltheme now carries that exact look in its owndragChrome, so the showcase is unchanged. - Live-drag observability layers default OFF.
TILING_OBSERVABILITY_COLOR_ENABLE_DEFAULTS.dragSourceBorderEnabled/dragTargetBorderEnabledare nowfalse: the pink seat tint + source border and the cyan drop-target border / fill / inset shadow were inline-style debug overlays painting over every consumer's drag. Toggle them in the observability panel (or passobservabilityColorEnables) when diagnosing drop resolution. Preview-mode projected-landing layers are unchanged. - Stable pane identity across drag → drop → settle (`paneIdentity` prop, `TilingPaneIdentityMode = "auto" | "stable" | "slot"`). The split tree is reconciled positionally, so an insert drop (leaf moves to another branch) used to unmount + remount the pane and a swap handed each instance the other tile's props — host content re-initialized after every drop. In
"stable"mode panes render ONCE each in a hidden tile-keyed pool and their DOM node is relocated (layout-effectappendChild) into whichever slot shows their tile; the React instance (hooks, state, refs, iframes, scroll) is the same object through drag → drop → settle and every other tree edit."auto"(default) is"stable"on a client-only mount and"slot"(the legacy in-place render, SSR-faithful) when hydrating server markup; pass"stable"explicitly to opt an SSR host in (server HTML then carries empty slots; content is placed on the client). DOM shape in stable mode: the leaf wrapper carriesdata-hpt-pane-slot="<tileId>", the pane sits in adisplay: contentswrapper[data-hpt-pane], and a[data-hpt-pane-pool](display: none) sits after the viewport inside the root. The drag ghost is still a transient second render of the tile throughrenderTile(the overlay portal container, defaultdocument.body, seeoverlayPortalContainer/ 26.9.1) soposition: fixedstays window-relative AND the ghost stays outside the React root's event-delegation scope — see_agent/drag-subsystem-audit.md§11. - Leaf wrapper drag attributes.
data-drag-source-pane(preview-mode dimmed source) anddata-drop-target-pane(resolved drop target) on the leaf wrapper for host CSS hooks; the ghost wrapper carriesdata-drag-ghost-wrapper.
26.7.2
2026-07-30- Layout reconciliation (`normalizeLayout`). Commit-time normalization on every resize/rearrange
pointerup/pointercancel/lostpointercapturedemotes unfit static pins, clamps ratios against min-pane + full gutters, and enforces the both-static filler invariant so panes+gutters fill the container. Live resize ratio updates are rAF-coalesced; an idle settle (~150ms) and a dev fill-slack invariant (measureLayoutFillSlackPx) are belt-and-suspenders. Exported from@n-uf/hypr-tiling/engineasnormalizeLayout,measureLayoutFillSlackPx,LAYOUT_RECONCILE_IDLE_MS,LAYOUT_FILL_SLACK_TOLERANCE_PX. - Tile-slot integrity on reconcile.
normalizeLayoutaccepts optionalexpectedTileIds/fallbackLayout. When the host declares expected tiles, reconcile detects duplicate/missing/unknown/emptytileIds and rebuilds a sane default dwindle (or the host fallback) instead of leaving void slots. Collapsed ratios near 0/1 are clamped in place. New helpers:assessLayoutTileIntegrity,buildDefaultDwindleLayout,LAYOUT_COLLAPSED_RATIO_EPS.isStructurallyValidLayoutalso rejects duplicatetileIds (at most one tile per slot). - Hard layout integrity (empty / overlap / gone). Reconcile now auto-derives
expectedTileIdsfrom the hosttilesregistry on every resize/rearrange commit and idle settle. Gap-closed rearrange candidates that drop a host tile are refused (cancel fly-back). Geometry assessment flags zero-area leaves, overlapping footprints, and fill-slack voids; unhealable cases rebuild. Clear API:assertLayoutIntegrity,repairLayout,expectedTileIdsFromHostTiles,layoutCoversExpectedTiles. - Pane titlebar middle slot.
TilingTile.titleBarContentrenders custom chrome between the title (left) and native window controls (right). The default tile wires the slot; customrenderTilepanes can wraptile.titleBarContentwith the newTilingPaneTitleBarContentprimitive (stops pointer-down so toolbar interactions do not start a rearrange drag). Drag ghosts carry the same slot. Existing title-only headers are unchanged.
26.7.1
A docs/tooling patch — no runtime or public-API changes. (Calendar-aligned versioning YY.M.R; the trailing .1 is the release counter within 26.7.)
- npm README now uses absolute URLs. The README rendered on npmjs.org resolves asset and doc references against npm's own CDN, not the GitHub repo, so the previous repo-relative paths broke: the logo image 404'd and the
etc/hypr-tiling.api.mdAPI-report links went nowhere. Asset references now point at absoluteraw.githubusercontent.comURLs and doc links atgithub.com/n-uf/hypr-tiling/blob/main/..., so the logo renders and the API-report links resolve on the npm package page. - Removed an invalid `ignoreDeprecations: "6.0"` value from the
apps/webandpackages/showcasetsconfigs. That value is not a legalignoreDeprecationstarget under TypeScript 6.0.3 and was rejected by the compiler; dropping it lets those workspaces typecheck cleanly.
26.7.0
First published release of @n-uf/hypr-tiling, a dynamic tiling layout engine for React. This entry is the baseline: earlier working versions were never published, so the changelog tracks the package from this release forward.
Entry points
The package exposes three import paths through package.json#exports ("sideEffects": false):
- `@n-uf/hypr-tiling` (
.) — the public API. A small, hand-authored facade of explicit named exports (neverexport *); this is the ONLY consumer surface and the one tracked for compatibility. - `@n-uf/hypr-tiling/devtools` — opt-in observability overlays, on their own entry so a renderer-only consumer never bundles them.
- `@n-uf/hypr-tiling/engine` — a
@betaescape hatch that re-exports the engine-grade, framework-free internals (layout-tree reducers, low-level tree walkers, keymap and drag-adjacent math) for power users driving the tree headlessly. No stability guarantees — it may change or disappear in any release and is kept off the consumer documentation site.
Public API surface (`.`)
- `TilingRenderer` — the layout renderer: controlled layout tree, focus and maximize, drag-and-drop with FLIP animation and self-healing recovery, multi-select grouping (Alt/Opt+G), pane switching, and per-tile accents. A custom
renderTilereceives the clean, debug-freeTilingRenderTilePropscontract (tile payload + pane state flags + interaction handlers) — the drag/drop observability + debug fields are OFF this surface (they route through the internal default pane and/devtools). - Custom-pane helper primitives — optional, unstyled conveniences layered over
renderTilethat encode the pane wiring rules so a custom pane can't get them wrong:TilingPaneRoot(data-leaf-idroot + focus/hover handlers),TilingDragHandle(drag pickup +touch-action: none+ Alt/Opt group toggle),TilingPaneAction(action button that stops propagation), andTilingPaneBody(renders children only inrender-contentmode). The rawrenderTileargs stay the full escape hatch. - Theming —
TilingThemeProvider/TilingThemefor token-driven styling. - Layout inspection & mutation — the layout is a recursive tree of
TilingLayoutNode(TilingLeafNode/TilingSplitNode/TilingGroupNode). Read it withqueryTilingLayout(aTilingLayoutQueryview: leaf ids, splits, groups, tile order, directional-neighbor lookup). Mutate it declaratively viaonLayoutChangeor imperatively by dispatching a typedTilingCommandthrough the renderer'sTilingCommandHandle(gated byisCommandEnabled). The raw pure reducers (groupLeaves,insertLeafAdjacent,updateSplitRatio, …) are NOT on the public entry — they live on@n-uf/hypr-tiling/engine. - Interaction & presets —
TilingInteractionCapabilities,resolveInteractionCapabilities,TILING_DASHBOARD_PRESET, and the theming registry constants. Every interaction is on by default and narrowed by passing a partialinteractionprop; the single opt-IN exception is the group tab strip's dev/demo "show pane body" checkbox (paneSwitching.showContentToggle, defaultfalse), so a consumer that renders its own pane content never surfaces an end-user control that blanks it and panes paint content at rest with no wiring.
Consumer theming & chrome contract
Every painted pixel is reachable from a consumer-authored TilingTheme or routed through the consumer renderTile — no renderer state paints chrome the consumer didn't choose. Defaults stay zero-config (built-in themes, DefaultTilingTile, the built-in group strip). Concretely:
- `theme` prop on `TilingRendererProps` — a full consumer-authored
TilingTheme; takes precedence overthemeId.TilingTheme.idis open (TilingThemeId | (string & {})) so a consumer mints its own theme id;TILING_THEME_REGISTRYstays keyed by the closed built-in union, and the built-in theme switcher (onThemeChange, typedTilingThemeId) remains built-ins-only — consumers running a custom theme simply don't wire it. - `grouping` capability object form —
boolean | TilingGroupingCapability({ enable?, showGroupTabStrip? }; bare boolean ={ enable },showGroupTabStripdefaulttrue).showGroupTabStrip: falsesuppresses the built-in per-group tab strip so a consumer paints its own group chrome; keyboard group commands stay live.paneSwitching.showTabStripgoverns the TOP-LEVEL tab strip only. - `surface` discriminator on `TilingRenderTileProps` (
TilingRenderSurface:"pane" | "drag-ghost" | "drag-cancel"). The two drag surfaces carry the REAL resolved capability display flags (no mid-drag silhouette pop) with inert no-op handlers; a custom pane that wants different drag chrome branches onsurface. - `group` context on `TilingRenderTileProps` —
TilingRenderTileGroupContext | null, populated for a tabbed group's ACTIVE member: theTilingGroupMemberViewmember list (leaf/tile ids, resolved tile, 1-basedmemberNumber,isActive) plusactivateMember/removeMember/ungroupcallbacks that route through the same internal command router as the built-in strip and the keyboard layer.nullfor loose leaves and drag surfaces. - Cancel fly-back fidelity — the drag-cancel overlay renders through the consumer
renderTile(surface: "drag-cancel") when one is supplied, so a custom skin keeps its own chrome for the whole cancel glide; the built-in shell remains the no-renderTilefallback. - Hand-authored facade — 104 public API items. Engine-grade internals are physically layered under
engine/and reached only through the.facade (viareact/) or the explicit./engineentry. An API Extractor report per entry is checked in (etc/hypr-tiling{,.devtools,.engine}.api.md);pnpm api:checkfails CI if the.surface drifts or an unexported type leaks onto it, and an architectural guardrail keeps theengine/layer framework-free and blocks deep consumer imports.
Developer / observability tooling — `@n-uf/hypr-tiling/devtools`
The observability panel, its seed defaults, and the whole debug/observability input surface live on a separate /devtools subpath, so a renderer-only consumer never pulls them into its bundle:
``ts import { TilingObservabilityPanel, ANIMATION_CONTROL_DEFAULTS, TilingRenderer, // the observability-instrumented view of the same renderer } from "@n-uf/hypr-tiling/devtools"; ``
The renderer's observability inputs — overlay colors (observabilityColors / observabilityColorEnables), the hit-zone / drop-intent debug flags, and the onDropIntentChange / onLiveHitLogChange / onProjectedOverlayCountChange telemetry hooks — are collected into TilingRendererObservabilityProps and kept OFF the consumer TilingRendererProps contract. /devtools exports both those props and the observability-typed view of the SAME TilingRenderer component that accepts them, plus the debug/observability snapshot types they reference (TilingDropIntentDebugState, TilingLiveHitLogState, TilingObservabilityColorConfig, TilingPaneHitZone*, …). The consumer . surface carries none of them.
Documentation
Guides and a generated API reference are published at hypr-tiling.n-uf.com/docs. Every public symbol carries TSDoc (coverage enforced in CI), so summaries and examples surface in editor hover-docs.
Tailwind requirement
The package ships no CSS — it emits Tailwind utility classes. Add @n-uf/hypr-tiling to your Tailwind content glob or the renderer is unstyled. See the README "Tailwind content requirement" section.
API reference
The generated per-symbol reference for the curated public API surface, produced from the library’s source TSDoc via API Extractor and API Documenter. This is a fallback — reach for it once you already know a symbol name; the guides above are the way in. Internal and devtools-only symbols are excluded, so every entry is part of the supported consumer contract, grouped by category and browsable from the sidebar tree. The full machine-readable report lives in the API report.
Renderer & tiles
The TilingRenderer component, its props, and the tile + custom render-prop contract.
TilingRenderer
variableThe controlled tiling renderer — the single component a consumer mounts. Accepts either a controlled layout (TilingRendererProps) or a controlled workspace set (TilingRendererWorkspaceSetProps); the two are discriminated by the presence of workspaces. The public prop surface is the curated, debug-free contract. The underlying component also accepts the devtools-tier TilingRendererObservabilityProps; that widened view is exported as TilingRenderer from @n-uf/hypr-tiling/devtools for the observability panel.
Signature:
TilingRenderer: React.ForwardRefExoticComponent<TilingRendererModeProps & React.RefAttributes<TilingCommandHandle>>
TilingRendererProps
interfaceProps for the TilingRenderer component in SINGLE-LAYOUT mode — the full controlled-component surface. layout + tiles + config + onLayoutChange are the four required props; everything else is optional and resolves to a documented default. The mode-independent part is TilingRendererCommonProps; the workspace-set alternative is TilingRendererWorkspaceSetProps.
Signature:
interface TilingRendererProps extends TilingRendererCommonProps
Extends: TilingRendererCommonProps
Remarks
The renderer is a CONTROLLED component: you hold the layout tree in state and apply every edit it reports through onLayoutChange. Focus, maximize, theme, and accent can each be left uncontrolled (renderer-managed) or lifted into your state via the matching on*Change callback. Interaction behavior is tuned entirely through the single interaction prop (TilingInteractionCapabilities). This surface is debug-free: the drag/drop observability overlays, hit-zone visualizations, and telemetry feeds are a devtools concern, exposed through TilingRendererObservabilityProps on the @n-uf/hypr-tiling/devtools entry rather than here.
Example
const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
return (
<TilingRenderer
layout={layout}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
onLayoutChange={setLayout}
/>
);
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
The controlled layout tree to render. Apply every reported edit back here. | |||
(layout: TilingLayoutNode) => void | Called with the next layout tree whenever the renderer edits it (controlled). |
TilingRenderTileProps
interfaceThe argument object passed to a custom renderTile. This is the clean, consumer-facing pane contract: the tile payload, this pane's derived state (focus / drag / drop / sizing), and the imperative callbacks a custom pane surface wires to its header, drag handle, and controls. Every field is something a custom pane legitimately reads to style itself or wires to drive an interaction — nothing here is a debug, observability, or tooling concern (those live on the internal render path and the /devtools surface).
Signature:
interface TilingRenderTileProps
Remarks
A minimal custom pane needs only three wiring rules, each of which the optional public helper primitives (TilingPaneRoot(), TilingDragHandle(), TilingPaneAction(), TilingPaneBody()) encode so they cannot be gotten wrong: - the pane root must carry data-leaf-id={leafId} (the renderer resolves the drag source through the [data-leaf-id] attribute) and wire onFocus, onPointerMove, onPointerLeave; - the drag handle wires onHandlePointerDown with touch-action: none; - header action buttons must stopPropagation on pointer-down + click so a click does not start a drag or steal focus; - the body renders only when paneBodyRenderMode === "render-content" (the drag ghost reuses the same render path).
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether the current multi-selection ( | ||
TilingDimension | null | Which dimension collapse pinned ( | ||
TilingLeafDropZone | null | The resolved drop zone under the cursor for this pane, or | ||
| TilingRenderTileGroupContext | null | Group context when this pane is a tabbed group's ACTIVE member (the only member that renders, per the stacking contract); | |
| React.RefCallback<HTMLElement | null> | Ref callback for the element that accepts a drag-to-group drop on this pane (a custom chip strip, the title bar, or both — the same callback may be attached to more than one element). While The hit is resolved before pane-body zone classification, so it wins over the centre swap and over any edge band the element overlaps — the same way the built-in strip wins over the group body. Pixels the element does not cover keep the body partition (edge bands stay Gated by | |
This pane's current HEIGHT sizing mode (static/flexible) — drives the active control state. | |||
boolean | Whether THIS pane is currently collapsed to titlebar-only (HT-PANE-COLLAPSE). | ||
boolean | Whether the per-pane collapse control is enabled ( | ||
boolean | Whether this pane is the source of the in-flight drag. | ||
boolean | Whether this pane is a valid drop destination for the current drag. | ||
boolean | Whether this pane is the resolved drop target of the in-flight drag. | ||
boolean | Whether this pane currently holds focus. | ||
boolean | Whether the cursor is currently hovering this pane as a drop candidate. | ||
boolean | Whether the current hover over this pane would be an invalid drop. | ||
boolean | Whether this pane is currently maximized (fills the tiling viewport). | ||
boolean | Whether the maximize capability is enabled (controls header button visibility). | ||
boolean | Whether this pane is the SOURCE of an in-flight keyboard move (the keyboard analog of a drag source). Drives the "MOVING" affordance. | ||
boolean | Whether THIS pane is currently a member of the multi-selection set. | ||
boolean | Whether the Alt/Opt+click header multi-selection feature is live ( | ||
boolean | Global pane-content visibility toggle from the tab strip checkbox. | ||
boolean | Whether drag-to-rearrange is enabled (drives handle affordance). | ||
boolean | Whether the per-pane title-bar directional acquire-space controls (→ ← ↑ ↓) are enabled. | ||
boolean | Whether the per-pane title-bar sizing control (FLEX / STATIC H / STATIC W / BOTH) is enabled. | ||
string | The leaf node id this render targets. | ||
TilingMovePlacement | null | When this pane is the pending move-mode DESTINATION, the edge of this pane the moved source will land on ( | ||
| number | How many leaves are multi-selected in the active workspace. | |
(direction: TilingFocusDirection) => void | Grow THIS pane to claim the maximum available space in | ||
| () => void | Clear the whole multi-selection set (all panes). No-op when the set is empty or the feature is off. Wire to a host "Cancel" control. | |
(event?: React.SyntheticEvent<HTMLElement>) => void | Establish single focus on this pane (and clear any in-progress multi-selection). Wire to the pane root's | ||
(clickedLeafId: string) => void | Group the current multi-selection into ONE flat tabbed group occupying the CLICKED pane's slot (pass this pane's | ||
(event: React.PointerEvent<HTMLElement>) => void | Pointer-Events drag pickup on the pane's drag handle (the title-bar grip). Wire this to the handle's | ||
(event: React.PointerEvent<HTMLElement>) => void | Clears this pane's hover telemetry when the pointer leaves it. | ||
(event: React.PointerEvent<HTMLElement>) => void | Pre-drag hover telemetry (drop-intent hit-log); inert while a drag is in flight. | ||
(mode: TilingTitleBarSizingMode) => void | Set THIS pane's sizing mode. STATIC modes measure the pane's current bbox (getBoundingClientRect on its | ||
() => void | Toggle THIS pane's collapse (titlebar-only) state. Collapse pins the leaf's along-parent-axis dimension (see TilingLeafNode.collapsed: | ||
() => void | Toggle maximize/restore for this pane. | ||
() => void | Toggle THIS pane in/out of the multi-selection set. Wire to an Alt/Opt+click on the pane header. Does not change focus. | ||
Canonical pane-body visibility decision resolved by the renderer policy. Keeps custom tile renderers aligned with the default drag/hidden semantics. | |||
number | 1-based pane ordinal in current tab order (for generic pane labels). | ||
number | Current pane viewport width in pixels (for responsive header/control density). | ||
TilingLeafDropPreview | null | The projected landing/result preview for this pane, or | ||
number | How many workspaces in the set seat this leaf's | ||
| Which renderer surface these args paint (see TilingRenderSurface). | ||
The resolved tile payload for this leaf. | |||
This pane's current WIDTH sizing mode (static/flexible) — drives the active control state. | |||
string | Id of the workspace whose tree seats this leaf. In single-layout mode this is always |
TilingTile
interfaceGeneric tile payload. Only id + title are required so a product consumer (e.g. a dashboard) can supply a minimal { id, title, content } tile and a custom renderTile. The accent / rows fields drive DefaultTilingTile and the drag-pane snapshot chrome; when omitted they fall back (accent → "cyan", rows → []), so a tile without them renders correctly under the default tile surface. content is the slot a custom renderTile reads. titleBarContent is the optional middle slot between the title and the native pane controls (sizing / maximize / …).
Signature:
interface TilingTile
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
(Optional) Optional per-tile identity accent. Falls back to | |||
React.ReactNode | (Optional) Rich body a custom | ||
string | (Optional) Optional one-line description surfaced by the default tile chrome. | ||
string | Stable tile identity; leaf nodes reference a tile by this | ||
ReadonlyArray<string> | (Optional) Optional text rows for the default tile body. Falls back to | ||
string | Title shown in the pane header / tab. | ||
React.ReactNode | (Optional) Optional custom chrome for the pane titlebar middle slot — rendered between the title (left) and native window controls (right: W/H/max, …). The default tile (and the |
TilingTileAccent
typePer-pane identity accent. A closed, typed palette so a consumer can drive a picker from the enumerable TILING_TILE_ACCENTS list (exported from the renderer) with exhaustive type-checking — adding a member here forces every accent theme map to cover it.
Signature:
type TilingTileAccent = "cyan" | "sky" | "violet" | "indigo" | "emerald" | "amber" | "rose" | "pink";
Layout & query
The layout tree node kinds you own in state, the config, and queryTilingLayout for reading structure back out.
DEFAULT_TILING_LAYOUT_CONFIG
variableRecommended baseline layout config — the generic gap / min-pane / handle scale. config stays a required renderer prop (spacing is explicit at the call site), but consumers that don't tune spacing can spread this for a tasteful, well-readable inter-pane gutter out of the box instead of inventing their own magic numbers. gapPx + handleSizePx together form the visible inter-pane gap (the divider element's hit-target spans the full gapPx + handleSizePx gutter via padding; chrome paints only the center handleSizePx through bg-clip-content. Each child subtracts (gapPx + handleSizePx) / 2 of basis — see splitGapOffsetPx in the split renderer for the balancing math).
Signature:
DEFAULT_TILING_LAYOUT_CONFIG: TilingLayoutConfig
queryTilingLayout()
functionBuild a read-only TilingLayoutQuery over layout. The single higher-level layout-inspection helper on the public API: it composes the low-level tree walkers (which stay on the ./engine escape hatch) into the leaf/tile/group/split views and directional-neighbor lookup an app needs to render layout-aware controls.
Signature:
declare function queryTilingLayout(layout: TilingLayoutNode): TilingLayoutQuery;
Parameters
Parameter | Type | Description |
|---|---|---|
layout |
Returns:
Example
Inspect the tree you own in state — count panes, list groups, or find a directional neighbor — without walking it by hand:
import { queryTilingLayout, type TilingLayoutNode } from "@n-uf/hypr-tiling";
function paneCount(layout: TilingLayoutNode): number {
return queryTilingLayout(layout).leafIds.length;
}
const query = queryTilingLayout(layout);
const rightOfFocus = query.neighborLeafId(focusedLeafId, "right");
TilingGroupNode
interfaceA group/member slot (HT-GROUP-TABBED-STACKING): N leaves share ONE layout slot as a stacked group with a tab strip — only activeMemberId renders / is hit-tested, the tabs switch the active member. A group is a SLOT (like a leaf), so a slot in either dwindle or master layout can hold a group. Members are leaves only (no nested split/group — a group of one is degenerate and collapses back to a bare leaf).
Signature:
interface TilingGroupNode
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
string | Always one of | ||
string | Stable node identity, unique within the layout tree. | ||
"group" | Node discriminant. Always | ||
ReadonlyArray<TilingLeafNode> | ≥1 member; array order is the tab order. | ||
(Optional) A group can be static like a leaf (per-dimension static/flexible sizing). |
TilingLayoutConfig
interfaceGlobal geometry configuration for the renderer (the config prop).
Signature:
interface TilingLayoutConfig
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
(Optional) Collapsed pane body mount policy (HT-COLLAPSE-BODY-MODE) — see TilingCollapseBodyMode. Undefined → | |||
number | (Optional) Collapsed titlebar-only extent (CSS px) — the along-parent-split-axis extent a leaf is pinned to when COLLAPSED (HT-PANE-COLLAPSE): | ||
number | Gap (CSS px) painted in the gutters between panes. | ||
number | Resize-handle chrome thickness (CSS px). The interactive hit-target spans the full boundary gutter ( | ||
number | Library-wide default minimum pane extent (CSS px) a resize divider will not shrink a pane below — the LAST-resort fallback in the min-pane precedence chain, behind a direct child leaf's own TilingLeafNode.minBBoxPx (along-axis component) and behind TilingSplitNode.minPaneSizePx. | ||
(Optional) Library-wide default for which floor an interactive resize bounds a pane to (HT-RESIZE-FLOOR) — a direct-child leaf's own TilingLeafNode.resizeFloor wins over this. Undefined → |
TilingLayoutNode
typeA node in the layout tree: a leaf (single pane), a split (two subtrees), or a group (tabbed leaves in one slot). The recursive union you own in state and feed to TilingRenderer via the layout prop.
Signature:
type TilingLayoutNode = TilingLeafNode | TilingSplitNode | TilingGroupNode;
References: TilingLeafNode, TilingSplitNode, TilingGroupNode
TilingLayoutQuery
interfaceA read-only structural view of a TilingLayoutNode tree, aggregating the queries an app typically needs to drive layout-aware UI (shortcut chips, pane counters, directional focus) without walking the tree by hand or reaching for the low-level ./engine walkers.
Signature:
interface TilingLayoutQuery
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| ReadonlyArray<TilingGroupNode> | Every group in the tree (depth-first, reading order) — the tab-strip source. | |
| boolean | Whether any split node is in | |
| ReadonlyArray<string> | The leaf ids the OUTER layout sees, in reading order — a leaf contributes its id; a group contributes ONLY its active member id (the group presents as one pane to the outer layout). | |
| (fromLeafId: string, direction: TilingFocusDirection) => string | null | The leaf id reached from | |
| ReadonlyArray<TilingSplitNode> | Every split node in the tree (depth-first, reading order). | |
| ReadonlyArray<string> | The tile ids the tree renders, in reading order (group members reported in tab order). Drives tile-order-dependent UI such as a tab strip. |
TilingLeafNode
interfaceA leaf slot: a single pane that renders one tile. The renderer's smallest addressable layout unit — focus, drag, resize, and sizing all target a leaf by its id.
Signature:
interface TilingLeafNode
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Whether this leaf is COLLAPSED to titlebar-only (HT-PANE-COLLAPSE, axis-aware). A collapsed leaf is pinned STATIC, to the chrome/titlebar extent (
This mirrors the split's own along-axis/cross-axis convention ( | ||
(Optional) Which dimension | |||
(Optional) The pre-collapse TilingPaneSizing snapshot, captured when the leaf is collapsed and restored verbatim on expand (so the leaf returns to its exact prior flexible/static contribution). Undefined when the leaf was flexible before collapse (expand then clears the pin → flexible) or when the leaf is not collapsed. Only meaningful while | |||
string | Stable node identity, unique within the layout tree. | ||
"leaf" | Node discriminant. Always | ||
(Optional) Leaf-scoped resize floor (CSS px) — a per-pane along-axis minimum that TRAVELS WITH the pane across rearrange, because it is stored on the leaf node itself rather than on a split boundary. When this leaf sits as a DIRECT child of a split, the component that runs ALONG that split's axis (
An undefined or non-positive per-dimension value falls through to the next precedence level. The cross-axis component is reserved for a future cross-axis floor; today's clamp pipeline only bounds the split-axis ratio, so only the along-axis component is consulted. Persisted alongside | |||
(Optional) Per-leaf override of which floor an interactive resize bounds this pane to (HT-RESIZE-FLOOR) — wins over TilingLayoutConfig.resizeFloor. Undefined falls through to the config default, itself defaulting to | |||
(Optional) Per-dimension static/flexible sizing. Undefined dimensions are flexible. | |||
string | The |
TilingSplitNode
interfaceA binary split node: divides its region between two child subtrees along axis by ratio. In layoutMode: "master" the binary structure defines slot membership/order while the master resolver drives geometry.
Signature:
interface TilingSplitNode
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
Split orientation ( | |||
The first (top/left) child subtree. | |||
number | (Optional) Optional per-split gap override (CSS px) between the two children. | ||
string | Stable node identity, unique within the layout tree. | ||
"split" | Node discriminant. Always | ||
(Optional) Arrangement of this subtree's slots. Undefined → | |||
number | (Optional) | ||
(Optional) | |||
number | (Optional) Optional per-split minimum pane extent (CSS px) enforced on resize. Sits in the MIDDLE of the min-pane precedence chain: a direct child leaf's own TilingLeafNode.minBBoxPx (along-axis component) wins over this; this wins over TilingLayoutConfig.minPaneSizePx. Unlike | ||
number | Fraction | ||
The second (bottom/right) child subtree. | |||
(Optional) Per-dimension static/flexible sizing. Undefined dimensions are flexible. |
Theming
Built-in themes, per-pane accents, the theme provider, and useTilingTheme for reading tokens inside a custom pane.
DEFAULT_TILING_THEME_ID
variableDefault library theme id (preserves the prior look at the round-1 checkpoint).
Signature:
DEFAULT_TILING_THEME_ID: TilingThemeId
resolveTilingTheme()
functionResolve a theme id (or the default) to its TilingTheme.
Signature:
declare function resolveTilingTheme(themeId: TilingThemeId | undefined): TilingTheme;
Parameters
Parameter | Type | Description |
|---|---|---|
themeId | TilingThemeId | undefined |
Returns:
TILING_THEMES
variableOrdered, enumerable theme list — the generic capability a theme switcher iterates.
Signature:
TILING_THEMES: readonly TilingTheme[]
TilingTheme
interfaceA complete theme: the static surface token groups plus the accent-composition resolvers. The resolvers are the contract for "how per-pane accents compose with the theme" — each theme decides how much of an accent's hue/glow it spends on the resting surface, title text, focus frame, and active tab.
Signature:
interface TilingTheme
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| Split-divider / gap handle tokens. | ||
| Partial<TilingThemeDragChromeTokens> | (Optional) Drag-state chrome tokens (ghost wrapper delta, seat, source dim, drop target, cursor badge). Optional + partial — omitted tokens resolve through | |
| Drag-ghost shell tokens. | ||
| (ctx: TilingGhostChipContext) => React.ReactNode | (Optional) Compact-ghost chip CONTENTS. The renderer wraps the return in the cursor-anchored chip shell ( | |
| TilingThemeId | (string & {}) | The theme's id. A built-in theme uses a | |
| string | Human-readable theme label (e.g. for a theme picker). | |
| Pane header chrome tokens. | ||
| Pane host shell surface + interaction-state tokens. | ||
| (accent: TilingTileAccent | undefined) => string | Accent title-text color. | |
| (accent: TilingTileAccent | undefined) => string | Full focused-pane frame. Prefer a SINGLE radius-following stroke ( | |
| (accent: TilingTileAccent | undefined) => string | Resting pane accent composition (border tint + colored shadow). | |
| (accent: TilingTileAccent | undefined) => string | Active tab / switcher / group-member chip. | |
| Renderer-root + viewport surface tokens. | ||
| Top-bar / tab-strip chrome tokens. | ||
| string | (Optional) Every workspace | |
| string | (Optional) Composed onto the ACTIVE workspace tab ( | |
| string | (Optional) Composed onto the tab a dragged leaf is hovering ( | |
| string | (Optional) Workspace tab-strip | |
| Partial<TilingThemeWorkspaceTransitionTokens> | (Optional) Workspace-switch slide / fade timing. Optional + partial — omitted tokens resolve through |
TilingThemeId
typeClosed set of built-in visual theme ids for the renderer. A theme bundles the class-string tokens for every surface (pane shell, header, focus frame, ghost, dividers, root, tab strip) plus the accent-composition resolvers. The token interfaces + registry live in ./theme; this union is the central contract a consumer types its themeId state against.
Signature:
type TilingThemeId = "neon-terminal" | "clean-flat" | "mosaic";
TilingThemeProvider()
functionProvides the active theme to every renderer subcomponent.
Signature:
declare function TilingThemeProvider(input: {
theme: TilingTheme;
children: React.ReactNode;
}): React.ReactElement;
Parameters
Parameter | Type | Description |
|---|---|---|
{ theme, children, } | (not declared) | |
input | { theme: TilingTheme; children: React.ReactNode; } |
Returns:
React.ReactElement
useTilingTheme()
functionReads the active theme from context (defaults to the library default).
Signature:
declare function useTilingTheme(): TilingTheme;
Returns:
Commands
The imperative command handle and the typed command union you dispatch from your own controls.
TilingCommand
typeThe public, typed, dispatch-style command set — the public API's imperative entry point (the Hyprland dispatch analog). Every internal renderer action is enumerated here so an embedding app can invoke tiler behavior programmatically (via the TilingCommandHandle) and so a keyboard binding can target any of them (TilingKeyBinding).
Context-dependent commands carry an OPTIONAL leafId; when omitted the renderer resolves it against the CURRENT focused leaf at dispatch time (the "act on the focused pane" ergonomic). Commands that need explicit operands (swap / insert / split ops) require them.
Signature:
type TilingCommand = {
kind: "focus-pane";
leafId: string;
} | {
kind: "focus-direction";
direction: TilingFocusDirection;
} | {
kind: "focus-cycle";
direction: TilingPaneCycleDirection;
} | {
kind: "focus-jump";
paneNumber: number;
} | {
kind: "focus-current-or-last";
} | {
kind: "toggle-maximize";
leafId?: string;
} | {
kind: "maximize";
leafId?: string;
} | {
kind: "restore";
} | {
kind: "enter-move-mode";
leafId?: string;
} | {
kind: "move-aim";
direction: TilingFocusDirection;
} | {
kind: "commit-move-mode";
} | {
kind: "cancel-move-mode";
} | {
kind: "swap-panes";
sourceLeafId: string;
targetLeafId: string;
} | {
kind: "insert-adjacent";
sourceLeafId: string;
targetLeafId: string;
placement: TilingMovePlacement;
} | {
kind: "acquire-space";
leafId?: string;
direction: TilingFocusDirection;
} | {
kind: "set-sizing";
leafId?: string;
mode: TilingTitleBarSizingMode;
} | {
kind: "toggle-collapse";
leafId?: string;
} | {
kind: "set-collapsed";
leafId?: string;
collapsed: boolean;
} | {
kind: "set-split-ratio";
splitId: string;
ratio: number;
} | {
kind: "toggle-split-axis";
splitId: string;
} | {
kind: "set-layout-mode";
splitId?: string;
mode: TilingLayoutMode;
} | {
kind: "cycle-layout-mode";
splitId?: string;
} | {
kind: "set-master-count";
splitId?: string;
count: number;
} | {
kind: "adjust-master-count";
splitId?: string;
delta: number;
} | {
kind: "set-master-orientation";
splitId?: string;
orientation: TilingMasterOrientation;
} | {
kind: "cycle-master-orientation";
splitId?: string;
} | {
kind: "adjust-master-ratio";
splitId?: string;
delta: number;
} | {
kind: "group-leaves";
leafIds: ReadonlyArray<string>;
hostLeafId?: string;
} | {
kind: "toggle-group";
leafId?: string;
} | {
kind: "ungroup";
groupId?: string;
} | {
kind: "add-to-group";
groupId: string;
sourceLeafId: string;
} | {
kind: "remove-from-group";
groupId: string;
memberId: string;
} | {
kind: "group-tab-cycle";
groupId?: string;
direction: TilingPaneCycleDirection;
} | {
kind: "group-tab-jump";
groupId?: string;
memberNumber: number;
} | {
kind: "switch-workspace";
workspaceId: string;
}
/**
* Switch to the 1-based n-th workspace in tab order (`Alt+1` → `index: 1`).
* Bindable without a set at key-binding construction time; the wrapper
* resolves `index` against the live `workspaces` array at dispatch.
*/
| {
kind: "switch-workspace";
index: number;
} | {
kind: "cycle-workspace";
direction: TilingPaneCycleDirection;
} | {
kind: "move-leaf-to-workspace";
leafId?: string;
workspaceId: string;
placement?: TilingWorkspacePlacement;
follow?: boolean;
}
/**
* Move the focused (or explicit) leaf to the previous / next workspace in
* tab order. Neighbour is resolved at dispatch so the binding stays valid
* after `activeId` changes (unlike a bind-time `workspaceId`).
*/
| {
kind: "move-leaf-to-workspace";
leafId?: string;
direction: TilingPaneCycleDirection;
placement?: TilingWorkspacePlacement;
follow?: boolean;
} | {
kind: "reveal-tile";
tileId: string;
}
/**
* Replace one workspace's `layout` (and `name`) from
* {@link TilingRendererWorkspaceSetProps.workspaceDefaults}. `workspaceId`
* omitted → the active workspace. `activeId` is unchanged (no switch
* transition). No-op without `workspaceDefaults`. Host opt-in — no default
* key binding.
*/
| {
kind: "reset-workspace";
workspaceId?: string;
}
/**
* Replace the whole set from
* {@link TilingRendererWorkspaceSetProps.workspaceDefaults}, keeping the
* current `activeId` when that id exists in the seed. No-op without
* `workspaceDefaults`. Host opt-in — no default key binding.
*/
| {
kind: "reset-workspaces";
};
References: TilingFocusDirection, TilingPaneCycleDirection, TilingMovePlacement, TilingTitleBarSizingMode, TilingLayoutMode, TilingMasterOrientation, TilingWorkspacePlacement
TilingCommandHandle
interfaceThe imperative handle exposed via ref on TilingRenderer. A consumer holds the ref and drives the tiler programmatically — dispatch routes a command through the SAME internal router the keyboard layer uses, so a disabled-capability command is a safe no-op (it never mutates the layout).
Signature:
interface TilingCommandHandle
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
(command: TilingCommand) => void | Route a TilingCommand through the renderer's command router. |
Advanced helpers
Power-user helpers and the resolved / capability / keymap / debug type surface — isCommandEnabled, the interaction-capability shapes, and the query utilities you reach for only when building custom command bars, keyboard layers, and observability. Each carries a consumer-usage @example in its hover-docs.
activeWorkspace()
functionThe workspace set.activeId names, or null when it names none (an integrity issue).
Signature:
declare function activeWorkspace(set: TilingWorkspaceSet): TilingWorkspace | null;
Parameters
Parameter | Type | Description |
|---|---|---|
set |
Returns:
TilingWorkspace | null
adoptIncomingWorkspaceTrees()
functionAdopt a host value. Drop local / pending trees for workspaces whose incoming layout differs from last committed (the host is authoritative). Keep overlays whose incoming layout still matches last committed (host echo of our last persist). Optionally drop entries for workspaces the host no longer has.
Signature:
declare function adoptIncomingWorkspaceTrees(input: AdoptIncomingWorkspaceTreesInput): AdoptIncomingWorkspaceTreesResult;
Parameters
Parameter | Type | Description |
|---|---|---|
input |
Returns:
AdoptIncomingWorkspaceTreesInput
interfaceInput to adoptIncomingWorkspaceTrees().
Signature:
interface AdoptIncomingWorkspaceTreesInput
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| boolean | (Optional) When | |
| The host's current set. | ||
| Per-workspace layouts of the last committed (or adopted) set. | ||
| Local tree overlays (edited since last persist / adopt). | ||
| Trees awaiting a debounced |
AdoptIncomingWorkspaceTreesResult
interfaceResult of adoptIncomingWorkspaceTrees().
Signature:
interface AdoptIncomingWorkspaceTreesResult
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| boolean |
| |
| Map<TilingWorkspaceId, TilingLayoutNode | null> | Last-committed layouts after adopting the host's trees. | |
| Map<TilingWorkspaceId, TilingLayoutNode | null> | Local overlays after dropping superseded / released trees. | |
| boolean |
| |
| Map<TilingWorkspaceId, TilingLayoutNode | null> | Pending map after dropping superseded trees. |
canElementScrollFurther()
functionWhether ONE element can still scroll along axis in direction: its computed overflow-{x,y} lets the user scroll, its scroll extent exceeds its client extent, and its scroll offset is not already at the bound the direction heads to. On X the RTL sign convention is honoured (a direction: rtl scroller reports scrollLeft in [-(scrollWidth − clientWidth), 0], so "visually right" is scrollLeft → 0).
Signature:
declare function canElementScrollFurther(element: HTMLElement, axis: ScrollChainAxis, direction: ScrollChainDirection): boolean;
Parameters
Parameter | Type | Description |
|---|---|---|
element | HTMLElement | |
axis | ||
direction |
Returns:
boolean
CapturedViewClone
interfaceResult of captureViewClone().
Signature:
interface CapturedViewClone
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| boolean |
| |
| HTMLElement | Inert clone of |
captureViewClone()
functionFreeze root (typically the set-mode viewport) as an inert overlay clone.
Steps: cloneNode(true) → drop existing transition layers → copy canvas bitmaps → strip id + hit-test data-* → pointer-events: none, aria-hidden, inert. A failed / oversized canvas sets degraded.
Signature:
declare function captureViewClone(root: HTMLElement): CapturedViewClone;
Parameters
Parameter | Type | Description |
|---|---|---|
root | HTMLElement |
Returns:
clampUnitProgress()
functionClamp a progress sample into unit [0, 1]. NaN collapses to 0.
Signature:
declare function clampUnitProgress(progress: number): number;
Parameters
Parameter | Type | Description |
|---|---|---|
progress | number |
Returns:
number
classifyIncomingWorkspaceSet()
functionClassify an incoming host value against the last committed set. See IncomingWorkspaceSetKind.
Signature:
declare function classifyIncomingWorkspaceSet(incoming: TilingWorkspaceSet, lastCommitted: TilingWorkspaceSet, ahead: boolean): IncomingWorkspaceSetKind;
Parameters
Parameter | Type | Description |
|---|---|---|
incoming | ||
lastCommitted | ||
ahead | boolean |
Returns:
clientRectContains()
functionWhether point lies inside rect (edges inclusive).
Signature:
declare function clientRectContains(rect: TilingClientRect, point: TilingClientPoint): boolean;
Parameters
Parameter | Type | Description |
|---|---|---|
rect | ||
point |
Returns:
boolean
createDomScrollChainPort()
functionDefault DOM-backed ScrollChainPort: walks from element up through its ancestors to the viewport root (inclusive) and reports true as soon as one of them canElementScrollFurther(). Text nodes / non-HTML targets start from their parent element; a target outside the root walks to the document root. false when the root is unmounted or element is null.
Signature:
declare function createDomScrollChainPort(rootRef: ElementRef<HTMLElement | null>): ScrollChainPort<EventTarget>;
Parameters
Parameter | Type | Description |
|---|---|---|
rootRef | ElementRef<HTMLElement | null> |
Returns:
ScrollChainPort<EventTarget>
createDomWheelTouchPort()
functionDefault DOM-backed WheelTouchInputPort over one viewport root element.
wheel: awheellistener onelement. Registeredpassive: trueat rest so page scrolling is never blocked;setTracking(true)re-registers itpassive: falseand every sample then callspreventDefault()so the browser's own horizontal handling (history back-swipe, ancestor scroll) stays out of a gesture the FSM owns.setTracking(false)restores the passive listener. -touch:pointerdown(pointerType === "touch") onelementstarts a gesture;pointermove/pointerup/pointercancelonwindowfollow that onepointerIduntil it ends. The scroll chain is judged from the TOUCH-START target for the finger's current direction on every move.
Samples carry element.clientWidth as widthPx, or null when the element has no laid-out width (so the FSM keeps its context / config width).
Signature:
declare function createDomWheelTouchPort(element: HTMLElement, options: DomWheelTouchPortOptions): WheelTouchInputPort;
Parameters
Parameter | Type | Description |
|---|---|---|
element | HTMLElement | |
options |
Returns:
createPersistedTilingLayout()
functionBuild a PersistedTilingLayout — a thin persist adapter over the engine's assertLayoutIntegrity / repairLayout. The storage key, expected tile ids, and fallback tree stay host-owned; repair stays engine-owned.
Signature:
declare function createPersistedTilingLayout(options: CreatePersistedTilingLayoutOptions): PersistedTilingLayout;
Parameters
Parameter | Type | Description |
|---|---|---|
options |
Returns:
Example
const persisted = createPersistedTilingLayout({
storageKey: "annotate.tilingLayout",
expectedTileIds: ["cases", "document", "review"],
fallbackLayout: DEFAULT_LAYOUT,
config: TILING_CONFIG,
});
const [layout, setLayout] = useState(() => persisted.load());
// onLayoutChange: setLayout(persisted.commit(next));
CreatePersistedTilingLayoutOptions
interfaceOptions for createPersistedTilingLayout().
Signature:
interface CreatePersistedTilingLayoutOptions
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
Gap / min-pane / handle geometry, forwarded to the repair pass. | |||
ReadonlyArray<string> | Host tile ids that must appear exactly once across the persisted tree. Forwarded verbatim to | ||
Host-authored replacement tree used when the persisted value is absent, unparseable, or fails hard integrity ( | |||
(Optional) Resolves the current container geometry for the repair pass. Called on every | |||
(Optional) Key/value store to persist into. Defaults to | |||
string | Storage key the serialized layout tree is read from / written to. |
createWorkspace()
functionAppend (or insert at at) a workspace. Unchanged when the id is empty or taken, the name is invalid, or the set already holds TILING_WORKSPACES_MAX workspaces.
Signature:
declare function createWorkspace(set: TilingWorkspaceSet, input: CreateWorkspaceInput): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
input |
Returns:
CreateWorkspaceInput
interfaceInput to createWorkspace().
Signature:
interface CreateWorkspaceInput
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| boolean | (Optional) Make the new workspace active. Default | |
| number | (Optional) Insertion index in tab order. Default: append. Clamped to | |
| Unique within the set. Refused (unchanged set) when empty or already present. | ||
| TilingLayoutNode | null | (Optional) Initial tree. Default | |
| string | Display name; refused when it normalises to |
cycleWorkspace()
functionActivate the next / previous workspace in tab order (wraps). Unchanged with one workspace.
Signature:
declare function cycleWorkspace(set: TilingWorkspaceSet, direction: TilingPaneCycleDirection): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
direction |
Returns:
DEFAULT_DRAG_ANIMATION_SPEED_PERCENT
variableDefault drag animation speed percent (100 = the baseline duration).
Signature:
DEFAULT_DRAG_ANIMATION_SPEED_PERCENT: number
DEFAULT_DRAG_HOP_EASING
variableThe default ghost hop / pickup / hop-out timing function (a snappy decel).
Signature:
DEFAULT_DRAG_HOP_EASING: string
DEFAULT_DRAG_REFLOW_EASING
variableThe default survivor-reflow settle timing function. Equals the hop curve so the ghost and the survivors read as one coordinated motion (the renderer's dragReflowEasing prop falls back to the resolved dragHopEasing when undefined, so this default only applies when BOTH are unset).
Signature:
DEFAULT_DRAG_REFLOW_EASING: string
DEFAULT_TILE_ACCENT
variableFirst palette member — the fallback when a tile omits accent.
Signature:
DEFAULT_TILE_ACCENT: TilingTileAccent
DEFAULT_WORKSPACE_TRANSITION_CONFIG
variableDefault config: no motion until a host opts in (interaction.workspaces.switch.transition, follow-up wiring).
Signature:
DEFAULT_WORKSPACE_TRANSITION_CONFIG: TilingWorkspaceTransitionConfig
DEFAULT_WORKSPACE_TRANSITION_DURATION_MS
variableDefault duration for a timed workspace-switch settle.
Signature:
DEFAULT_WORKSPACE_TRANSITION_DURATION_MS: number
DEFAULT_WORKSPACE_TRANSITION_EASING
variableDefault easing — the same snappy decel as DEFAULT_DRAG_HOP_EASING so workspace switches share the drag-chrome motion language.
Signature:
DEFAULT_WORKSPACE_TRANSITION_EASING: string
deleteWorkspace()
functionDelete a workspace. The LAST workspace is never deleted (refused, unchanged set). Leaves shown only there leave the set (reported as removedTileIds); leaves also shown elsewhere keep their other seats. When the deleted workspace was active, the previous one in tab order becomes active, else the next.
Signature:
declare function deleteWorkspace(set: TilingWorkspaceSet, id: TilingWorkspaceId): TilingDeleteWorkspaceResult;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
id |
Returns:
diffWorkspaceTreeLayouts()
functionWorkspaces in next whose layout reference differs from the same id in current. New workspaces (no prior id) are ignored — membership changes are lifecycle, not tree edits.
Signature:
declare function diffWorkspaceTreeLayouts(current: TilingWorkspaceSet, next: TilingWorkspaceSet): ReadonlyArray<TilingWorkspaceTreeDiff>;
Parameters
Parameter | Type | Description |
|---|---|---|
current | ||
next |
Returns:
ReadonlyArray<TilingWorkspaceTreeDiff>
DomWheelTouchPortOptions
interfaceOptions of createDomWheelTouchPort().
Signature:
interface DomWheelTouchPortOptions
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
() => number | (Optional) Monotonic clock (ms); default | ||
ScrollChainPort<EventTarget> | Scroll-chain oracle for | ||
boolean | Attach the touch ( | ||
boolean | Attach the |
DRAG_ANIMATION_SPEED_MAX_PERCENT
variableFastest (ceiling) drag animation speed percent the slider + clamp allow.
Signature:
DRAG_ANIMATION_SPEED_MAX_PERCENT: number
DRAG_ANIMATION_SPEED_MIN_PERCENT
variableSlowest (floor) drag animation speed percent the slider + clamp allow.
Signature:
DRAG_ANIMATION_SPEED_MIN_PERCENT: number
findWorkspaceById()
functionThe workspace with id, or null when absent.
Signature:
declare function findWorkspaceById(set: TilingWorkspaceSet, id: TilingWorkspaceId): TilingWorkspace | null;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
id |
Returns:
TilingWorkspace | null
foldPendingTrees()
functionReplace matching workspaces' trees with the pending overlay. Same set reference when pending is empty or every overlay is already the seated tree (setWorkspaceLayout same-ref discipline).
Signature:
declare function foldPendingTrees(set: TilingWorkspaceSet, pending: TilingWorkspaceTreeMap): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
pending |
Returns:
hideFromWorkspace()
functionRemove a leaf's seat from workspace from (DashAI hideItem). When that was its only seat the leaf leaves the set — consult queryWorkspaceSet().workspacesOfLeaf first if the host must keep every item shown somewhere. Unchanged when from does not show the leaf.
Signature:
declare function hideFromWorkspace(set: TilingWorkspaceSet, leafId: string, from: TilingWorkspaceId): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
leafId | string | |
from |
Returns:
hideTileFromWorkspace()
functionRemove a tile's seat from workspace from. orphaned is true when the tile now has no seat anywhere (including when it was already seated nowhere). Unchanged (same set reference) when from does not show the tile.
Signature:
declare function hideTileFromWorkspace(set: TilingWorkspaceSet, tileId: string, from: TilingWorkspaceId): TilingHideTileResult;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
tileId | string | |
from |
Returns:
IncomingWorkspaceSetKind
typeHow an incoming host value relates to the last set the controller committed.
echo— same workspaces, names, layout refs, andactiveIdas last committed (the host applied our persist). Keep local overlays that still differ; release ones the host has caught up with. -stale— we are ahead (a commit the host has not applied yet): shared workspace layouts still match, but membership / names /activeIddo not. Keep the optimistic set. -authoritative— at least one incoming layout differs from last committed, or we are not ahead and the set is not an echo. The host document wins; drop local trees for those workspaces.
Signature:
type IncomingWorkspaceSetKind = "echo" | "stale" | "authoritative";
isCommandEnabled()
functionWhether command would do anything given the current capability gates: true when the command's required capability is enabled (or it requires none). A keyboard binding to a disabled-capability command stays browser-graceful (the caller does not preventDefault); an imperative dispatch of one is a no-op.
Signature:
declare function isCommandEnabled(command: TilingCommand, gates: TilingCommandGates): boolean;
Parameters
Parameter | Type | Description |
|---|---|---|
command | ||
gates |
Returns:
boolean
Example
Build your own command bar / keyboard shortcut that only fires (and only renders its button) when the target command is actually enabled — the gate the renderer itself uses for its shortcut chips:
import {
resolveInteractionCapabilities,
isCommandEnabled,
type TilingCommand,
type TilingCommandGates,
type TilingCommandHandle,
type TilingInteractionCapabilities,
} from "@n-uf/hypr-tiling";
function gatesFor(interaction?: TilingInteractionCapabilities): TilingCommandGates {
const caps = resolveInteractionCapabilities(interaction);
return {
maximizeEnabled: caps.maximize.enable,
paneSwitchingEnabled: caps.paneSwitching.enable,
focusEnabled: caps.focus,
rearrangeEnabled: caps.rearrange,
sizingEnabled: caps.paneTitleBarControls.sizing,
acquireSpaceEnabled: caps.paneTitleBarControls.acquireSpace,
collapseEnabled: caps.paneTitleBarControls.collapse,
resizeEnabled: caps.resize !== "none",
layoutEnabled: caps.masterLayout,
groupingEnabled: caps.grouping.enable,
workspacesEnabled: caps.workspaces.enable,
};
}
function MaximizeButton(props: {
handle: React.RefObject<TilingCommandHandle | null>;
interaction?: TilingInteractionCapabilities;
}) {
const command: TilingCommand = { kind: "toggle-maximize" };
if (!isCommandEnabled(command, gatesFor(props.interaction))) {
return null; // maximize is disabled — don't render a dead control
}
return <button onClick={() => props.handle.current?.dispatch(command)}>Maximize</button>;
}
isMultiSelectModifierActive()
functiontrue when the event carries the multi-select modifier — Alt/Opt (altKey), the single chord both the header toggle and the Alt+G group key key off.
Signature:
declare function isMultiSelectModifierActive(event: MultiSelectModifierState): boolean;
Parameters
Parameter | Type | Description |
|---|---|---|
event |
Returns:
boolean
Example
In a custom renderTile, add the clicked pane to the multi-selection only when the platform multi-select modifier is held (a plain click falls through to focus):
import { isMultiSelectModifierActive } from "@n-uf/hypr-tiling";
<header
onClick={(event) => {
if (args.isMultiSelectGroupingEnabled && isMultiSelectModifierActive(event)) {
event.preventDefault();
args.onToggleMultiSelect();
}
}}
/>
isWorkspaceNavigationCommand()
functionWhether command is a workspace-set kind (switch-workspace, cycle-workspace, move-leaf-to-workspace, reveal-tile, reset-workspace, reset-workspaces). Used by the set-mode wrapper to intercept those kinds before they reach the single-layout router.
Signature:
declare function isWorkspaceNavigationCommand(command: TilingCommand): command is Extract<TilingCommand, {
kind: "switch-workspace" | "cycle-workspace" | "move-leaf-to-workspace" | "reveal-tile" | "reset-workspace" | "reset-workspaces";
}>;
Parameters
Parameter | Type | Description |
|---|---|---|
command |
Returns:
command is Extract<TilingCommand, { kind: "switch-workspace" | "cycle-workspace" | "move-leaf-to-workspace" | "reveal-tile" | "reset-workspace" | "reset-workspaces"; }>
moveLeafToWorkspace()
functionMove a leaf to workspace to at placement: it leaves EVERY workspace that shows it and is seated once in to (the drag-onto-a-tab commit; DashAI moveItem). The node payload (sizing, collapsed, minBBoxPx) travels from the active workspace's seat when it has one, else the first seat in tab order; the collapse pin is re-asserted on the destination axis by insertLeafInto. When to is the leaf's only workspace this is a same-tree re-seat (extract + insert). Unchanged when the leaf or to is unknown. A source tree whose only leaf moved out becomes null.
Signature:
declare function moveLeafToWorkspace(set: TilingWorkspaceSet, leafId: string, to: TilingWorkspaceId, placement?: TilingWorkspacePlacement): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
leafId | string | |
to | ||
placement | (Optional) |
Returns:
moveTileToWorkspace()
functionMove a tile to workspace to at placement: it leaves EVERY workspace that shows it and is seated once in to. Resolves the leaf through queryWorkspaceSet().workspacesOfTile (one leaf id ↔ one tile id). When the tile is seated nowhere it is seated only if options.leaf.id is given (a fresh { kind: "leaf", id, tileId }); otherwise the set is unchanged. Unchanged when to is unknown, the minted leaf id is empty or already used, or the tile is unknown and no mint id is given.
Signature:
declare function moveTileToWorkspace(set: TilingWorkspaceSet, tileId: string, to: TilingWorkspaceId, placement?: TilingWorkspacePlacement, options?: MoveTileToWorkspaceOptions): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
tileId | string | |
to | ||
placement | (Optional) | |
options | (Optional) |
Returns:
MoveTileToWorkspaceOptions
interfaceOptions for moveTileToWorkspace(). The engine never invents leaf ids: a tile seated nowhere is seated only when leaf.id is given.
Signature:
interface MoveTileToWorkspaceOptions
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| { readonly id: string; } | (Optional) Leaf id to mint when |
MultiSelectModifierState
interfaceThe modifier-key subset of a pointer/mouse event that discriminates a multi-select click from a plain click. The multi-select / grouping modifier is unified on Alt/Opt across every surface (header click + the Alt+G key), chosen for minimal cross-browser interference: altKey = Opt on macOS, Alt on Windows/Linux.
Signature:
interface MultiSelectModifierState
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| boolean | Whether Alt/Opt was held (the multi-select discriminator). |
normaliseWheelDelta()
functiondeltaX / deltaY in CSS px whatever the event's deltaMode.
Signature:
declare function normaliseWheelDelta(event: WheelEvent, clientWidth: number): {
dx: number;
dy: number;
};
Parameters
Parameter | Type | Description |
|---|---|---|
event | WheelEvent | |
clientWidth | number |
Returns:
{ dx: number; dy: number; }
normalizeWorkspaceName()
functionTrim a workspace name and validate it against TILING_WORKSPACE_NAME_MAX_CHARS. null when the trimmed name is empty or too long — the ops refuse (return the set unchanged) on null.
Signature:
declare function normalizeWorkspaceName(name: string): string | null;
Parameters
Parameter | Type | Description |
|---|---|---|
name | string |
Returns:
string | null
PersistedTilingLayout
interfacePersisted-layout adapter returned by createPersistedTilingLayout(). Every method returns the tree the host should render.
Signature:
interface PersistedTilingLayout
Methods
Method | Description |
|---|---|
Validate + heal | |
Read + validate + heal the persisted tree. Returns the healed tree on a clean hit, | |
Persist and return the |
queryWorkspaceSet()
functionBuild a read-only TilingWorkspaceSetQuery over set.
Signature:
declare function queryWorkspaceSet(set: TilingWorkspaceSet): TilingWorkspaceSetQuery;
Parameters
Parameter | Type | Description |
|---|---|---|
set |
Returns:
removeTile()
functionRemove every seat of tileId. Unchanged when the tile is seated nowhere.
Signature:
declare function removeTile(set: TilingWorkspaceSet, tileId: string): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
tileId | string |
Returns:
renameWorkspace()
functionRename a workspace. Unchanged when the id is unknown, the name is invalid, or already equal.
Signature:
declare function renameWorkspace(set: TilingWorkspaceSet, id: TilingWorkspaceId, name: string): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
id | ||
name | string |
Returns:
repairWorkspaceSet()
functionDeterministically repair a set so workspaceSetIssues() (with the same options) is empty afterwards. Rules run in the order of TilingWorkspaceSetRepairReason; every fix is reported. A sound set is returned by reference with no reasons. Pure.
Signature:
declare function repairWorkspaceSet(set: TilingWorkspaceSet, options?: RepairWorkspaceSetOptions): TilingWorkspaceSetRepairResult;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
options | (Optional) |
Returns:
RepairWorkspaceSetOptions
interfaceOptions for repairWorkspaceSet().
Signature:
interface RepairWorkspaceSetOptions extends WorkspaceSetIntegrityOptions
Extends: WorkspaceSetIntegrityOptions
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| (tileId: string) => string | (Optional) Leaf id for a tile the repair has to seat from scratch (an orphan with no seat anywhere). Default `` | |
| (Optional) Placement for seated orphans. Default TILING_DEFAULT_WORKSPACE_PLACEMENT. | ||
| (Optional) Where orphan expected tiles are seated. Default: the active workspace. |
resetWorkspaceLayout()
functionReplace ONE workspace's layout and name from the workspace of the same id in defaults. activeId is unchanged. When defaults has no such id, the workspace's layout becomes null and its name is kept. Unchanged (same reference) when the id is unknown or the workspace already matches.
Signature:
declare function resetWorkspaceLayout(set: TilingWorkspaceSet, defaults: TilingWorkspaceSet, workspaceId?: string): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
defaults | ||
workspaceId | string | (Optional) |
Returns:
resetWorkspaceSet()
functionReplace the whole set with defaults, keeping set.activeId when that id exists in defaults (otherwise defaults.activeId). Same reference when the result is already structurally equal to set.
Signature:
declare function resetWorkspaceSet(set: TilingWorkspaceSet, defaults: TilingWorkspaceSet): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
defaults |
Returns:
resolveDragChrome()
functionResolve a theme's drag-state chrome: every theme.dragChrome token a theme omits is filled from the PANE-SHELL-INHERITING default. The default drags with the theme's own resting chrome — the seat wears paneShell.surface (same radius / border / background / shadow as the at-rest pane), the seat frame reuses resolveFocusFrame, the ghost wrapper carries only a small neutral elevation + opacity delta, and no drop-target highlight is painted (focus follows the dragged pane). A theme that passes no dragChrome at all therefore never shows a look it did not author. Pure + referentially cheap (no memo needed; callers may still useMemo on theme). Tokens are Tailwind-JIT literals, not portal-scoped; a host that paints the ghost with inherited CSS variables must still pass TilingRendererProps.overlayPortalContainer.
Signature:
declare function resolveDragChrome(theme: TilingTheme): TilingThemeDragChromeTokens;
Parameters
Parameter | Type | Description |
|---|---|---|
theme |
Returns:
ResolvedTilingDragRecoveryCapability
interfaceFully-resolved drag-recovery configuration (no optional fields).
Signature:
interface ResolvedTilingDragRecoveryCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Resolved self-healing recovery enable flag. | ||
number | Resolved rAF-fallback slack (ms). | ||
number | Resolved idle-watchdog deadline (ms). | ||
number | Resolved transition-completion slack (ms). |
ResolvedTilingDropHitZoneGeometryCapability
interfaceFully-resolved drop hit-zone geometry (no optional fields). centerRatio is retained as the symmetric/representative value (equals centerRatioX when no per-axis override diverges them) for telemetry + the single-knob display; centerRatioX / centerRatioY are the per-axis values the resolver actually consumes.
Signature:
interface ResolvedTilingDropHitZoneGeometryCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
number | Resolved floor (CSS px) for the center rectangle extent. | ||
number | Resolved symmetric/representative center swap-zone fraction. | ||
number | Resolved per-axis HORIZONTAL swap-zone fraction the resolver consumes. | ||
number | Resolved per-axis VERTICAL swap-zone fraction the resolver consumes. | ||
number | Resolved boundary stickiness (pane-local CSS px). |
ResolvedTilingGroupingCapability
interfaceResolved group / tabbed-stacking capability (no optional fields).
Signature:
interface ResolvedTilingGroupingCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether group / tabbed-stacking is enabled. | ||
Resolved built-in strip options (present even when the strip is hidden). | |||
boolean | Whether the per-group tab strip renders. Default |
ResolvedTilingGroupTabStripOptions
interfaceFully resolved built-in strip options. renderTabLabel stays optional.
Signature:
interface ResolvedTilingGroupTabStripOptions
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
number | Strip height in CSS pixels. | ||
"top" | "bottom" |
| ||
(member: TilingGroupTabMember) => React.ReactNode | (Optional) Custom tab label, when the host passed one. | ||
boolean | Whether the eject control renders. | ||
boolean | Whether the ungroup control renders. | ||
Resolved CSS-value tokens. |
ResolvedTilingGroupTabStripTheme
interfaceFully resolved TilingGroupTabStripTheme (every token set).
Signature:
interface ResolvedTilingGroupTabStripTheme
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
string | Keyboard | ||
string | Strip row background. | ||
string | Strip row hairline border color. | ||
string | Eject / ungroup control color. | ||
string | Eject / ungroup control color on hover. | ||
string | Tab label font family. | ||
string | Tab label font size (CSS length). | ||
string | Gap between pills inside the rail (CSS length). | ||
string | Tab label letter spacing (CSS length). | ||
string | Active pill background when the group strip is the nested tier. | ||
string | Active pill label color when the group strip is the nested tier. | ||
string | Horizontal padding of the strip row (CSS length). | ||
string | Tab (pill) corner radius (CSS length). Rail uses | ||
string | Rounded rail background behind the pills. | ||
string | Rounded rail 1px border color. | ||
string | Active tab background (standalone / pane strip). | ||
string | Active tab label color (standalone / pane strip). | ||
string | Inactive tab background. | ||
string | Inactive tab label color. | ||
string | Inactive tab label color on hover. |
ResolvedTilingInteractionCapabilities
interfaceFully-resolved interaction capabilities (no optional fields). The resolved shape is a structural superset of TilingInteractionCapabilities, so a resolved object can be re-fed to resolveInteractionCapabilities (idempotent) and passed straight back into the interaction prop.
Signature:
interface ResolvedTilingInteractionCapabilities
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether coherent non-intersecting transit is enabled. | ||
boolean | Whether the custom-rendered drag cursor is used during live drag. | ||
Resolved drag feedback mode ( | |||
Resolved drag/transition self-healing recovery configuration. | |||
Resolved adjustable drop hit-zone geometry. | |||
boolean | Whether pane focus selection is enabled. | ||
number | Resolved ghost pickup scale as a percent of the source pane bbox. | ||
Resolved group / tabbed-stacking capability. | |||
Resolved consumer key bindings (the raw registry passed through; an empty binding list + | |||
Resolved keymap (every chord explicit). | |||
boolean | Whether the master/stack layout engine is enabled. | ||
Resolved maximize-to-viewport capability. | |||
Resolved tab-like pane-switching capability. | |||
Resolved per-pane title-bar controls capability. | |||
boolean | Whether drag-to-rearrange is enabled. | ||
Resolved divider resize capability. | |||
boolean | Whether split-divider resize handles are visibly rendered. | ||
Resolved live-drag slot-commitment configuration. | |||
boolean | Whether the single ghost hops into and fills the resolved slot. | ||
Resolved touch-drag hardening configuration. | |||
Resolved workspace-set navigation capability. |
ResolvedTilingKeyBindings
interfaceFully-resolved key-binding registry (no optional fields).
Signature:
interface ResolvedTilingKeyBindings
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
ReadonlyArray<TilingKeyBinding> | Resolved consumer chord→command bindings (empty when none supplied). | ||
boolean | Whether the default keymap bindings are suppressed. |
ResolvedTilingKeyChord
interfaceFully-resolved key chord (every modifier explicit). Matches KeyboardEvent.code.
Signature:
interface ResolvedTilingKeyChord
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether the Alt key must be held. | ||
string | Physical | ||
boolean | Whether the Ctrl key must be held. | ||
boolean | Whether the Meta (Cmd / Win) key must be held. | ||
boolean | Whether the Shift key must be held. |
ResolvedTilingKeyChordModifiers
interfaceFully-resolved jump-to-pane modifier set.
Signature:
interface ResolvedTilingKeyChordModifiers
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether the Alt key must be held. | ||
boolean | Whether the Ctrl key must be held. | ||
boolean | Whether the Meta (Cmd / Win) key must be held. | ||
boolean | Whether the Shift key must be held. |
ResolvedTilingKeymap
interfaceFully-resolved keymap (no optional fields).
Signature:
interface ResolvedTilingKeymap
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
Resolved chord to toggle the focused subtree dwindle ⇄ master. | |||
Resolved chord to cycle the master-area orientation. | |||
Resolved chord to remove one tile from the master area. | |||
Resolved chord to shrink the master-area fraction. | |||
Resolved chord to enter keyboard move mode. | |||
Resolved chord for the MRU focus current-or-last toggle. | |||
Resolved chord to move focus to the neighbor BELOW. | |||
Resolved chord to move focus to the LEFT neighbor. | |||
Resolved chord to move focus to the RIGHT neighbor. | |||
Resolved chord to move focus to the neighbor ABOVE. | |||
Resolved chord to activate the next member tab of the focused group. | |||
Resolved chord to activate the previous member tab of the focused group. | |||
Resolved chord to add one tile to the master area. | |||
Resolved chord to grow the master-area fraction. | |||
Resolved modifier set for the | |||
Resolved chord to cycle to the next pane. | |||
Resolved chord to cycle to the previous pane. | |||
Resolved chord to restore from maximize. | |||
Resolved chord to group/ungroup the focused pane. | |||
Resolved chord to toggle maximize on the focused pane. |
ResolvedTilingMaximizeCapability
interfaceResolved maximize capability (no optional fields).
Signature:
interface ResolvedTilingMaximizeCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether the maximize/restore control + shortcuts are live. | ||
boolean | Whether a maximized group member renders inside its group (strip stays visible) instead of as a bare leaf. |
ResolvedTilingPaneSwitchingCapability
interfaceResolved pane-switching capability (no optional fields).
Signature:
interface ResolvedTilingPaneSwitchingCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether pane switching (tab strip + cycle/jump shortcuts) is live. | ||
boolean | Whether Alt/Opt+click header multi-selection grouping is live. | ||
boolean | Whether the tab strip's pane-content visibility checkbox renders. | ||
boolean | Whether the Cmd+Tab-style switcher overlay renders while cycling. | ||
boolean | "maximized" | Whether the tab strip renders: always, never, or only while maximized. | ||
boolean | Whether double-clicking a tab toggles that pane's maximize state. | ||
Resolved built-in pane tab strip options. |
ResolvedTilingPaneTabStripOptions
interfaceFully resolved pane tab strip options. renderTabLabel stays optional.
Signature:
interface ResolvedTilingPaneTabStripOptions
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
number | Strip height in CSS pixels. | ||
"top" | "bottom" |
| ||
(tab: TilingPaneTab) => React.ReactNode | (Optional) Custom tab label, when the host passed one. | ||
Resolved CSS-value tokens (same vocabulary as the group strip). |
ResolvedTilingPaneTitleBarControlsCapability
interfaceResolved per-pane title-bar control capability (no optional fields).
Signature:
interface ResolvedTilingPaneTitleBarControlsCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether the per-pane directional acquire-space controls render. | ||
boolean | Whether the per-pane collapse-to-titlebar control + collapse commands are live. | ||
boolean | Whether the per-pane FLEX / STATIC H / STATIC W / BOTH sizing control renders. |
ResolvedTilingSlotCommitmentCapability
interfaceFully-resolved slot-commitment configuration (no optional fields).
Signature:
interface ResolvedTilingSlotCommitmentCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
Resolved re-resolution policy after the ghost seats in a slot. | |||
number | Resolved |
ResolvedTilingTouchDragCapability
interfaceFully-resolved touch-drag configuration (no optional fields).
Signature:
interface ResolvedTilingTouchDragCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Resolved touch-drag enable flag. | ||
number | Resolved long-press delay (ms) before a held touch becomes a drag. |
ResolvedTilingWorkspacesCapability
interfaceResolved workspace-set navigation capability (no optional fields).
Signature:
interface ResolvedTilingWorkspacesCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether workspace commands are enabled (still gated off in single-layout mode). | ||
boolean | Default | ||
TilingSpringLoadConfig | null | Spring-loaded tab drop config, or | ||
Gesture switching (both inputs default off). |
ResolvedTilingWorkspaceSwitchCapability
interfaceResolved TilingWorkspaceSwitchCapability (no optional fields).
Signature:
interface ResolvedTilingWorkspaceSwitchCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
The swipe FSM config both inputs run with. | |||
boolean | Touch swipe is on. | ||
The requested switch transition ( | |||
boolean | Wheel swipe is on. |
resolveInteractionCapabilities()
functionSingle defaulting helper for TilingInteractionCapabilities. undefined / null → all enabled; a partial override merges field-by-field over the all-enabled defaults. Uses nullish coalescing so an explicit false (e.g. rearrange: false, maximize.enable: false) is preserved and never overridden by the default. Idempotent: re-resolving a resolved object yields the same result.
Signature:
declare function resolveInteractionCapabilities(capabilities?: TilingInteractionCapabilities | null): ResolvedTilingInteractionCapabilities;
Parameters
Parameter | Type | Description |
|---|---|---|
capabilities | (Optional) |
Returns:
resolveJumpedPaneId()
functionResolve the jump-to-N leaf id (1-based), or null when out of range (no-op).
Signature:
declare function resolveJumpedPaneId(leafIds: ReadonlyArray<string>, paneNumber: number): string | null;
Parameters
Parameter | Type | Description |
|---|---|---|
leafIds | ReadonlyArray<string> | |
paneNumber | number |
Returns:
string | null
Example
Wire your own "jump to pane N" buttons against the leaf order from queryTilingLayout(), dispatching the built-in focus-jump command:
import { queryTilingLayout, resolveJumpedPaneId } from "@n-uf/hypr-tiling";
const { leafIds } = queryTilingLayout(layout);
leafIds.forEach((_, i) => {
const paneNumber = i + 1;
const targetLeafId = resolveJumpedPaneId(leafIds, paneNumber); // null → out of range
// render a button that dispatches { kind: "focus-jump", paneNumber } when targetLeafId != null
});
resolveTransitionMode()
functionResolve the paint mode a stage should actually run.
Reduced motion always wins ("none", instant, no clone). A degraded capture cannot slide, so a requested "slide" falls back to "fade". "none" and "fade" stay as requested when only degraded is set.
Signature:
declare function resolveTransitionMode(requested: TilingWorkspaceTransitionMode, flags: ResolveTransitionModeFlags): TilingWorkspaceTransitionMode;
Parameters
Parameter | Type | Description |
|---|---|---|
requested | ||
flags |
Returns:
ResolveTransitionModeFlags
interfaceInputs that can override a requested paint mode.
Signature:
interface ResolveTransitionModeFlags
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| boolean | Capture marked the clone degraded (tainted canvas | |
| boolean |
|
resolveWorkspaceTabHover()
functionThe workspace-tab hover for point, or null when it is over no tab. The FIRST target (in targets order) containing the point wins, so overlapping rects resolve deterministically. Pure; returns a fresh hover object.
Signature:
declare function resolveWorkspaceTabHover(targets: ReadonlyArray<TilingWorkspaceTabTarget>, point: TilingClientPoint): TilingWorkspaceTabDragHover | null;
Parameters
Parameter | Type | Description |
|---|---|---|
targets | ReadonlyArray<TilingWorkspaceTabTarget> | |
point |
Returns:
TilingWorkspaceTabDragHover | null
resolveWorkspaceTabKey()
functionThe roving-focus keyboard model of a tablist (WAI-ARIA tabs pattern, manual activation). focusedIndex is the tab holding focus, count the number of tabs. Pure; null for keys the strip does not handle so the host can let them propagate.
Signature:
declare function resolveWorkspaceTabKey(key: string, focusedIndex: number, count: number, orientation?: TilingWorkspaceTabsOrientation): TilingWorkspaceTabKeyAction | null;
Parameters
Parameter | Type | Description |
|---|---|---|
key | string | |
focusedIndex | number | |
count | number | |
orientation | (Optional) |
Returns:
TilingWorkspaceTabKeyAction | null
resolveWorkspaceTransition()
functionResolve a theme's workspace-switch motion tokens. Omitted fields fall back to 200 ms and the drag-hop easing so a theme that passes nothing never invents a look it did not author. Pure.
Signature:
declare function resolveWorkspaceTransition(theme: TilingTheme): TilingThemeWorkspaceTransitionTokens;
Parameters
Parameter | Type | Description |
|---|---|---|
theme |
Returns:
revealTile()
functionReveal a tile: pick prefer when that workspace shows it, else the first workspace in tab order that does. Switch activeId if needed (changed includes "workspace"); if the leaf is a group member that is not the group's activeMemberId, make it active (changed includes "tab"). Returns null when the tile is seated nowhere. Same set reference when changed is "none".
Signature:
declare function revealTile(set: TilingWorkspaceSet, tileId: string, prefer?: TilingWorkspaceId): TilingRevealTileResult | null;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
tileId | string | |
prefer | (Optional) |
Returns:
TilingRevealTileResult | null
sampleTransitionEase()
functionSample the timed settle curve at t in [0, 1]. Ease-out cubic so the JS requestAnimationFrame path reads as a snappy decel without parsing a CSS <easing-function> string.
Signature:
declare function sampleTransitionEase(t: number): number;
Parameters
Parameter | Type | Description |
|---|---|---|
t | number |
Returns:
number
ScrollChainAxis
typeThe axis a scroll-chain query asks about.
Signature:
type ScrollChainAxis = "x" | "y";
ScrollChainDirection
typeScroll direction along an axis: 1 towards the end (right / down), -1 towards the start.
Signature:
type ScrollChainDirection = -1 | 1;
ScrollChainPort
interfaceRead-only capability: can any scroll container from element up to (and including) the viewport root still scroll along axis in direction? The FSM refuses to arm while true — the inner content owns that wheel / pan. element is opaque to the engine (the DOM adapter narrows it).
Signature:
interface ScrollChainPort<TElement = unknown>
Methods
Method | Description |
|---|---|
|
setWorkspaceLayout()
functionReplace ONE workspace's tree — the in-tree commit path the renderer uses in set mode (a drag / resize / collapse edit of the active tree). Only that workspace changes; other seats are untouched (a cross-tree move is moveLeafToWorkspace()). Unchanged when the id is unknown, the tree is the same reference, or a non-null tree fails isStructurallyValidLayout.
Signature:
declare function setWorkspaceLayout(set: TilingWorkspaceSet, id: TilingWorkspaceId, layout: TilingLayoutNode | null): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
id | ||
layout | TilingLayoutNode | null |
Returns:
showInWorkspace()
functionSeat a leaf that is already in the set in ANOTHER workspace too (shown in both; DashAI showItem). The new seat is a fresh { id, tileId } node — sizing / collapsed are per workspace. Unchanged when the leaf is unknown, to is unknown, or to already shows it.
Signature:
declare function showInWorkspace(set: TilingWorkspaceSet, leafId: string, to: TilingWorkspaceId, placement?: TilingWorkspacePlacement): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
leafId | string | |
to | ||
placement | (Optional) |
Returns:
showTileInWorkspace()
functionSeat a tile that is already in the set in ANOTHER workspace too (shown in both). Unchanged when the tile is unknown, to is unknown, or to already shows it. Never mints a leaf id — a tile seated nowhere stays seated nowhere.
Signature:
declare function showTileInWorkspace(set: TilingWorkspaceSet, tileId: string, to: TilingWorkspaceId, placement?: TilingWorkspacePlacement): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
tileId | string | |
to | ||
placement | (Optional) |
Returns:
switchWorkspace()
functionMake id the active workspace. Unchanged when unknown or already active.
Signature:
declare function switchWorkspace(set: TilingWorkspaceSet, id: TilingWorkspaceId): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
id |
Returns:
TILING_ACCENT_HUES
variableThe hue atoms for every accent. Keyed by the closed TilingTileAccent union so the compiler enforces full coverage. Theme-independent: themes choose which atoms to apply.
Signature:
TILING_ACCENT_HUES: Record<TilingTileAccent, TilingAccentHue>
TILING_DASHBOARD_PRESET
variableInteraction preset for static (config-driven) product dashboards: only height dividers resize (resize: "vertical"), and every interactive surface that assumes a rearrangeable/maximizable lab layout is off (no drag-rearrange, no pane focus selection, no maximize, no pane switching / tab strip). Dashboards pass this instead of hand-repeating the disable set.
Per-pane title-bar controls (sizing + acquire-space) are OFF here too: dashboards ship a curated layout with PRE-CONFIGURED static panes and are resize-only by intent, so letting an end-user re-pin a pane's bbox or have one pane absorb its siblings' space would fight the authored composition. End-user per-pane sizing belongs to interactive workspaces (the all-enabled default), not config-driven dashboards.
Signature:
TILING_DASHBOARD_PRESET: TilingInteractionCapabilities
TILING_DEFAULT_COLLAPSED_EXTENT_PX
variableDefault collapsed titlebar-only extent (CSS px) when TilingLayoutConfig.collapsedExtentPx is undefined — sized to the built-in pane header height so a collapsed pane shows just its title bar.
Signature:
TILING_DEFAULT_COLLAPSED_EXTENT_PX: number
TILING_DEFAULT_WORKSPACE_PLACEMENT
variableDefault placement when a leaf enters a tree without a host placement: a new root split, leaf last.
Signature:
TILING_DEFAULT_WORKSPACE_PLACEMENT: TilingWorkspacePlacement
TILING_INTERACTION_CAPABILITY_DEFAULTS
variableAll-enabled defaults. An undefined capability config (or any undefined field) resolves to these via resolveInteractionCapabilities.
The lone opt-IN exception is paneSwitching.showContentToggle: it defaults to false. That checkbox (a group tab strip's "show pane body" toggle) is a development / demo affordance — a consumer app renders its own pane content and never wants an end-user control that blanks it. Suppressed by default, the initial pane-content-visible flag pins ON (see resolveInitialPaneContentVisible), so panes paint their content at rest with no wiring. A tooling surface that genuinely wants the toggle (e.g. a tooling surface) opts back in with paneSwitching: { showContentToggle: true }.
Signature:
TILING_INTERACTION_CAPABILITY_DEFAULTS: ResolvedTilingInteractionCapabilities
TILING_MAIN_WORKSPACE_ID
variableId of the single workspace workspaceSetOfLayout mints for a one-tree host.
Signature:
TILING_MAIN_WORKSPACE_ID: TilingWorkspaceId
TILING_MAIN_WORKSPACE_NAME
variableName of the single workspace workspaceSetOfLayout mints for a one-tree host.
Signature:
TILING_MAIN_WORKSPACE_NAME: string
TILING_PANE_TAB_STRIP_THEME_DEFAULTS
variableDefault CSS tokens for the top-level pane tab strip: same row as the group strip, with a solid active pill. Hosts override any subset via paneSwitching.tabStrip.theme.
Signature:
TILING_PANE_TAB_STRIP_THEME_DEFAULTS: ResolvedTilingGroupTabStripTheme
TILING_SPRING_LOAD_DEFAULTS
variableTILING_THEME_REGISTRY
variableBuilt-in theme registry, keyed by the closed TilingThemeId union. Adding a member to TilingThemeId forces a new entry here (the Record fails to compile until filled in).
Signature:
TILING_THEME_REGISTRY: Record<TilingThemeId, TilingTheme>
TILING_TILE_ACCENT_SWATCHES
variablePicker-ready metadata (accent + label + swatch class) for every accent.
Signature:
TILING_TILE_ACCENT_SWATCHES: readonly TilingTileAccentSwatch[]
TILING_TILE_ACCENTS
variableOrdered, enumerable accent palette — the generic capability a consumer iterates to build an accent picker.
Signature:
TILING_TILE_ACCENTS: readonly TilingTileAccent[]
TILING_WORKSPACE_NAME_MAX_CHARS
variableMaximum workspace name length (after trim) createWorkspace / renameWorkspace admit.
Signature:
TILING_WORKSPACE_NAME_MAX_CHARS: number
TILING_WORKSPACE_SWIPE_DEFAULTS
variableDefaults of TilingWorkspaceSwipeConfig.
Signature:
TILING_WORKSPACE_SWIPE_DEFAULTS: TilingWorkspaceSwipeConfig
TILING_WORKSPACES_MAX
variableMaximum number of workspaces createWorkspace admits.
Signature:
TILING_WORKSPACES_MAX: number
TilingAccentHue
interfacePer-accent HUE tokens — the raw color atoms a theme composes. Decoupled from the previous bundled accent theme so a calm theme can borrow an accent's hue (border/text/ring) WITHOUT inheriting its heavy neon glow, and a refined theme can pick the softened glow. Every field is a literal Tailwind class so the JIT emits it.
Signature:
interface TilingAccentHue
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | Focused-pane border color. | |
| string | Full-intensity neon focus glow (box-shadow). | |
| string | Dialed-back focus glow for refined / calm themes (box-shadow). | |
| string | Focused-pane focus-ring color. | |
| string | Human-facing palette label (also the picker swatch label). | |
| string | Resting pane border tint (low alpha). | |
| string | Resting pane colored drop-shadow tint. | |
| string | Solid background for a palette swatch dot. | |
| string | Active tab/switcher chip translucent fill. | |
| string | Subtle active-tab fill for low-contrast themes. | |
| string | Active tab/switcher chip border. | |
| string | Accent title / metadata text. | |
| string | Strong accent text used on active chips. |
TilingChromeFocusOutline
typeHow the renderer handles native user-agent focus outlines on its own chrome (pane roots article[data-leaf-id], tabs, header buttons/links inside .hpt-root). The renderer injects a stylesheet SCOPED to .hpt-root, so it never touches app chrome outside the tiling tree.
"suppress"(default): inject the scoped stylesheet that removes UA focus outlines (and tap-highlight) on hypr chrome. Hypr paints its own focus affordance (theme border/frame), so the raw UA:focus-visiblering — which a bare Shift/modifier keypress can promote on an already-focused pane — is redundant and visually noisy. This is the historical default. -"native": do NOT inject the suppression rules; native UA focus outlines are preserved on hypr chrome. Pick this for a fully keyboard-accessible default that relies on the browser focus ring. (Theme token classes such asoutline-noneon the pane surface are authored in thethemeobject and are controlled there, independently of this switch.)
Signature:
type TilingChromeFocusOutline = "suppress" | "native";
TilingClientPoint
interfaceTilingClientRect
interfaceA window-client rectangle (CSS px), the DOMRect subset the hit-test reads.
Signature:
interface TilingClientRect
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| number | Bottom edge (window-client CSS px). | |
| number | Left edge (window-client CSS px). | |
| number | Right edge (window-client CSS px). | |
| number | Top edge (window-client CSS px). |
TilingCollapseBodyMode
typeCollapsed pane body MOUNT policy (HT-COLLAPSE-BODY-MODE) — lib-controlled, not a per-consumer renderTile decision (see TilingPaneBody(), which is the ONE place this policy takes effect):
"keep-mounted"(default) — a collapsed pane's body stays mounted (its React subtree is preserved, only hidden viadisplay: none), so scroll position, focus, form state, iframes, etc. inside the pane survive collapse/expand. -"unmount"— a collapsed pane's body is removed from the tree entirely (like the content-visibility toggle turned off), trading state preservation for a lighter DOM / fewer live subtrees while collapsed.
Signature:
type TilingCollapseBodyMode = "keep-mounted" | "unmount";
TilingCommandGates
interfaceThe capability enable-flags a command may require. Superset of TilingKeymapActionGuards (which only covers the keyboard-reachable actions) with the sizing / acquire-space / resize gates the programmatic command set also touches. The renderer builds this from its resolved capabilities.
Signature:
interface TilingCommandGates
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Directional acquire-space command. | ||
boolean | Per-pane collapse ( | ||
boolean | Focus selection commands. | ||
boolean | Group / tabbed-stacking commands (HT-GROUP-TABBED-STACKING). | ||
boolean | Master/stack layout-mode commands (HT-LAYOUT-MASTER-STACK). | ||
boolean | Maximize / restore commands. | ||
boolean | Pane-switching (focus-cycle / focus-jump) commands. | ||
boolean | Drag/keyboard rearrange (move / swap / insert) commands. | ||
boolean | Divider resize ( | ||
boolean | Per-pane title-bar sizing ( | ||
boolean | Workspace-set navigation and reset commands ( |
TilingDeleteWorkspaceResult
interfaceThe result of deleteWorkspace().
Signature:
interface TilingDeleteWorkspaceResult
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| ReadonlyArray<string> | Tile ids that were shown ONLY in the deleted workspace, in that tree's reading order — they are now seated nowhere, so a host with a tile registry reconciles (DashAI: items shown nowhere leave | |
| The set without the workspace (the input by reference when refused). |
TilingDimension
typeThe two layout dimensions a pane can be sized along, independent of split axis.
Signature:
type TilingDimension = "width" | "height";
TilingDragGhostMode
typeHow the live-drag ghost paints while a leaf is carried.
"footprint"(default) — today's tile-sized ghost, origin-offset from the pickup grab point. Unchanged behaviour. -"compact"— always the small chip under the cursor. -"auto"—compactwhile TilingRendererCommonProps.externalDragHover is non-null,footprintotherwise. The 26.9.x forward-compatible subset of the WorkspaceSet drag scope (_agent/workspace-set-concept.md§5.5).
Signature:
type TilingDragGhostMode = "footprint" | "compact" | "auto";
TilingDragHandle()
functionThe drag-pickup surface of a custom pane (typically the header). Wires the renderer's onHandlePointerDown and sets touch-action: none so a touch press starts a drag instead of scrolling, and folds in the Alt/Opt+click multi-select toggle (which must not establish focus). Renders a <div>; bring your own className / style / children.
Signature:
declare function TilingDragHandle(input: TilingDragHandleProps): React.ReactElement;
Parameters
Parameter | Type | Description |
|---|---|---|
{ pane, style, ref, ...rest } | (not declared) | |
input |
Returns:
React.ReactElement
TilingDragHandleProps
interfaceProps for TilingDragHandle().
Signature:
interface TilingDragHandleProps extends Omit<React.HTMLAttributes<HTMLElement>, "onPointerDown" | "onClick">
Extends: Omit<React.HTMLAttributes<HTMLElement>, "onPointerDown" | "onClick">
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
Pick<TilingRenderTileProps, "onHandlePointerDown" | "isMultiSelectGroupingEnabled" | "onToggleMultiSelect"> | The | ||
React.Ref<HTMLDivElement> | (Optional) Forwarded to the handle element (a host group-drop target, for example). |
TilingDragMode
typeDrag-to-rearrange feedback mode — how a pending move is shown WHILE dragging, before it is committed on drop.
"preview"— non-committing preview (default). The pending result is shown via the translucent PROJECTED landing overlays (S' / T' / successor) painted over the unchanged layout; the layout tree is NOT mutated until drop. -"live"— Hyprland-faithful detach drag. On pickup the dragged source leaf is detached and the remaining tree reflows ONCE to close the gap (removeLeafTile); that frozen provisional tree is held for the whole drag (it does NOT re-reflow as the cursor moves). The detached source follows the cursor as a ghost, and the drop is resolved + committed exactly ONCE on release against the original layout via the SAME resolver + reducers preview uses (swapLeafTiles/insertLeafAdjacent), so the live commit equals the preview commit for the same intent. The prop tree is never mutated pre-commit, so cancel / Escape / dragleave-abort / off-viewport revert is lossless.
NOTE on vocabulary: "projected" names the RENDERING TECHNIQUE for the preview overlays (projected geometry, S' / T' / successor). "preview" vs "live" is the interaction MODE. The mode is never called "projected".
Signature:
type TilingDragMode = "preview" | "live";
TilingDragRecoveryCapability
interfaceDrag / transition self-healing recovery configuration. Hardens the live-drag ANIMATION + TIMING layer (not the FSM, which is already un-wedgeable) against frame starvation — background-tab requestAnimationFrame suspension, CPU throttling, long tasks, dropped/late pointer events, interrupted transitions. The recovery primitives live in drag-recovery.ts; this is their typed knob surface. All deadlines are documented multiples of the animation duration or of a frame, never symptom-tuned constants. Only meaningful in dragMode: "live".
enable— master gate for the recovery layer (idle watchdog + rAF-with-timeout arming + idempotent style teardown). Defaulttrue. -maxDraggingIdleMs— the drag idle-watchdog deadline:armed/draggingwith noPOINTER_MOVE/TARGET_RESOLVEDprogress for longer than this (measured MONOTONICALLY, so throttle-robust) force-reconciles toidlevia the existingPOINTER_CANCELedge with pointer capture released. Default30 × BASELINE_DRAG_HOP_DURATION_MS ≈ 5100ms. -frameDeadlineMs— the rAF-fallback slack: each FLIP "play-to-identity" write is armed as arequestAnimationFrameracing asetTimeoutof this long, first-wins + idempotent, so a starved frame never leaves an element frozen at its invertedFirst. Default~2 frames ≈ 32ms. -transitionSlackMs— the transition-completion slack: style cleanup fires ontransitionendORduration + transitionSlackMs, whichever first. Default60ms(names the existing+60mask-close slack).
Signature:
interface TilingDragRecoveryCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Enable the self-healing recovery layer. Default | ||
number | (Optional) rAF-fallback slack (ms). Default | ||
number | (Optional) Idle-watchdog deadline (ms). Default | ||
number | (Optional) Transition-completion slack (ms). Default |
TilingDropAction
typeThe outcome a drop resolves to: exchange tiles ("swap"), insert at an edge ("edge-insert"), insert into an existing split container ("split-container-insert"), merge into a tabbed group ("group-merge"), or no valid action ("none").
Signature:
type TilingDropAction = "swap" | "edge-insert" | "split-container-insert" | "group-merge" | "none";
TilingDropHitZoneGeometryCapability
interfaceAdjustable drag-drop HIT-ZONE GEOMETRY. These knobs shape the five-region partition every pane uses to map a cursor position to a drop intent: one central SWAP rectangle plus four directional INSERT trapezoids (top / right / bottom / left) formed by the diagonals from each pane corner to the nearest center-rectangle corner (see drop-intent-resolver.ts). The partition tiles the full pane with no gaps; the same geometry drives both resolution and the visual overlay, so the drawn zone is exactly the resolved zone.
The model is SYMMETRIC: a single centerRatio sizes the center rectangle on BOTH axes, which means the directional edge band depth is fully derived from it as (1 - centerRatio) / 2 per axis (there is no independent edge-band knob, and no separate horizontal/vertical asymmetry — the diagonal-trapezoid construction uses one ratio). CORNERS are not a distinct zone: a corner resolves to whichever edge trapezoid contains it, with a deterministic tie-break order (top → right → bottom → left) on an exact diagonal.
Every field is optional; an undefined field resolves to the documented default (which equals today's TILING_DROP_INTENT_CONFIG), so omitting this object leaves drop-zone behavior exactly as it was.
Signature:
interface TilingDropHitZoneGeometryCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
number | (Optional) Floor (CSS px) for the center rectangle's extent on each axis so tiny panes keep a usable swap target even when | ||
number | (Optional) Fraction of each pane axis spanned by the central SWAP rectangle. The directional INSERT edge band depth is the complement, | ||
number | (Optional) Per-axis HORIZONTAL (width) swap-zone fraction override. When set, it sizes the center rectangle's X extent independently of | ||
number | (Optional) Per-axis VERTICAL (height) swap-zone fraction override. When set, it sizes the center rectangle's Y extent independently of | ||
number | (Optional) Boundary stickiness (pane-local CSS px): once the cursor is in a zone it must cross the boundary by this much before the classification switches, suppressing sub-pixel flicker at the trapezoid edges. |
TilingExternalDragHover
typeHost-reported "pointer is over an external drop target" (a workspace tab, a chat panel — anything outside the tiling tree). The engine does not hit-test host chrome; the host (or useTilingWorkspaceTabs) drives this from its own over state. Discriminated on kind; a plain { targetId, point } literal is the TilingExternalDropHover variant.
Signature:
type TilingExternalDragHover = TilingExternalDropHover | TilingWorkspaceTabDragHover;
References: TilingExternalDropHover, TilingWorkspaceTabDragHover
TilingExternalDragHoverResolver
typeEngine-driven external hover resolution: called with the pointer's window- client position (once per processed drag frame and synchronously at release) while a leaf is being dragged; returns the external target under the pointer or null. Lets the renderer own the hit-test — the host's tab strip only registers rects (useTilingWorkspaceTabs supplies one).
Signature:
type TilingExternalDragHoverResolver = (point: TilingClientPoint, leafId: string) => TilingExternalDragHover | null;
References: TilingClientPoint, TilingExternalDragHover
TilingExternalDropHover
interfaceHost-reported "pointer is over an external drop target" the ENGINE cannot settle itself (a chat panel, a sidebar — anything outside the tiling tree that is not a workspace tab). The host claims the leaf on release through TilingRendererCommonProps.onExternalDrop. kind may be omitted — a bare { targetId, point } literal is this variant.
Signature:
interface TilingExternalDropHover
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| "external" | (Optional) Discriminant; omitted on a bare literal. | |
| Pointer position over the target (window-client CSS px). | ||
| string | The host target's id (what |
TilingFocusDirection
typeA directional focus / move target relative to the current pane.
Signature:
type TilingFocusDirection = "left" | "right" | "up" | "down";
TilingGhostChipContext
interfaceArguments the theme ghostChip slot receives when the compact ghost is painted. targetId is present only while TilingExternalDragHover is set.
Signature:
interface TilingGhostChipContext
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | The dragged leaf. | |
| Where the chip is anchored (window-client CSS px). | ||
| string | (Optional) The external target under the pointer, when TilingExternalDragHover is set. | |
| string | (Optional) The dragged tile's title, when it has one. | |
| string | (Optional) Present while the hover is a TilingWorkspaceTabDragHover: the destination workspace. |
TilingGroupingCapability
interfaceGroup / tabbed-stacking capability (HT-GROUP-TABBED-STACKING), the object form of the grouping capability. A bare boolean at the grouping slot is shorthand for { enable }.
Signature:
interface TilingGroupingCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Enable grouping. When | ||
(Optional) Built-in group tab strip. Ignored when | |||
boolean | (Optional) Render the per-group tab strip (the member tabs a tabbed-stacking group paints above its active member). Default |
TilingGroupMemberView
interfaceOne member of a tabbed group, as seen by a custom renderTile through TilingRenderTileGroupContext.members. Mirrors what the built-in group tab strip paints per tab: the member's identity, its resolved tile payload, its 1-based position (the group-tab-jump operand — also the keyboard Alt+<n> label), and whether it is the active (rendered) member.
Signature:
interface TilingGroupMemberView
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| boolean | Whether this member is the group's active (rendered) member. | |
| string | The member's leaf node id. | |
| number | 1-based position in the group's member order — the operand TilingRenderTileGroupContext.activateMember takes (and the number the built-in strip / keyboard | |
| TilingTile | null | The resolved tile payload, or | |
| string | The tile id the member's leaf points at. |
TilingGroupTabMember
interfaceOne member of a built-in group tab strip, as seen by TilingGroupTabStripOptions.renderTabLabel.
Signature:
interface TilingGroupTabMember
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether this member is the group's active member. | ||
string | Group-member leaf id. | ||
string | Tile id the member displays. | ||
string | Tile title, or the tile id when the tile has no title. |
TilingGroupTabStripOptions
interfaceBuilt-in group tab strip options. The strip is the canonical group chrome: hosts theme it instead of painting their own tabs.
Signature:
interface TilingGroupTabStripOptions
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
number | (Optional) Strip height in CSS pixels. Default | ||
"top" | "bottom" | (Optional) | ||
(member: TilingGroupTabMember) => React.ReactNode | (Optional) Custom tab label. Default is the tile title. The string title is always the tab's | ||
boolean | (Optional) Show the eject control at the right end. Ejects the active member out of the group ( | ||
boolean | (Optional) Show the ungroup control at the right end. Default | ||
(Optional) CSS-value tokens. Omitted tokens keep the library default. |
TilingGroupTabStripTheme
interfaceCSS-value tokens for the built-in group tab strip. Every field is optional; omitted tokens resolve to the library default (dark row, quiet active pill). Applied as CSS custom properties on the strip element.
Signature:
interface TilingGroupTabStripTheme
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
string | (Optional) Keyboard | ||
string | (Optional) Strip row background. | ||
string | (Optional) Strip row hairline border color. | ||
string | (Optional) Eject / ungroup control color. | ||
string | (Optional) Eject / ungroup control color on hover. | ||
string | (Optional) Tab label font family. | ||
string | (Optional) Tab label font size (CSS length). | ||
string | (Optional) Gap between pills inside the rail (CSS length). | ||
string | (Optional) Tab label letter spacing (CSS length). | ||
string | (Optional) Active pill background when the group strip is the nested tier. | ||
string | (Optional) Active pill label color when the group strip is the nested tier. Omitted → | ||
string | (Optional) Horizontal padding of the strip row (CSS length). | ||
string | (Optional) Tab (pill) corner radius (CSS length). Rail uses | ||
string | (Optional) Rounded rail background behind the pills. | ||
string | (Optional) Rounded rail 1px border color. Omitted → | ||
string | (Optional) Active tab background (standalone / pane strip). | ||
string | (Optional) Active tab label color (standalone / pane strip). | ||
string | (Optional) Inactive tab background. | ||
string | (Optional) Inactive tab label color. | ||
string | (Optional) Inactive tab label color on hover. |
TilingHideTileResult
interfaceThe result of hideTileFromWorkspace().
Signature:
interface TilingHideTileResult
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| boolean |
| |
| The set without that seat (the input by reference when refused). |
TilingInactiveWorkspacesMode
typeMount policy for panes seated only in inactive workspaces — see TilingRendererWorkspaceSetProps.inactiveWorkspaces.
Signature:
type TilingInactiveWorkspacesMode = "unmount" | "keep-mounted";
TilingInteractionCapabilities
interfacePublic, reactive interaction-capability flags for the tiling renderer. All fields are optional; an undefined field (or an undefined config object) resolves to the all-enabled default via resolveInteractionCapabilities.
Signature:
interface TilingInteractionCapabilities
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Coherent non-intersecting transit. When | ||
boolean | (Optional) Custom-rendered drag cursor (interaction tier "c"). When | ||
(Optional) Drag-to-rearrange feedback mode: | |||
(Optional) Drag / transition self-healing recovery (idle watchdog + rAF-with-timeout animation arming + idempotent transient-style teardown). Hardens the live-drag animation/timing layer against frame starvation. Only meaningful in | |||
(Optional) Adjustable drag-drop hit-zone geometry (center swap fraction, center floor, boundary hysteresis). Undefined → today's | |||
boolean | (Optional) Pane focus selection. When | ||
number | (Optional) Ghost pickup scale as a percent of the source pane's full bbox. On drag start the live-mode ghost animates from the source pane's full bbox to this fraction of it; values <100% read as lifted/shrunk. The free-following ghost rests at this scale and morphs toward the resolved slot's bbox on hop-in. Range | ||
boolean | TilingGroupingCapability | (Optional) Group / tabbed-stacking (HT-GROUP-TABBED-STACKING). A bare boolean is shorthand for | ||
(Optional) Public chord→command binding registry. Consumer bindings augment (and on a chord collision override) the default keymap bindings; set | |||
(Optional) Top-level keymap. Capability-level | |||
boolean | (Optional) Master/stack layout engine (HT-LAYOUT-MASTER-STACK). When | ||
(Optional) Maximize-to-viewport capability. Default all-enabled. | |||
(Optional) Tab-like pane-switching capability. Default all-enabled. | |||
(Optional) Per-pane title-bar controls (in-header sizing + acquire-space). Default all-enabled. Reactive: toggling a flag at runtime shows/hides the controls without remount. | |||
boolean | (Optional) Drag-to-rearrange (move / swap / edge-insert) capability. When | ||
(Optional) Divider resize capability. Default | |||
boolean | (Optional) Whether split-divider resize handles are visibly rendered. | ||
(Optional) Live-drag slot re-resolution / commitment policy (the single ghost hops INTO and FILLS the resolved slot). Only meaningful in | |||
boolean | (Optional) Live-drag slot hop-in. Selects between the two drag-presentation behaviors for the dragged pane:
Orthogonal to the CONTENT toggle: in BOTH modes the pane body honors content uniformly (see | ||
(Optional) Touch-drag hardening (touch enable + long-press disambiguation). Only meaningful when | |||
boolean | TilingWorkspacesCapability | (Optional) Workspace-set navigation and reset commands ( |
TilingKeyBinding
interfaceA single keyboard binding: an arbitrary chord mapped to an arbitrary command. The public chord→command registration surface (the Hyprland bind analog), distinct from the fixed TilingKeymap chord overrides (which only re-chord the built-in action set). The chord matches on KeyboardEvent.code exactly like TilingKeyChord.
Signature:
interface TilingKeyBinding
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
The chord that triggers this binding (matched on | |||
The command dispatched when the chord matches. |
TilingKeyBindings
interfaceConsumer keyboard-binding registry. bindings augment (and, on a chord collision, override) the default keymap bindings; setting replaceDefaults suppresses the built-in keymap path entirely so ONLY these bindings are live.
Signature:
interface TilingKeyBindings
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
ReadonlyArray<TilingKeyBinding> | (Optional) Consumer chord→command bindings; augment (or override on collision) the defaults. | ||
boolean | (Optional) When |
TilingKeyChord
interfaceA single keyboard chord: a PHYSICAL KeyboardEvent.code value plus the modifier state required to match it. Absent modifiers resolve to false (the modifier must NOT be held) — a chord fully specifies its modifier requirements.
The chord matches on event.code (the physical key, e.g. "Enter", "Escape", "BracketLeft", "BracketRight"), NOT event.key (the produced character). This is mandatory on macOS, where holding Option(Alt) rewrites event.key into dead-key glyphs (Option+] → "‘", Option+digits → special glyphs), so a event.key-based comparison never matches. event.code is stable across keyboard layouts and modifier state.
Signature:
interface TilingKeyChord
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Require the Alt key. Default | ||
string | Matches | ||
boolean | (Optional) Require the Ctrl key. Default | ||
boolean | (Optional) Require the Meta (Cmd / Win) key. Default | ||
boolean | (Optional) Require the Shift key. Default |
TilingKeyChordModifiers
interfaceModifier requirements for the jump-to-pane digit family (Alt+1..Alt+9). The 1..9 digit is implied (matched against the physical Digit1..Digit9 codes); only the modifier state is configurable.
Signature:
interface TilingKeyChordModifiers
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Require the Alt key. Default | ||
boolean | (Optional) Require the Ctrl key. Default | ||
boolean | (Optional) Require the Meta (Cmd / Win) key. Default | ||
boolean | (Optional) Require the Shift key. Default |
TilingKeymap
interfacePublic, reactive keymap. Every field is optional; an undefined field resolves to its documented default via resolveKeymap. Action-level merge: supplying one binding leaves the others at their defaults.
Signature:
interface TilingKeymap
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
(Optional) Toggle the focused subtree's layout mode dwindle ⇄ master (the Hyprland layout-toggle analog). Default | |||
(Optional) Cycle the master-area orientation left → top → right → bottom → left. Default | |||
(Optional) Remove one tile from the master area ( | |||
(Optional) Shrink the master-area fraction ( | |||
(Optional) Enter keyboard MOVE MODE on the focused pane (the keyboard analog of a drag pickup). Default | |||
(Optional) Toggle focus between the current pane and the most-recently-focused other pane (the MRU "focus current-or-last" toggle; Hyprland | |||
(Optional) Move focus to the geometric neighbor BELOW. Default bare | |||
(Optional) Move focus to the geometric neighbor on the LEFT. Default bare | |||
(Optional) Move focus to the geometric neighbor on the RIGHT. Default bare | |||
(Optional) Move focus to the geometric neighbor ABOVE. Default bare | |||
(Optional) Activate the next member tab of the focused group. Default | |||
(Optional) Activate the previous member tab of the focused group. Default | |||
(Optional) Add one tile to the master area ( | |||
(Optional) Grow the master-area fraction ( | |||
(Optional) Modifier set for the | |||
(Optional) Cycle to the next pane. Default | |||
(Optional) Cycle to the previous pane. Default | |||
(Optional) Restore from maximize. Default | |||
(Optional) Toggle grouping for the focused pane: group it with its reading-order neighbor, or ungroup it if already grouped (the Hyprland | |||
(Optional) Toggle maximize on the focused pane. Default |
TilingLayoutContainerSize
interfaceContainer geometry for the integrity/repair pass performed on load/commit.
Signature:
interface TilingLayoutContainerSize
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
number | Root container height in CSS pixels. | ||
number | Root container width in CSS pixels. |
TilingLayoutMode
typeArrangement of a split subtree's slots:
"dwindle"— the default recursive binary-split layout: each split distributes its two children along itsaxisbyratio(the original and only Phase-1/2 algorithm). -"master"— a master-area + stack layout (the Hyprlandmasteranalog). The split's descendant slots (leaves / groups, flattened in reading order) are laid out asmasterCountmaster tiles in a master area plus the remaining tiles in a stack; the split'sratiois reused as the master-area fraction andmasterOrientationplaces the master area. The binary structure beneath a master split is flattened (ignored for geometry); it still defines slot membership + order + identity, and the reducers still operate on it.
Signature:
type TilingLayoutMode = "dwindle" | "master";
TilingLayoutStorage
interfaceMinimal synchronous key/value store, structurally compatible with the Web Storage API (window.localStorage / sessionStorage). Supply a custom implementation for tests, SSR, or a non-localStorage backend.
Signature:
interface TilingLayoutStorage
Methods
Method | Description |
|---|---|
Read the serialized layout tree stored under | |
Remove any serialized layout tree stored under | |
Write the serialized layout tree |
TilingLeafDropPreview
interfaceA projected landing/result preview shown on a pane during a drag.
Signature:
interface TilingLeafDropPreview
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
Whether the preview depicts a swap, an edge-insert, or a group-merge. | |||
string | The other leaf involved (swap counterpart or insert neighbor). | ||
Whether this preview is the drag-source landing or the drop-target result. | |||
The drop zone the preview corresponds to. |
TilingLeafDropZone
typeThe five drop regions of a pane: a central swap zone plus four insert edges.
Signature:
type TilingLeafDropZone = "center" | "left" | "right" | "top" | "bottom";
TilingLeafPreviewMode
typeWhether a preview depicts a swap, an edge-insert, or a group-merge result.
Signature:
type TilingLeafPreviewMode = "swap" | "edge-insert" | "group-merge";
TilingLeafPreviewRole
typeWhich side of a preview a shadow represents (drag source vs drop target).
Signature:
type TilingLeafPreviewRole = "drag-source-landing-shadow" | "drop-target-result-shadow";
TilingMasterOrientation
typeWhere the master area sits in layoutMode: "master". left/right divide the container along WIDTH (master area a column, members stacked vertically); top/bottom divide along HEIGHT (master area a row, members stacked horizontally). The complement holds the stack.
Signature:
type TilingMasterOrientation = "left" | "right" | "top" | "bottom";
TilingMaximizeCapability
interfaceMaximize (expand-to-viewport) capability. The maximize render-mode is non-destructive — it hides sibling panes and renders the focused pane filling the tiling viewport without mutating the layout tree.
Signature:
interface TilingMaximizeCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Enable the per-pane maximize/restore control + shortcuts. Default | ||
boolean | (Optional) When the maximized leaf belongs to a group, render the GROUP node at viewport size (strip above the maximized member) instead of the leaf alone. Default | ||
Pick<TilingKeymap, "toggleMaximize" | "restore"> | (Optional) Per-capability keybinding overrides for |
TilingMinBBoxPx
interfaceLeaf-scoped minimum bounding-box floor (CSS px), one optional component per dimension. See TilingLeafNode.minBBoxPx for the precedence this floor takes over TilingSplitNode.minPaneSizePx and TilingLayoutConfig.minPaneSizePx.
Signature:
interface TilingMinBBoxPx
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
number | (Optional) Minimum height (CSS px) this leaf's pane must retain during resize. | ||
number | (Optional) Minimum width (CSS px) this leaf's pane must retain during resize. |
TilingMovePlacement
typeWhich edge of a target pane an inserted/moved pane lands on.
Signature:
type TilingMovePlacement = "left" | "right" | "top" | "bottom";
TilingOnExternalDrop
typeFired on release while TilingRendererCommonProps.externalDragHover is non-null, **before** the drag FSM settles, so the host can claim the leaf synchronously and skip the cancel fly-back. Return false to DECLINE the claim (the target refused the leaf): the release then settles through the ordinary cancel fly-back. void / true claims.
Signature:
type TilingOnExternalDrop = (leafId: string, targetId: string, point: TilingClientPoint, hover: TilingExternalDragHover) => void | boolean;
References: TilingClientPoint, TilingExternalDragHover
TilingOrphanTilePolicy
typeOrphan-tile policy for the set-mode renderer — see TilingRendererWorkspaceSetProps.orphanTiles.
Signature:
type TilingOrphanTilePolicy = "seat-in-active" | "ignore" | "report";
TilingOverlayPortalContainer
typeHost node for the renderer's position: fixed drag overlays (ghost, custom cursor, cancel fly-back). An element, null (fall back to document.body), or a thunk evaluated every render so a late-mounted container (ref / callback) is picked up without remounting the renderer. See TilingRendererCommonProps.overlayPortalContainer for the containing-block caveat, SSR null, and stacking-context z-index note.
Signature:
type TilingOverlayPortalContainer = HTMLElement | null | (() => HTMLElement | null);
TilingPaneAction()
functionA header action button (maximize, group, …) that stopPropagations on both pointer-down and click, so activating it never starts a drag or steals pane focus. Defaults type to "button" and calls your onClick after stopping propagation. Bring your own className / style / children.
Signature:
declare function TilingPaneAction(input: TilingPaneActionProps): React.ReactElement;
Parameters
Parameter | Type | Description |
|---|---|---|
{ onClick, type, ...rest } | (not declared) | |
input |
Returns:
React.ReactElement
TilingPaneActionProps
typeProps for TilingPaneAction().
Signature:
type TilingPaneActionProps = React.ButtonHTMLAttributes<HTMLButtonElement>;
TilingPaneBody()
functionThe body wrapper of a custom pane. Always renders its wrapper <div> (so the pane keeps its layout), but renders children ONLY when paneBodyRenderMode === "render-content". This keeps a custom pane aligned with the renderer's drag-ghost / hidden-body semantics — the ghost reuses the same render path (overlay portal container, default document.body), so an empty body never rides along. Bring your own className / style.
HT-PANE-COLLAPSE: while pane.isCollapsed is true (and the pane is NOT maximized — see below) the wrapper is forced to display: none (merged into any consumer style) rather than merely emptying its children. A collapsed leaf is pinned to the chrome extent (titlebar-only, along its parent split's axis), but a bare empty body still keeps its flex-1 / min-h-0 box in the layout — the leftover slack between that box and the pinned extent rendered as a dead strip beside/below the titlebar. Hiding the box entirely removes that strip so the collapsed pane is exactly titlebar-sized.
HT-COLLAPSE-BODY-MODE: this display: none hide is INDEPENDENT of whether children are mounted. With the default collapseBodyMode: "keep-mounted", resolvePaneBodyRenderMode resolves "render-content" while collapsed, so children stay mounted (state/scroll/focus survive collapse/expand) behind the display: none wrapper; "unmount" resolves "render-empty" instead, removing children from the tree exactly like the legacy behavior.
HT-PANE-COLLAPSE + maximize: pane.isMaximized SUSPENDS the display: none gate for exactly this leaf, matching resolvePaneBodyRenderMode's own maximize override — otherwise a maximized collapsed pane would render render-content children into a wrapper still forced invisible, i.e. a titlebar strip in an empty full-screen frame.
Signature:
declare function TilingPaneBody(input: TilingPaneBodyProps): React.ReactElement;
Parameters
Parameter | Type | Description |
|---|---|---|
{ pane, children, style, ...rest } | (not declared) | |
input |
Returns:
React.ReactElement
TilingPaneBodyProps
interfaceProps for TilingPaneBody().
Signature:
interface TilingPaneBodyProps extends React.HTMLAttributes<HTMLDivElement>
Extends: React.HTMLAttributes<HTMLDivElement>
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
Pick<TilingRenderTileProps, "paneBodyRenderMode" | "isCollapsed" | "isMaximized"> | The |
TilingPaneBodyRenderMode
typeResolved pane-body render decision: - "render-content" — paint the tile body. Also resolved for a COLLAPSED pane under the default collapseBodyMode: "keep-mounted" (HT-COLLAPSE-BODY-MODE) — TilingPaneBody() still visually hides it via its own independent display: none gate. - "render-empty" — body removed from the tree (content toggle off, OR a collapsed pane under collapseBodyMode: "unmount"). - "render-reservation" — empty ghost-seat reservation slot during a drag.
Signature:
type TilingPaneBodyRenderMode = "render-content" | "render-empty" | "render-reservation";
TilingPaneCollapsedChangeEvent
interfaceThe payload of TilingRendererCommonProps.onPaneCollapsedChange: which leaf changed collapse state and its NEW state. Emitted after the collapse edit is reported via onLayoutChange, so a host reacting here (e.g. retitling the pane) reads a consistent post-edit world.
Signature:
interface TilingPaneCollapsedChangeEvent
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| boolean | The leaf's new collapse state — | |
| string | The leaf node id whose collapse state changed. |
TilingPaneCycleDirection
typePane cycle direction for the reading-order ring (next / previous, wraparound).
Signature:
type TilingPaneCycleDirection = "next" | "previous";
TilingPaneIdentityMode
typePane identity binding — WHICH React instance renders a tile, and whether it survives the tile's slot moving in the split tree.
"slot"— a pane is rendered in place at its leaf's tree position. React reconciles the recursive split tree POSITIONALLY, so any edit that moves a leaf to a different branch (an insert drop, a group fold, a master-stack reorder) unmounts the pane at the old position and mounts a fresh instance at the new one; a swap keeps both instances but hands each the OTHER tile's props. Host content re-initializes (the "loading flash" after a drop). This is the legacy behavior and the only SSR-faithful one (server HTML carries the pane content in-slot). -"stable"— panes are rendered ONCE each in a hidden, tile-keyed pool and their DOM node is RELOCATED into whichever slot currently shows their tile (a layout-effectappendChild, never a React re-parent). The React instance — hooks, state, refs, subscriptions, iframes, scroll positions — is the same object through drag → drop → settle and every other tree edit; only the DOM parent changes. During a live drag the picked-up pane is parked in the pool (its slot shows the content-less seat; the single ghost paints the pane through the overlay portal container, defaultdocument.body, see TilingRendererCommonProps.overlayPortalContainer) and reseats on drop. Server render emits EMPTY slots (content is placed on the client), so hydration-time flash is the cost. -"auto"(default) —"stable"on a client-only mount,"slot"when the first render is a hydration of server markup.
Signature:
type TilingPaneIdentityMode = "auto" | "stable" | "slot";
TilingPaneRoot()
functionThe root element of a custom pane. Renders an <article data-leaf-id> (the attribute the renderer resolves the drag source from) and wires the pane's onFocus (on both focus and click), onPointerMove, and onPointerLeave handlers, so focus, resize, and pre-drag hover telemetry keep working. Bring your own className / style / children; defaults tabIndex to -1.
Signature:
declare function TilingPaneRoot(input: TilingPaneRootProps): React.ReactElement;
Parameters
Parameter | Type | Description |
|---|---|---|
{ pane, className, ...rest } | (not declared) | |
input |
Returns:
React.ReactElement
TilingPaneRootProps
interfaceProps for TilingPaneRoot().
Signature:
interface TilingPaneRootProps extends Omit<React.HTMLAttributes<HTMLElement>, "onFocus" | "onClick" | "onPointerMove" | "onPointerLeave">
Extends: Omit<React.HTMLAttributes<HTMLElement>, "onFocus" | "onClick" | "onPointerMove" | "onPointerLeave">
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
Pick<TilingRenderTileProps, "leafId" | "onFocus" | "onPointerMove" | "onPointerLeave"> | The |
TilingPaneSizing
interfacePer-dimension sizing declaration for a node. Each dimension is independent: a pane can be static in height, in width, or in both. An undefined dimension defaults to "flexible".
The object shape is chosen over an axis list (e.g. staticAxes: ("width" | "height")[]) because it is explicit per-dimension, self-documenting at the call site (sizing: { height: "static" }), and lets a dimension be stated "flexible" explicitly rather than only inferred from absence.
Signature:
interface TilingPaneSizing
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
(Optional) Sizing mode for the HEIGHT dimension. Undefined → | |||
number | (Optional) Pinned HEIGHT in CSS px, captured from the pane's measured bounding box at the moment the user freezes the pane via the title-bar STATIC H / BOTH control. Only meaningful when | ||
(Optional) Sizing mode for the WIDTH dimension. Undefined → | |||
number | (Optional) Pinned WIDTH in CSS px, captured from the pane's measured bounding box at the moment the user freezes the pane via the title-bar STATIC W / BOTH control. Only meaningful when |
TilingPaneSizingMode
typePer-dimension sizing mode for a node placed inside a split.
"flexible"— the node shares space by ratio along the split axis and stretches/reflows along the cross axis (the default for every dimension). -"static"— the node is sized to its INTRINSIC/CONTENT extent along that dimension; it is excluded from ratio distribution and resize dividers when the static dimension runs ALONG the parent split axis, and simply content-sizes (no stretch) when the static dimension is the CROSS axis.
Signature:
type TilingPaneSizingMode = "static" | "flexible";
TilingPaneSwitchingCapability
interfaceTab-like pane-switching capability. Renders a tab strip across the top of the tiling region and binds the cycle / jump shortcuts. When maximized, switching panes also switches which pane is maximized.
Signature:
interface TilingPaneSwitchingCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Enable pane switching (tab strip + cycle/jump shortcuts). Default | ||
Pick<TilingKeymap, "previousPane" | "nextPane" | "jumpToPane"> | (Optional) Per-capability keybinding overrides for | ||
boolean | (Optional) Alt/Opt+click a pane HEADER to toggle that pane into a multi-selection set, then group the selected panes into one flat tabbed group via the existing | ||
boolean | (Optional) Render the tab strip's pane-content visibility checkbox (the "content" toggle that flips the default-tile body between content and empty). Default | ||
boolean | (Optional) Render the macOS Cmd+Tab-style visual switcher overlay while cycling panes with a held modifier ( | ||
boolean | "maximized" | (Optional) Render the TOP-LEVEL tab strip (the single strip across the tiling region listing every pane). Default | ||
boolean | (Optional) Toggle a pane's maximize/restore state when its TAB in the tab strip is double-clicked. Default | ||
(Optional) Built-in top-level pane tab strip. Ignored when |
TilingPaneTab
interfaceOne entry of the top-level pane tab strip. A group contributes a single tab, labelled by the maximized-or-active member.
Signature:
interface TilingPaneTab
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | Whether this tab's leaf is the focused pane. | ||
string | null | Group id when this tab stands for a group; | ||
string | Outer-layout leaf id this tab activates. | ||
boolean | Whether this tab's leaf is the maximized pane. | ||
number | Group member count when grouped; | ||
number | 1-based position in the strip (reading order). | ||
string | Tile id that leaf displays. | ||
string | Tile title, or the leaf id when the tile has no title. |
TilingPaneTabStripOptions
interfaceBuilt-in top-level pane tab strip. Shares the group-strip theme vocabulary (TilingGroupTabStripTheme) and the same role="tablist" keyboard model. Omitted fields resolve to placement "top", the group-strip default height, and the pane-strip theme defaults (solid active pill).
Signature:
interface TilingPaneTabStripOptions
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
number | (Optional) Strip height in CSS pixels. Default is the group-strip height ( | ||
"top" | "bottom" | (Optional) | ||
(tab: TilingPaneTab) => React.ReactNode | (Optional) Custom tab label. Default is the tile title. The string title is always the tab's | ||
(Optional) CSS-value tokens. Omitted tokens keep the pane-strip library default. |
TilingPaneTitleBarContent()
functionMiddle slot of a custom pane titlebar — between the title (left) and native window controls (right). Stops pointer-down propagation ONLY when the press lands on a genuine interactive control inside the slot (button, link, input/textarea/select, role="button", or a contenteditable node — see isInteractiveControlTarget), so a real toolbar click / find input does not start a rearrange drag from the surrounding TilingDragHandle(). A press anywhere ELSE in the slot — decorative labels, badges, a selected- case id, any non-interactive content — falls through untouched, so the titlebar stays draggable from the whole slot minus its actual controls (HT-TITLEBAR-DRAG-THRU: a blanket stop here previously turned any non-empty titleBarContent into a dead zone that swallowed drag pickup entirely, even where nothing inside it was interactive).
Unstyled: bring className (typically min-w-0 flex-1 so the slot fills remaining header width and truncates). Prefer reading tile.titleBarContent from TilingRenderTileProps and wrapping it here.
Signature:
declare function TilingPaneTitleBarContent(input: TilingPaneTitleBarContentProps): React.ReactElement;
Parameters
Parameter | Type | Description |
|---|---|---|
{ onPointerDown, ...rest } | (not declared) | |
input |
Returns:
React.ReactElement
TilingPaneTitleBarContentProps
interfaceProps for TilingPaneTitleBarContent().
Signature:
interface TilingPaneTitleBarContentProps extends React.HTMLAttributes<HTMLDivElement>
Extends: React.HTMLAttributes<HTMLDivElement>
TilingPaneTitleBarControlsCapability
interfacePer-pane TITLE-BAR control capability. These controls render directly in each pane's own header (next to maximize), so they target THAT pane (no central target-pane selector). Two independent control groups:
sizing— the FLEX / STATIC H / STATIC W / BOTH segmented control. STATIC actions measure the pane's current rendered bbox and pin the chosen dimension(s) to that pixel value; FLEX clears the pin. -acquireSpace— the four directional (→ ← ↑ ↓) "grow to claim space" buttons, driven by thegrowLeafTowardreducer.
Signature:
interface TilingPaneTitleBarControlsCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Render the per-pane directional (→ ← ↑ ↓) acquire-space controls. Default | ||
boolean | (Optional) Render the per-pane COLLAPSE-to-titlebar control + enable the | ||
boolean | (Optional) Render the per-pane FLEX / STATIC H / STATIC W / BOTH sizing control. Default |
TilingRendererCommonProps
interfaceThe mode-independent TilingRenderer props — everything except the layout source. Extended by TilingRendererProps (single controlled layout) and TilingRendererWorkspaceSetProps (controlled TilingWorkspaceSet).
Signature:
interface TilingRendererCommonProps
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
(Optional) How native UA focus outlines are handled on the renderer's own chrome. Undefined resolves to | |||
string | (Optional) Optional class name applied to the tiling root element. | ||
Global geometry configuration (gap, min pane size, handle size). | |||
boolean | (Optional) Master gate for all drag-motion choreography. When | ||
(Optional) Live-drag ghost presentation. Default | |||
string | (Optional) CSS | ||
string | (Optional) CSS | ||
TilingExternalDragHover | null | (Optional) Host tells the engine the pointer is over an external drop target (and where). In | ||
string | null | (Optional) Controlled focused-leaf id. | ||
number | (Optional) Speed of the dragged GHOST's transit animation (hop-in / hop-out / pickup entrance) as a percent of the 170ms baseline ( | ||
(Optional) Reactive interaction-capability flags. Undefined resolves to all-enabled. Changing this prop at runtime updates renderer behavior immediately. | |||
string | null | (Optional) Controlled maximized-pane id. | ||
(hover: TilingExternalDragHover | null) => void | (Optional) Notified when the hover produced by TilingRendererCommonProps.resolveExternalDragHover changes identity ( | ||
(Optional) Synchronous claim hook. Fired on | |||
(leafId: string) => void | (Optional) Notified whenever the focused leaf changes. | ||
(leafId: string | null) => void | (Optional) Notified whenever the maximized pane changes ( | ||
(event: TilingPaneCollapsedChangeEvent) => void | (Optional) Notified whenever a pane's COLLAPSE state changes (HT-PANE-COLLAPSE) — fired on every | ||
(themeId: TilingThemeId) => void | (Optional) Notified when the in-renderer theme switcher (top-bar control) requests a different theme. Present this control only when wired; the consumer owns the | ||
(tileId: string, accent: TilingTileAccent) => void | (Optional) When provided, the top-bar tab strip surfaces an accent palette picker (the enumerable | ||
(Optional) DOM node that hosts the Two independent reasons the default is Containing-block caveat: the container MUST NOT sit under an ancestor with Stacking: overlay z-indexes (ghost 220, cursor 230, cancel 219) are relative to the container's stacking context, not the document. | |||
(Optional) How a pane's React instance is bound to its slot in the split tree — see TilingPaneIdentityMode. Default | |||
number | (Optional) Background opacity | ||
(args: TilingRenderTileProps) => React.ReactNode | (Optional) Custom pane renderer. Receives TilingRenderTileProps; omit to use the default tile. | ||
(Optional) Engine-driven alternative to TilingRendererCommonProps.externalDragHover: the renderer calls this with the pointer position on every processed drag frame (and synchronously at release) and treats the result as the external hover. A non-null | |||
boolean | (Optional) Whether drop border hints are painted on candidate panes during a drag. | ||
boolean | (Optional) Whether translucent projected landing overlays are shown during a drag. | ||
number | (Optional) Speed of the affected ("survivor") panes' REFLOW transform animation as a percent of the 170ms baseline ( | ||
number | (Optional) Swap-landing bounce magnitude as a percent of full overshoot ( | ||
(Optional) Full consumer-authored theme; takes precedence over | |||
(Optional) Active visual theme id. Selects which built-in | |||
ReadonlyArray<TilingTile> | ReadonlyMap<string, TilingTile> | Tile registry, accepted as either an ordered array (resolved by |
TilingRendererModeProps
typeEither prop shape TilingRenderer accepts: the single controlled layout (TilingRendererProps) or a controlled workspace set (TilingRendererWorkspaceSetProps). Discriminated by the presence of workspaces.
Signature:
type TilingRendererModeProps = TilingRendererProps | TilingRendererWorkspaceSetProps;
References: TilingRendererProps, TilingRendererWorkspaceSetProps
TilingRendererWorkspaceSetProps
interfaceProps for TilingRenderer in WORKSPACE-SET mode: the host holds a TilingWorkspaceSet and the renderer paints the ACTIVE workspace's tree. Every tree edit is reported as a whole next set through onWorkspacesChange (the active workspace's layout replaced via setWorkspaceLayout), so the host reducer stays a single set → set step.
Signature:
interface TilingRendererWorkspaceSetProps extends TilingRendererCommonProps
Extends: TilingRendererCommonProps
Remarks
Switching workspaces.activeId does NOT remount panes whose leaf exists in both workspaces (stable pane identity keys on the leaf/tile id, not the workspace). Uncontrolled focus and maximize are remembered PER WORKSPACE (_agent/workspace-set-concept.md §5.3): switching away and back restores that workspace's focused / maximized pane; a workspace never inherits the previous one's maximize. A drag in flight when the active workspace changes is cancelled. Pass focusedLeafId / maximizedLeafId to own that scoping yourself.
A TilingWorkspaceTabDragHover in externalDragHover settles the drag natively: on release the renderer applies moveLeafToWorkspace and reports the next set — no host onExternalDrop needed for tab drops.
Example
const [set, setSet] = useState<TilingWorkspaceSet>(() => workspaceSetOfLayout(initialLayout));
return (
<TilingRenderer
workspaces={set}
onWorkspacesChange={setSet}
tiles={tiles}
config={DEFAULT_TILING_LAYOUT_CONFIG}
/>
);
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
(Optional) Whether panes seated only in inactive workspaces stay mounted. Default | |||
(tileId: string) => string | (Optional) Leaf id for a tile seated by | ||
(issues: ReadonlyArray<TilingWorkspaceSetIssue>) => void | (Optional) Set-wide integrity report ( | ||
(leafId: string, fromWorkspaceId: string, toWorkspaceId: string) => void | (Optional) Notified after a drag settled on a workspace tab and the leaf was moved (the moved set has already been reported through | ||
(workspaces: TilingWorkspaceSet) => void | Called with the next set whenever the renderer edits a tree or moves a leaf. | ||
(event: TilingWorkspaceSwitchEvent) => void | (Optional) Fired beside TilingRendererWorkspaceSetProps.onWorkspacesChange whenever | ||
(Optional) What the renderer does with a pool tile that no workspace seats (and with a seated tile the pool no longer contains). In set mode TilingRendererCommonProps.tiles is the WHOLE tile pool and coverage is judged set-wide — the active tree only heals the tiles it seats itself, so a tile seated only in an inactive workspace is never pulled into the active tree. Default
| |||
(workspace: TilingWorkspace) => React.ReactNode | (Optional) Rendered inside the tiling root while the active workspace's | ||
(Optional) Seed set for | |||
The controlled workspace set. The |
TilingRenderSurface
typeThe renderer surface a renderTile invocation paints:
"pane"— the seated in-tree pane (the normal, interactive case). -"drag-ghost"— the floating, portaled pickup ghost that travels with the cursor during a drag (the overlay portal container, defaultdocument.body, see TilingRendererCommonProps.overlayPortalContainer).aria-hidden+pointer-events-none: handlers are inert no-ops, capability display flags stay real. -"drag-cancel"— the cancel fly-back overlay (the ghost gliding home after a cancelled drag; same overlay portal container as"drag-ghost"). Same inert-handlers / real-flags semantics as"drag-ghost".
Signature:
type TilingRenderSurface = "pane" | "drag-ghost" | "drag-cancel";
TilingRenderTileGroupContext
interfaceGroup context on TilingRenderTileProps.group: present when the pane being rendered is a tabbed group's ACTIVE member (the only member that renders, per the stacking contract), null for loose leaves and for the drag-ghost / drag-cancel surfaces. A custom pane uses it to paint its own group chrome (tabs, member list, eject/ungroup controls) instead of the built-in strip — typically with the strip suppressed via the grouping capability's showGroupTabStrip: false.
The callbacks route through the SAME internal command router the built-in strip and the keyboard layer use (group-tab-jump / remove-from-group / ungroup), so capability gating and observability stay uniform.
Signature:
interface TilingRenderTileGroupContext
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| (memberNumber: number) => void | Activate the member at | |
| string | The group node id. | |
| ReadonlyArray<TilingGroupMemberView> | The group's members in member order (see TilingGroupMemberView). | |
| (leafId: string) => void | Eject the member with | |
| () => void | Dissolve the whole group into loose panes — dispatches |
TilingResizeCapability
typeResize capability across the two divider orientations.
RESIZE AXIS CONVENTION (read carefully — the divider orientation and the resize axis are deliberately the opposite words):
- A **vertical divider** sits between **side-by-side** panes. Dragging it changes their **widths** → this is a **horizontal** resize (x-axis). In the layout tree this is a split whose
axisis"horizontal"(aflex-rowcontainer with acursor-col-resizehandle). - A **horizontal divider** sits between **stacked** panes. Dragging it changes their **heights** → this is a **vertical** resize (y-axis). In the layout tree this is a split whoseaxisis"vertical"(aflex-colcontainer with acursor-row-resizehandle).
The resize-capability token therefore matches the split axis token directly: "horizontal" gates width dividers, "vertical" gates height dividers.
"both"— every divider is resizable (this is the "entire" option). Default. -"horizontal"— only width dividers (side-by-side panes) are resizable. -"vertical"— only height dividers (stacked panes) are resizable. -"none"— no divider is resizable.
Signature:
type TilingResizeCapability = "both" | "horizontal" | "vertical" | "none";
TilingResizeFloor
typeWhich floor an interactive resize (pointer drag / keyboard) bounds a pane to (HT-RESIZE-FLOOR):
"chrome"(LIBRARY DEFAULT, HT-RESIZE-FLOOR-DEFAULT) — "size-out": the gutter may shrink all the way to the collapsed titlebar-only extent (TilingLayoutConfig.collapsedExtentPx), REPLACING the body floor for this leaf entirely (not layered on top of it). This is purely a resize-time ratio bound: it never touchescollapsed/collapsedRestore(no auto-collapse, no state change from resize — only the titlebar-collapse control ownscollapsed), and the pane stays flexible/ratio-based (re-grows with the window), never asizing: "static"pin. Settle-on-release (snapping to a fixed size when released near the chrome floor) is intentionally NOT implemented. -"body"— the ordinary content floor: the min-pane precedence chain (TilingLeafNode.minBBoxPx → TilingSplitNode.minPaneSizePx → TilingLayoutConfig.minPaneSizePx). A resize cannot shrink the pane below its usable content size. Opt in per-leaf via TilingLeafNode.resizeFloor or library-wide via TilingLayoutConfig.resizeFloor for consumers that want the old content-floor behavior back.
Signature:
type TilingResizeFloor = "body" | "chrome";
TilingRevealTileChanged
typeWhat revealTile() changed: nothing, only the group tab, only the active workspace, or both.
Signature:
type TilingRevealTileChanged = "none" | "tab" | "workspace" | "both";
TilingRevealTileResult
interfaceThe result of revealTile() when the tile is seated in at least one workspace.
Signature:
interface TilingRevealTileResult
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| Whether the active workspace and/or a group tab changed. | ||
| string | The leaf id bound to the tile (set-unique). | |
| The set after the reveal (the input by reference when | ||
| The workspace the tile was revealed in. |
TilingSlotCommitmentCapability
interfaceLive-drag slot-commitment configuration. mode selects the re-resolution policy; reresolveDeltaPx is the delta-responsive movement threshold (CSS px) — a coarse "should I re-aim" gate distinct from the fine 6px geometric zone hysteresis, so the two never double-count.
Signature:
interface TilingSlotCommitmentCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
(Optional) Re-resolution policy after the ghost seats in a slot. Default | |||
number | (Optional) |
TilingSlotCommitmentMode
typeLive-drag slot re-resolution / commitment policy after the single ghost hops INTO and FILLS a resolved slot (see _agent/single-instance-hop-in-drag-design.md §8):
"zone-exit-hold"— anchored / sticky: the seated slot is pinned through small cursor movements and re-resolves only when the cursor crosses OUT of the seated target's hit footprint (high hysteresis). -"delta-responsive"— DEFAULT: the seated slot re-resolves eagerly once the cursor travels beyondreresolveDeltaPxfrom the seat anchor (or exits the footprint), so the user can re-aim without fully exiting the pane.
Only meaningful in dragMode: "live".
Signature:
type TilingSlotCommitmentMode = "zone-exit-hold" | "delta-responsive";
TilingSplitAxis
typeOrientation of a binary split. "horizontal" places its two children side-by-side (a flex-row, resized by a width divider); "vertical" stacks them (a flex-col, resized by a height divider).
Signature:
type TilingSplitAxis = "horizontal" | "vertical";
TilingSpringLoadCapability
typeThe capability value interaction.workspaces.springLoad will accept once the renderer wiring lands: false (default — spring-load off) or a partial TilingSpringLoadConfig. Resolved by resolveSpringLoadCapability (./engine) to a config, or null when disabled.
Signature:
type TilingSpringLoadCapability = Partial<TilingSpringLoadConfig> | false;
References: TilingSpringLoadConfig
TilingSpringLoadConfig
interfaceTunables of the dwell FSM. Defaults in TILING_SPRING_LOAD_DEFAULTS.
Signature:
interface TilingSpringLoadConfig
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
number | How long (ms) the pointer must stay over the SAME non-active workspace tab, without releasing, before the drag commits into that workspace and re-arms there. Default |
TilingThemeDividerTokens
interfaceSplit-divider / gap handle chrome across visible + hidden states.
Signature:
interface TilingThemeDividerTokens
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | Structural base incl. focus-visible ring color. Include | |
| string | Hidden handle (no chrome, hit-area only). | |
| string | Visible + resizable handle (resting + hover). | |
| string | Visible but resize-disabled handle. |
TilingThemeDragChromeTokens
interfaceDrag-state chrome — how the rearrange drag LOOKS around the pane shell: the ghost wrapper's elevation delta, the content-less seat the ghost hops into, the picked-up source pane's dimming, the drop-target highlight, and the pointer-pinned drag-cursor badge. Every renderer-painted drag surface reads these tokens (never an inline class string), so a host can make the drag state read exactly like its at-rest pane — square, flat, hairline — without CSS overrides against [data-drag-ghost] / [data-drag-source-reservation].
TilingTheme.dragChrome is OPTIONAL and PARTIAL: resolveDragChrome(theme) fills every omitted token from a pane-shell-inheriting default derived from the theme itself (the seat wears paneShell.surface, the seat frame reuses resolveFocusFrame, the ghost carries a small neutral elevation/opacity delta). A theme that passes nothing therefore drags with its own resting chrome; the built-in neon-terminal look is expressed purely through this slot. Tokens stay literal class strings (Tailwind JIT) — that is not a portal constraint. Scoped host CSS vars still need overlayPortalContainer.
Signature:
interface TilingThemeDragChromeTokens
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | Cancel fly-back wrapper (the pane gliding back to its origin). | |
| string | Drag-cursor badge (the pointer-pinned drop-validity affordance — insert / swap / invalid / grab glyph) shape + base: radius, border width, backdrop. | |
| string | Cursor badge surface / border / glyph color over a blocked target. | |
| string | Cursor badge surface / border / glyph color while free-following (grip). | |
| string | Cursor badge surface / border / glyph color over a committable target. | |
| string | The default tile's drop-intent overlay FRAME (edge/center zone hints): its radius + border width. Colors come from the observability layer. | |
| string | The resolved DROP-TARGET leaf wrapper. Default | |
| string | Ghost WRAPPER while the ghost free-follows the pointer (lifted): the elevation / scale / opacity / tint delta layered OUTSIDE the pane shell ( | |
| string | Ghost wrapper while the ghost is SEATED in the hop-in slot (and under | |
| string | Ghost wrapper transition between the lifted and seated looks. Omitted entirely under | |
| (accent: TilingTileAccent | undefined) => string | The frame the seat wears during the hop-in flight, composed from the DRAGGED pane's accent (focus follows the dragged pane). Default: the theme's | |
| string | The picked-up SOURCE pane as a whole (applied by the renderer to the leaf wrapper, so it dims a custom | |
| string | The content-less SEAT the single ghost hops into ( |
TilingThemeGhostTokens
interfaceDrag-ghost shell — the lifted, portaled copy of the dragged pane.
Signature:
interface TilingThemeGhostTokens
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | Ghost body text color. | |
| string | Ghost header bar. | |
| string | Ghost subtitle text color. | |
| string | Ghost article shell (a touch more opaque + deeper shadow than a pane). |
TilingThemePaneHeaderTokens
interfacePane header chrome — resting + focused + the per-pane control buttons.
Signature:
interface TilingThemePaneHeaderTokens
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | Resting header bar: border-b, bg, inset sheen. | |
| string | Active/pressed header control button. | |
| string | Resting header control button (maximize etc.). | |
| string | Additive classes when the pane is focused. | |
| string | Additive header classes when the pane is part of the Alt/Opt+click multi-selection set. Deliberately NEUTRAL (no accent) so it never collides with the accent focus frame — multi-selection and focus are orthogonal states a pane can hold simultaneously. | |
| string | The small neutral "selected" check affordance rendered in the header of a multi-selected pane. Neutral tone, distinct from any accent control. | |
| string | Pane title base typography (accent color applied separately). |
TilingThemePaneShellTokens
interfacePane host shell surfaces + interaction-state rings.
Signature:
interface TilingThemePaneShellTokens
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | Pane body scroll region text color/leading. | |
| string | Ring on an invalid drop target. | |
| string | Pane subtitle text color. | |
| string | Pane article shell: bg/gradient, radius, shadow/rim, backdrop-filter. |
TilingThemeRootTokens
interfaceRenderer-root + viewport surfaces.
Gap flanks (divider margins / static spacers) and rounded-pane corner gutters show whatever paints here. Prefer bg-transparent so the host chrome shows through; set an opaque bg only when the tiling surface is a self-contained canvas (neon-terminal). Consumers can override via the theme prop without forking the library.
Signature:
interface TilingThemeRootTokens
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | Outer renderer container: bg/gradient, radius, padding, outline. | |
| string | Inner viewport (where the pane tree lays out): bg + radius. |
TilingThemeTopBarTokens
interfaceTop-bar / tab-strip chrome.
Signature:
interface TilingThemeTopBarTokens
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | Tab-strip container: border, bg, shadow, backdrop. | |
| string | Switcher-control group wrapper (theme picker etc.). | |
| string | Accent-picker group wrapper. | |
| string | Centered pane-switcher overlay card. | |
| string | Switcher card when the pane is NOT selected. | |
| string | Tab chip base typography/layout. | |
| string | Inactive tab chip. | |
| string | Strip title text. |
TilingThemeWorkspaceTransitionTokens
interfaceWorkspace-switch motion tokens (N2). Optional + partial — omitted fields resolve through resolveWorkspaceTransition() to the library defaults (200 ms, drag-hop easing). Same additive slot pattern as dragChrome / ghostChip.
Signature:
interface TilingThemeWorkspaceTransitionTokens
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| number | Timed commit / cancel duration in milliseconds. Default 200. | |
| string | CSS |
TilingTileAccentSwatch
interfaceA pickable accent paired with a human label and a solid Tailwind background class for rendering a swatch dot — the generic metadata a palette control (e.g. a host accent picker) iterates to offer accent selection.
Signature:
interface TilingTileAccentSwatch
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
The accent this swatch selects. | |||
string | Human-readable label for the swatch (e.g. in a picker). | ||
string | Tailwind background class rendering the solid swatch dot. |
TilingTitleBarSizingMode
typeThe four title-bar sizing actions a pane offers for ITSELF (no target-pane indirection — the pane the control lives in is the target):
"flexible"— clear any pinned dimension and return the pane to ratio distribution (FLEX). -"static-height"— freeze the pane's height to its measured bbox px. -"static-width"— freeze the pane's width to its measured bbox px. -"static-both"— freeze both dimensions to the measured bbox.
Signature:
type TilingTitleBarSizingMode = "flexible" | "static-height" | "static-width" | "static-both";
TilingTouchDragCapability
interfaceTouch-drag hardening configuration. The drag FSM runs on Pointer Events, so it already covers touch uniformly with mouse/pen — this capability tunes the touch-only choreography that must differ from mouse:
enable— whenfalse, a touch press on the drag handle never starts a drag (touch is reserved for tap/scroll); mouse/pen drag is unaffected. Defaulttrue. -longPressMs— how long a finger must be held before the press becomes a drag pickup (the tap/scroll-vs-drag disambiguator). A pre-long-press scroll-axis flick releases to the page; mouse/pen skip this delay entirely. Default220.
Signature:
interface TilingTouchDragCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Allow touch pointers to start a drag. Default | ||
number | (Optional) Long-press delay (ms) before a held touch becomes a drag. Default |
TilingWorkspace
interfaceOne workspace: a stable id, a host-facing name, and its layout tree (null = empty).
Signature:
interface TilingWorkspace
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| Unique within the set. Any non-empty string (e.g. | ||
| TilingLayoutNode | null | The workspace's tree, or | |
| string | Display name; 1..TILING_WORKSPACE_NAME_MAX_CHARS chars after trim. |
TilingWorkspaceId
typeHost-minted workspace id — any non-empty string, unique within the set.
Signature:
type TilingWorkspaceId = string;
TilingWorkspacePanelElementProps
interfaceProps UseTilingWorkspaceTabsResult.panelProps hands to a workspace's tabpanel.
Signature:
interface TilingWorkspacePanelElementProps
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | The owning tab's id. | |
| boolean |
| |
| string |
| |
| "tabpanel" | WAI-ARIA role. |
TilingWorkspacePlacement
typeWhere a leaf is seated when it enters a tree — the placement vocabulary the workspace-set ops (moveLeafToWorkspace, showInWorkspace, orphan repair) and the engine's insertLeafInto share. Each variant maps onto one existing tree reducer's insert half:
root— a new root-level split on the current root's axis, the leaf onside(moveLeafToRoot). The one placement that exists in every tree; the default is{ kind: "root", side: "second" }(reading order: last). -adjacent— besidetargetLeafIdonplacement's side, wrapping both in a new split (insertLeafAdjacent). -split-container— into the existing splitsplitIdonside(moveLeafToSplitContainer). -group— appended as the active member of groupgroupId(addLeafToGroup). -region— beside the reading-order first (start) or last (end) leaf of the destination tree:leftof the first /rightof the last, via the same pathadjacentuses (insertLeafAdjacent). Reading order is the flattenqueryWorkspaceSet(set).leafIds(workspaceId)returns (group members in tab order). When that neighbour is a group member, the group itself is the seat neighbour — wrap the group, do not enter it.
A placement whose target (leaf / split / group id) is absent from the destination tree falls back to { kind: "root", side: "second" }; a null destination becomes the bare leaf. TILING_DEFAULT_WORKSPACE_PLACEMENT stays { kind: "root", side: "second" }.
Signature:
type TilingWorkspacePlacement = {
readonly kind: "root";
readonly side: "first" | "second";
} | {
readonly kind: "adjacent";
readonly targetLeafId: string;
readonly placement: TilingMovePlacement;
} | {
readonly kind: "split-container";
readonly splitId: string;
readonly side: "first" | "second";
} | {
readonly kind: "group";
readonly groupId: string;
} | {
readonly kind: "region";
readonly region: "start" | "end";
};
References: TilingMovePlacement
TilingWorkspacesCapability
interfaceWorkspace-set navigation capability, the object form of interaction.workspaces. A bare boolean at the workspaces slot is shorthand for { enable }.
Signature:
interface TilingWorkspacesCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) Enable the workspace commands. When | ||
boolean | (Optional) Default | ||
TilingSpringLoadCapability | null | (Optional) Spring-loaded workspace-tab drop (N3): hold a dragged pane over another workspace's tab for | ||
(Optional) Gesture switching between neighbouring workspaces (N1). Both inputs are opt-in (default |
TilingWorkspaceSet
interfaceThe ordered collection of workspaces plus the active one — the value a set host owns in state and hands to TilingRenderer via the workspaces prop. Array order is tab order.
Signature:
interface TilingWorkspaceSet
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| The rendered workspace. Always one of | ||
| ReadonlyArray<TilingWorkspace> | Tab order. At least one workspace. |
TilingWorkspaceSetCommitReason
typeWhy useTilingWorkspaceSetController() handed a set to the host.
Signature:
type TilingWorkspaceSetCommitReason = "tree" | "lifecycle" | "move" | "reveal";
TilingWorkspaceSetController
interfaceThe controller useTilingWorkspaceSetController() returns. Pass set and onWorkspacesChange to <TilingRenderer> and useTilingWorkspaceTabs.
Signature:
interface TilingWorkspaceSetController
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| boolean |
| |
| () => TilingWorkspaceId | null | mint + | |
| () => void | Commit pending trees now (reason | |
| (tileId: string, to: TilingWorkspaceId, placement?: TilingWorkspacePlacement) => void | Move a tile to | |
| (next: TilingWorkspaceSet) => void | Pass to | |
| boolean |
| |
| (id: TilingWorkspaceId) => ReadonlyArray<string> | Delete a workspace. Returns | |
| (id: TilingWorkspaceId, name: string) => void | Rename a workspace. No-op when read-only or the engine refuses. | |
| (scope: "workspace" | "all", workspaceId?: string) => boolean | Restore the seed: | |
| (tileId: string) => TilingRevealTileResult | null | Reveal a tile ( | |
| The VIEWED set: | ||
| (id: TilingWorkspaceId) => void | Switch the viewed workspace. Local when |
TilingWorkspaceSetControllerOptions
interfaceOptions of useTilingWorkspaceSetController().
Signature:
interface TilingWorkspaceSetControllerOptions
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| (Optional) Seed set for TilingWorkspaceSetController.reset and TilingWorkspaceSetController.atDefaults. Absent → | ||
| number | (Optional) Optional cap for | |
| () => string | Host policy for | |
| (existing: ReadonlyArray<TilingWorkspace>) => string | Host policy for | |
| (next: TilingWorkspaceSet, reason: TilingWorkspaceSetCommitReason) => void | Receives the set the host should persist. | |
| boolean | (Optional) When true: switches stay local, nothing commits (view-only board). Default false. | |
| number | (Optional) Tree edits (divider, rearrange, tab activation, in-tree drop) coalesce for this long. Default 400. | |
| Authoritative set from the host (e.g. its persisted document). |
TilingWorkspaceSetIssue
interfaceOne integrity finding of workspaceSetIssues().
Signature:
interface TilingWorkspaceSetIssue
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| Which invariant is broken. | ||
| string | (Optional) The leaf the finding is about, when leaf-local. | |
| string | Human-readable summary. | |
| string | (Optional) The tile the finding is about, when tile-local. | |
| (Optional) The workspace the finding is about, when it is workspace-local. |
TilingWorkspaceSetIssueKind
typeThe set-level invariants workspaceSetIssues() checks (per-tree structure is isStructurallyValidLayout):
no-workspaces— the set is empty. -empty-workspace-id/duplicate-workspace-id— workspace ids are non-empty and unique. -invalid-workspace-name— a name that fails normalizeWorkspaceName(). -active-workspace-missing—activeIdnames no workspace. -invalid-tree— a tree failsisStructurallyValidLayout(duplicate leaf or tile inside one tree, empty ids, bad split). -leaf-tile-binding-mismatch— a leaf id bound to a different tile id (or a tile id to a different leaf id) than in an earlier workspace; a leaf id resolves to ONE logical pane across the set. -orphan-tile— anexpectedTileIdsmember seated in no workspace. -unknown-tile— a seated tile absent fromexpectedTileIds.
Signature:
type TilingWorkspaceSetIssueKind = "no-workspaces" | "empty-workspace-id" | "duplicate-workspace-id" | "invalid-workspace-name" | "active-workspace-missing" | "invalid-tree" | "leaf-tile-binding-mismatch" | "orphan-tile" | "unknown-tile";
TilingWorkspaceSetQuery
interfaceRead-only view over a set — the set-level analogue of queryTilingLayout.
Signature:
interface TilingWorkspaceSetQuery
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| The active workspace (the first one when | ||
| (workspaceId: TilingWorkspaceId) => number | Number of leaves a workspace shows ( | |
| (workspaceId: TilingWorkspaceId) => ReadonlyArray<string> | Every leaf id of one workspace in reading order (group members in tab order). | |
| (workspaceId: TilingWorkspaceId, direction: TilingPaneCycleDirection) => TilingWorkspaceId | null | The workspace after / before | |
| (workspaceId: TilingWorkspaceId) => ReadonlyArray<string> | Every tile id of one workspace in reading order. | |
| (id: TilingWorkspaceId) => TilingWorkspace | null | Look a workspace up by id. | |
| (leafId: string) => ReadonlyArray<TilingWorkspaceId> | Ids of the workspaces showing | |
| (tileId: string) => ReadonlyArray<TilingWorkspaceId> | Ids of the workspaces showing |
TilingWorkspaceSetRepairReason
typeWhat repairWorkspaceSet() did, in application order:
workspace-created— empty set → onemain/ "Main" workspace. -workspace-dropped— a workspace with an empty or duplicate id was removed; leaves shown only there moved to the kept workspace. -workspace-renamed— an invalid name was replaced. -tree-rebuilt— a structurally invalid tree was rebuilt from its distinct leaves in reading order. -leaf-rebound— a leaf whose id / tile binding disagreed with an earlier seat was rewritten to the canonical id. -leaf-removed— a mismatched seat that could not be rebound was removed. -unknown-tile-pruned— a seat of a tile outsideexpectedTileIdswas removed. -orphan-seated— an expected tile shown nowhere was seated. -active-reset— a danglingactiveIdnow names the first workspace.
Signature:
type TilingWorkspaceSetRepairReason = "workspace-created" | "workspace-dropped" | "workspace-renamed" | "tree-rebuilt" | "leaf-rebound" | "leaf-removed" | "unknown-tile-pruned" | "orphan-seated" | "active-reset";
TilingWorkspaceSetRepairResult
interfaceThe result of repairWorkspaceSet().
Signature:
interface TilingWorkspaceSetRepairResult
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| ReadonlyArray<TilingWorkspaceSetRepairReason> | Every rule that fired, in application order, one entry per fix. | |
| The repaired set (the input by reference when nothing was wrong). |
TilingWorkspaceSwipeConfig
interfaceTunables of the swipe FSM. Every field has a default in TILING_WORKSPACE_SWIPE_DEFAULTS; interaction.workspaces.switch.wheelSwipe accepts a partial override.
Signature:
interface TilingWorkspaceSwipeConfig
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
number |
| ||
number | Peak sample velocity (CSS px per ms, measured towards the target) at or above which the swipe commits regardless of distance — the flick. Default | ||
number | After a commit every | ||
Keyboard modifier that must be held on the arming samples of a wheel run ( | |||
number | Horizontal travel (CSS px, summed wheel | ||
number | A wheel gesture ends when no | ||
number | Fallback viewport width (CSS px) used to normalise travel into | ||
boolean | Whether a swipe past the first / last workspace wraps around. |
TilingWorkspaceSwipeModifier
typeKeyboard modifier a wheel swipe must be held with on its arming samples. null (default) requires none — today's behaviour. Touch swipes ignore this (no keyboard). ctrl is never a swipe modifier: ctrlKey is always a pinch-zoom reject.
Signature:
type TilingWorkspaceSwipeModifier = "meta" | "alt" | "shift" | null;
TilingWorkspaceSwipePhase
typeThe lifecycle phase of the swipe FSM state (TilingWorkspaceSwipeState).
Signature:
type TilingWorkspaceSwipePhase = "idle" | "armed" | "tracking" | "settling" | "lockout";
TilingWorkspaceSwipeScope()
functionShares one swipe store between a set-mode TilingRenderer and host chrome (a tab strip) so useWorkspaceSwipe() outside the renderer follows the gesture. Optional: without it the renderer keeps a private store.
Signature:
declare function TilingWorkspaceSwipeScope(input: {
children?: React.ReactNode;
}): React.ReactElement;
Parameters
Parameter | Type | Description |
|---|---|---|
{ children, } | (not declared) | |
input | { children?: React.ReactNode; } |
Returns:
React.ReactElement
TilingWorkspaceSwipeSnapshot
interfaceThe host-facing projection of the FSM state a tab strip indicator reads.
Signature:
interface TilingWorkspaceSwipeSnapshot
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
The lifecycle phase. | |||
number | Signed progress | ||
TilingWorkspaceSwipeTarget | null | The side the swipe heads to; |
TilingWorkspaceSwipeTarget
typeWhich side of the active workspace a swipe is heading to.
Signature:
type TilingWorkspaceSwipeTarget = "prev" | "next";
TilingWorkspaceSwitchCapability
interfaceGesture switching, the switch slot of interaction.workspaces. A swipe ends as a cycle-workspace command through the ordinary dispatch path and reports onWorkspaceSwitch({ via: "swipe" }). While either input is on the renderer root carries overscroll-behavior-x: contain; with touchSwipe also touch-action: pan-y.
Signature:
interface TilingWorkspaceSwitchCapability
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | (Optional) One-finger horizontal pan on the viewport. Default | ||
(Optional) Visual transition played by the set-mode renderer on every | |||
boolean | Partial<TilingWorkspaceSwipeConfig> | (Optional) Trackpad / horizontal-wheel swipe. |
TilingWorkspaceSwitchEvent
interfaceSemantic event beside onWorkspacesChange when activeId changes.
Signature:
interface TilingWorkspaceSwitchEvent
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
string | Workspace id that was active before the switch. | ||
string | Workspace id that is active after the switch. | ||
Which renderer surface initiated the switch. |
TilingWorkspaceSwitchVia
typeHow a workspace switch was initiated through the renderer. "swipe" is a trackpad / touch swipe (interaction.workspaces.switch). "spring-load" is the spring-loaded tab drop (interaction.workspaces.springLoad): the dwell elapsed, the dragged leaf moved into the hovered workspace and the set switched there. "tab-drop" is a plain (non-spring-loaded) drag release on a workspace tab with interaction.workspaces.followMovedLeaf: true: the leaf moved and the set switched in the same onWorkspacesChange. "tab" is reserved for a host tab strip that routes through the same dispatch path.
Signature:
type TilingWorkspaceSwitchVia = "tab" | "tab-drop" | "key" | "command" | "swipe" | "spring-load" | "reveal";
TilingWorkspaceTab
interfaceOne workspace as the strip presents it.
Signature:
interface TilingWorkspaceTab
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| () => void | Switch to this workspace. | |
| Delete this workspace (refused for the last one). Returns the engine result. | ||
| string | The | |
| number | Position in the strip. | |
| boolean | Is this the set's active workspace. | |
| boolean | Is a dragged leaf hovering this tab (the renderer's resolved workspace-tab hover). | |
| boolean | Does this tab hold the roving focus (the one | |
| (name: string) => boolean | Rename; | |
| () => void | Fire the host's close affordance ( | |
| () => void | Fire the host's rename affordance ( | |
| Spread onto the | ||
| The workspace this tab presents. |
TilingWorkspaceTabDragHover
interface"Pointer is over a WORKSPACE TAB" — the engine-settled external target. In workspace-set mode the renderer applies moveLeafToWorkspace(leafId, workspaceId, placement) on release and reports the next set through onWorkspacesChange (claim-before-settle: no cancel fly-back, no host hit-test). In single-layout mode it behaves like TilingExternalDropHover — the host's onExternalDrop claims. targetId is the tab's own id (what the ghostChip slot receives).
Signature:
interface TilingWorkspaceTabDragHover
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| "workspace-tab" | Discriminant. | |
| (Optional) Where in the destination tree the leaf lands. Undefined → root, second side. | ||
| Pointer position over the tab (window-client CSS px). | ||
| string | The tab's own id (what the | |
| string | The workspace the tab stands for — the |
TilingWorkspaceTabElementProps
interfaceProps TilingWorkspaceTab.tabProps hands to the tab element.
Signature:
interface TilingWorkspaceTabElementProps
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| string | The matching | |
| boolean |
| |
| "" | undefined | Present ( | |
| "" | undefined | Present ( | |
| string | The workspace id, for styling hooks and tests. | |
| string | undefined | Theme | |
| string |
| |
| () => void | Activates the workspace and takes the roving focus. | |
| () => void | Takes the roving focus. | |
| (event: React.KeyboardEvent<HTMLElement>) => void | The keyboard model (arrows / Home / End / Enter / Space / F2 / Delete). | |
| (element: HTMLElement | null) => void | Registers the element for focus management and drop hit-testing. | |
| "tab" | WAI-ARIA role. | |
| 0 | -1 | Roving focus: | |
| "button" | Keeps a |
TilingWorkspaceTabKeyAction
typeWhat a key press on a focused workspace tab asks for. focus moves the roving focus (arrows wrap; Home / End jump); activate switches to the focused workspace (Enter / Space); rename (F2) and close (Delete) are affordance requests the host answers. null — not a tab-strip key.
Signature:
type TilingWorkspaceTabKeyAction = {
readonly kind: "focus";
readonly index: number;
} | {
readonly kind: "activate";
} | {
readonly kind: "rename";
} | {
readonly kind: "close";
};
TilingWorkspaceTablistElementProps
interfaceProps UseTilingWorkspaceTabsResult.tablistProps hands to the tablist element.
Signature:
interface TilingWorkspaceTablistElementProps
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| The strip's axis (drives which arrow keys move focus). | ||
| string | undefined | Theme | |
| "tablist" | WAI-ARIA role. |
TilingWorkspaceTabs()
functionRender-prop form of useTilingWorkspaceTabs(). Paints nothing itself.
Signature:
declare function TilingWorkspaceTabs(input: TilingWorkspaceTabsProps): React.ReactElement;
Parameters
Parameter | Type | Description |
|---|---|---|
{ children, ...options } | (not declared) | |
input |
Returns:
React.ReactElement
Example
<TilingWorkspaceTabs workspaces={set} onWorkspacesChange={setSet}>
{({ tabs, tablistProps }) => (
<div {...tablistProps}>
{tabs.map((tab) => (
<button key={tab.workspace.id} {...tab.tabProps}>{tab.workspace.name}</button>
))}
</div>
)}
</TilingWorkspaceTabs>
TilingWorkspaceTabsOrientation
typeWhich axis the tab strip lays out along; picks the arrow keys that move focus.
Signature:
type TilingWorkspaceTabsOrientation = "horizontal" | "vertical";
TilingWorkspaceTabsProps
interfaceProps of TilingWorkspaceTabs(): the hook options plus the render prop.
Signature:
interface TilingWorkspaceTabsProps extends UseTilingWorkspaceTabsOptions
Extends: UseTilingWorkspaceTabsOptions
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| (tabs: UseTilingWorkspaceTabsResult) => React.ReactNode | Receives the hook result; return the strip's markup. |
TilingWorkspaceTabTarget
interfaceOne workspace tab registered as a drop target: its id, workspace and measured rect.
Signature:
interface TilingWorkspaceTabTarget
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| (Optional) Where a dropped leaf lands in that workspace. Undefined → root, second side. | ||
| Where the tab sits, in window-client coordinates. | ||
| string | The tab's own id — what | |
| string | The workspace the tab stands for (the |
TilingWorkspaceTransitionConfig
interfaceDuration / easing / default mode for a workspace switch. Matches the planned interaction.workspaces.switch.transition capability plus the theme workspaceTransition tokens.
Signature:
interface TilingWorkspaceTransitionConfig
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| number | Timed commit / cancel duration in milliseconds. Default 200. | |
| string | CSS | |
| Paint mode. Default |
TilingWorkspaceTransitionDirection
typeTab-order direction of a workspace switch.
"next" slides the clone toward −100 % (incoming from +100 %); "prev" slides the clone toward +100 % (incoming from −100 %).
Signature:
type TilingWorkspaceTransitionDirection = "prev" | "next";
TilingWorkspaceTransitionLayerStyle
interfaceTilingWorkspaceTransitionMode
typeHow a workspace switch is painted.
"none"— instant; no clone, no motion (default, and the reduced-motion resolution). -"slide"— outgoing clone and live incoming treetranslateXin opposite directions. -"fade"— outgoing clone opacity1 → 0over the live incoming tree.
Signature:
type TilingWorkspaceTransitionMode = "none" | "slide" | "fade";
TilingWorkspaceTransitionTransform
interfaceOutgoing clone + incoming live-tree styles at one progress sample.
Signature:
interface TilingWorkspaceTransitionTransform
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| Live incoming tree. | ||
| Frozen outgoing clone. |
TilingWorkspaceTreeDiff
interfaceOne workspace whose layout reference differs between two sets.
Signature:
interface TilingWorkspaceTreeDiff
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| TilingLayoutNode | null | The incoming tree ( | |
| Workspace whose tree changed. |
TilingWorkspaceTreeMap
typeWorkspace id → tree overlay (local edits or trees awaiting a commit).
Signature:
type TilingWorkspaceTreeMap = ReadonlyMap<TilingWorkspaceId, TilingLayoutNode | null>;
References: TilingWorkspaceId, TilingLayoutNode
TouchInputSample
interfaceOne touch position sample (TOUCH_START / TOUCH_MOVE).
Signature:
interface TouchInputSample
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean | The scroll chain under the TOUCH-START target can still scroll on X in the finger's direction (judged per move; | ||
number | Monotonic clock reading (ms). | ||
number | null | Viewport width (CSS px) at the sample; | ||
number | Finger client X. | ||
number | Finger client Y. |
transitionTransform()
functionTransform / opacity for the outgoing clone and the live incoming tree.
progress is unit (0 = outgoing fully covering, 1 = incoming fully covering). Slide uses translateX only (compositor-friendly). Fade keeps both transforms at none and drops outgoing opacity 1 → 0 (incoming stays 1 — the live tree sits underneath). "none" is identity on both sides.
Signature:
declare function transitionTransform(progress: number, direction: TilingWorkspaceTransitionDirection, mode: TilingWorkspaceTransitionMode): TilingWorkspaceTransitionTransform;
Parameters
Parameter | Type | Description |
|---|---|---|
progress | number | |
direction | ||
mode |
Returns:
unitProgressFromSigned()
functionConvert N1 swipe progress (−1..1) into unit progress (0..1) for direction. Positive signed progress is "next"; negative is "prev". The opposite sign clamps to 0.
Signature:
declare function unitProgressFromSigned(signed: number, direction: TilingWorkspaceTransitionDirection): number;
Parameters
Parameter | Type | Description |
|---|---|---|
signed | number | |
direction |
Returns:
number
useTilingWorkspaceSetController()
functionControlled workspace-set session: the host owns the persisted document (value / onCommit); the controller owns the in-flight tree overlays, the debounce timer, and the read-only local active id.
Every mutation first folds pending trees into the base so a lifecycle commit never drops a tree edit made a moment earlier. Same-reference engine results are ignored (no commit). Unmount with pending trees flushes synchronously via onCommit.
Signature:
declare function useTilingWorkspaceSetController(options: TilingWorkspaceSetControllerOptions): TilingWorkspaceSetController;
Parameters
Parameter | Type | Description |
|---|---|---|
options |
Returns:
Example
const ctl = useTilingWorkspaceSetController({
value: persisted,
onCommit: (next, reason) => persist(next, reason),
readOnly,
mintWorkspaceId: () => crypto.randomUUID(),
nextWorkspaceName: (existing) => `Workspace ${existing.length + 1}`,
});
const tabs = useTilingWorkspaceTabs({
workspaces: ctl.set,
onWorkspacesChange: ctl.onWorkspacesChange,
});
return (
<TilingRenderer
workspaces={ctl.set}
onWorkspacesChange={ctl.onWorkspacesChange}
tiles={tiles}
{...tabs.rendererProps}
/>
);
useTilingWorkspaceTabs()
functionHeadless workspace tab strip over a controlled TilingWorkspaceSet. Returns per-tab props (role="tab", aria-selected, roving tabIndex, keyboard handling), tablist / tabpanel props, rename / close affordances, and the renderer props that make every tab a native drop target. Renders nothing itself.
Signature:
declare function useTilingWorkspaceTabs(options: UseTilingWorkspaceTabsOptions): UseTilingWorkspaceTabsResult;
Parameters
Parameter | Type | Description |
|---|---|---|
options |
Returns:
UseTilingWorkspaceTabsOptions
interfaceOptions of useTilingWorkspaceTabs().
Signature:
interface UseTilingWorkspaceTabsOptions
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| "manual" | "automatic" | (Optional) | |
| (workspace: TilingWorkspace) => TilingWorkspacePlacement | undefined | (Optional) Where a leaf dropped on a workspace's tab lands in that workspace. Undefined → the engine default (root, second side). | |
| string | (Optional) Prefix for the generated DOM ids: tabs are | |
| (workspace: TilingWorkspace) => void | (Optional) The close affordance: Delete on a focused tab (or | |
| (workspace: TilingWorkspace) => void | (Optional) The rename affordance: F2 on a focused tab (or | |
| (workspaces: TilingWorkspaceSet) => void | Receives the next set for every edit the strip performs (switch, rename, close, create). | |
| (Optional) Arrow keys that move focus. Default | ||
| (Optional) Theme whose | ||
| The controlled workspace set the strip presents (the same object the renderer gets). |
UseTilingWorkspaceTabsResult
interfaceWhat useTilingWorkspaceTabs() returns.
Signature:
interface UseTilingWorkspaceTabsResult
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| TilingWorkspaceTab | null | The set's active tab ( | |
| (input: CreateWorkspaceInput) => boolean | Create a workspace ( | |
| string | null | The workspace whose tab a dragged leaf is hovering, else | |
| (workspaceId: string) => void | Move the roving focus to a tab (and focus its element). | |
| (workspaceId: string) => TilingWorkspacePanelElementProps | Props for the | |
| { readonly resolveExternalDragHover: TilingExternalDragHoverResolver; readonly onExternalDragHoverChange: (hover: TilingExternalDragHover | null) => void; } | Spread onto | |
| Spread onto the | ||
| ReadonlyArray<TilingWorkspaceTab> | One entry per workspace, in set order. |
useWorkspaceSwipe()
functionHeadless read of the workspace swipe in progress: { progress, target, phase }. progress is -1..1 (negative = towards the previous workspace), target the side the swipe heads to, phase the FSM phase. Live only under a TilingWorkspaceSwipeScope() that also contains the set-mode TilingRenderer; elsewhere it is the idle snapshot. Use it to move a tab indicator in step with the gesture.
Signature:
declare function useWorkspaceSwipe(): TilingWorkspaceSwipeSnapshot;
Returns:
useWorkspaceTransition()
functionImperative workspace-switch transition. begin freezes the outgoing viewport; scrub follows N1 swipe progress; finish runs the timed commit / cancel curve and drops the clone.
Signature:
declare function useWorkspaceTransition(options: UseWorkspaceTransitionOptions): UseWorkspaceTransitionResult;
Parameters
Parameter | Type | Description |
|---|---|---|
options |
Returns:
UseWorkspaceTransitionOptions
interfaceOptions of useWorkspaceTransition().
viewportRef is the set-mode viewport root (position: relative; overflow: hidden). begin clones that node.
Signature:
interface UseWorkspaceTransitionOptions
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| number | (Optional) Timed commit / cancel duration. Default theme token or 200. | |
| string | (Optional) CSS easing token (stored for hosts; the rAF path uses ease-out cubic). | |
| (Optional) Requested paint mode. Resolved against reduced-motion and capture degradation. Default | ||
| (kind: WorkspaceTransitionSettleKind) => void | (Optional) Fired after the clone is removed. | |
| boolean | (Optional) Override | |
| React.RefObject<HTMLElement | null> | Viewport whose outgoing tree is cloned on |
UseWorkspaceTransitionResult
interfaceImperative handle returned by useWorkspaceTransition().
Signature:
interface UseWorkspaceTransitionResult
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| Resolved mode of the in-flight transition ( | ||
| (input: { direction: TilingWorkspaceTransitionDirection; mode?: TilingWorkspaceTransitionMode; }) => void | Capture the current viewport (outgoing) and arm the stage. Call **before** the live tree switches to the incoming workspace. A second | |
| Direction of the in-flight transition ( | ||
| (kind: WorkspaceTransitionSettleKind) => void | Run the timed curve to the end ( | |
| React.CSSProperties | Style for the live incoming wrapper (transform + opacity). | |
| HTMLElement | null | Frozen clone ( | |
| React.CSSProperties | Style for the clone overlay (transform + opacity). | |
| Current phase. | ||
| (progress: number) => void | Scrub with N1 swipe progress ( | |
| number | Unit progress in |
VIEW_CAPTURE_CANVAS_PIXEL_BUDGET
variablePixel-area budget for a single canvas copy (width * height). Above this the capture is marked degraded so the caller falls back from "slide" to "fade" (a 16 megapixel bitmap is too large to snapshot synchronously).
Signature:
VIEW_CAPTURE_CANVAS_PIXEL_BUDGET: number
viewedWorkspaceSet()
functionThe set a renderer / tab strip should see: value with local tree overlays and an optional local activeId (read-only view switching, or an optimistic switch that has not landed in value yet). Same reference when nothing overlays.
Signature:
declare function viewedWorkspaceSet(value: TilingWorkspaceSet, localTrees: TilingWorkspaceTreeMap, localActiveId?: TilingWorkspaceId | null): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
value | ||
localTrees | ||
localActiveId | TilingWorkspaceId | null | (Optional) |
Returns:
WheelInputSample
interfaceOne wheel sample, in the FSM's WHEEL event shape plus the port's annotations.
Signature:
interface WheelInputSample
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
boolean |
| ||
boolean | The scroll chain under the event target can still scroll on X in | ||
boolean |
| ||
number | Horizontal scroll delta (CSS px, | ||
number | Vertical scroll delta (CSS px). | ||
boolean |
| ||
boolean |
| ||
number | Monotonic clock reading (ms). | ||
number | null | Viewport width (CSS px) at the sample; |
WheelTouchInputListener
interfaceThe sink a WheelTouchInputPort delivers samples to.
Signature:
interface WheelTouchInputListener
Methods
Method | Description |
|---|---|
The finger lifted or the browser took the touch ( | |
The tracked touch pointer moved. | |
A primary touch pointer went down on the host. | |
One |
WheelTouchInputPort
interfaceThe wheel / touch subscription the swipe host runs the FSM on.
subscribeattaches the listeners (idempotent per listener) and returns the detach function. -setTracking(true)tells the port the FSM owns the gesture: the adapter may now cancel the browser default (a non-passivewheellistener that callspreventDefault, stopping history back-swipe). Outside tracking the listeners are passive so the page scrolls normally.
Signature:
interface WheelTouchInputPort
Methods
Method | Description |
|---|---|
The FSM is ( | |
Attach |
WORKSPACE_KEY_BINDINGS
variableOptional workspace-navigation key-binding fragment. Hosts merge it into interaction.keyBindings.bindings — it is **not** part of defaultKeyBindings / TILING_KEYMAP_DEFAULTS (no default chords; hosts opt in). switch-workspace uses 1-based index (and) move-leaf-to-workspace uses direction) so the fragment does not need a live workspace set at construction time; the set-mode wrapper resolves those against the current tab order at dispatch.
Alt+ArrowLeft/Alt+ArrowRight→cycle-workspaceprevious / next -Alt+Digit1..Alt+Digit9→switch-workspaceto the n-th workspace -Alt+Shift+ArrowLeft/Alt+Shift+ArrowRight→move-leaf-to-workspaceprevious / next withfollow: true
Signature:
WORKSPACE_KEY_BINDINGS: ReadonlyArray<TilingKeyBinding>
workspaceSetEquals()
functionStructural equality of two workspace sets: tab-order ids, names, activeId, and every persisted layout-tree field (leaf ids / tile ids / sizing / collapse / min-bbox, split axis / ratio / children / master flags, group ids / members / active tab). Same-reference inputs are equal.
Signature:
declare function workspaceSetEquals(a: TilingWorkspaceSet, b: TilingWorkspaceSet): boolean;
Parameters
Parameter | Type | Description |
|---|---|---|
a | ||
b |
Returns:
boolean
WorkspaceSetIntegrityOptions
interfaceOptions for workspaceSetIssues() / repairWorkspaceSet().
Signature:
interface WorkspaceSetIntegrityOptions
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| ReadonlyArray<string> | (Optional) The host's tile registry, set-wide. When given, every expected tile must be seated in ≥ 1 workspace ( |
workspaceSetIssues()
functionWalk a set and report every violated invariant (see TilingWorkspaceSetIssueKind). Empty array = the set is sound. Pure.
Signature:
declare function workspaceSetIssues(set: TilingWorkspaceSet, options?: WorkspaceSetIntegrityOptions): ReadonlyArray<TilingWorkspaceSetIssue>;
Parameters
Parameter | Type | Description |
|---|---|---|
set | ||
options | (Optional) |
Returns:
ReadonlyArray<TilingWorkspaceSetIssue>
workspaceSetLayoutMap()
functionPer-workspace layout refs of a set — the last-committed map adoptIncomingWorkspaceTrees() reads.
Signature:
declare function workspaceSetLayoutMap(set: TilingWorkspaceSet): Map<TilingWorkspaceId, TilingLayoutNode | null>;
Parameters
Parameter | Type | Description |
|---|---|---|
set |
Returns:
Map<TilingWorkspaceId, TilingLayoutNode | null>
workspaceSetOfLayout()
functionSingle-tree sugar: the one-workspace set a layout / onLayoutChange host is projected onto ({ workspaces: [{ id, name, layout }], activeId: id }).
Signature:
declare function workspaceSetOfLayout(layout: TilingLayoutNode | null, id?: TilingWorkspaceId, name?: string): TilingWorkspaceSet;
Parameters
Parameter | Type | Description |
|---|---|---|
layout | TilingLayoutNode | null | |
id | (Optional) | |
name | string | (Optional) |
Returns:
workspaceSetsAlign()
functiontrue when both sets have the same tab order, names, layout refs, and activeId.
Signature:
declare function workspaceSetsAlign(left: TilingWorkspaceSet, right: TilingWorkspaceSet): boolean;
Parameters
Parameter | Type | Description |
|---|---|---|
left | ||
right |
Returns:
boolean
WorkspaceTransitionPhase
typePhase of useWorkspaceTransition().
Signature:
type WorkspaceTransitionPhase = "idle" | "scrubbing" | "settling";
WorkspaceTransitionSettleKind
typeWhy the stage released the clone.
Signature:
type WorkspaceTransitionSettleKind = "commit" | "cancel";
WorkspaceTransitionStage
variableVisual workspace-switch stage: an absolutely positioned overlay (the frozen outgoing clone) over the live incoming tree. Drive it through the imperative ref (begin / scrub / finish) or feed N1 progress.
Signature:
WorkspaceTransitionStage: React.ForwardRefExoticComponent<WorkspaceTransitionStageProps & React.RefAttributes<UseWorkspaceTransitionResult>>
WorkspaceTransitionStageProps
interfaceProps of WorkspaceTransitionStage. The stage wraps the live incoming tree and paints the clone as an absolutely positioned overlay. Imperative control is the ref (UseWorkspaceTransitionResult); progress is the N1 swipe feed (non-null → scrub).
Signature:
interface WorkspaceTransitionStageProps
Properties
Property | Modifiers | Type | Description |
|---|---|---|---|
| React.ReactNode | (Optional) Live incoming tree. | |
| string | (Optional) Optional class on the stage root. | |
| number | (Optional) Timed duration. Default theme / 200 ms. | |
| string | (Optional) CSS easing token. Default theme / drag-hop easing. | |
| (Optional) Requested paint mode. Default | ||
| (kind: WorkspaceTransitionSettleKind) => void | (Optional) Fired after the clone is removed. | |
| number | null | (Optional) Signed swipe progress ( | |
| boolean | (Optional) Override | |
| React.RefObject<HTMLElement | null> | Viewport cloned on |