Skip to content

PanelStack

Defined in: components/panels.ts:727

The panel stack behind a routed S.main(), and what that call hands back: the open Panels, which of them is current, and the four ways to change that. Everything on it is scoped to its own shell.

Every navigation settles asynchronously (closes travel through the browser’s history), so the methods resolve once it has: true when it landed, false when it didn’t — an unsaved panel refused to close, an app-registered route guard said no, or another navigation superseded it. Ignore the promise unless you care.

const shell = S.main({ title: "Trackle", routes: { ... } });
S.button({ content: "New task", click: async () => {
const task = await createTask();
shell.pushPanel(`/tasks/${task.id}`);
}});

readonly panels: readonly Panel<Record<string, string | number | string[]>>[]

Defined in: components/panels.ts:735

The open panels, oldest first — the stack itself, as live objects rather than a copy of it. Writing through one is how you pin a panel, or rename it, from outside its own handler. Reactive on the stack’s shape; don’t hold a Panel across a navigation, since a closed one is dropped here while its element plays out its exit.


readonly currentPanelIndex: number

Defined in: components/panels.ts:737

Index into PanelStack.panels of the current panel. Reactive.


readonly currentPanel: Panel<Record<string, string | number | string[]>>

Defined in: components/panels.ts:744

The current panel — shorthand for panels[currentPanelIndex], and undefined only while the stack is still empty. Reactive on which panel is current; the fields you then read (title, actions, …) are reactive in their own right.

pushPanel(path): Promise<boolean>

Defined in: components/panels.ts:761

Opens path in a new panel on top of the current one, closing the unpinned panels that were after it (pinned ones stay, sliding in beneath the new panel).

The same rules as a link click apply: pushing a path that is already open returns to it — closing whatever was stacked on top — rather than opening it twice, and a panel holding Panel.unsaved work is never closed, only parked. That’s what a plain link does, and what data-panel=push says outright.

Note that a link builds on the panel it is drawn in, which is the current panel only while no column beside it has the focus. Code navigating on behalf of a particular screen — a row’s click handler — wants that panel’s own Panel.open instead.

string

Promise<boolean>


replacePanel(path): Promise<boolean>

Defined in: components/panels.ts:768

Opens path in place of the current panel, which closes. The panels beneath it stay as they are. The history entry is replaced too — a redirect, so the back button skips the replaced panel. That’s what a data-panel=replace link does.

string

Promise<boolean>


openPanelStack(path, beneath?): Promise<boolean>

Defined in: components/panels.ts:789

Opens path as a whole stack rather than on top of what’s there: the same thing a nav item or a fresh tab does. Without beneath, the stack under it is worked out the way a cold link’s is (see MainOptions.ancestors); with it, the paths you give are opened underneath, shallowest first.

That’s the one for a screen whose URL doesn’t say where it belongs — the thread a notification opens — and for seeding a stack from code in general. Panels the new stack also holds stay as they are; ones it drops close, except panels with Panel.unsaved work, which stay, parked.

A data-panel=open link does the same thing (without a beneath): it leaves the panel it sits in behind rather than stacking on it, which is what a link to somewhere else in the app wants — a search hit, a mention.

string

readonly string[]

Promise<boolean>

shell.openPanelStack(`/thread/${id}`, [`/mailbox/${mailboxId}`]);

closePanel(path?): Promise<boolean>

Defined in: components/panels.ts:801

Closes the current panel, or, given a path, whichever panel is open at it. Closing the current panel hands the focus to the panel on its left; closing any other panel takes just it away, leaving the columns around it exactly as they are, with their state. Either way it becomes a history entry, so the browser’s back button brings the panel back.

Resolves false if the panel didn’t close: it holds Panel.unsaved work, path isn’t open, it was the stack’s only panel, or another navigation got there first.

string

Promise<boolean>