Dockable.Popout
The popout windows. The core opens a native window per window layout of the state, and this part portals your layout into it.
"The popout windows. Place it once, directly under Dockable.Root, with the model's registry as
its type argument (<Dockable.Popout<Types>>). The core keeps a native window open for every
window layout of the state; once one is ready (styles copied), the child function's result is
portalled into it. A node-less Dockable.Row inside renders that layout's root row, and panels
of tabs in that layout are positioned in the window. Pop a tab out with tab.popout."
<Dockable.Root
model={model}
popoutTitle={(layout) => `My app (${layout.id})`}
onPopoutOpen={(_layout, _window, doc) => {
doc.documentElement.dataset.theme = document.documentElement.dataset.theme;
}}
>
{/* … */}
<Dockable.Popout<Types> className="palette-surface bg-palette-base">
{() => (
<Dockable.Row<Types> renderSplitter={renderSplitter}>
{renderNode}
</Dockable.Row>
)}
</Dockable.Popout>
</Dockable.Root>A window is state: model.get("windows") lists one WindowLayout<T> per popout (its id,
its screen rect, its root row, its active and maximized tabsets). tab.popout and
tabset.popout add one, window.close removes one (docking its tabs back into the main layout),
and window.configure records its rect. A saved layout with windows reopens them when it loads.
model.run("tab.popout", { tabId: "chart-1" }); // a new window layout: the root opens its window
model.run("window.close", { windowId: "window-1" }); // its tabs dock back, the window closesProps
PopoutProps<T> extends the common primitive props. The common
props apply to each window's root element.
| prop | type | default | description |
|---|---|---|---|
children | (layout: WindowLayout<T>) => ReactNode | required | renders a window layout, usually <Dockable.Row>{renderNode}</Dockable.Row> |
The window itself is set up on Dockable.Root: popoutURL,
supportsPopout, openWindow, popoutMirrorRoot, popoutTitle, onPopoutOpen,
onPopoutClose.
State
PopoutState:
| field | type | description |
|---|---|---|
layoutId | string | the id of the window layout |
Data attributes
| attribute | value |
|---|---|
data-layout-path | the window layout's path, e.g. /sublayout1 |
Its tabsets and tabs carry paths under it: /sublayout1/ts0/tb0. n in /sublayout<n> is the
lowest number free when the window is first seen, kept while it is open (closing one window never
renames another's elements). The element it is portalled into (created by the core in the popout
document) carries data-dockable-popout (POPOUT_ATTRIBUTE), whose value is the window layout's
id.
Structural style
position: absolute; inset: 0: it fills the window and is the containing block the window's
panels are positioned in.
Behaviour
- Renders nothing for a window until it has loaded and its styles are copied.
- The core owns the windows: it opens one per window layout while the root is attached, and closes those whose layout left the state. Opening is idempotent per layout, and a release is deferred, so a React StrictMode remount keeps one window.
- Closing a window from the browser runs
window.close: its tabs dock back into the main layout's active tabset (its first tabset when none is active), andonPopoutCloseis called first. The content of those tabs moves back to the main document before the window goes, so it keeps its state. - Without popout support (
supportsPopoutfalse), or when the window cannot open (openWindowreturnsnull, a blockedwindow.open), the window layout docks back (window.close). - The window's screen rect is recorded with a transient
window.configurewhen it is resized, and by a poll (a window that only moves fires no event), only when it changed.
The core: PopoutManager
The React part only portals. The windows belong to the core's PopoutManager<T>, one per main
layout's engine, on the adapter's side (engine.adapter.getPopoutManager()), which an adapter for
another framework uses the same way (an app never needs it):
| member | description |
|---|---|
setOptions(options: PopoutOptions<T>) | the window options (below); the engine passes its popout option |
attach() / detach() | the main layout's engine was attached / detached: opens the windows of the state's window layouts / closes them after the current task, unless the engine is attached again |
sync() | opens a window for every window layout without one and closes those whose layout is gone (the engine calls it on every change) |
subscribe(listener), getSnapshot() | a window opened, became ready or closed |
getContentRoot(layoutId) | the element to render the window layout into, once its window is ready (undefined before) |
getLayoutEngine(layoutId) | the window layout's own LayoutEngine, created when its window opens |
getWindow(layoutId), getOpenLayoutIds() | a layout's native window; the layouts with an open window |
getPopoutURL(), isSupportsPopout() | the host page; whether window layouts open as popouts |
close(layoutId) | closes a layout's window now (the layout stays in the state) |
dispose() | closes every window and releases every resource |
PopoutOptions<T>, the engine's popout option (Dockable.Root fills it from its props):
| option | type | description |
|---|---|---|
popoutURL | string | the popout host page; default "popout.html". The window layout's id is passed as ?id= |
supportsPopout | boolean | whether window layouts open as popouts at all; default: the main window has a fine hover pointer (a desktop) |
title | (layout: WindowLayout<T>) => string | undefined | the popout document's title; with none, the host page's title is kept |
onPopoutOpen | PopoutCallback<T> | the popout document is ready, before its content renders (e.g. to set up a css-in-js cache) |
onPopoutClose | PopoutCallback<T> | the popout window is closing |
mirrorRoot | boolean | readonly string[] | copies the main document's <html> and <body> attributes into each popout and keeps them in sync |
openWindow | OpenWindow | opens the native window (default: the main window's open) |
OpenWindow is (url: string, name: string, features: string) => Window | null. The name is
scoped to the layout (dockable-<scope><layoutId>), so two models on one page never share a
window; features carries the window layout's rect (left, top, width, height). Inject it
to open windows another way (an Electron BrowserWindow, a test double), or return null to
refuse, which docks the window layout back.
PopoutCallback<T> is (layout: WindowLayout<T>, window: Window, document: Document) => void.
Style mirroring
A popout document gets the main document's styles, and keeps them in sync while it is open: the
<link rel="stylesheet"> and <style> elements of the main document's <head> (added, removed,
or with text edited in place), CSSOM rules inserted by css-in-js libraries (polled), and adopted
(constructable) stylesheets, copied into one <style> element. A window renders once its linked
stylesheets loaded, failed or timed out, so a blocked stylesheet never keeps it blank.
<html> lang is always copied, and <html> dir is always the main layout's direction (it
follows a flip); mirrorRoot copies more of <html> and <body>, never dir.
A <style> or <link> placed in <body> is not copied: keep the styles a popout needs in <head>.
| export | description |
|---|---|
POPOUT_ATTRIBUTE | "data-dockable-popout": the attribute of the element an adapter renders a window layout into |
See the Popouts guide.