API reference
Every public export of @dashfoo/react, @dashfoo/core, and @dashfoo/theme, with types and defaults.
This page lists the full public surface of the three packages. The guides cover
the concepts; this is the lookup table. All three packages are ESM-only
("type": "module" with an exports map), so your tsconfig needs
moduleResolution: "bundler", "node16", or "nodenext". Classic "node"
resolution cannot find them. @dashfoo/react ships a "use client" banner, so
it works as a client component in the Next.js App Router without a wrapper.
@dashfoo/react
The React binding: DashfooLayout, the Panel compound parts, the hooks, and
the storage adapters.
Source: packages/react/src.
DashfooLayout props
The component props. The props are a union: pass either model (controlled)
or defaultModel (uncontrolled), never both and never neither. persist
only exists on the defaultModel side. All three rules are enforced by the
type, so the combinations that used to type-check and silently do nothing no
longer compile. DashfooLayoutCommonProps is exported for helpers that spread
overrides.
| Prop | Type | Default | Description |
|---|---|---|---|
model | Dashfoo | none | Controlled document, and the only source of truth: every dispatch is applied to this prop, so a change you do not apply is simply not applied. Normalized before render. |
defaultModel | Dashfoo | none | Uncontrolled initial document. The component owns it from there. |
onModelChange | (model: Dashfoo, action?: Action) => void | none | Fires after every change with the next model and the action that caused it (action is omitted for undo/redo). Not required for persistence: persist saves on its own. |
components | Record<string, TabComponent> | none | Registry mapping tab.component keys to components. Each receives { node: TabNode }. An unregistered key renders nothing and logs one dev warning. |
factory | (tab: TabNode) => ReactNode | none | Render override. When set it resolves every tab and components is ignored. |
persist | string | { key: string; storage?: StorageAdapter; debounceMs?: number } | none | Auto-save and restore the model. Only accepted alongside defaultModel. A bare string is a localStorage key; the debounce defaults to 300 ms. See persistence. |
onAction | (action: Action) => Action | null | none | Intercept each action before it commits: return it, a replacement, or null to veto. |
onActiveTabsetChange | (tabsetId: string | undefined) => void | none | Fires when the active tabset changes. |
onMaximizedTabsetChange | (tabsetId: string | undefined) => void | none | Fires when a tabset is maximized or restored. |
renderTabLabel | (tab: TabNode) => ReactNode | none | Override the tab label. The accessible name stays tab.name. |
renderTabsetToolbar | (tabset: TabsetNode) => ReactNode | none | Inject custom controls into a tabset's toolbar. |
editable | boolean | true | The umbrella switch. false renders a static layout: no tab/tabset drag, close, rename, splitter resize, or external drops. |
closableTabs | boolean | true | Show the per-tab close control. |
renamableTabs | boolean | true | Allow double-click inline rename. |
maximizable | boolean | true | Show the tabset maximize/restore control. |
floatable | boolean | false | Show a per-tabset control that floats the panel into a movable, resizable overlay, and render any floating panels (Dashfoo.floats). Opt-in. See floating panels. |
draggableTabs | boolean | true | Allow individual tabs to be dragged between tabsets. |
draggableTabsets | boolean | true | Show the grip that drags a whole tabset. |
resizableSplits | boolean | true | Allow dragging the splitters between panels. false disables them but keeps them mounted, so the gutters hold their size. |
keepMounted | boolean | false | Keep inactive tab panels mounted (hidden) so their state survives a tab switch. By default inactive panels unmount. |
responsive | { maxWidth: number; orientation?: Orientation } | none | At or below maxWidth (the layout's own container width, not the viewport) render a stacked column and lock drag + resize. A view-only projection: the model is untouched, so widening up is lossless and the layout never remounts. See responsive. |
snap | { step?: number; divisions?: number | "panels"; threshold?: number } | none | Magnetic snapping for splitter drags. The dragged boundary pulls onto a grid: the union of step (multiples of a fixed percent) and divisions (even splits: multiples of 100/d, where d is the number, or "panels" to divide by the row's panel count so a 3-panel row snaps to thirds). Within threshold percent (default 4) it snaps and commits on release as one undo step. Omitted โ off; replaces (not merges with) global.snap; a row can override via its own snap. Locked off in compact mode. |
The model's global block layers under the boolean props: a feature is on only
if neither the prop nor the global (tabEnableClose, tabEnableRename,
tabSetEnableMaximize, tabEnableDrag, enableSplitResize) turns it off, and
(for everything except maximize) only while editable is on. The root element
is <div data-dashfoo="layout"> at 100% of its parent in both axes. A parent
without a real height paints nothing (see getting started).
Static layouts
editable={false} freezes the arrangement in one move: tabs and tabsets cannot
be dragged, tabs cannot be closed or renamed, splitters stop resizing (they stay
mounted, so nothing reflows), and the layout rejects drops from
useExternalTabSource, even under a shared DashfooDragProvider while a
sibling layout stays editable. The dashboard remains interactive as a viewer:
tab selection, keyboard tab navigation, the overflow menu, and maximize/restore
remain available, and the imperative DashfooHandle / dispatch API still
mutates the model programmatically. Flip the prop at runtime to build an "edit
mode" toggle without a remount.
DashfooHandle
A ref on DashfooLayout exposes 14 imperative methods.
| Method | Signature | Effect |
|---|---|---|
addTab | (tab: TabNode, target: { targetId: string; location?: DockLocation; index?: number }) => void | Dispatches addNode. location defaults to "center"; index picks the slot when stacking into the target. |
closeTab | (tabId: string) => void | Dispatches deleteTab. |
selectTab | (tabsetId: string, index: number) => void | Dispatches selectTab. |
renameTab | (tabId: string, name: string) => void | Dispatches renameTab. |
maximizeTabset | (tabsetId: string | null) => void | Dispatches setMaximizedTabset; null restores. |
floatTab | (tabId: string) => void | Floats a tab into a movable, resizable panel. |
dockFloat | (floatId: string) => void | Docks a floating panel back into the main layout. |
dispatch | (action: Action) => void | Raw escape hatch for any of the 16 actions. |
getModel | () => Dashfoo | The current model. |
undo | () => void | Step back one action. No-op in controlled mode. |
redo | () => void | Step forward. No-op in controlled mode. |
canUndo | () => boolean | Reads the live history snapshot. |
canRedo | () => boolean | Reads the live history snapshot. |
resetLayout | () => void | Restore the original defaultModel, clearing undo history and any persisted copy. Unavailable in controlled mode (warns once). |
import type { TabNode } from "@dashfoo/core";
import { model, row, tab, tabset } from "@dashfoo/core";
import type { DashfooHandle } from "@dashfoo/react";
import { DashfooLayout } from "@dashfoo/react";
import type { ReactNode } from "react";
import { useRef } from "react";
const Notes = ({ node }: { node: TabNode }): ReactNode => <div>{node.name}</div>;
const initial = model(row([tabset([tab("notes", "Notes")], { id: "main" })]));
const App = (): ReactNode => {
const ref = useRef<DashfooHandle>(null);
const handleAdd = (): void => {
// ids default to the component name; pass an explicit id for a duplicate.
ref.current?.addTab(tab("notes", "Scratch", { id: "notes-2" }), {
location: "split-right",
targetId: "main",
});
};
return (
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
<button onClick={handleAdd} type="button">
Add tab
</button>
<div style={{ flex: 1, minHeight: 0 }}>
<DashfooLayout components={{ notes: Notes }} defaultModel={initial} ref={ref} />
</div>
</div>
);
};
export { App };See controlled & history for the controlled-mode pairing and undo/redo wiring.
Panel compound parts
Unstyled building blocks for panel content. Each forwards its native props and
adds a data-dashfoo attribute for styling.
| Part | Element | Attribute |
|---|---|---|
Panel.Root | <div> | data-dashfoo="panel" |
Panel.Header | <div> | data-dashfoo="panel-header" |
Panel.Body | <div> | data-dashfoo="panel-body" |
Panel.Title | <span> | data-dashfoo="panel-title" |
Panel.Icon | <span> | data-dashfoo="panel-icon" |
Panel.Badge | <span> | data-dashfoo="panel-badge" |
Prop types are exported as PanelRootProps, PanelHeaderProps,
PanelBodyProps, PanelTitleProps, PanelIconProps, and PanelBadgeProps.
Layout and Tabset compound parts
The primitives DashfooLayout itself is built from, exported for hand-built
layouts. Layout.Root (the frame + layout store), Layout.DragLayer
(drag-dock, optional), Layout.Rows (the split tree, with a renderTabset
render prop), Layout.Tabset (the stock tabset composition), and, for
floating panels, Layout.FloatLayer (wrap the tree's content,
passing floats + global, to render each floated panel as a draggable overlay).
Plus the full tabset decomposition under Tabset.*: Root, TabStrip, Tablist,
Tab, Trigger, RenameInput, CloseButton, Content, Toolbar, OverflowMenu,
Grip, MaximizeButton, and FloatButton. Each part spreads native props and is
unstyled beyond minimal structural flex. See
Build your own layout for the part table and a
complete example.
useDashfooStore
The store DashfooLayout uses internally, exposed for hosts that render their
own chrome. Throws when given neither model nor defaultModel; every entry
point is normalized.
UseDashfooStoreOptions:
| Option | Type | Description |
|---|---|---|
defaultModel | Dashfoo | Uncontrolled seed; the actor owns it with full undo/redo. |
model | Dashfoo | Controlled document; every change routes through onModelChange. |
onAction | (action: Action) => Action | null | Veto or replace an action before it commits. |
onActiveTabsetChange | (tabsetId: string | undefined) => void | Active-tabset notifications. |
onMaximizedTabsetChange | (tabsetId: string | undefined) => void | Maximize/restore notifications. |
onModelChange | (model: Dashfoo, action?: Action) => void | Fires once per real change (no-op transitions emit nothing). |
Returns a DashfooStore:
| Field | Type | Description |
|---|---|---|
model | Dashfoo | The current document. |
dispatch | (action: Action) => void | Commit one action. |
undo | () => void | Uncontrolled only. |
redo | () => void | Uncontrolled only. |
canUndo | () => boolean | Reads the live actor snapshot, not a stale render value. |
canRedo | () => boolean | Reads the live actor snapshot. |
setModel | (model: Dashfoo) => void | Replace the whole document and reset undo history. |
useLayout
Selector hook over the layout store: the layout's resolved configuration and
dispatch, for custom chrome (a renderTabsetToolbar button, a custom tab
label). useLayout((state) => state.dispatch) re-renders only when the
selected slice changes. Throws outside <DashfooLayout> / <Layout.Root>.
The LayoutState fields:
| Field | Type | Description |
|---|---|---|
dispatch | (action: Action) => void | The store's dispatch. |
closableTabs, renamableTabs, maximizable, draggableTabs, draggableTabsets, resizableSplits | boolean | Effective values after editable and the model globals are layered in (maximizable ignores editable). |
editable | boolean | The umbrella prop as passed; gates every structural capability above except maximizable. |
keepMounted | boolean | The resolved prop. |
maximizedTabsetId | string | undefined | The currently maximized tabset, if any. Must live in the main layout: normalize clears it once that tabset moves into a float. |
renderTab | (tab: TabNode) => ReactNode | The resolved content renderer (factory or registry). |
renderTabLabel, renderTabsetToolbar | optional render props | As passed to DashfooLayout / Layout.Root. |
splitDock | boolean | From global.enableSplitDock. |
tabLocation | "top" | "bottom" | From global.tabLocation, default "top". |
tabsetMinSize | number | From global.tabSetMinSize, default 320 here. |
tabStripEnabled | boolean | From global.tabSetEnableTabStrip. |
useTabset / useTab / useDragSubject / useDropIntent
useTabset((state) => โฆ) selects from the per-tabset store inside
<Tabset.Root>: node, visualSelected, activeTab, isMaximized,
editingTabId, overflowItems, the resolved flags (tabsClosable,
tabsRenamable, showMaximize), and the actions (selectTab, closeTab,
renameTab, startEditing, toggleMaximize, selectOverflowTab).
useTab() returns { tab, index } (which tab the part belongs to) inside
<Tabset.Tab>.
useDragSubject() returns the live drag subject ({ kind: "tab" | "tabset" | "external", id }) or null, including outside a drag layer. See
Build your own layout.
useDropIntent() returns the live drop intent ({ targetId, location, index? },
where the drag would land if dropped right now) or null when nothing is
dragging or the pointer is over no valid target (empty space, a non-editable
layout, or a suppressed no-op drop). Both hooks work anywhere under a layout;
under a DashfooDragProvider they work anywhere under the provider. Pair with
dockZonePolygons from @dashfoo/core for custom drop indicators and drop-zone
visualizations.
usePersistence
The load/save half behind the persist prop, usable standalone.
usePersistence(config: PersistConfig | null, defaultModel: Dashfoo | undefined)
It loads the saved model once (validated; falls back to defaultModel on miss or
corruption, pruning corrupt values with a warning), debounce-saves every change,
and flushes a pending save on page hide (pagehide /
visibilitychange โ hidden) and on unmount. A null config makes it a no-op.
initialModel starts as the seed, then updates after mount when storage is restored.
Hosts that create a store once must apply that update with setModel; persist
on DashfooLayout already does this without a history entry or a save.
| Type | Shape |
|---|---|
PersistConfig | { key: string; storage: StorageAdapter; debounceMs: number } |
Persistence | { initialModel: Dashfoo | undefined; save: (model: Dashfoo) => void; clear: () => void } |
Storage adapters
| Export | Type | Description |
|---|---|---|
StorageAdapter | { getItem: (key: string) => string | null; setItem: (key: string, value: string) => void; removeItem: (key: string) => void } | The minimal localStorage-shaped contract. |
localStorageAdapter | StorageAdapter | SSR-safe browser adapter: reads return null and writes warn instead of throwing when storage is unavailable. |
memoryStorageAdapter() | () => StorageAdapter | Factory returning a fresh Map-backed adapter, for tests, SSR, and ephemeral layouts. |
External drag sources
Drag new tabs in from outside the layout. See drag & dock for the full flow.
| Export | Signature | Description |
|---|---|---|
DashfooDragProvider | ({ children }) => ReactNode | Shares one drag manager and one drag lifecycle between a layout, its floats and outside sources. A standalone layout needs no provider: it mounts one internally. |
useExternalTabSource | (options: ExternalTabSourceOptions) => { ref } | Registers an element as a drag source; put ref on it. |
ExternalTabSourceOptions | { createTab: () => TabNode; label?: string; disabled?: boolean } | createTab runs at drag start and must mint a fresh tab id per call. |
useContainerWidth
() => [ref: (el: HTMLElement | null) => void, width: number] tracks one
element's content-box width via ResizeObserver. SSR-safe: seeds
POSITIVE_INFINITY so the first render assumes the widest layout. The building
block for hand-built responsive layouts (pair with stackModel).
useResponsiveModel
Picks a model per breakpoint, keyed off the container's own width
(ResizeObserver) and/or media queries, and derives the structural-interaction
flags from the active breakpoint's compact. Feed the result onto
DashfooLayout/Layout.Root as reactive props; no key or remount is needed.
SSR-safe.
See responsive.
| Export | Signature | Description |
|---|---|---|
useResponsiveModel | (options: { breakpoints: Array<Breakpoint> }) => ResponsiveModel | Put containerRef on the wrapper; pass model + the lock flags onto DashfooLayout (use model, not defaultModel). |
matchBreakpoint | (breakpoint: Breakpoint, width: number) => boolean | No query matches always; maxWidth matches at or below; media consults matchMedia. |
activeBreakpoint | (breakpoints: Array<Breakpoint>, width: number) => Breakpoint | First match wins; the last entry is the fallback. Throws on an empty array. |
| Type | Shape |
|---|---|
Breakpoint | { id: string; model: Dashfoo; query?: { maxWidth: number } | { media: string }; compact?: boolean } |
ResponsiveModel | { breakpoint: string; containerRef: (el: HTMLElement | null) => void; model: Dashfoo; isCompact: boolean; draggableTabs: boolean; draggableTabsets: boolean; resizableSplits: boolean } |
@dashfoo/core
The framework-free engine: schema, builders, actions, reducer, tree queries,
geometry, history, serialization, and the XState machines.
Source: packages/core/src.
Actions
Every document mutation is one of 18 immutable, discriminated actions. The
reducer is exhaustive over the union; validate untrusted payloads with
actionSchema before dispatch.
action.type | Payload | Effect |
|---|---|---|
addNode | { tab, targetId, location, index? } | Insert a new tab at a dock location. |
moveNode | { sourceId, targetId, location, index? } | Move an existing tab to a dock target. |
moveTabset | { sourceId, targetId, location } | Move a whole tabset to a dock target (the grip drag). No index: a tabset merges or docks beside, so there is no strip slot to land at. |
selectTab | { tabsetId, index } | Set a tabset's selected index. |
setActiveTabset | { tabsetId } | Mark the focused tabset. |
setMaximizedTabset | { tabsetId: string | null } | Maximize one tabset, or restore with null. |
renameTab | { tabId, name } | Change a tab's name. |
deleteTab | { tabId } | Remove a tab. |
deleteTabset | { tabsetId } | Remove a whole tabset. |
adjustSplit | { rowId, weights } | Set the weights of a row's children (the splitter drag). |
floatTab | { tabId, geometry?, floatId? } | Float a tab out into a new floating panel. |
floatTabset | { tabsetId, geometry?, floatId? } | Float a whole tabset (with its tabs) into a panel. |
dockFloat | { floatId, targetId?, location? } | Dock a float back as its own panel (or center-merge). |
moveFloat | { floatId, geometry } | Update a floating panel's stored rect (drag/resize). |
setFloatMinimized | { floatId, minimized } | Collapse a float to a chip, or restore it. |
renameFloat | { floatId, name } | Rename a float's title (deduped to stay unique). |
updateNodeAttributes | { nodeId, attrs } | Patch mutable attributes on a tab, tabset, or row. |
updateGlobalAttributes | { attrs } | Patch the model's global block. |
Related exports: actionSchema, dockLocationSchema, mutableNodeAttrsSchema,
and the types Action, MutableNodeAttrs,
DockLocation ("center" | "split-top" | "split-bottom" | "split-left" | "split-right"),
and DropIntent ({ targetId: string; location: DockLocation; index?: number }).
See drag & dock for how drops become actions.
Builders
Constructors that fill the mechanical fields (type, version, selected,
weight, floats) so seed models stay short.
| Builder | Signature | Notes |
|---|---|---|
tab | (component: string, name: string, options?: TabOptions) => TabNode | id defaults to component. Pass an explicit id when reusing a component. |
tabset | (children: Array<TabNode>, options?: TabsetOptions) => TabsetNode | selected defaults to 0 and is clamped into the strip; weight defaults to 1; id is auto-generated when omitted. |
row | (children: RowNode["children"], options?: RowOptions) => RowNode | orientation defaults to "row"; weight defaults to 1; id is auto-generated when omitted. |
model | (layout: RowNode, options?: ModelOptions) => Dashfoo | Fills version: 1, global: {} and floats: []; warns once on duplicate node ids. |
floatNode | (layout: RowNode, geometry: Geometry, options?: FloatOptions) => FloatNode | A floating panel wrapping a layout subtree at an in-app rect. |
Option types: TabOptions (config, enableClose, enableDrag,
enableRename, id), TabsetOptions (enableClose, enableMaximize, id,
max, min, name, selected, weight), RowOptions (id, max, min,
orientation, weight), ModelOptions (activeTabsetId, floats, global,
maximizedTabsetId), FloatOptions (id, name). min/max
accept a bare number (treated as px) or a Dimension.
Schema
Zod schemas and their inferred types. Field-by-field tables live in the model guide.
| Schema | Inferred type |
|---|---|
dashfooSchema | Dashfoo |
rowNodeSchema | RowNode |
tabsetNodeSchema | TabsetNode |
tabNodeSchema | TabNode |
floatNodeSchema | FloatNode |
geometrySchema | Geometry |
globalAttributesSchema | GlobalAttributes |
dimensionSchema | Dimension |
edgeSchema | Edge |
orientationSchema | Orientation |
unitSchema | Unit |
jsonValueSchema | Json |
DashfooInput (z.input<typeof dashfooSchema>) is the loose counterpart of
Dashfoo: the shape parseModel accepts, with weight and floats optional.
Hold host-supplied or stored data as DashfooInput and parse it into a
Dashfoo. tabsetNodeObjectSchema is tabsetNodeSchema before its clamping
transform, for .pick()-ing mutable keys off.
Node (RowNode | TabsetNode | TabNode) is exported as a type only.
FloatNode ({ type: "float"; id; layout: RowNode; geometry: Geometry; name?; minimized? })
holds a floating panel; see floating panels. The model's
version is the pinned literal 1; payloads with any other version fail
validation.
Reducer and normalize
| Export | Signature | Description |
|---|---|---|
reducer | (model: Dashfoo, action: Action) => Dashfoo | The pure engine: deep-copies via structuredClone (input never mutated), applies one action, runs normalize. Returns the input by reference when the action changed nothing, so identity equality means "no edit"; a rejected action skips normalize. |
normalize | (model: Dashfoo) => Dashfoo | The structural canonical pass: removes empty tabsets, collapses single-child rows, heals each float's layout and drops an emptied float, keeps activeTabsetId on a live tabset anywhere, and maximizedTabsetId on a live tabset in the main layout (floats opt out of maximize, so floating the maximized tabset clears it). selected and weight are settled at the parse boundary instead. |
Tree helpers
Read-only queries over a model. All of them take the whole model and span
every root, so a floated node is as reachable as a docked one. Matching is
predicate-first: an id that also names a node of another kind cannot make two
of these disagree about which node wins.
| Function | Signature | Description |
|---|---|---|
collectRoots | (model: Dashfoo) => Array<RowNode> | The main layout plus each floating panel's layout. |
collectTabsets | (model: Dashfoo) => Array<TabsetNode> | Every tabset across all roots, depth-first. |
getFirstTabset | (model: Dashfoo) => TabsetNode | undefined | The first tabset in document order. |
findTabset | (model: Dashfoo, tabsetId: string) => TabsetNode | undefined | A tabset by id. |
findTab | (model: Dashfoo, tabId: string) => TabLocation | undefined | A tab anywhere in the model: { tab, container, index }. |
findFloat | (model: Dashfoo, floatId: string) => FloatNode | undefined | A floating panel by id. |
findRow | (model: Dashfoo, id: string) => RowNode | undefined | A row by id. |
findRootContaining | (model: Dashfoo, nodeId: string) => RowNode | undefined | Which root (main layout or a float) holds a node. |
findAttributedNode | (model: Dashfoo, id: string) => AttributedNode | undefined | Any row, tabset, or tab by id. Backs updateNodeAttributes. |
findTabsetParent | (model: Dashfoo, tabsetId: string) => { index: number; parent: RowNode } | undefined | The row holding a tabset and its index within it. |
findDuplicateIds | (model: Dashfoo) => Array<string> | Ids appearing more than once across the whole model. |
Types: AttributedNode, TabContainer, TabLocation.
Ids
| Function | Signature | Description |
|---|---|---|
createNodeId | (prefix?: string) => string | `${prefix}-${uuid}`, prefix defaults to "node". |
createTabId | () => string | createNodeId("tab"). |
stackModel
stackModel(model: Dashfoo, orientation: Orientation = "column"): Dashfoo
flattens any layout into a single row of all its tabsets in document order, each
with equal weight, and clears maximize. It is the building block for a mobile
breakpoint. See responsive.
Geometry
| Export | Signature | Description |
|---|---|---|
resolveDockTarget | (pointer: Point, rect: Rect, opts?: BandOptions) => DockLocation | Center of a tabset resolves to "center" (stack); within an outer edge band (default 22%) to "split-left" and friends. |
dockZonePolygons | (rect: Rect, opts?: BandOptions) => Array<DockZone> | The full hit-region partition behind resolveDockTarget: the inner center rect plus four edge trapezoids, as polygons. Shares the band default, so a painted map always agrees with the live hit-test. |
zoneRect | (rect: Rect, location: DockLocation) => Rect | The region the dock indicator highlights: the whole rect for center, the matching half for a split-*. |
Types: Point ({ x: number; y: number }), Rect
({ x: number; y: number; width: number; height: number }),
BandOptions ({ bandFraction?: number }).
History
Past/present/future snapshots over the reducer. Every dispatched action is one
undo step; there is no coalescing. A splitter drag is still a single step
because react-resizable-panels v4 commits one adjustSplit per release.
| Export | Signature | Description |
|---|---|---|
createHistory | (present: Dashfoo) => History | A fresh history with empty past and future. |
dispatch | (history: History, action: Action) => History | Reduce present, push the old one to past, clear future. |
undo | (history: History) => History | Step back; returns the same history at the bottom. |
redo | (history: History) => History | Step forward; returns the same history at the top. |
canUndo | (history: History) => boolean | past is non-empty. |
canRedo | (history: History) => boolean | future is non-empty. |
History is { past: Array<Dashfoo>; present: Dashfoo; future: Array<Dashfoo> }.
Serialization
| Export | Signature | Description |
|---|---|---|
toJSON | (model: Dashfoo) => string | JSON.stringify, since the model is plain data. |
fromJSON | (json: string) => Dashfoo | Parse + parseModel. Throws on invalid input. |
parseModel | (value: unknown) => Dashfoo | Validate against dashfooSchema (including the pinned version: 1), normalize, and warn on duplicate node ids. Takes DashfooInput-shaped data: weight and floats may be omitted and come back filled in. |
Machines
Two XState machines, for hosts that bypass the React binding.
dashfooMachine: the document actor. Input{ model: Dashfoo }, context{ history: History }. Events:DISPATCH({ action }),UNDO,REDO, andSET_MODEL({ model }, which resets history). Its context and event unions are internal and not exported.dragDockMachine: the drag/dock interaction lifecycle. Events:START({ subject: DragSubject }),OVER({ drop: DropResolution | null }),DROP,CANCEL. Its context is oneDragState({ kind: "idle" } | { kind: "dragging"; subject; drop }), so a drop target without a subject is unrepresentable. A valid drop emits{ type: "COMMIT", action, scope }, wherescopeechoes back theDropResolution.scopethat resolved the intent so the host can route the action:moveNodefor a tab drag,moveTabsetfor a tabset-grip drag;addNodeonly for an external source.dropAction(drag)is the same decision as a plain function. Types:DragState,DragSubject,DropResolution.
@dashfoo/theme
The opt-in default skin over --dashfoo-* tokens, light by default, dark via
data-dashfoo-theme="dark" on any ancestor. The full token table is in
theming.
| Entry point | Contents |
|---|---|
@dashfoo/theme/tailwind.css | Tailwind v4 source entry: tokens + skin in cascade layers, plus an @theme bridge that generates dashfoo-* utilities. |
@dashfoo/theme/dashfoo.css | The prebuilt skin, compiled from the same source (tokens included, flattened). One import styles everything; no Tailwind needed. |
@dashfoo/theme/tokens.css | The tokens alone, for building your own skin on the same override surface. |
The only JS export is DASHFOO_THEME_VERSION, a version constant for runtime
checks.