Maximize
Let a tabset fill its layout with the tabset.maximize command, and style the maximized state from data attributes.
Maximizing a tabset makes it fill its layout: every other tabset, every row that does not contain
it and every row splitter is hidden (structurally, display: none), so the maximized tabset takes
the whole root row, however deeply it is nested.
This differs from FlexLayout, which moves (portals) the maximized tabset over the layout. Dockable
hides what is off the maximized tabset's path instead: nothing remounts, and the content keeps
its moveable element. The rule is the core's model.is("node-hidden-by-maximize", { nodeId }), which
TabSet and Row read (TabSetState.hidden, RowState.hidden).
The command
tabset.maximize with value: true maximizes a tabset (and makes it the active one);
value: false restores it. It is refused when the tabset's maximizable resolves to false, and for
the only tabset of its layout (there is nothing to hide). model.can answers the same question
without running anything, so a button can hide itself:
import { type TabsetNode, useDockable } from "@fragiola/dockable-react";
type Types = { tabs: { card: undefined } };
function MaximizeButton({ tabset }: { tabset: TabsetNode<Types> }) {
const { model } = useDockable<Types>();
const maximized = model.is("tabset-maximized", { tabsetId: tabset.id });
if (!model.can("tabset.maximize", { tabsetId: tabset.id, value: true })) return null;
return (
<button
type="button"
aria-pressed={maximized}
aria-label={maximized ? "Restore" : "Maximize"}
onClick={() => model.run("tabset.maximize", { tabsetId: tabset.id, value: !maximized })}
>
{maximized ? <MinimizeIcon aria-hidden="true" /> : <MaximizeIcon aria-hidden="true" />}
</button>
);
}The command needs only the tabset: each layout has its own maximized tabset, so in a popout window
maximize applies to that window's layout, and model.is("tabset-maximized", { tabsetId }) answers for the
tabset's own layout. layoutId from useDockable is the layout the component renders in
(MAIN_LAYOUT or a window's id), which model.get("maximized-tabset", { layoutId }) takes.
A double click on the tab strip is a common shortcut; attach it to your header element, and skip presses on the tabs and buttons inside it:
<div
className="header"
onDoubleClick={(event) => {
if ((event.target as Element).closest('[role="tab"], button')) return;
model.run("tabset.maximize", {
tabsetId: node.id,
value: !model.is("tabset-maximized", { tabsetId: node.id }),
});
}}
>Maximizing is allowed unless maximizable: false, per tabset or for every tabset through
defaults.tabset.maximizable. Moving the maximized tabset (or a row holding it) elsewhere
restores the layout.
Styling the state
| attribute | on | meaning |
|---|---|---|
data-maximized | Dockable.Root | a tabset of the main layout is maximized |
data-maximized | Dockable.TabSet | this tabset is maximized |
<Dockable.TabSet node={node} className="border border-palette-line data-maximized:border-palette-ring" />The hidden tabsets and splitters need no styling: the primitives hide them. Their tabs' panels
are hidden too (Panel's data-visible is absent), and their content stays mounted.
Reading it
model.get("maximized-tabset", { layoutId }) returns the maximized tabset of a layout (the main
one without layout), or undefined. To re-render when it changes, select it with useModelState:
const maximized = useModelState((_state, model) => model.get("maximized-tabset")?.id);The maximized state is part of the layout: model.get("layout-json") saves it as the layout's maximized
(the tabset's id; a window layout has its own), and createModel or layout.load restores it.
Keyboard
There is no default key binding for maximize. Add one in your own key handler (the maximize example restores on Escape) and run the same command (see Keyboard and accessibility).