staffa 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -48
- package/dist/components/autocomplete.js +1 -1
- package/dist/components/box.d.ts +8 -16
- package/dist/components/box.js +21 -27
- package/dist/components/button.d.ts +40 -0
- package/dist/components/button.js +85 -12
- package/dist/components/buttonChooser.js +1 -1
- package/dist/components/checkbox.js +3 -3
- package/dist/components/field.js +3 -3
- package/dist/components/main.d.ts +134 -71
- package/dist/components/main.js +245 -174
- package/dist/components/menu.d.ts +72 -14
- package/dist/components/menu.js +231 -34
- package/dist/components/pages.d.ts +638 -0
- package/dist/components/pages.js +1510 -0
- package/dist/components/panels.d.ts +448 -225
- package/dist/components/panels.js +819 -435
- package/dist/components/tabs.d.ts +37 -0
- package/dist/components/tabs.js +128 -69
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/glyphs.d.ts +24 -0
- package/dist/glyphs.js +25 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -4
- package/dist/staffa.esm.js +1 -1
- package/dist/theme.d.ts +67 -0
- package/dist/theme.js +12 -2
- package/package.json +2 -2
- package/skill/BoxOptions.md +7 -12
- package/skill/IconButtonOptions.md +41 -0
- package/skill/MainOptions.md +106 -58
- package/skill/MenuItem.md +16 -1
- package/skill/MenuListOptions.md +24 -0
- package/skill/MenuOptions.md +3 -2
- package/skill/Panel.md +190 -0
- package/skill/PanelStack.md +106 -0
- package/skill/SKILL.md +172 -64
- package/skill/ScrollStripOptions.md +21 -0
- package/skill/box.md +1 -4
- package/skill/closeNav.md +3 -3
- package/skill/iconButton.md +27 -0
- package/skill/main.md +13 -9
- package/skill/menu.md +29 -0
- package/skill/scrollStrip.md +28 -0
- package/src/components/autocomplete.ts +1 -1
- package/src/components/box.ts +29 -39
- package/src/components/button.ts +109 -8
- package/src/components/buttonChooser.ts +1 -1
- package/src/components/checkbox.ts +3 -3
- package/src/components/field.ts +3 -3
- package/src/components/main.ts +381 -188
- package/src/components/menu.ts +265 -37
- package/src/components/panels.ts +1136 -526
- package/src/components/tabs.ts +134 -68
- package/src/core.ts +1 -1
- package/src/index.ts +4 -4
- package/src/theme.ts +14 -3
- package/skill/Page.md +0 -119
- package/skill/panels.md +0 -10
|
@@ -1,16 +1,29 @@
|
|
|
1
|
+
import { OPAQUE } from "aberdeen";
|
|
2
|
+
import { type Slot } from "../core.js";
|
|
1
3
|
/**
|
|
2
4
|
* Routed, multi-column panel navigation for {@link main}.
|
|
3
5
|
*
|
|
4
|
-
* Each route draws one screen of the app, called a panel
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the
|
|
6
|
+
* Each route draws one screen of the app, called a *panel*. The open panels
|
|
7
|
+
* form a **stack**, and one of them is the **current** panel: the one the URL
|
|
8
|
+
* names, and the rightmost column on screen. As many panels as fit are shown,
|
|
9
|
+
* ending at the current one — on a phone that is one at a time, on a wider
|
|
10
|
+
* screen the panels that would have covered each other sit side by side
|
|
11
|
+
* instead. The app's own code is the same either way.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
13
|
+
* Going to a panel that is already open — a breadcrumb, or any link to it —
|
|
14
|
+
* just moves the current-panel cursor along the stack: panels right of it stay
|
|
15
|
+
* open, parked past the right edge of the viewport, and nothing closes.
|
|
16
|
+
* Opening a *new* panel is what prunes: everything after the panel it came
|
|
17
|
+
* from closes, except panels the user pinned — which ride along beneath the
|
|
18
|
+
* new panel — and panels holding unsaved work, which no navigation ever tears
|
|
19
|
+
* down. Escape steps one panel left, closing the panel it leaves only when
|
|
20
|
+
* that panel is the stack's discardable end.
|
|
21
|
+
*
|
|
22
|
+
* Navigation runs through `aberdeen/route`: the URL holds the current panel,
|
|
23
|
+
* and the rest of the arrangement — the panels before it, the ones parked
|
|
24
|
+
* after it, and which are pinned — is stored beside it in the history entry.
|
|
25
|
+
* So back and forward step through whole arrangements of columns, and a reload
|
|
26
|
+
* (or a shared link) brings the same columns back.
|
|
14
27
|
*/
|
|
15
28
|
/** Flattens an intersection into a single object type, so hovers read nicely. */
|
|
16
29
|
type Prettify<T> = {
|
|
@@ -39,21 +52,21 @@ export type SegParams<S extends string> = S extends `[...${infer Name}]` ? {
|
|
|
39
52
|
* `{ id: string; taskId: number }`.
|
|
40
53
|
*/
|
|
41
54
|
export type PathParams<P extends string> = P extends `${infer Head}/${infer Rest}` ? SegParams<Head> & PathParams<Rest> : SegParams<P>;
|
|
42
|
-
/** A panel draw function: it receives the panel's {@link
|
|
43
|
-
export type RouteHandler<P = any> = (
|
|
55
|
+
/** A panel draw function: it receives the panel's {@link Panel} and draws into the current scope. */
|
|
56
|
+
export type RouteHandler<P = any> = (panel: Panel<P>) => void;
|
|
44
57
|
/**
|
|
45
58
|
* A route table: path templates mapped to panel draw functions. Used as the
|
|
46
59
|
* loose (non-inferred) type; `S.main()` infers a more precise type from the
|
|
47
|
-
* literal you pass, so each handler's `$
|
|
60
|
+
* literal you pass, so each handler's `$panel.params` is typed per its key.
|
|
48
61
|
*/
|
|
49
62
|
export type Routes = Record<string, RouteHandler>;
|
|
50
63
|
/**
|
|
51
64
|
* The shape `S.main()`'s `routes` option is checked against: every key types its
|
|
52
65
|
* own handler's `params`. Used as a self-referential generic constraint, which
|
|
53
|
-
* is what makes `$
|
|
66
|
+
* is what makes `$panel.params` infer from the route key.
|
|
54
67
|
*/
|
|
55
68
|
export type RouteTable<R> = {
|
|
56
|
-
[K in keyof R & string]: (
|
|
69
|
+
[K in keyof R & string]: (panel: Panel<Prettify<PathParams<K>>>) => void;
|
|
57
70
|
};
|
|
58
71
|
/**
|
|
59
72
|
* What belongs beneath a path that arrives cold, worked out from the params of
|
|
@@ -76,128 +89,323 @@ export type AncestorTable<R> = {
|
|
|
76
89
|
* you can set things later, such as a `title` that arrives with your data or
|
|
77
90
|
* `loading` going back to `false`, and the shell keeps up.
|
|
78
91
|
*
|
|
79
|
-
* Search params and the `#hash` belong to the
|
|
80
|
-
*
|
|
81
|
-
*
|
|
92
|
+
* Search params and the `#hash` belong to the current panel only. Any other
|
|
93
|
+
* panel keeps just its path, so anything a panel needs in order to redraw
|
|
94
|
+
* itself has to live in that path. (A panel browsed away from does get its
|
|
95
|
+
* search and hash back when a crumb makes it current again.)
|
|
82
96
|
*/
|
|
83
|
-
export interface
|
|
97
|
+
export interface Panel<P = Record<string, string | number | string[]>> {
|
|
84
98
|
/**
|
|
85
99
|
* The params matched from this panel's path, typed per its route key:
|
|
86
100
|
* `[x]` is a `string`, `[x=integer]` a `number`, `[...x]` a `string`.
|
|
87
101
|
* Read-only.
|
|
88
102
|
*/
|
|
89
103
|
readonly params: P;
|
|
104
|
+
/**
|
|
105
|
+
* The stack this panel is in — the very object `S.main()` hands back. It is
|
|
106
|
+
* here as well because a route handler runs *while* that call is still
|
|
107
|
+
* going, so its return value isn't available to it yet; this always is.
|
|
108
|
+
*/
|
|
109
|
+
readonly stack: PanelStack;
|
|
90
110
|
/** This panel's path, e.g. `"/projects/7"`. Read-only. */
|
|
91
111
|
readonly path: string;
|
|
92
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* Names this screen, in the top bar's breadcrumb stack and in
|
|
114
|
+
* `document.title` while the panel is current. A panel that doesn't set one
|
|
115
|
+
* borrows the first line of text in its own body, so the stack never shows a
|
|
116
|
+
* blank — but a borrowed paragraph makes a poor name, so say it yourself.
|
|
117
|
+
*
|
|
118
|
+
* It does **not** conjure a heading: naming a screen and heading its content
|
|
119
|
+
* are different jobs, and a screen that wants its name in its own body
|
|
120
|
+
* writes it there, where it owns the typography.
|
|
121
|
+
*/
|
|
93
122
|
title?: string;
|
|
94
123
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
124
|
+
* This screen's own actions: a couple of buttons, a menu. The shell draws
|
|
125
|
+
* them — never the panel — and *where* depends on facts only the shell has:
|
|
126
|
+
* a quiet strip at the top of this panel's column while several columns are
|
|
127
|
+
* up, and the top bar (where they take the app's own `menu` slot) once the
|
|
128
|
+
* shell is narrow and this panel is the screen.
|
|
129
|
+
*
|
|
130
|
+
* They are drawn in exactly one of those places at a time, so crossing the
|
|
131
|
+
* threshold redraws them; anything stateful inside (the focus in a search
|
|
132
|
+
* box) is lost. Buttons and menus are fine.
|
|
133
|
+
*/
|
|
134
|
+
actions?: Slot;
|
|
135
|
+
/**
|
|
136
|
+
* How wide this panel's column actually is, in pixels — what
|
|
137
|
+
* {@link Panel.maxWidth} asked for, resolved against the window. Reactive and
|
|
138
|
+
* read-only, and correct *before* your handler draws, so content that sizes
|
|
139
|
+
* itself can read it instead of measuring.
|
|
98
140
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* Nothing fits beside a medium on a standard 1280px page, though on a wide
|
|
108
|
-
* enough window a small still can.
|
|
109
|
-
* - `"large"` takes the whole window, with no upper limit (~1750px on a
|
|
110
|
-
* 1920px screen): for boards, wide tables and dense dashboards. While it's
|
|
111
|
-
* open the whole shell (top bar, content and footer) stretches to the
|
|
112
|
-
* screen edges rather than stopping at 1280px.
|
|
141
|
+
* You rarely need it: the shell places the chrome for you. It's for content
|
|
142
|
+
* that genuinely differs by width, such as a table that becomes a list.
|
|
143
|
+
*/
|
|
144
|
+
readonly width: number;
|
|
145
|
+
/**
|
|
146
|
+
* Whether this panel is on screen right now: not crowded out from under the
|
|
147
|
+
* visible run, not parked past its right end, and not on its way out.
|
|
148
|
+
* Reactive and read-only.
|
|
113
149
|
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
150
|
+
* The one to hang per-panel floating UI on (a FAB, a "3 selected" bar), for
|
|
151
|
+
* which "am I the current panel?" is the wrong question — two columns can be
|
|
152
|
+
* visible at once, and both of them are really there.
|
|
153
|
+
*/
|
|
154
|
+
readonly visible: boolean;
|
|
155
|
+
/**
|
|
156
|
+
* The widest this panel can usefully be. Every panel must work at 360–540px,
|
|
157
|
+
* because that is what it gets when two columns fit; this says how much
|
|
158
|
+
* *more* it can take.
|
|
116
159
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
160
|
+
* - `"half"` — nothing more. Half the content area (360–540px), so a second
|
|
161
|
+
* column fits beside it. For lists and detail forms.
|
|
162
|
+
* - `"full"` (the default) — the whole content area, up to ~1100px.
|
|
163
|
+
* - `"screen"` — the whole window, unbounded: boards, wide tables, dense
|
|
164
|
+
* dashboards. While one is open the shell itself stretches to the screen
|
|
165
|
+
* edges instead of stopping at the standard 1280px page.
|
|
120
166
|
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* default: a handler that *assigns* `layout` is drawn at the medium width and
|
|
125
|
-
* reflowed immediately after — in time for the frame, but not for a
|
|
126
|
-
* measurement taken in the same breath.
|
|
167
|
+
* Below the width two columns need, everything takes the content area
|
|
168
|
+
* whatever it asked for. Widths depend only on the window, never on what
|
|
169
|
+
* else is open, so opening or closing a panel never resizes another.
|
|
127
170
|
*
|
|
128
|
-
*
|
|
129
|
-
* you
|
|
130
|
-
*
|
|
131
|
-
* beside it move over.
|
|
171
|
+
* Set it at the top of your handler and the panel is already that wide when
|
|
172
|
+
* you draw (see {@link Panel.width}); set it later — when your data tells you
|
|
173
|
+
* — and the panel reflows without being redrawn, keeping its state, while
|
|
174
|
+
* the columns beside it move over.
|
|
132
175
|
*/
|
|
133
|
-
|
|
176
|
+
maxWidth?: "half" | "full" | "screen";
|
|
134
177
|
/**
|
|
135
178
|
* Set this while you're fetching what the panel needs, and back to `false`
|
|
136
179
|
* when you're done. A new panel waits a moment before sliding in, so it can
|
|
137
180
|
* arrive with real content instead of empty; if the wait drags on it slides
|
|
138
181
|
* in anyway and shows a loading indicator until the flag clears. It only
|
|
139
|
-
* affects the animation; the stack
|
|
140
|
-
* for it.
|
|
182
|
+
* affects the animation; the stack and the URL never wait for it.
|
|
141
183
|
*/
|
|
142
184
|
loading?: boolean;
|
|
143
185
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
186
|
+
* Keeps this panel from being closed by navigation happening *elsewhere*.
|
|
187
|
+
* Opening a new panel normally closes everything after the panel it came
|
|
188
|
+
* from; a pinned panel survives that, staying in the stack — parked past the
|
|
189
|
+
* right edge of the viewport — slotted in beneath the new panel, one crumb
|
|
190
|
+
* click away. The user toggles it from the crumb's context menu
|
|
191
|
+
* (right-click or long-press), which is also where the pin shows; setting
|
|
192
|
+
* it from code does the same thing.
|
|
193
|
+
*
|
|
194
|
+
* A pin never blocks an *explicit* close: Escape at the stack's end,
|
|
195
|
+
* {@link Panel.close}, the crumb menu's Close and `data-panel=replace` all
|
|
196
|
+
* still close the panel.
|
|
149
197
|
*/
|
|
150
|
-
|
|
198
|
+
pinned?: boolean;
|
|
151
199
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
200
|
+
* Set this while the panel holds work that must not be lost — a dirty form,
|
|
201
|
+
* an upload in flight. An unsaved panel cannot be closed, by anything:
|
|
202
|
+
* navigation that would prune it parks it instead, past the viewport's
|
|
203
|
+
* right edge, wearing a ● in its crumb — even the browser's back button
|
|
204
|
+
* only parks it. {@link Panel.close} and the crumb menu's Close refuse,
|
|
205
|
+
* Escape on it steps left along the stack rather than closing, and closing
|
|
206
|
+
* the browser tab runs into the browser's own are-you-sure (after which the
|
|
207
|
+
* shell brings the unsaved panel back on screen).
|
|
157
208
|
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
209
|
+
* Only the app clears it; the user has no toggle. A Save or Discard button
|
|
210
|
+
* clears it and then closes:
|
|
211
|
+
*
|
|
212
|
+
* ```ts
|
|
213
|
+
* A(() => { $panel.unsaved = $form.dirty || undefined; });
|
|
214
|
+
* S.button({ content: "Discard", attrs: ".neutral", click: () => {
|
|
215
|
+
* $panel.unsaved = false; // explicitly — see below
|
|
216
|
+
* void $panel.close();
|
|
217
|
+
* }});
|
|
218
|
+
* ```
|
|
219
|
+
*
|
|
220
|
+
* The explicit `unsaved = false` before `close()` matters when the flag is
|
|
221
|
+
* kept by a reactive scope, as above: resetting the form marks that scope
|
|
222
|
+
* dirty, but it reruns *after* the running handler — after `close()` has
|
|
223
|
+
* already been refused.
|
|
224
|
+
*/
|
|
225
|
+
unsaved?: boolean;
|
|
226
|
+
/**
|
|
227
|
+
* Closes **this** panel, wherever it sits in the stack. Closing the current
|
|
228
|
+
* panel hands the focus to the panel on its left; closing any other panel
|
|
229
|
+
* takes just it away, leaving the columns around it where they are, with
|
|
230
|
+
* their state. Either way it becomes a history entry, so the browser's
|
|
231
|
+
* back button brings the panel back.
|
|
232
|
+
*
|
|
233
|
+
* Resolves `false` if the panel didn't close: it holds
|
|
234
|
+
* {@link Panel.unsaved} work, it was the only panel on the stack (so
|
|
235
|
+
* there's nothing to show instead), or another navigation got there first.
|
|
236
|
+
* The shell's breadcrumbs already travel back, so reach for this when a
|
|
237
|
+
* screen wants a more explicit way out: a Cancel button, or a Save that
|
|
238
|
+
* closes.
|
|
163
239
|
*
|
|
164
240
|
* @example
|
|
165
241
|
* ```ts
|
|
166
|
-
* S.button({ content: "Cancel", attrs: ".neutral", click: () => void $
|
|
242
|
+
* S.button({ content: "Cancel", attrs: ".neutral", click: () => void $panel.close() });
|
|
167
243
|
* ```
|
|
168
244
|
*/
|
|
169
245
|
close(): Promise<boolean>;
|
|
170
246
|
}
|
|
171
|
-
/** Options the
|
|
247
|
+
/** Options the stack needs from its shell. */
|
|
172
248
|
export interface PanelStackOptions {
|
|
173
249
|
routes: Routes;
|
|
174
250
|
notFound?: RouteHandler<{}>;
|
|
175
251
|
/** What to open beneath a path that arrives cold. See {@link MainOptions.ancestors}. */
|
|
176
252
|
ancestors?: Record<string, AncestorsHandler | undefined>;
|
|
177
|
-
/** Set `false` to show only the
|
|
253
|
+
/** Set `false` to show only the current panel, however much room there is. */
|
|
178
254
|
stacking?: boolean;
|
|
179
255
|
/** The shell's own title, used as the suffix of `document.title`. */
|
|
180
256
|
title?: unknown;
|
|
257
|
+
/**
|
|
258
|
+
* The shell's live narrow flag (see `main()`), which decides where a panel's
|
|
259
|
+
* chrome goes: in its own column, or promoted into the top bar. Shared rather
|
|
260
|
+
* than measured again here, so the bar and the columns can't disagree about
|
|
261
|
+
* which regime they are in.
|
|
262
|
+
*/
|
|
263
|
+
$shell: {
|
|
264
|
+
narrow: boolean;
|
|
265
|
+
};
|
|
181
266
|
}
|
|
182
|
-
|
|
267
|
+
/**
|
|
268
|
+
* The panel stack behind a routed `S.main()`, and what that call hands back:
|
|
269
|
+
* the open {@link Panel}s, which of them is current, and the four ways to
|
|
270
|
+
* change that. Everything on it is scoped to its own shell.
|
|
271
|
+
*
|
|
272
|
+
* Every navigation settles asynchronously (closes travel through the
|
|
273
|
+
* browser's history), so the methods resolve once it has: `true` when it
|
|
274
|
+
* landed, `false` when it didn't — an unsaved panel refused to close, an
|
|
275
|
+
* app-registered route guard said no, or another navigation superseded it.
|
|
276
|
+
* Ignore the promise unless you care.
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* ```ts
|
|
280
|
+
* const shell = S.main({ title: "Trackle", routes: { ... } });
|
|
281
|
+
*
|
|
282
|
+
* S.button({ content: "New task", click: async () => {
|
|
283
|
+
* const task = await createTask();
|
|
284
|
+
* shell.pushPanel(`/tasks/${task.id}`);
|
|
285
|
+
* }});
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
export interface PanelStack {
|
|
289
|
+
/**
|
|
290
|
+
* The open panels, oldest first — the stack itself, as live objects rather
|
|
291
|
+
* than a copy of it. Writing through one is how you pin a panel, or rename
|
|
292
|
+
* it, from outside its own handler. Reactive on the stack's shape; don't
|
|
293
|
+
* hold a {@link Panel} across a navigation, since a closed one is dropped
|
|
294
|
+
* here while its element plays out its exit.
|
|
295
|
+
*/
|
|
296
|
+
readonly panels: readonly Panel[];
|
|
297
|
+
/** Index into {@link PanelStack.panels} of the current panel. Reactive. */
|
|
298
|
+
readonly currentPanelIndex: number;
|
|
299
|
+
/**
|
|
300
|
+
* The current panel — shorthand for `panels[currentPanelIndex]`, and
|
|
301
|
+
* `undefined` only while the stack is still empty. Reactive on *which*
|
|
302
|
+
* panel is current; the fields you then read (`title`, `actions`, …) are
|
|
303
|
+
* reactive in their own right.
|
|
304
|
+
*/
|
|
305
|
+
readonly currentPanel: Panel | undefined;
|
|
306
|
+
/**
|
|
307
|
+
* Opens `path` in a new panel on top of the current one, closing the
|
|
308
|
+
* unpinned panels that were after it (pinned ones stay, sliding in beneath
|
|
309
|
+
* the new panel).
|
|
310
|
+
*
|
|
311
|
+
* The same rules as a link click apply: pushing a path that is already open
|
|
312
|
+
* goes back to it — a focus move along the stack, closing nothing — rather
|
|
313
|
+
* than opening it twice, and a panel holding {@link Panel.unsaved} work is
|
|
314
|
+
* never closed, only parked. That's what a plain link does, and what
|
|
315
|
+
* `data-panel=push` says outright.
|
|
316
|
+
*/
|
|
317
|
+
pushPanel(path: string): Promise<boolean>;
|
|
318
|
+
/**
|
|
319
|
+
* Opens `path` in place of the current panel, which closes. The panels
|
|
320
|
+
* beneath it stay as they are. That's what a `data-panel=replace` link does.
|
|
321
|
+
*/
|
|
322
|
+
replacePanel(path: string): Promise<boolean>;
|
|
323
|
+
/**
|
|
324
|
+
* Opens `path` as a whole stack rather than on top of what's there: the same
|
|
325
|
+
* thing a nav item or a fresh tab does. Without `beneath`, the stack under it
|
|
326
|
+
* is worked out the way a cold link's is (see {@link MainOptions.ancestors});
|
|
327
|
+
* with it, the paths you give are opened underneath, shallowest first.
|
|
328
|
+
*
|
|
329
|
+
* That's the one for a screen whose URL doesn't say where it belongs — the
|
|
330
|
+
* thread a notification opens — and for seeding a stack from code in general.
|
|
331
|
+
* Panels the new stack also holds stay as they are; ones it drops close,
|
|
332
|
+
* except panels with {@link Panel.unsaved} work, which stay, parked.
|
|
333
|
+
*
|
|
334
|
+
* A `data-panel=open` link does the same thing (without a `beneath`): it
|
|
335
|
+
* leaves the panel it sits in behind rather than stacking on it, which is
|
|
336
|
+
* what a link to somewhere else in the app wants — a search hit, a mention.
|
|
337
|
+
*
|
|
338
|
+
* @example
|
|
339
|
+
* ```ts
|
|
340
|
+
* shell.openPanelStack(`/thread/${id}`, [`/mailbox/${mailboxId}`]);
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
openPanelStack(path: string, beneath?: readonly string[]): Promise<boolean>;
|
|
344
|
+
/**
|
|
345
|
+
* Closes the current panel, or, given a `path`, whichever panel is open at
|
|
346
|
+
* it. Closing the current panel hands the focus to the panel on its left;
|
|
347
|
+
* closing any other panel takes just it away, leaving the columns around it
|
|
348
|
+
* exactly as they are, with their state. Either way it becomes a history
|
|
349
|
+
* entry, so the browser's back button brings the panel back.
|
|
350
|
+
*
|
|
351
|
+
* Resolves `false` if the panel didn't close: it holds {@link Panel.unsaved}
|
|
352
|
+
* work, `path` isn't open, it was the stack's only panel, or another
|
|
353
|
+
* navigation got there first.
|
|
354
|
+
*/
|
|
355
|
+
closePanel(path?: string): Promise<boolean>;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* The controller behind a routed shell. It implements {@link PanelStack} —
|
|
359
|
+
* the app-facing face, and the only part of it that is Staffa API — and on
|
|
360
|
+
* top of that draws the columns and the breadcrumbs for `main()`, which
|
|
361
|
+
* constructs it.
|
|
362
|
+
*/
|
|
363
|
+
export declare class PanelStackController implements PanelStack {
|
|
364
|
+
/**
|
|
365
|
+
* Kept out of Aberdeen's proxy wrapping: this is a class instance holding
|
|
366
|
+
* DOM nodes, timers and route handlers, and it rides inside every
|
|
367
|
+
* {@link Panel.stack}. Its reactivity doesn't need the wrapper — it comes
|
|
368
|
+
* from `$state` and the panels, which are proxies in their own right.
|
|
369
|
+
*/
|
|
370
|
+
readonly [OPAQUE] = true;
|
|
183
371
|
private compiled;
|
|
184
372
|
/** The `ancestors` table, compiled like the routes it is keyed by. */
|
|
185
373
|
private ancestors;
|
|
186
374
|
private opts;
|
|
187
|
-
/**
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
375
|
+
/**
|
|
376
|
+
* The live stack, oldest first (closing panels are no longer part of it),
|
|
377
|
+
* and which of its panels is current — the one reactive fact about the
|
|
378
|
+
* stack's *shape*. The getters, the crumbs and `document.title` subscribe
|
|
379
|
+
* to it simply by reading it; each commit publishes the next shape by
|
|
380
|
+
* assigning a fresh `live` array. The entries themselves are opaque (see
|
|
381
|
+
* {@link PanelEntry}), so the array carries their comings, goings and
|
|
382
|
+
* order — nothing deeper; a panel's own facts stay separately reactive on
|
|
383
|
+
* its `$panel`, which is what lets a panel rename itself without the
|
|
384
|
+
* stack redrawing.
|
|
385
|
+
*
|
|
386
|
+
* One rule makes this safe to touch from anywhere: **queries subscribe,
|
|
387
|
+
* commands peek**. The getters below are the queries. Every navigation
|
|
388
|
+
* entry point (`navigate`, `closePath`, `back`, …) wraps itself in
|
|
389
|
+
* `A.peek`, so an app calling one from inside a reactive scope (a
|
|
390
|
+
* redirect in a route handler, say) can't subscribe that scope to the
|
|
391
|
+
* very stack it is changing — and everything those commands call through
|
|
392
|
+
* to, `propose` and `commit` included, inherits the same guarantee and
|
|
393
|
+
* reads the stack plainly.
|
|
394
|
+
*/
|
|
395
|
+
private $state;
|
|
396
|
+
/**
|
|
397
|
+
* The open panels again, keyed by path — the shape as the DOM consumes it.
|
|
398
|
+
* `drawColumns`' `onEach` mounts and unmounts panels by key, so a panel
|
|
399
|
+
* spliced out of the middle of the stack touches exactly one key, and the
|
|
400
|
+
* DOM of the retained columns — scroll positions, half-typed forms — is
|
|
401
|
+
* left alone. (Iterating `live` itself would key panels by array index,
|
|
402
|
+
* and a splice renumbers every index after it, redrawing them all.) A
|
|
403
|
+
* key's value is its entry, by reference, and is never reassigned, so a
|
|
404
|
+
* panel only ever redraws wholesale when its path closes.
|
|
405
|
+
*/
|
|
406
|
+
private $open;
|
|
407
|
+
/** Feeds {@link PanelEntry.order}: one shared counter, so keys never tie. */
|
|
408
|
+
private nextOrder;
|
|
201
409
|
private containerEl?;
|
|
202
410
|
/** The shell's measurements, shared by everything drawn since they were taken. */
|
|
203
411
|
private geom?;
|
|
@@ -205,10 +413,12 @@ export declare class PanelController {
|
|
|
205
413
|
private lastBodyW;
|
|
206
414
|
private layoutQueued;
|
|
207
415
|
private timers;
|
|
208
|
-
/** The
|
|
416
|
+
/** The arrangement the navigation in flight is heading for; see {@link intended}. */
|
|
209
417
|
private intent;
|
|
210
418
|
/** The navigation the router hasn't settled yet, if any. */
|
|
211
419
|
private settling;
|
|
420
|
+
/** The route last seen by the observer, for stashing a left panel's query. */
|
|
421
|
+
private lastSeen;
|
|
212
422
|
/** The one navigation waiting behind it; see {@link issue}. */
|
|
213
423
|
private queued;
|
|
214
424
|
constructor(opts: PanelStackOptions);
|
|
@@ -227,9 +437,9 @@ export declare class PanelController {
|
|
|
227
437
|
* and the matching ones become the stack. Either way, a path with no route is
|
|
228
438
|
* skipped rather than opened as a "not found" column, so an app that doesn't
|
|
229
439
|
* want one screen stacked under another simply doesn't route it. The path
|
|
230
|
-
* itself
|
|
440
|
+
* itself always ends the derived stack, matched or not.
|
|
231
441
|
*/
|
|
232
|
-
deriveStack
|
|
442
|
+
private deriveStack;
|
|
233
443
|
/**
|
|
234
444
|
* Ask the `ancestors` table what belongs beneath `path`. The first key that
|
|
235
445
|
* matches answers — with its own matched params, so it never has to take the
|
|
@@ -239,30 +449,41 @@ export declare class PanelController {
|
|
|
239
449
|
private askAncestors;
|
|
240
450
|
/** Every prefix of `path` that has a route, shallowest first. */
|
|
241
451
|
private prefixesOf;
|
|
242
|
-
/** The stack a route implies: its snapshot topped by its path, or — without a snapshot — derived. */
|
|
243
|
-
private targetFor;
|
|
244
|
-
/** The stack the current history entry asks for. Subscribes to path + snapshot. */
|
|
245
|
-
private computeTarget;
|
|
246
452
|
/**
|
|
247
|
-
* The
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
|
|
251
|
-
|
|
453
|
+
* The pinned panels among `stack`, in order, minus `omit` — the ones a
|
|
454
|
+
* navigation must carry along rather than close. Pin flags live on the
|
|
455
|
+
* panels themselves, so a path without a live panel can't be pinned.
|
|
456
|
+
*/
|
|
457
|
+
private pinnedIn;
|
|
458
|
+
/** Whether the panel open at `path` (if any) holds unsaved work. */
|
|
459
|
+
private unsavedAt;
|
|
460
|
+
/**
|
|
461
|
+
* The arrangement a route implies: its snapshot around its path, or —
|
|
462
|
+
* without a snapshot — derived, with the new panel current at the end and
|
|
463
|
+
* any pinned panels carried along beneath it.
|
|
464
|
+
*
|
|
465
|
+
* The snapshot reads subscribe — they are the URL's, exactly what the
|
|
466
|
+
* route observer is for. The derivation is peeked instead: it reads the
|
|
467
|
+
* live stack for its pins, which is the very thing that observer rewrites,
|
|
468
|
+
* and subscribing to it would re-run the observer once per commit.
|
|
252
469
|
*/
|
|
253
|
-
private
|
|
470
|
+
private targetFor;
|
|
471
|
+
/** The arrangement the current history entry asks for. Subscribes to path + snapshot. */
|
|
472
|
+
private computeTarget;
|
|
254
473
|
private paths;
|
|
255
|
-
/** The live panels a target stack drops — by path, so a splice removes only its own column. */
|
|
256
|
-
private removedBy;
|
|
257
474
|
/**
|
|
258
|
-
* Adopt
|
|
259
|
-
*
|
|
260
|
-
*
|
|
475
|
+
* Adopt an arrangement proposed by the URL — after repairing it: panels
|
|
476
|
+
* holding unsaved work are never torn down by a navigation, wherever it
|
|
477
|
+
* came from — a link, a nav item, even a browser back to an entry from
|
|
478
|
+
* before the panel existed. Whatever the target drops, they stay, parked
|
|
479
|
+
* after the current panel and wearing the ● that says why. (They are
|
|
480
|
+
* deliberately not written into history entries: the work they protect
|
|
481
|
+
* lives in the page's DOM, which a reload clears anyway.)
|
|
261
482
|
*/
|
|
262
483
|
private propose;
|
|
263
484
|
/**
|
|
264
|
-
* Apply a target
|
|
265
|
-
* difference.
|
|
485
|
+
* Apply a target arrangement: unmount what's gone, mount what's new, animate
|
|
486
|
+
* the difference.
|
|
266
487
|
*
|
|
267
488
|
* Reconciliation is BY PATH (a stack can't hold the same path twice, so that's
|
|
268
489
|
* well-defined): a panel present in both stacks stays mounted *even if its
|
|
@@ -293,94 +514,158 @@ export declare class PanelController {
|
|
|
293
514
|
*/
|
|
294
515
|
private playExit;
|
|
295
516
|
/**
|
|
296
|
-
* The
|
|
297
|
-
* is still settling, and the one on screen otherwise.
|
|
517
|
+
* The arrangement navigation works from: the one we're on the way to while a
|
|
518
|
+
* change is still settling, and the one on screen otherwise.
|
|
298
519
|
*
|
|
299
|
-
* Settling takes a moment more often than it looks:
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
* one is already taking away — so two quick Escapes would peel one panel.
|
|
520
|
+
* Settling takes a moment more often than it looks: every `route.back()`
|
|
521
|
+
* travels through the browser's history and lands on a `popstate`, and an
|
|
522
|
+
* app-registered route guard may be async. Working from the committed
|
|
523
|
+
* arrangement in that window would make a second Escape aim at the panel the
|
|
524
|
+
* first one is already taking away — so two quick Escapes would peel one panel.
|
|
304
525
|
*/
|
|
305
526
|
private intended;
|
|
527
|
+
/** The history `state` describing `arr` — exactly what `targetFor` reads back. */
|
|
528
|
+
private stateFor;
|
|
529
|
+
/** The paths of the pinned panels — the pin list history entries persist. */
|
|
530
|
+
private pinnedPaths;
|
|
306
531
|
/**
|
|
307
532
|
* Put a navigation to the router, or — while one is still settling — behind
|
|
308
533
|
* the one that is. Only the newest waits: each was worked out against
|
|
309
534
|
* {@link intended}, so the newest is the one that means what the user last
|
|
310
535
|
* asked for, and the one it displaces resolves `false`.
|
|
311
536
|
*
|
|
312
|
-
* A refusal empties the queue instead of running it
|
|
313
|
-
*
|
|
314
|
-
*
|
|
537
|
+
* A refusal empties the queue instead of running it: a navigation can still
|
|
538
|
+
* fail to land — an app-registered route guard vetoes it, or another one
|
|
539
|
+
* supersedes it — and what was queued behind it was worked out against the
|
|
540
|
+
* arrangement it would have produced.
|
|
315
541
|
*/
|
|
316
542
|
private issue;
|
|
317
543
|
private start;
|
|
318
544
|
/**
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
545
|
+
* Make the stack's `index`th panel current: the URL and the visible run move
|
|
546
|
+
* to it, while the panels right of it stay open, parked past the right edge
|
|
547
|
+
* of the viewport. Nothing closes; it is a history entry, so the browser's
|
|
548
|
+
* back button returns the focus to where it was. What a click on a
|
|
549
|
+
* breadcrumb — any link to an open panel — comes down to.
|
|
550
|
+
*/
|
|
551
|
+
private focusAt;
|
|
552
|
+
/**
|
|
553
|
+
* One step back along the stack — what Escape does (`main()` calls this;
|
|
554
|
+
* it is not {@link PanelStack} API). At the stack's end this closes the
|
|
555
|
+
* current panel; mid-stack — with panels parked to the right — or when the
|
|
556
|
+
* panel holds {@link Panel.unsaved} work, the panel stays open and the
|
|
557
|
+
* focus just moves to the panel on its left, parking the one it leaves.
|
|
558
|
+
* Resolves `false` at the stack's start, where there is no left to go.
|
|
559
|
+
*/
|
|
560
|
+
back(): Promise<boolean>;
|
|
561
|
+
/**
|
|
562
|
+
* Close whichever panel is open at `path`, current or not — what
|
|
563
|
+
* {@link Panel.close}, {@link PanelStack.closePanel} and the crumb menu's
|
|
564
|
+
* Close come down to. `false` when that path isn't open, is the stack's
|
|
565
|
+
* only panel, or holds {@link Panel.unsaved} work — nothing may close an
|
|
566
|
+
* unsaved panel; the app clears the flag first, which is its explicit
|
|
567
|
+
* "this is now discardable".
|
|
336
568
|
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
* state
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
569
|
+
* Closing the current panel at the stack's very end pops back through the
|
|
570
|
+
* browser's history to the entry beneath it, when it is there (restoring its
|
|
571
|
+
* scroll and search state); the arrangement is part of the match, so an
|
|
572
|
+
* entry where the closing panel was merely parked won't do. Every other
|
|
573
|
+
* close is a *splice*: the columns around the closed one keep their place
|
|
574
|
+
* and state (the commit reconciles by path). That still gets its own
|
|
575
|
+
* history entry, so the browser's back button restores the closed column
|
|
576
|
+
* like any other arrangement — which is why it goes through `route.go` here
|
|
577
|
+
* rather than through `navigate()`, whose "link to an open panel" check
|
|
578
|
+
* would turn it into a focus move.
|
|
579
|
+
*/
|
|
580
|
+
private closePath;
|
|
348
581
|
/**
|
|
349
582
|
* Navigate to `href`. `origin` is the path of the panel the link lives in, or
|
|
350
583
|
* `null` when it has none — a nav item, or a programmatic call, which builds
|
|
351
584
|
* the whole stack instead (see {@link deriveStack}). `replace` swaps the
|
|
352
585
|
* originating panel rather than stacking on top of it, and `beneath` says what
|
|
353
586
|
* the stack under the target is outright, for callers that know.
|
|
587
|
+
*
|
|
588
|
+
* Resolves the way every {@link PanelStack} method does: `true` once the
|
|
589
|
+
* navigation lands, `false` when it doesn't (already there counts as
|
|
590
|
+
* landed).
|
|
354
591
|
*/
|
|
355
|
-
navigate
|
|
356
|
-
/** Programmatic push/replace, with the
|
|
357
|
-
pushPath
|
|
358
|
-
/** Programmatic open-as-a-whole-stack: `beneath` as given, or derived. */
|
|
359
|
-
openPath(path: string, beneath?: readonly string[]): void;
|
|
592
|
+
private navigate;
|
|
593
|
+
/** Programmatic push/replace, with the current panel as the implied origin. */
|
|
594
|
+
private pushPath;
|
|
360
595
|
/**
|
|
361
596
|
* Link handling through `route.interceptLinks()`, whose handler hook hands us
|
|
362
597
|
* the anchor so we can decide what the click *means*: the originating
|
|
363
|
-
* `.s-panel` (which decides what the click truncates), `data-panel
|
|
364
|
-
* and return-to-an-open-panel semantics. The exclusion rules
|
|
365
|
-
* downloads, modified clicks, external URLs) live in Aberdeen; the
|
|
366
|
-
* guards run in `checkChange` when our navigation reaches the router.
|
|
598
|
+
* `.s-panel` (which decides what the click truncates), the `data-panel`
|
|
599
|
+
* attribute, and return-to-an-open-panel semantics. The exclusion rules
|
|
600
|
+
* (targets, downloads, modified clicks, external URLs) live in Aberdeen; the
|
|
601
|
+
* close guards run in `checkChange` when our navigation reaches the router.
|
|
602
|
+
*
|
|
603
|
+
* `data-panel` names which of the three {@link PanelStack} navigations the
|
|
604
|
+
* click is: `push` (the default), `replace`, or `open`, which drops the
|
|
605
|
+
* originating panel so the target arrives with its own stack beneath it,
|
|
606
|
+
* exactly as a nav item's link does. An unrecognised value is a `push`.
|
|
367
607
|
*/
|
|
368
608
|
private interceptLinks;
|
|
369
|
-
|
|
609
|
+
get currentPanel(): Panel | undefined;
|
|
610
|
+
get panels(): readonly Panel[];
|
|
611
|
+
get currentPanelIndex(): number;
|
|
612
|
+
pushPanel(path: string): Promise<boolean>;
|
|
613
|
+
replacePanel(path: string): Promise<boolean>;
|
|
614
|
+
openPanelStack(path: string, beneath?: readonly string[]): Promise<boolean>;
|
|
615
|
+
closePanel(path?: string): Promise<boolean>;
|
|
616
|
+
/**
|
|
617
|
+
* The breadcrumb stack, drawn by `main()` into the top bar: every open
|
|
618
|
+
* panel, oldest first, the ones on screen right now in bold, pinned ones
|
|
619
|
+
* wearing their pin. Every crumb but the current panel's is a plain link to
|
|
620
|
+
* that panel, and a link to an open panel is a focus move (see `navigate`) —
|
|
621
|
+
* so clicking along the stack closes nothing, in either direction, and the
|
|
622
|
+
* panels right of the current one wait just past the viewport's edge.
|
|
623
|
+
* Right-click (or long-press) offers pinning, and closing just that one
|
|
624
|
+
* panel — the close that splices it out of the middle when it isn't last.
|
|
625
|
+
*/
|
|
626
|
+
drawCrumbs(): void;
|
|
627
|
+
private drawCrumb;
|
|
628
|
+
/**
|
|
629
|
+
* Flip a panel's pin (see {@link Panel.pinned}). The flag lives on the panel;
|
|
630
|
+
* the current history entry's snapshot is rewritten too, so a reload keeps
|
|
631
|
+
* the pin — a same-panel state tweak, which the router applies unguarded.
|
|
632
|
+
*/
|
|
633
|
+
private togglePin;
|
|
634
|
+
/**
|
|
635
|
+
* `"<panel title> · <app title>"`, kept in sync with the current panel — and
|
|
636
|
+
* prefixed `"• "` while *any* open panel holds unsaved work, the way editors
|
|
637
|
+
* mark a dirty document. Any panel, not just the current one: the risk of
|
|
638
|
+
* losing the work is tab-wide, so the mark on the tab is too.
|
|
639
|
+
*/
|
|
370
640
|
private watchTitle;
|
|
371
641
|
/**
|
|
372
|
-
*
|
|
642
|
+
* While any open panel holds unsaved work, closing the tab — or navigating
|
|
643
|
+
* the whole browser away — runs into the browser's own are-you-sure. When
|
|
644
|
+
* the user stays, the unsaved panel is brought back on screen if it wasn't,
|
|
645
|
+
* so what held the tab is in front of them rather than parked out of sight.
|
|
646
|
+
*/
|
|
647
|
+
private guardTabClose;
|
|
648
|
+
/**
|
|
649
|
+
* Draw the column viewport into the current element. Called by `main()`.
|
|
373
650
|
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
651
|
+
* A column is the panel's own content, plus the one bit of chrome the shell
|
|
652
|
+
* places for it: its {@link Panel.actions}, in a strip on wide shells and in
|
|
653
|
+
* the top bar on narrow ones (see {@link drawActions}).
|
|
377
654
|
*/
|
|
378
|
-
|
|
655
|
+
drawColumns(): void;
|
|
379
656
|
private drawPanel;
|
|
657
|
+
/**
|
|
658
|
+
* The one bit of column chrome the shell draws: the panel's actions, in a
|
|
659
|
+
* quiet strip above the scroll area — and only while the shell is wide, the
|
|
660
|
+
* top bar carrying them otherwise. Everything else in a column is the panel's
|
|
661
|
+
* own content: a screen that wants a heading or a card draws them itself.
|
|
662
|
+
* Going back isn't here either — that is the breadcrumbs' job, in the bar.
|
|
663
|
+
*/
|
|
664
|
+
private drawActions;
|
|
380
665
|
scheduleLayout(): void;
|
|
381
666
|
/**
|
|
382
667
|
* Measure the shell, and with it the width the window gives a panel of each
|
|
383
|
-
* layout. Measured on the *shell*, not on the
|
|
668
|
+
* layout. Measured on the *shell*, not on the column region: the region's width
|
|
384
669
|
* is the layout engine's own output, so reading it back would nail the layout
|
|
385
670
|
* to whatever it happened to be a frame ago. Fractional widths throughout — a
|
|
386
671
|
* rounded column edge would drift a pixel away from the chrome above it.
|
|
@@ -396,7 +681,7 @@ export declare class PanelController {
|
|
|
396
681
|
* would be a forced reflow each, in the middle of building their DOM.
|
|
397
682
|
*/
|
|
398
683
|
private geometry;
|
|
399
|
-
/** How wide a panel
|
|
684
|
+
/** How wide a panel asking for this is, right now; 0 while the shell can't be measured. */
|
|
400
685
|
private roomFor;
|
|
401
686
|
/**
|
|
402
687
|
* Size and position every panel, and publish the width of the whole ensemble
|
|
@@ -410,66 +695,4 @@ export declare class PanelController {
|
|
|
410
695
|
/** Let a `loading` panel's enter animation wait — but not indefinitely. */
|
|
411
696
|
private holdEnter;
|
|
412
697
|
}
|
|
413
|
-
/**
|
|
414
|
-
* Navigating the routed `S.main()` shell from code, for the times it isn't a
|
|
415
|
-
* link click, such as opening the screen for a record you just created.
|
|
416
|
-
*
|
|
417
|
-
* The same rules as a link click apply: pushing a path that is already open
|
|
418
|
-
* goes back to it rather than opening it twice, and anything that would close a
|
|
419
|
-
* panel asks its {@link Page.requestClose} first.
|
|
420
|
-
*
|
|
421
|
-
* @example
|
|
422
|
-
* ```ts
|
|
423
|
-
* S.button({ content: "New task", click: async () => {
|
|
424
|
-
* const task = await createTask();
|
|
425
|
-
* S.panels.push(`/tasks/${task.id}`);
|
|
426
|
-
* }});
|
|
427
|
-
* ```
|
|
428
|
-
*/
|
|
429
|
-
export declare const panels: {
|
|
430
|
-
/** Opens `path` in a new panel on top of the top one. */
|
|
431
|
-
push(path: string): void;
|
|
432
|
-
/**
|
|
433
|
-
* Opens `path` in place of the top panel, which closes (asking its
|
|
434
|
-
* {@link Page.requestClose} first). The panels beneath it stay as they are.
|
|
435
|
-
*/
|
|
436
|
-
replace(path: string): void;
|
|
437
|
-
/**
|
|
438
|
-
* Opens `path` as a whole arrangement rather than on top of what's there: the
|
|
439
|
-
* same thing a nav item or a fresh tab does. Without `beneath`, the stack under
|
|
440
|
-
* it is worked out the way a cold link's is (see `S.main()`'s `ancestors`);
|
|
441
|
-
* with it, the paths you give are opened underneath, shallowest first.
|
|
442
|
-
*
|
|
443
|
-
* That's the one for a screen whose URL doesn't say where it belongs — the
|
|
444
|
-
* thread a notification opens — and for seeding a stack from code in general.
|
|
445
|
-
* Panels the new arrangement also holds stay as they are, and any it drops are
|
|
446
|
-
* asked their {@link Page.requestClose} first.
|
|
447
|
-
*
|
|
448
|
-
* @example
|
|
449
|
-
* ```ts
|
|
450
|
-
* S.panels.open(`/thread/${id}`, [`/mailbox/${mailboxId}`]);
|
|
451
|
-
* ```
|
|
452
|
-
*/
|
|
453
|
-
open(path: string, beneath?: readonly string[]): void;
|
|
454
|
-
/**
|
|
455
|
-
* Closes the top panel, or, given a `path`, whichever panel is open at it,
|
|
456
|
-
* asking {@link Page.requestClose} first. A panel that isn't on top is taken
|
|
457
|
-
* out on its own, leaving the columns to its right exactly as they are.
|
|
458
|
-
*
|
|
459
|
-
* Resolves `false` if the panel didn't close: `requestClose` said no, `path`
|
|
460
|
-
* isn't open, or another navigation got there first.
|
|
461
|
-
*/
|
|
462
|
-
close(path?: string): Promise<boolean>;
|
|
463
|
-
/** The paths of the open panels, oldest first. Reactive: safe to read in a scope. */
|
|
464
|
-
readonly stack: readonly string[];
|
|
465
|
-
};
|
|
466
|
-
/**
|
|
467
|
-
* Closes the panel `el` sits in, working out which one that is from the DOM.
|
|
468
|
-
* That is what lets a close button work without being handed a `$page`, from
|
|
469
|
-
* any column, whether or not it is on top. Used by `S.box`'s `close: true`.
|
|
470
|
-
*
|
|
471
|
-
* Outside a routed shell (or outside any panel, such as a box in a dialog) there is
|
|
472
|
-
* nothing to close: it warns and resolves `false`.
|
|
473
|
-
*/
|
|
474
|
-
export declare function closeContainingPanel(el: Element | null | undefined): Promise<boolean>;
|
|
475
698
|
export {};
|