Lattice Grid Buy a licence

api reference

Shell API Reference

createShell: an application layout of nested rows and columns of panels, with splitters, collapse to a header strip, tabs, and content that keeps its state through hide, collapse and tab switches.

API reference › The application shell

All 25 pages Everything on one page → Developer guide →

The shell module

modules/shell is an opt-in layout container: a tree of cells nested as rows and columns to any depth, sized by pure CSS flexbox, in its own bundle that grows grid core by nothing. It replaces DHX Layout in DemandFlow and is a sold Lattice feature. The main owner pain it removes is asynchronous redraw: when createShell(el, config) returns, every cell element is already in the document and already sized - a fixed size is a flex-basis, gravity is a flex-grow, and min/max are the CSS min-*/max-* properties - so there is no awaitRedraw(), no deferred JS paint and no requestAnimationFrame to wait on. A third-party widget mounted into getCell(id).el on the very next line measures a non-zero size in the same synchronous task, and a window resize reflows by CSS alone, with no listener or timer in the module.

import { createShell } from '@toclocoinc/lattice-grid/modules/shell';

const shell = createShell(document.querySelector('#shell'), {
  type: 'line',                  // separators: 'line' | 'wide' | 'space' | 'none'
  rows: [
    { id: 'nav', width: 220 },   // fixed px, never grows
    { id: 'main', gravity: 1,    // takes the remaining space
      cols: [
        { id: 'header', height: 40 },
        { id: 'body', gravity: 1 },
      ] },
  ],
});

shell.getCell('body').el.appendChild(widget);  // already sized
shell.destroy();

One element per cell. A cell is the flex item of its parent and, when it declares nested rows or cols, the flex container of its children - getCell(id).el is that single element, so a leaf cell is a plain block a widget fills and a nested cell already lays its own children out. Every size is an inline style, never a class, so the geometry a test reads is the geometry a host gets: width/height take a pixel number or any CSS length string ('30%', '12rem', 'calc(…)'); a cell with a fixed size never grows, and the gravity of the cells without one shares the space that is left after the fixed sizes are subtracted, in proportion. minWidth/maxWidth/minHeight/maxHeight clamp a cell exactly as the CSS properties do, padding pads the cell (its widget mounts inside the padding), align is the cell's align-self, and css is an object of camelCase properties or a string of declarations written straight onto the element - never parsed as HTML.

Headers and collapse. A cell may carry a header - text, a custom element to mount, or an image via headerImage/headerIcon - drawn as a strip headerHeight tall (default 32) above the content, with the cell's padding pushed down into the content area so the header spans the cell edge-to-edge. A collapsable cell gets a collapse control in that strip and collapse()/expand()/toggle() on its handle; collapsed: true starts it folded. Folding writes one thing - the cell's own flex-basis becomes the strip height - so the neighbours take the space and expand() restores the exact previous size, with the content element back in flow and measurable on the very next line: the transition is one synchronous pass of inline styles, no animation and no deferred paint. A folded strip among rows runs its text vertically (writing-mode: vertical-rl) so a narrow strip still reads. The change is gated and reported: cancellable beforeCollapse/beforeExpand (a handler calls preventDefault(reason?) or returns false) precede the move, and afterCollapse/afterExpand follow it, all subscribed through shell.on(name, fn). The control's accessible label is translated through config.messages, which the module resolves with its own English fallback.

Separators are the shell's type, not markup. 'line' draws a thin border between cells, 'wide' a thick one, 'space' an even gap, and 'none' (the default) nothing - each reads a --lattice-* theme token with a literal fallback, so a shell inside a themed grid inherits the theme and one on a bare page still draws, dark mode included. The interactive pieces the module adds are the collapse control on a collapsable cell - a native <button>, so Enter and Space toggle it and its aria-expanded/aria-label announce the state - and the splitters below; destroy() empties the element, removes the class the module added, restores the host's own inline style and drops every cell record and splitter listener, and the host still owns the element.

State round-trips the whole arrangement, content untouched. getState() snapshots the separator, the root padding/css and every cell's sizes, gravity, min/max and mutable collapsed/hidden/selected flags as one JSON-able object. setState(state) re-resolves and re-mounts it through the very same path a fresh config takes, then moves the exact DOM nodes a host mounted into each cell back into the matching cell by id - re-parented, never cloned - and fires stateChange with the state now live; on(name, fn)/off(name, fn) subscribe to it. The same snapshot is what the htmx data-lattice-shell history save/restore carries, and what the React, Vue, Svelte and Angular shell components re-emit, so a save/restore round-trip reproduces the layout exactly while a host's widgets survive it.

A cell marked resizable: true gets a splitter, not a separator. The splitter sits on the cell's trailing border and resizes that cell and its next sibling together, within both cells' minWidth/maxWidth (or minHeight/maxHeight on a column axis). It is a focusable role="separator" with aria-valuenow/aria-valuemin/aria-valuemax: drag it (mouse or touch) or focus it and use the arrow keys to step, Home/End to jump to the range, and double-click to restore the configured sizes. Every gesture runs through the shell's events - beforeResizeStart (cancellable: call event.preventDefault(reason) or return false to keep the sizes), then resize as it moves, then afterResizeEnd when it stops, each carrying the two cells' ids and their sizes - observed with on(name, fn) and detached with off(name, fn).

Content that never dies. cell.attach(content) takes a Lattice view (a grid, Gantt, scheduler, chart or layout), a DOM element or an HTML string (converted once), and the cell keeps the same element for its whole life: hide/show, collapse/expand, tab switches, resizes and moving the cell never re-render, innerHTML or destroy it, so a third-party widget's state and listeners persist. cell.getWidget() reads it, cell.detach() hands it back intact, and only detach() or destroy() releases it. Optional onHidden/onVisible hooks - in attach's options or on a tab - fire when the content hides and shows, and an attached Lattice view is re-measured on becoming visible through its own resize/refresh method, never a DHX-era paint(). A cell may instead declare tabs: [{ id, label, content? }] to render a keyboard-operable tab strip (ARIA tabs, arrow keys) with one attached content per tab, each preserved across switches; cell.getTab(id) reaches a tab and shell.on('tabChange', …) fires { cellId, tabId, previousTabId } on every switch.

Live structure. The tree is not frozen at creation. cell.hide() sets the cell's flex item to display:none, so its space is handed to its neighbours by CSS reflow alone; cell.show() restores the exact display the cell was built with, and cell.isVisible() reads the current state - a cell may also start hidden with hidden: true. shell.addCell(parentId, config, index?) builds a new cell through the view's one synchronous pass, so its element is already in the document and sized when the call returns (mountable on the very next line), and shell.removeCell(id) removes a cell and its whole subtree live, releasing every piece of content inside it (detached, never destroyed) and handing the space back. cell.progressShow() overlays an indeterminate spinner on one cell without touching its size or content, cell.progressHide() clears it, and a cell declared progressDefault: true starts with the overlay on. Every mutation is gated by a cancellable before* event and announced by an after* event - beforeHide/afterHide, beforeShow/afterShow, beforeAddCell/afterAddCell, beforeRemoveCell/afterRemoveCell - and a handler that calls event.preventDefault(reason) or returns false vetoes the change, which then happens not at all.

MemberDescription
createShell(el, config)Create a shell. config is { rows | cols, type, padding, css, messages }; rows lays the top-level cells left-to-right, cols top-to-bottom (used when rows is absent), and messages supplies a t(key, params) catalogue for the collapse control's labels.
cellEach cell: id, width/height (px or %), gravity (default 1), minWidth/maxWidth/minHeight/maxHeight, padding, css, align, a header (text, headerIcon, headerImage or a custom element) with headerHeight, collapsable/collapsed, hidden, selected, progressDefault, resizable (a splitter on the trailing border), nested rows/cols, or tabs for tabbed content.
getCell(id)The cell handle { el, config, collapsed, getParent(), collapse(), expand(), toggle(), attach, detach, getWidget, getTab, hide, show, isVisible, progressShow, progressHide } - el is the content element, config the normalised cell, getParent() the parent handle or null at the root. undefined for an unknown id.
attach(content, opts?)Mount a Lattice view, a DOM element or an HTML string; the cell keeps the same element for its life. On a tabbed cell this attaches to the active tab. opts is { onHidden, onVisible }.
detach()Remove the cell's (or active tab's) content and hand it back intact. Returns null when nothing is attached.
getWidget()The attached content - a view, element or the converted element of a string - or null.
getTab(id)One tab of a tabbed cell: { id, label, el, active(), activate(), attach(), detach(), getWidget() }. undefined on a non-tabbed cell or an unknown id.
getState()The arrangement as one JSON-able snapshot: { version, type, padding, css, rows | cols }, every cell carrying its sizes and mutable state. Exactly what setState accepts.
setState(state)Apply a snapshot: re-resolve and re-mount the cells, move the host's mounted content back by id (the same nodes, never cloned), and fire stateChange.
hide() / show() / isVisible()Hide the cell (display:none, so its space reflows to its neighbours) or show it again, restoring its exact display; isVisible() reads the state. A cell declared hidden: true starts hidden. beforeHide/beforeShow gate each, afterHide/afterShow announce it.
addCell(parentId, config, index?)Add a cell to the tree live and synchronously - in the document and sized when it returns. parentId is the parent cell id (or null for the root); a leaf or tabbed cell is refused. index places it among the parent's children (the end when omitted).
removeCell(id)Remove a cell and its whole subtree live, releasing every content inside it (detached, never destroyed) and returning the removed handle.
progressShow() / progressHide()Overlay an indeterminate spinner on the cell - or clear it - without touching its size or content. progressDefault: true starts a cell with the overlay on.
forEach(fn)Walk every cell in tree (document) order, handing each handle to fn.
on(name, fn)Subscribe to a shell event - beforeCollapse/afterCollapse, beforeExpand/afterExpand, beforeAddCell/afterAddCell, beforeRemoveCell/afterRemoveCell, beforeHide/afterHide, beforeShow/afterShow, tabChange, stateChange, or a resize event - beforeResizeStart (cancellable), resize, afterResizeEnd - or to '*' for every past-tense event; returns an unsubscribe. A before* handler cancels by calling preventDefault(reason?) or returning false.
off(name, fn)Remove a handler registered with on.
destroy()Release every content, empty the element, remove only what the module added, and drop every cell record, splitter listener and handler. The host still owns the element and every content element again.

DHX Layout migration

DemandFlow moves from dhtmlx Layout to modules/shell. The arrangement is the same shape - nested rows/cols of named cells - but the shell is synchronous, token-themed and state-serialisable where DHX Layout is an imperative widget API.

DHX LayoutLattice shell
new dhx.Layout(el, { rows: [...] })createShell(el, { rows: [...] })
{ id, header }{ id } - headers are content a host mounts into getCell(id).el, so there is no header bar to theme or chase.
{ width, height, gravity }The same keys: width/height are pixels or any CSS length, gravity shares the space left after the fixed sizes.
{ minWidth, maxWidth, minHeight, maxHeight, padding }The same keys, written as the CSS min-*/max-*/padding properties.
{ collapsable, resizable }collapsed/hidden/selected - mutable per-cell state, round-tripped by getState()/setState().
cell.attach(widget) / attachHTML(html)shell.getCell(id).el.appendChild(widget) - a plain element, sized synchronously.
cell.collapse() / expand() / toggle(), hide() / show()shell.setState({ ...shell.getState(), rows: [...] }) with the flags changed; content is restored by id.
layout.destructor()shell.destroy() - empties the element and drops every record; the host still owns the element.

Three levels, state round-trip, and teardown, executed

A row of a fixed nav and a gravity main, the main split into a fixed header and two gravity columns, walked, snapshot-and-restored through getState()/setState(), and torn down over the in-tree DOM test double - the sizes themselves are asserted against real Chrome in test/shell-browser.test.js.

const { createTestDom } = await import('../packages/dom/src/renderer/testdom.js');
const { createShell } = await import('../packages/modules/shell/index.js');

const { root, document: doc } = createTestDom();
const shell = createShell(root, {
  type: 'line', padding: 4, css: { position: 'relative' },
  rows: [
    { id: 'nav', width: 200 },
    { id: 'main', gravity: 1, cols: [
      { id: 'top', height: 40, padding: 8 },
      { id: 'body', gravity: 2, minWidth: 50, maxWidth: 900 },
      { id: 'foot', gravity: 1 },
    ] },
  ],
});

const tag = shell.getCell('nav').el.tagName;        // DIV - the content element itself
const parent = shell.getCell('body').getParent().config.id; // 'main' - the cell that nests it
let walked = 0;
shell.forEach(() => { walked += 1; });              // nav, main, top, body, foot
const unknown = shell.getCell('nope') === undefined; // true - no such cell

// State round-trips the whole arrangement, content untouched.
const widget = doc.createElement('div');
widget.textContent = 'kept';
shell.getCell('body').el.appendChild(widget);
let events = 0;
const onStateChange = () => { events += 1; };
shell.on('stateChange', onStateChange);
shell.setState(shell.getState());                    // re-mounts and fires stateChange
shell.off('stateChange', onStateChange);             // off - the subscription is removed
const kept = shell.getCell('body').el.contains(widget); // true - the same node, re-parented
const widthAfter = shell.getState().rows[0].width;  // 200 - sizes survived
shell.destroy();
return [tag, parent, walked, unknown, root.children.length, kept, events, widthAfter].join('|');

A resizable cell, its splitter and the resize events, executed

A cell marked resizable: true puts a focusable role="separator" on its trailing border; dragging or arrow keys resize it and its next sibling within both cells' min/max, and on(name, fn) observes the gesture. The drag itself is asserted against real Chrome in test/shell-splitter-browser.test.js; this block asserts the splitter's DOM and the event surface over the test DOM.

const { createTestDom } = await import('../packages/dom/src/renderer/testdom.js');
const { createShell } = await import('../packages/modules/shell/index.js');

const { root } = createTestDom();
const shell = createShell(root, { type: 'line', rows: [
  { id: 'a', width: 200, minWidth: 100, maxWidth: 300, resizable: true },
  { id: 'b', gravity: 1 },
] });

const splitter = root.querySelector('[role="separator"]');
const oriented = splitter.getAttribute('aria-orientation');  // 'vertical' - a row resizes widths
const labelled = splitter.getAttribute('aria-label');         // 'Resize'
const tabbable = splitter.tabIndex === 0;                     // true - keyboard reachable

let resized = 0;
const off = shell.on('resize', () => { resized += 1; });      // subscribe; returns an unsubscribe fn
off();                                                          // remove the handler
shell.on('nope', () => { resized += 1; });                      // unknown name: never subscribed
shell.destroy();                                                // removes the splitter too
return [oriented, labelled, tabbable, resized, typeof shell.off].join('|');

Headers, collapse, a vetoed fold and translated labels, executed

A column cell with a text header folds to its strip, a beforeCollapse handler vetoes the first fold, expand() restores it, and config.messages supplies the control's own labels - all synchronous, over the in-tree DOM test double; the real rects are asserted against Chrome in test/shell-browser.test.js.

const { createTestDom } = await import('../packages/dom/src/renderer/testdom.js');
const { createShell } = await import('../packages/modules/shell/index.js');

const { root } = createTestDom();
const shell = createShell(root, {
  messages: { t: (key, params) => (key === 'shell.collapse' ? `Hide ${params.name}` : `Show ${params.name}`) },
  cols: [
    { id: 'top', height: 120, header: 'Top', collapsable: true },
    { id: 'bottom', gravity: 1 },
  ],
});

const top = shell.getCell('top');
const btn = top.el.parentNode.querySelector('.lat-shell__header-btn');
const label = btn.getAttribute('aria-label');       // 'Hide Top' - the catalogue reached the control
const start = top.collapsed;                         // false - open at mount

const refuse = (e) => e.preventDefault('not yet');
shell.on('beforeCollapse', refuse);
const vetoed = top.collapse();                       // false - the host refused the fold
shell.off('beforeCollapse', refuse);
const folded = top.collapse();                       // true - folds to the strip
const after = top.collapsed;                         // true
const restored = top.expand();                       // true - back to the exact size
shell.destroy();
return [label, start, vetoed, folded, after, restored].join('|');

Attach, tab switching and detach, executed

A tabbed cell holds one content per tab; switching hides the outgoing panel without touching its content, tabChange fires, and detach() hands the element back - run over the in-tree DOM test double, with the same-element identity and a real grid's re-measure asserted against real Chrome in test/shell-content-browser.test.js.

const { createTestDom } = await import('../packages/dom/src/renderer/testdom.js');
const { createShell } = await import('../packages/modules/shell/index.js');

const { root } = createTestDom();
const shell = createShell(root, {
  rows: [{ id: 'main', tabs: [
    { id: 'a', label: 'A', content: '<b>one</b>' },
    { id: 'b', label: 'B' },
  ] }],
});
const cell = shell.getCell('main');
const widget = root.ownerDocument.createElement('div');
cell.getTab('b').attach(widget);                  // b was empty - attach straight to it
let last = '';
shell.on('tabChange', (e) => { last = e.tabId; });
cell.getTab('b').activate();                      // switch: a hides, b shows
const switched = last === 'b';                // tabChange fired
const kept = cell.getTab('a').getWidget().textContent === '<b>one</b>'; // a's content survived the switch
const same = cell.getWidget() === widget;     // the same element, not a clone
const back = cell.detach();                   // detach b's content intact
shell.destroy();
return [switched, kept, same, back === widget].join('|');

Hide, show, add, remove, progress and a vetoed add, executed

A cell hides and shows, a new cell is added and removed live, the progress overlay mounts and clears, and a beforeAddCell handler vetoes one add so nothing is created - run over the in-tree DOM test double, with the real flexbox geometry (the space a hide gives and a show restores, and the size of a synchronously added cell) asserted against real Chrome in test/shell-live-browser.test.js.

const { createTestDom } = await import('../packages/dom/src/renderer/testdom.js');
const { createShell } = await import('../packages/modules/shell/index.js');

const { root } = createTestDom();
const shell = createShell(root, { rows: [
  { id: 'a', width: 200 },
  { id: 'b', gravity: 1, progressDefault: true }, // starts with the spinner on
] });
const a = shell.getCell('a');
const hidden = a.hide().isVisible() === false;   // display:none, the space reflows
const shown = a.show().isVisible() === true;     // show restores the exact display
shell.on('beforeAddCell', (e) => { if (e.id === 'no') e.preventDefault(); });
const added = shell.addCell(null, { id: 'c', width: 100 });   // sized synchronously
const vetoed = shell.addCell(null, { id: 'no' }) === undefined; // vetoed: nothing made
const busy = shell.getCell('b').el.getAttribute('aria-busy') === 'true';
shell.getCell('c').progressShow();                // overlay the new cell
const overlay = shell.getCell('c').el.children.length === 1;
shell.getCell('c').progressHide();
const cleared = shell.getCell('c').el.children.length === 0;
const removed = shell.removeCell('c').config.id;  // live remove, content released
const gone = shell.getCell('c') === undefined;
shell.destroy();
return [hidden, shown, !!added, vetoed, busy, overlay, cleared, removed, gone].join('|');

Type reference

Generated from the type declarations, so it always matches the release. Each surface lists its properties, its methods and the events it raises as three tables; an option or value type lists its members once.

The shell module

ShellWidget

A Lattice view as `attach()` sees it: an object exposing its root element and, optionally, a resize/refresh method the shell nudges on show.

PropertyTypeDescription
elementHTMLElementThe view's root element (`createGrid` exposes `.element`). (optional)
elHTMLElementThe view's root element (the scheduler and layout expose `.el`). (optional)
MethodSignatureParametersReturnsDescription
resize(): void - voidRe-measure after a container change; preferred over `refresh` when both exist. (optional)
refresh(): void - voidRe-measure after a container change (the layout's method). Never a `paint()`. (optional)

ShellAttachHooks

The optional hooks `attach(content, opts)` takes, fired when the content hides or shows.

MethodSignatureParametersReturnsDescription
onHidden(content: ShellContent): voidcontent: ShellContentvoidCalled when the content hides (a tab switch away, a collapse, a hide). (optional)
onVisible(content: ShellContent): voidcontent: ShellContentvoidCalled when the content becomes visible again. (optional)

ShellTab

One tab of a tabbed cell: its identity, its strip label, and the content it holds across switches.

PropertyTypeDescription
idstringThe tab's identity for `getTab(id)`.
labelstringThe text on the tab. Defaults to `id`. (optional)
contentShellContentThe content the tab holds, attached the same way `attach()` attaches it. (optional)
MethodSignatureParametersReturnsDescription
onHidden(content: ShellContent): voidcontent: ShellContentvoidCalled when this tab's content hides on a switch away. (optional)
onVisible(content: ShellContent): voidcontent: ShellContentvoidCalled when this tab's content becomes visible on a switch to it. (optional)

ShellCell

One cell of a shell. A cell either mounts content or nests `rows`/`cols`.

PropertyTypeDescription
idstringThe cell's identity for `getCell(id)`. Auto-generated when omitted. (optional)
widthShellSize number | stringThe cell's width along a row (px or %). (optional)
heightShellSize number | stringThe cell's height along a column (px or %). (optional)
gravitynumberShare of the spare space when the cell has no fixed size. Default 1. (optional)
minWidthShellSize number | stringThe cell's minimum width. (optional)
maxWidthShellSize number | stringThe cell's maximum width. (optional)
minHeightShellSize number | stringThe cell's minimum height. (optional)
maxHeightShellSize number | stringThe cell's maximum height. (optional)
resizablebooleanWhen true, a splitter on the trailing border resizes this cell and its next sibling. (optional)
paddingnumber | stringThe cell's inner padding: a CSS padding value. (optional)
cssRecord<string, string | number> | stringExtra CSS: an object of camelCase properties, or a string of declarations. (optional)
alignstringThe cell's `align-self` across the parent's axis. (optional)
headerstring | NodeThe header strip's content: text, or a custom element to mount. (optional)
headerIconstringAn icon in the header strip: a URL (rendered as an image) or a glyph/emoji. (optional)
headerImagestringAn image in the header strip, rendered as an `<img>` with this `src`. (optional)
headerHeightShellSize number | stringThe header strip's height on the cell's main axis (px or a CSS length). Default 32. (optional)
collapsablebooleanShow a collapse control in the header and let `collapse()`/`expand()` fold the cell. (optional)
collapsedbooleanStart the cell folded to its header strip (implies `collapsable`); also the cell's current collapsed state. (optional)
hiddenbooleanMutable state: whether the cell is hidden from the layout; `hidden: true` also starts it hidden until `show()`. (optional)
selectedstringMutable state: the id of the selected tab inside the cell, when it hosts tabs. (optional)
rowsShellCell[]Nested cells, laid out left-to-right. (optional)
colsShellCell[]Nested cells, laid out top-to-bottom. (optional)
tabsShellTab[]Tabbed content: a strip with one attached content per tab, each preserved across switches. (optional)
progressDefaultbooleanStart the cell with its loading indicator already overlaid. Default false. (optional)

ShellConfig

The configuration for {@link createShell}.

PropertyTypeDescription
rowsShellCell[]The top-level cells, laid out left-to-right. (optional)
colsShellCell[]The top-level cells, laid out top-to-bottom (used when `rows` is absent). (optional)
typeShellType 'line' | 'wide' | 'space' | 'none'The separator between cells. Default `'none'`. (optional)
paddingnumber | stringThe shell root's inner padding: a CSS padding value. (optional)
cssRecord<string, string | number> | stringExtra CSS on the shell root. (optional)
messages{ t(key: string, params?: Record<string, string | number>): string }A message catalogue with a `t(key, params)` formatter, for the collapse control's labels. (optional)

ShellCellInstance

The cell handle `getCell(id)` and `forEach` hand back.

PropertyTypeDescription
elHTMLElementThe cell's content element - mount a widget into it directly. (read-only)
configShellCellThe cell's normalised config. (read-only)
collapsedbooleanWhether the cell is currently folded to its header strip. (read-only)
MethodSignatureParametersReturnsDescription
getParent(): ShellCellInstance | null - ShellCellInstance | nullThe parent cell's handle, or `null` for a top-level cell.
collapse(): boolean | Promise<boolean> - boolean | Promise<boolean>Fold the cell to its header strip. No-op on a non-collapsable cell.
expand(): boolean | Promise<boolean> - boolean | Promise<boolean>Open the cell back to its exact previous size. No-op when already open.
toggle(): boolean | Promise<boolean> - boolean | Promise<boolean>Collapse the cell when open, expand it when folded.
attach(content: ShellContent, opts?: ShellAttachHooks): ShellCellInstancecontent: ShellContent
opts?: ShellAttachHooks
ShellCellInstanceAttach a Lattice view, a DOM element or an HTML string to this cell. The cell keeps the same element for its life - hide/show, collapse/expand, tab switches, resizes and moves never re-render it - and only `detach()` or `destroy()` releases it. On a tabbed cell this attaches to the active tab.
detach(): ShellContent | null - ShellContent | nullDetach this cell's content (the active tab's, when tabbed) and hand it back intact.
getWidget(): ShellContent | null - ShellContent | nullThe content this cell (or its active tab) holds, or `null`.
getTab(id: string): ShellTabInstance | undefinedid: stringShellTabInstance | undefinedThe handle for one of a tabbed cell's tabs, or `undefined` for a non-tabbed cell or an unknown id.
isVisible(): boolean - booleanWhether the cell is currently visible (not hidden).
hide(): ShellCellInstance - ShellCellInstanceHide the cell, giving its space to its neighbours by CSS reflow. The cancellable `beforeHide` runs first; a veto leaves the cell as it was. Content stays mounted and its `onHidden` hook fires.
show(): ShellCellInstance - ShellCellInstanceShow a hidden cell, restoring its space. The cancellable `beforeShow` runs first; a veto leaves the cell hidden. An attached Lattice view is nudged to re-measure and its `onVisible` hook fires.
progressShow(): ShellCellInstance - ShellCellInstanceOverlay a loading indicator on the cell, without touching its size or content.
progressHide(): ShellCellInstance - ShellCellInstanceClear the cell's loading indicator.

ShellTabInstance

The tab handle `getTab(id)` hands back for one tab of a tabbed cell.

PropertyTypeDescription
idstringThe tab's id. (read-only)
labelstringThe tab's strip label. (read-only)
elHTMLElementThe tab's panel element - its content mounts here. (read-only)
MethodSignatureParametersReturnsDescription
active(): boolean - booleanWhether this is the active tab.
activate(): ShellTabInstance - ShellTabInstanceMake this tab active, firing `tabChange` and the content visibility hooks.
attach(content: ShellContent, opts?: ShellAttachHooks): ShellTabInstancecontent: ShellContent
opts?: ShellAttachHooks
ShellTabInstanceAttach content to this tab, replacing what it held.
detach(): ShellContent | null - ShellContent | nullDetach this tab's content and hand it back.
getWidget(): ShellContent | null - ShellContent | nullThis tab's content, or `null`.

ShellEventPayloads

What a handler receives, per shell event.

PropertyTypeDescription
beforeCollapseShellBeforeToggleEventThe fold about to happen, with `preventDefault` to refuse it.
beforeExpandShellBeforeToggleEventThe unfold about to happen, with `preventDefault` to refuse it.
afterCollapseShellAfterToggleEventThe fold that happened.
afterExpandShellAfterToggleEventThe unfold that happened.
beforeAddCellShellBeforeAddCellEventThe cell about to be added, with `preventDefault` to refuse it.
afterAddCellShellAfterAddCellEventThe cell that was just added.
beforeRemoveCellShellBeforeRemoveCellEventThe cell about to be removed, with `preventDefault` to refuse it.
afterRemoveCellShellAfterRemoveCellEventThe cell that was just removed.
beforeHideShellBeforeHideEventThe cell about to be hidden, with `preventDefault` to refuse it.
afterHideShellAfterHideEventThe cell that just hid.
beforeShowShellBeforeShowEventThe cell about to be shown, with `preventDefault` to refuse it.
afterShowShellAfterShowEventThe cell that just showed.
tabChangeShellTabChangeEventWhich tab became active, and which it replaced.
stateChangeShellStateChangeEventThe `stateChange` payload: the state now live, the shape `getState()` returns.
beforeResizeStartShellResizeStartEventThe two cells about to resize, and their current sizes.
resizeShellResizeEventThe two cells, and their new sizes.
afterResizeEndShellResizeEventThe two cells, and their final sizes.

ShellCellEvent

The fields every shell event carries.

PropertyTypeDescription
typestringWhich event this is: one of the `ShellEventName` names.
idstringThe id of the cell the event is about.
originViewerOrigin 'api' | 'user'Who asked for the change: `user` for the header control, `api` for `collapse()`/`expand()`/`toggle()`.

ShellBeforeToggleEvent

A `beforeCollapse` or `beforeExpand` payload. A handler refuses the change by calling `preventDefault(reason?)` or returning `false` (or a Promise that resolves `false`); the cell then does not move.

PropertyTypeDescription
defaultPreventedbooleanTrue once a handler has refused the change.
reasonstring | nullThe reason given to `preventDefault`, or null while nothing has refused.
MethodSignatureParametersReturnsDescription
preventDefault(reason?: string): voidreason?: stringvoidRefuse the change; the optional reason is surfaced to the caller.

ShellAfterToggleEvent

An `afterCollapse` or `afterExpand` payload - a notification, not a gate.

PropertyTypeDescription
collapsedbooleanThe cell is now collapsed (`afterCollapse`) or expanded (`afterExpand`).

ShellBeforeAddCellEvent

The payload of a `beforeAddCell` event.

PropertyTypeDescription
typestringWhich event this is: `beforeAddCell`.
idstringThe id the new cell will have.
parentIdstring | nullThe id of the cell it will be added into, or `null` for the shell root.
indexnumberThe index the new cell will take among its siblings.
reasonstring | nullThe reason given to `preventDefault`, or `null` while nothing has refused the add.
defaultPreventedbooleanTrue once a handler has refused the add.
MethodSignatureParametersReturnsDescription
preventDefault(reason?: string): voidreason?: stringvoidRefuse the add; the cell is then not created.

ShellAfterAddCellEvent

The payload of an `afterAddCell` event.

PropertyTypeDescription
typestringWhich event this is: `afterAddCell`.
idstringThe id of the cell that was added.
parentIdstring | nullThe id of the cell it was added into, or `null` for the shell root.
indexnumberThe index the cell took among its siblings.

ShellBeforeRemoveCellEvent

The payload of a `beforeRemoveCell` event.

PropertyTypeDescription
typestringWhich event this is: `beforeRemoveCell`.
idstringThe id of the cell about to be removed.
parentIdstring | nullThe id of the cell that owns it, or `null` for a top-level cell.
reasonstring | nullThe reason given to `preventDefault`, or `null` while nothing has refused the remove.
defaultPreventedbooleanTrue once a handler has refused the remove.
MethodSignatureParametersReturnsDescription
preventDefault(reason?: string): voidreason?: stringvoidRefuse the remove; the cell then stays mounted.

ShellAfterRemoveCellEvent

The payload of an `afterRemoveCell` event.

PropertyTypeDescription
typestringWhich event this is: `afterRemoveCell`.
idstringThe id of the cell that was removed.
parentIdstring | nullThe id of the cell that owned it, or `null` for a top-level cell.

ShellBeforeHideEvent

The payload of a `beforeHide` event.

PropertyTypeDescription
typestringWhich event this is: `beforeHide`.
cellIdstringThe id of the cell about to be hidden.
reasonstring | nullThe reason given to `preventDefault`, or `null` while nothing has refused the hide.
defaultPreventedbooleanTrue once a handler has refused the hide.
MethodSignatureParametersReturnsDescription
preventDefault(reason?: string): voidreason?: stringvoidRefuse the hide; the cell then stays visible.

ShellAfterHideEvent

The payload of an `afterHide` event.

PropertyTypeDescription
typestringWhich event this is: `afterHide`.
cellIdstringThe id of the cell that just hid.

ShellBeforeShowEvent

The payload of a `beforeShow` event.

PropertyTypeDescription
typestringWhich event this is: `beforeShow`.
cellIdstringThe id of the cell about to be shown.
reasonstring | nullThe reason given to `preventDefault`, or `null` while nothing has refused the show.
defaultPreventedbooleanTrue once a handler has refused the show.
MethodSignatureParametersReturnsDescription
preventDefault(reason?: string): voidreason?: stringvoidRefuse the show; the cell then stays hidden.

ShellAfterShowEvent

The payload of an `afterShow` event.

PropertyTypeDescription
typestringWhich event this is: `afterShow`.
cellIdstringThe id of the cell that just showed.

ShellTabChangeEvent

The payload of a `tabChange` event.

PropertyTypeDescription
cellIdstringThe id of the cell whose tab changed.
tabIdstringThe id of the now-active tab.
previousTabIdstring | nullThe id of the previously-active tab, or `null` on the first switch.

ShellState

The JSON-able snapshot `getState()` returns and `setState()` accepts.

PropertyTypeDescription
versionnumberThe state-format version; currently `1`.
typeShellType 'line' | 'wide' | 'space' | 'none'The separator between cells.
paddingnumber | stringThe shell root's inner padding. (optional)
cssRecord<string, string | number> | stringExtra CSS on the shell root. (optional)
rowsShellCell[]The top-level cells laid out left-to-right, each carrying its sizes, gravity and mutable state. (optional)
colsShellCell[]The top-level cells laid out top-to-bottom (used when `rows` is absent). (optional)

ShellStateChangeEvent

`stateChange`: `setState()` applied a snapshot and the arrangement now matches it. A notification, so it carries no `preventDefault`.

PropertyTypeDescription
typestringWhich event this is: `stateChange`.
stateShellStateThe state now live - the same shape `getState()` returns.

ShellBeforeEvent

The members every cancellable shell before-event carries.

PropertyTypeDescription
typestringThe event's own name.
originEventOrigin 'api' | 'user' | 'init' | 'ai'Where the action came from.
defaultPreventedbooleanTrue once any handler has cancelled the resize. (read-only)
reasonstring | nullThe first reason given to `preventDefault`, or null. (read-only)
MethodSignatureParametersReturnsDescription
preventDefault(reason?: string): voidreason?: stringvoidCancel the pending resize; the reason is surfaced on the event's `reason`.

ShellResizeEvent

The two neighbouring cells a splitter resize affects, plus their sizes.

PropertyTypeDescription
axis'rows' | 'cols'The layout axis being resized: `'rows'` resizes widths, `'cols'` resizes heights.
cells[string, string]The two cells' ids, in axis order (the `resizable` cell first).
sizes[number, number]Each cell's main-axis size in pixels, in the same order as `cells`.

Shell

The shell instance {@link createShell} returns.

Properties
PropertyTypeDescription
elHTMLElement | nullThe host element. (read-only)
configShellConfigThe normalised config. (read-only)
Methods
MethodSignatureParametersReturnsDescription
getCell(id: string): ShellCellInstance | undefinedid: stringShellCellInstance | undefinedThe cell handle for an id, or `undefined` when no cell has that id.
forEach(fn: (cell: ShellCellInstance) => void): voidfn: (cell: ShellCellInstance) => voidvoidWalk every cell in tree order, handing each handle to `fn`.
getState(): ShellState - ShellStateSnapshot the arrangement as JSON: sizes, gravity, and every cell's mutable state.
setState(state: ShellState): voidstate: ShellStatevoidApply a snapshot, preserving the content mounted into each cell; fires `stateChange`.
addCell(parentId: string | null | undefined, config?: ShellCell, index?: number): ShellCellInstance | undefinedparentId: string | null | undefined
config?: ShellCell
index?: number
ShellCellInstance | undefinedAdd a cell to the tree live, synchronously - its element is in the document and sized when this returns. `parentId` is the cell to add into (or `null`/`undefined` for the shell root; a leaf or tabbed cell is refused), and `index` is where among the parent's children (the end when omitted). The cancellable `beforeAddCell` runs first, then `afterAddCell`.
removeCell(id: string): ShellCellInstance | undefinedid: stringShellCellInstance | undefinedRemove a cell and its subtree from the tree live, releasing every piece of content inside it (detached, never destroyed). The cancellable `beforeRemoveCell` runs first, then `afterRemoveCell`.
on(name: ShellEventName | '*', fn: (event: ShellEventPayloads[ShellEventName]) => void): () => voidname: ShellEventName | '*'
fn: (event: ShellEventPayloads[ShellEventName]) => void
() => voidSubscribe to a shell event or to `'*'` for every event; returns an unsubscribe.
off(name: ShellEventName | '*', fn: (event: ShellEventPayloads[ShellEventName]) => void): voidname: ShellEventName | '*'
fn: (event: ShellEventPayloads[ShellEventName]) => void
voidRemove a handler registered with {@link Shell#on}.
destroy(): void - voidEmpty the element and drop every cell record, splitter listener and handler.
Events
EventWhenPayloadCancellable
beforeCollapseA cell is about to fold to its header strip; cancellable.ShellBeforeToggleEventyes
beforeExpandA cell is about to open back to its previous size; cancellable.ShellBeforeToggleEventyes
afterCollapseA cell folded to its header strip.ShellAfterToggleEventno
afterExpandA cell opened back to its previous size.ShellAfterToggleEventno
beforeAddCellA cell is about to be added; cancellable.ShellBeforeAddCellEventyes
afterAddCellA cell was added, already in the document and sized.ShellAfterAddCellEventno
beforeRemoveCellA cell is about to be removed; cancellable.ShellBeforeRemoveCellEventyes
afterRemoveCellA cell (and its subtree) was removed, its content released.ShellAfterRemoveCellEventno
beforeHideA cell is about to be hidden; cancellable.ShellBeforeHideEventyes
afterHideA cell hid, giving its space to its neighbours.ShellAfterHideEventno
beforeShowA cell is about to be shown; cancellable.ShellBeforeShowEventyes
afterShowA cell showed, restoring its space.ShellAfterShowEventno
tabChangeA tabbed cell's active tab changed.ShellTabChangeEventno
stateChange`stateChange`: `setState()` applied a snapshot and the arrangement now matches it.ShellStateChangeEventno
beforeResizeStartA pointer or keyboard resize gesture is about to begin; cancel it to keep the two cells' sizes.ShellResizeStartEvent -
resizeThe two cells' sizes changed during a drag, or after an arrow/Home/End key.ShellResizeEventno
afterResizeEndA resize gesture ended; the two cells hold their final sizes.ShellResizeEventno