Hooks
useDockable, useTabSet, useBorder, useTabOverflow, useSplitter, useDragNode, useDragSource, useDropZone and useTabSetDropState - the lower layer under the primitives.
The primitives are built on four hooks, exported as the lower layer. Use useDockable all the
time (it is how components inside the layout dispatch actions); use the others only when a
primitive's element does not fit your markup.
All of them must be called inside Dockable.Root.
useDockable
"The lower layer: the engine, model and layout of the enclosing Dockable.Root. Dispatch actions
with engine.doAction(Actions.x(...)) so they go through onAction."
import { Actions } from "@fragiola/dockable";
import { useDockable } from "@fragiola/dockable-react";
function ResetButton() {
const { engine, model } = useDockable();
const first = model.getFirstTabSet();
return (
<button type="button" onClick={() => first && engine.doAction(Actions.selectTab(first.getTabNodes()[0]?.getId() ?? ""))}>
Back to the first tab
</button>
);
}useDockable(): UseDockableResult
| field | type | description |
|---|---|---|
engine | LayoutEngine | the engine of the layout this component renders in (the main layout or a popout's) |
mainEngine | LayoutEngine | the main layout's engine |
model | Model | the model |
layoutId | string | the id of the layout this component renders in |
getLabel | GetLabel | undefined | the getLabel resolver given to Dockable.Root |
Inside tab content, engine is the engine of the window the tab is currently in; use
mainEngine for window-level things (isSupportsPopout(), moving a tab back to the main
layout).
useTabSet
"The lower layer of Dockable.TabSet: its state, measurement ref and activation handler."
function MyTabSet({ node, children }: { node: TabSetNode; children: ReactNode }) {
const { state, ref, onPointerDown } = useTabSet(node);
return (
<section ref={ref} onPointerDown={onPointerDown} data-active={state.active ? "" : undefined}>
{children}
</section>
);
}A custom tabset must still apply the structural flex sizing Dockable.TabSet sets (flex-grow
from node.getWeight(), the min/max sizes, display: none when state.hidden), and provide
the tabset to TabList/TabSetContent, which only Dockable.TabSet does. Prefer
Dockable.TabSet with render unless you need something it cannot express.
useTabSet(node: TabSetNode): UseTabSetResult
| field | type | description |
|---|---|---|
state | TabSetState | see below |
ref | RefCallback<HTMLElement> | callback ref for the tabset's element (measured as the tabset) |
onPointerDown | (event: PointerEvent<HTMLElement>) => void | makes the tabset active on a primary pointer press |
TabSetState:
| field | type | description |
|---|---|---|
active | boolean | the tabset is the model's active tabset |
maximized | boolean | the tabset is maximized |
hidden | boolean | another tabset of the layout is maximized, so this one is hidden |
empty | boolean | the tabset has no tabs |
dropTarget | boolean | the current drag would drop into or beside this tabset (its content, strip or a group) |
dropLocation | DropLocation | undefined | while it is the drop target: where the drag would dock relative to it |
dropRefused | boolean | the current drag is over this tabset, but a drop rule refuses it |
useBorder
"The lower layer of Dockable.Border: its state and measurement ref."
useBorder(node: BorderNode): UseBorderResult
| field | type | description |
|---|---|---|
state | BorderState | see Dockable.Border |
ref | RefCallback<HTMLElement> | callback ref for the border's strip (measured as the border's tab header) |
A custom strip must also provide the border to Dockable.TabList, which only Dockable.Border
does: prefer Dockable.Border with render.
useTabOverflow
"Tab overflow of a tabset or a border: which of its tabs are hidden because they do not fit in its
Dockable.TabList (the engine measures the list, the tabs and the Dockable.TabOverflowTrigger).
The selected tab is never hidden."
useTabOverflow(container: TabSetNode | BorderNode): UseTabOverflowResult
| field | type | description |
|---|---|---|
overflowing | boolean | some of the container's tabs do not fit, so they are hidden |
hidden | TabNode[] | the hidden tabs, in model order: what an overflow menu lists |
visible | TabNode[] | the tabs that stay in the strip, in model order |
It re-renders only when the container's hidden tabs change. See Tab overflow.
useSplitter
"The lower layer of Dockable.Splitter: a headless controller for the splitter before child
index (1-based) of node, its drag state and ARIA values."
function MySplitter({ node, index }: RowSplitterProps) {
const { controller, state, aria, hidden, ref } = useSplitter(node, index);
if (hidden) return null;
return (
<div
ref={ref}
role="separator"
tabIndex={0}
aria-orientation={aria.orientation}
aria-valuenow={aria.valueNow}
aria-valuetext={aria.valueText}
data-dragging={state.dragging ? "" : undefined}
onPointerDown={(event) => controller.onPointerDown(event.nativeEvent)}
onKeyDown={(event) => controller.onKeyDown(event.nativeEvent)}
/>
);
}useSplitter(node: RowNode | BorderNode, index = 0): UseSplitterResult (a border's splitter
has no index)
| field | type | description |
|---|---|---|
controller | SplitterController | the headless controller: onPointerDown, onKeyDown, getAria, isHidden, … |
state | ISplitterState | dragging and previewOffset (see Splitter) |
aria | ISplitterAria | orientation, valueNow, valueMin, valueMax, valueText |
hidden | boolean | row splitters are hidden while a tabset is maximized |
ref | RefCallback<HTMLElement> | callback ref for the splitter's element |
useDragNode
"The lower layer of a draggable part: wires a node (a tab, tabset or group) to the core's
drag-and-drop machine. Spread draggable, onDragStart and onDragEnd on the element and
attach ref to the element the browser should snapshot as the drag image."
function TabSetDragHandle({ tabset }: { tabset: TabSetNode }) {
const drag = useDragNode(tabset);
return (
<span
ref={drag.ref}
draggable={drag.draggable}
onDragStart={drag.onDragStart}
onDragEnd={drag.onDragEnd}
aria-hidden="true"
data-dragging={drag.dragging ? "" : undefined}
>
⠿
</span>
);
}useDragNode(node): UseDragNodeResult, where node is a Node that is IDraggable and has
isEnableDrag() (a TabNode or a TabSetNode).
| field | type | description |
|---|---|---|
draggable | boolean | whether the element is draggable: the node's enableDrag |
onDragStart | (event: DragEvent<HTMLElement>) => void | starts the drag in the core |
onDragEnd | (event: DragEvent<HTMLElement>) => void | ends it |
ref | RefCallback<HTMLElement> | callback ref for the element used as the drag image (the dragged element by default) |
dragging | boolean | this node is being dragged |
useDragSource
"The lower layer of Dockable.DragSource: turns any element, inside or outside the layout (a
sidebar item, a palette entry), into a source of new tabs. Dropping it on the layout dispatches
Actions.addTab(json, …) through the engine, so onAction and onAllowDrop apply. Spread
draggable, onDragStart and onDragEnd on the element."
function PaletteEntry({ model }: { model: Model }) {
const drag = useDragSource({
model,
json: { type: "tab", name: "Orders", component: "table" },
});
return (
<li
ref={drag.ref}
draggable={drag.draggable}
onDragStart={drag.onDragStart}
onDragEnd={drag.onDragEnd}
data-dragging={drag.dragging ? "" : undefined}
>
Orders table
</li>
);
}useDragSource(options: UseDragSourceOptions): UseDragSourceResult. It does not need to run
inside Dockable.Root.
UseDragSourceOptions:
| field | type | description |
|---|---|---|
model | Model | the model of the layout the new tab is dropped into (its Dockable.Root must be mounted) |
json | IJsonTabNode | (() => IJsonTabNode) | the tab a drop creates. A function is called at each drag start, so every drop can get a fresh name or config |
onDrop | (node: TabNode | undefined, event: DragEvent) => void | called after the drop with the created tab, or undefined when onAction vetoed it |
disabled | boolean | no drag starts while true |
UseDragSourceResult:
| field | type | description |
|---|---|---|
draggable | boolean | whether the element is draggable |
onDragStart | (event: DragEvent<HTMLElement>) => void | starts the drag in the core (cancels it when disabled or no layout is mounted) |
onDragEnd | (event: DragEvent<HTMLElement>) => void | ends it |
ref | RefCallback<HTMLElement> | callback ref for the element used as the drag image (the dragged element by default) |
dragging | boolean | a drag started by this source is in progress |
useDropZone
"The lower layer of Dockable.DropZone: makes an element, inside or outside the layout, a place
where a drag of the layout can be dropped for the consumer to handle (a trash can, an "open to
the right" pad). Attach ref to the element."
const zone = useDropZone({
model,
onDrop: (node) => engine.doAction(Actions.deleteTab(node.getId())),
});
return <div ref={zone.ref} data-over={zone.over ? "" : undefined}>Trash</div>;useDropZone(options: UseDropZoneOptions): UseDropZoneResult. It does not need to run inside
Dockable.Root.
UseDropZoneOptions:
| field | type | description |
|---|---|---|
model | Model | the model whose drags the zone takes |
accepts | (dragNode: Node) => boolean | whether the zone takes this drag (default: every drag of the model) |
onDrop | (dragNode: Node, event: DragEvent) => void | called when the drag is dropped on the zone, with the dragged node. Nothing is moved: dispatch the action you want |
UseDropZoneResult:
| field | type | description |
|---|---|---|
ref | RefCallback<HTMLElement> | callback ref for the zone's element |
over | boolean | a drag the zone takes is over it |
active | boolean | a drag the zone would take is in progress |
useTabSetDropState
useTabSetDropState(engine, tabsetId): TabSetDropState: whether the current drag targets (or is
refused by) a tabset, from the engine's indicator state. It re-renders only when the answer for
that tabset changes, not on every pointer move. Dockable.TabSet and Dockable.TabList use it for
their data-drop-* attributes; use it for anything they do not cover (an insertion caret between
two tabs).
TabSetDropState:
| field | type | description |
|---|---|---|
target | boolean | the current drag would drop into or beside this tabset |
location | DropLocation | undefined | while it is the target: where the drag would dock |
strip | boolean | while it is the target: the drop goes into its tab strip, at index |
index | number | for a strip drop: the insertion index among the tabset's children, else -1 |
refused | boolean | the current drag is over this tabset, but a drop rule refuses it |
Types
Also exported: TabSetState, UseDockableResult, UseTabSetResult, UseBorderResult, UseTabOverflowResult, UseSplitterResult,
UseDragNodeResult, UseDragSourceOptions, UseDragSourceResult, UseDropZoneOptions, UseDropZoneResult, TabSetDropState, and GetLabel ((key: DockableLabel, ...args: (string | number)[]) => string | undefined).