Documentation
API: primitives (@fragiola/dockable-react)

Dockable.PopoutTrigger

A button that pops the selected tab, or the whole tabset, out into a window, and docks it back from the window.

"A button, inside Dockable.TabSet, that pops the selected tab (or the whole tabset) out into a window, and, in a window, docks it back into the main layout. It renders nothing when neither is possible (popouts unsupported, the model refuses it, no tab): the engine decides (engine.get("popout-mode-by", { nodeId })). It has no name of its own: give it an aria-label, or name it from the state when the two actions need different names (render={(props, state) => <button {...props} aria-label={state.mode === "dock" ? … : …} />})."

<Dockable.TabSet node={tabset}>
    <div className="flex">
        <Dockable.TabList<Types>>{(tab) => <Dockable.Tab node={tab}>{tab.label}</Dockable.Tab>}</Dockable.TabList>
        <Dockable.PopoutTrigger aria-label="Pop out" className="data-[mode=dock]:rotate-180">
            <ExternalLinkIcon aria-hidden />
        </Dockable.PopoutTrigger>
    </div>
    <Dockable.TabSetContent />
</Dockable.TabSet>

One aria-label names both actions. To name each one, read mode in a render function:

<Dockable.PopoutTrigger
    render={(props, state) => (
        <button {...props} aria-label={state.mode === "dock" ? "Dock back" : "Pop out"} />
    )}
>
    <ExternalLinkIcon aria-hidden />
</Dockable.PopoutTrigger>

What a press runs

modetargetcommand
"popout""tab"tab.popout (engine.run("popout", { nodeId: tabId })): the tab moves into a new window layout, at its content area's screen rect
"popout""tabset"tabset.popout (engine.run("popout", { nodeId: tabsetId })): the whole tabset moves into a new window layout
"dock"eitherengine.run("dock-back", { nodeId: id }): when the tab (or the tabset's tabs) is all its window holds, window.close, which docks every tab back and removes the window; otherwise a batch of tab.move into the main layout's active tabset (its first tabset when none is active)

Every command goes through the model's middleware, so a veto stops it. A new window layout in the state is what opens the native window: the root's popout manager opens one for every window layout (see Dockable.Popout).

Props

PopoutTriggerProps<T> extends the common primitive props and the <button> attributes.

proptypedefaultdescription
target"tab" | "tabset""tab""tab" (default) pops out one tab; "tabset" pops out the whole tabset
nodeTabOf<T>the tabset's selected tabthe tab to act on; default: the enclosing tabset's selected tab
childrenReactNodenonethe button's content (an icon); the primitive renders no text of its own

State

PopoutTriggerState:

fieldtypedescription
mode"popout" | "dock"what a press does: open a window ("popout") or dock back into the main layout ("dock")
target"tab" | "tabset"what it acts on: the selected tab (or node), or the whole tabset

Data attributes and ARIA

attributevalue
data-layout-path<tabset path>/button/popout, e.g. /ts0/button/popout
data-mode"popout" or "dock"
data-target"tab" or "tabset"
aria-labelnone of its own: pass yours, or set it from state.mode in render
type"button"

Notes

  • In the main layout it renders when engine.can("popout", { nodeId: id }) is true: popouts are supported (supportsPopout) and model.can("tab.popout", { tabId }) (or tabset.popout) accepts it. The model refuses a tab whose resolved poppable is false (the built-in default: popout is opt-in, per tab or with defaults.tab.poppable), a pinned tab, and a tabset that is empty or holds a tab that does not allow it. A middleware veto hides it too.
  • In a window (model.is("node-in-window", { nodeId: id })) it always renders, in "dock" mode.
  • The engine actions it runs are public: engine.run("popout", { nodeId }) and engine.run("dock-back", { nodeId }), both returning a CommandResult. model.run("tab.popout", { tabId }) works as well, anywhere.