staffa 0.8.1 → 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 +130 -64
- 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 +187 -64
- package/dist/components/main.js +284 -158
- package/dist/components/menu.d.ts +72 -14
- package/dist/components/menu.js +235 -31
- package/dist/components/pages.d.ts +638 -0
- package/dist/components/pages.js +1510 -0
- package/dist/components/panels.d.ts +512 -206
- package/dist/components/panels.js +926 -414
- 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 +5 -5
- package/dist/index.js +4 -5
- 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/AncestorTable.md +10 -0
- package/skill/BoxOptions.md +7 -12
- package/skill/IconButtonOptions.md +41 -0
- package/skill/MainOptions.md +143 -54
- 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 +214 -77
- package/skill/ScrollStripOptions.md +21 -0
- package/skill/box.md +1 -4
- package/skill/closeNav.md +23 -0
- 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 +459 -167
- package/src/components/menu.ts +268 -34
- package/src/components/panels.ts +1254 -497
- package/src/components/tabs.ts +134 -68
- package/src/core.ts +1 -1
- package/src/index.ts +5 -5
- 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,36 @@ 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;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* What belongs beneath a path that arrives cold, worked out from the params of
|
|
73
|
+
* the path itself. Return the paths shallowest first, or nothing to leave this
|
|
74
|
+
* one to the parent-path derivation.
|
|
75
|
+
*/
|
|
76
|
+
export type AncestorsHandler<P = any> = (params: P, path: string) => readonly string[] | undefined | void;
|
|
77
|
+
/**
|
|
78
|
+
* A table of {@link AncestorsHandler}s keyed by path template, the same way
|
|
79
|
+
* `routes` is — so each one's `params` are matched and typed from its own key
|
|
80
|
+
* rather than parsed out of the path a second time. The keys are checked
|
|
81
|
+
* against the route table, so a stale one is a type error.
|
|
82
|
+
*/
|
|
83
|
+
export type AncestorTable<R> = {
|
|
84
|
+
[K in keyof R & string]?: (params: Prettify<PathParams<K>>, path: string) => readonly string[] | undefined | void;
|
|
57
85
|
};
|
|
58
86
|
/**
|
|
59
87
|
* What a route handler gets: the params from its route, plus everything the
|
|
@@ -61,124 +89,323 @@ export type RouteTable<R> = {
|
|
|
61
89
|
* you can set things later, such as a `title` that arrives with your data or
|
|
62
90
|
* `loading` going back to `false`, and the shell keeps up.
|
|
63
91
|
*
|
|
64
|
-
* Search params and the `#hash` belong to the
|
|
65
|
-
*
|
|
66
|
-
*
|
|
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.)
|
|
67
96
|
*/
|
|
68
|
-
export interface
|
|
97
|
+
export interface Panel<P = Record<string, string | number | string[]>> {
|
|
69
98
|
/**
|
|
70
99
|
* The params matched from this panel's path, typed per its route key:
|
|
71
100
|
* `[x]` is a `string`, `[x=integer]` a `number`, `[...x]` a `string`.
|
|
72
101
|
* Read-only.
|
|
73
102
|
*/
|
|
74
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;
|
|
75
110
|
/** This panel's path, e.g. `"/projects/7"`. Read-only. */
|
|
76
111
|
readonly path: string;
|
|
77
|
-
/**
|
|
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
|
+
*/
|
|
78
122
|
title?: string;
|
|
79
123
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
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.
|
|
83
140
|
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* Nothing fits beside a medium on a standard 1280px page, though on a wide
|
|
93
|
-
* enough window a small still can.
|
|
94
|
-
* - `"large"` takes the whole window, with no upper limit (~1750px on a
|
|
95
|
-
* 1920px screen): for boards, wide tables and dense dashboards. While it's
|
|
96
|
-
* open the whole shell (top bar, content and footer) stretches to the
|
|
97
|
-
* 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.
|
|
98
149
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
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.
|
|
101
159
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
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.
|
|
105
166
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* default: a handler that *assigns* `layout` is drawn at the medium width and
|
|
110
|
-
* reflowed immediately after — in time for the frame, but not for a
|
|
111
|
-
* 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.
|
|
112
170
|
*
|
|
113
|
-
*
|
|
114
|
-
* you
|
|
115
|
-
*
|
|
116
|
-
* 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.
|
|
117
175
|
*/
|
|
118
|
-
|
|
176
|
+
maxWidth?: "half" | "full" | "screen";
|
|
119
177
|
/**
|
|
120
178
|
* Set this while you're fetching what the panel needs, and back to `false`
|
|
121
179
|
* when you're done. A new panel waits a moment before sliding in, so it can
|
|
122
180
|
* arrive with real content instead of empty; if the wait drags on it slides
|
|
123
181
|
* in anyway and shows a loading indicator until the flag clears. It only
|
|
124
|
-
* affects the animation; the stack
|
|
125
|
-
* for it.
|
|
182
|
+
* affects the animation; the stack and the URL never wait for it.
|
|
126
183
|
*/
|
|
127
184
|
loading?: boolean;
|
|
128
185
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
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.
|
|
197
|
+
*/
|
|
198
|
+
pinned?: boolean;
|
|
199
|
+
/**
|
|
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).
|
|
208
|
+
*
|
|
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.
|
|
134
224
|
*/
|
|
135
|
-
|
|
225
|
+
unsaved?: boolean;
|
|
136
226
|
/**
|
|
137
|
-
* Closes **this** panel, wherever it sits in the stack.
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
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.
|
|
142
232
|
*
|
|
143
|
-
* Resolves `false` if the panel didn't close:
|
|
144
|
-
*
|
|
145
|
-
* or another navigation got there first.
|
|
146
|
-
*
|
|
147
|
-
*
|
|
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.
|
|
148
239
|
*
|
|
149
240
|
* @example
|
|
150
241
|
* ```ts
|
|
151
|
-
* S.button({ content: "Cancel", attrs: ".neutral", click: () => void $
|
|
242
|
+
* S.button({ content: "Cancel", attrs: ".neutral", click: () => void $panel.close() });
|
|
152
243
|
* ```
|
|
153
244
|
*/
|
|
154
245
|
close(): Promise<boolean>;
|
|
155
246
|
}
|
|
156
|
-
/** Options the
|
|
247
|
+
/** Options the stack needs from its shell. */
|
|
157
248
|
export interface PanelStackOptions {
|
|
158
249
|
routes: Routes;
|
|
159
250
|
notFound?: RouteHandler<{}>;
|
|
160
|
-
/**
|
|
251
|
+
/** What to open beneath a path that arrives cold. See {@link MainOptions.ancestors}. */
|
|
252
|
+
ancestors?: Record<string, AncestorsHandler | undefined>;
|
|
253
|
+
/** Set `false` to show only the current panel, however much room there is. */
|
|
161
254
|
stacking?: boolean;
|
|
162
255
|
/** The shell's own title, used as the suffix of `document.title`. */
|
|
163
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
|
+
};
|
|
164
266
|
}
|
|
165
|
-
|
|
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;
|
|
166
371
|
private compiled;
|
|
372
|
+
/** The `ancestors` table, compiled like the routes it is keyed by. */
|
|
373
|
+
private ancestors;
|
|
167
374
|
private opts;
|
|
168
|
-
/**
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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;
|
|
182
409
|
private containerEl?;
|
|
183
410
|
/** The shell's measurements, shared by everything drawn since they were taken. */
|
|
184
411
|
private geom?;
|
|
@@ -186,42 +413,77 @@ export declare class PanelController {
|
|
|
186
413
|
private lastBodyW;
|
|
187
414
|
private layoutQueued;
|
|
188
415
|
private timers;
|
|
416
|
+
/** The arrangement the navigation in flight is heading for; see {@link intended}. */
|
|
417
|
+
private intent;
|
|
418
|
+
/** The navigation the router hasn't settled yet, if any. */
|
|
419
|
+
private settling;
|
|
420
|
+
/** The route last seen by the observer, for stashing a left panel's query. */
|
|
421
|
+
private lastSeen;
|
|
422
|
+
/** The one navigation waiting behind it; see {@link issue}. */
|
|
423
|
+
private queued;
|
|
189
424
|
constructor(opts: PanelStackOptions);
|
|
190
425
|
/** Resolve a path to its route handler + params, falling back to `notFound`. */
|
|
191
426
|
private resolve;
|
|
192
427
|
private matches;
|
|
193
428
|
/**
|
|
194
|
-
* The
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
429
|
+
* The stack for origin-less navigation: a cold deep link, a nav item, a
|
|
430
|
+
* `route.go()` — anything arriving without a panel to build on and without a
|
|
431
|
+
* snapshot to restore.
|
|
432
|
+
*
|
|
433
|
+
* The app's {@link PanelStackOptions.ancestors} gets first say, since only it
|
|
434
|
+
* can know what belongs under a path that doesn't spell its own context out
|
|
435
|
+
* (a `/thread/[id]` reached from a notification). Failing that — or when it
|
|
436
|
+
* has no opinion — every prefix of the path is probed against the route table
|
|
437
|
+
* and the matching ones become the stack. Either way, a path with no route is
|
|
438
|
+
* skipped rather than opened as a "not found" column, so an app that doesn't
|
|
439
|
+
* want one screen stacked under another simply doesn't route it. The path
|
|
440
|
+
* itself always ends the derived stack, matched or not.
|
|
199
441
|
*/
|
|
200
|
-
deriveStack
|
|
201
|
-
/**
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
442
|
+
private deriveStack;
|
|
443
|
+
/**
|
|
444
|
+
* Ask the `ancestors` table what belongs beneath `path`. The first key that
|
|
445
|
+
* matches answers — with its own matched params, so it never has to take the
|
|
446
|
+
* path apart itself — and `undefined` from it means "no opinion", leaving the
|
|
447
|
+
* path to the prefix derivation just as an unlisted one is.
|
|
448
|
+
*/
|
|
449
|
+
private askAncestors;
|
|
450
|
+
/** Every prefix of `path` that has a route, shallowest first. */
|
|
451
|
+
private prefixesOf;
|
|
452
|
+
/**
|
|
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;
|
|
205
460
|
/**
|
|
206
|
-
* The route
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
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.
|
|
211
469
|
*/
|
|
212
|
-
private
|
|
470
|
+
private targetFor;
|
|
471
|
+
/** The arrangement the current history entry asks for. Subscribes to path + snapshot. */
|
|
472
|
+
private computeTarget;
|
|
213
473
|
private paths;
|
|
214
|
-
/** The live panels a target stack drops — by path, so a splice removes only its own column. */
|
|
215
|
-
private removedBy;
|
|
216
474
|
/**
|
|
217
|
-
* Adopt
|
|
218
|
-
*
|
|
219
|
-
*
|
|
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.)
|
|
220
482
|
*/
|
|
221
483
|
private propose;
|
|
222
484
|
/**
|
|
223
|
-
* Apply a target
|
|
224
|
-
* difference.
|
|
485
|
+
* Apply a target arrangement: unmount what's gone, mount what's new, animate
|
|
486
|
+
* the difference.
|
|
225
487
|
*
|
|
226
488
|
* Reconciliation is BY PATH (a stack can't hold the same path twice, so that's
|
|
227
489
|
* well-defined): a panel present in both stacks stays mounted *even if its
|
|
@@ -252,69 +514,158 @@ export declare class PanelController {
|
|
|
252
514
|
*/
|
|
253
515
|
private playExit;
|
|
254
516
|
/**
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
*
|
|
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.
|
|
519
|
+
*
|
|
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.
|
|
525
|
+
*/
|
|
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;
|
|
531
|
+
/**
|
|
532
|
+
* Put a navigation to the router, or — while one is still settling — behind
|
|
533
|
+
* the one that is. Only the newest waits: each was worked out against
|
|
534
|
+
* {@link intended}, so the newest is the one that means what the user last
|
|
535
|
+
* asked for, and the one it displaces resolves `false`.
|
|
536
|
+
*
|
|
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.
|
|
541
|
+
*/
|
|
542
|
+
private issue;
|
|
543
|
+
private start;
|
|
544
|
+
/**
|
|
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".
|
|
568
|
+
*
|
|
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;
|
|
581
|
+
/**
|
|
582
|
+
* Navigate to `href`. `origin` is the path of the panel the link lives in, or
|
|
583
|
+
* `null` when it has none — a nav item, or a programmatic call, which builds
|
|
584
|
+
* the whole stack instead (see {@link deriveStack}). `replace` swaps the
|
|
585
|
+
* originating panel rather than stacking on top of it, and `beneath` says what
|
|
586
|
+
* the stack under the target is outright, for callers that know.
|
|
271
587
|
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
*/
|
|
280
|
-
closePanelAt(index: number): Promise<boolean>;
|
|
281
|
-
/** Guarded close of whichever panel `path` is open as. False when it isn't open. */
|
|
282
|
-
closeByPath(path: string): Promise<boolean>;
|
|
283
|
-
/** Guarded close of the panel whose `.s-panel` element this is. */
|
|
284
|
-
closePanelEl(el: HTMLElement): Promise<boolean>;
|
|
285
|
-
/**
|
|
286
|
-
* Navigate to `href`. `originIndex` is the depth of the panel the link lives
|
|
287
|
-
* in (−1 when it has none — a nav item or a programmatic call, which derives
|
|
288
|
-
* the whole stack instead). `replace` swaps the originating panel rather than
|
|
289
|
-
* stacking on top of it.
|
|
290
|
-
*/
|
|
291
|
-
navigate(href: string, originIndex: number, replace?: boolean): void;
|
|
292
|
-
/** Programmatic push/replace, with the top panel as the implied origin. */
|
|
293
|
-
pushPath(path: string, replace: boolean): void;
|
|
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).
|
|
591
|
+
*/
|
|
592
|
+
private navigate;
|
|
593
|
+
/** Programmatic push/replace, with the current panel as the implied origin. */
|
|
594
|
+
private pushPath;
|
|
294
595
|
/**
|
|
295
596
|
* Link handling through `route.interceptLinks()`, whose handler hook hands us
|
|
296
597
|
* the anchor so we can decide what the click *means*: the originating
|
|
297
|
-
* `.s-panel` (which decides what the click truncates), `data-panel
|
|
298
|
-
* and return-to-an-open-panel semantics. The exclusion rules
|
|
299
|
-
* downloads, modified clicks, external URLs) live in Aberdeen; the
|
|
300
|
-
* 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`.
|
|
301
607
|
*/
|
|
302
608
|
private interceptLinks;
|
|
303
|
-
|
|
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
|
+
*/
|
|
304
640
|
private watchTitle;
|
|
305
641
|
/**
|
|
306
|
-
*
|
|
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()`.
|
|
307
650
|
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
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}).
|
|
311
654
|
*/
|
|
312
|
-
|
|
655
|
+
drawColumns(): void;
|
|
313
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;
|
|
314
665
|
scheduleLayout(): void;
|
|
315
666
|
/**
|
|
316
667
|
* Measure the shell, and with it the width the window gives a panel of each
|
|
317
|
-
* layout. Measured on the *shell*, not on the
|
|
668
|
+
* layout. Measured on the *shell*, not on the column region: the region's width
|
|
318
669
|
* is the layout engine's own output, so reading it back would nail the layout
|
|
319
670
|
* to whatever it happened to be a frame ago. Fractional widths throughout — a
|
|
320
671
|
* rounded column edge would drift a pixel away from the chrome above it.
|
|
@@ -330,7 +681,7 @@ export declare class PanelController {
|
|
|
330
681
|
* would be a forced reflow each, in the middle of building their DOM.
|
|
331
682
|
*/
|
|
332
683
|
private geometry;
|
|
333
|
-
/** How wide a panel
|
|
684
|
+
/** How wide a panel asking for this is, right now; 0 while the shell can't be measured. */
|
|
334
685
|
private roomFor;
|
|
335
686
|
/**
|
|
336
687
|
* Size and position every panel, and publish the width of the whole ensemble
|
|
@@ -344,49 +695,4 @@ export declare class PanelController {
|
|
|
344
695
|
/** Let a `loading` panel's enter animation wait — but not indefinitely. */
|
|
345
696
|
private holdEnter;
|
|
346
697
|
}
|
|
347
|
-
/**
|
|
348
|
-
* Navigating the routed `S.main()` shell from code, for the times it isn't a
|
|
349
|
-
* link click, such as opening the screen for a record you just created.
|
|
350
|
-
*
|
|
351
|
-
* The same rules as a link click apply: pushing a path that is already open
|
|
352
|
-
* goes back to it rather than opening it twice, and anything that would close a
|
|
353
|
-
* panel asks its {@link Page.requestClose} first.
|
|
354
|
-
*
|
|
355
|
-
* @example
|
|
356
|
-
* ```ts
|
|
357
|
-
* S.button({ content: "New task", click: async () => {
|
|
358
|
-
* const task = await createTask();
|
|
359
|
-
* S.panels.push(`/tasks/${task.id}`);
|
|
360
|
-
* }});
|
|
361
|
-
* ```
|
|
362
|
-
*/
|
|
363
|
-
export declare const panels: {
|
|
364
|
-
/** Opens `path` in a new panel on top of the top one. */
|
|
365
|
-
push(path: string): void;
|
|
366
|
-
/**
|
|
367
|
-
* Opens `path` in place of the top panel, which closes (asking its
|
|
368
|
-
* {@link Page.requestClose} first). The panels beneath it stay as they are.
|
|
369
|
-
*/
|
|
370
|
-
replace(path: string): void;
|
|
371
|
-
/**
|
|
372
|
-
* Closes the top panel, or, given a `path`, whichever panel is open at it,
|
|
373
|
-
* asking {@link Page.requestClose} first. A panel that isn't on top is taken
|
|
374
|
-
* out on its own, leaving the columns to its right exactly as they are.
|
|
375
|
-
*
|
|
376
|
-
* Resolves `false` if the panel didn't close: `requestClose` said no, `path`
|
|
377
|
-
* isn't open, or another navigation got there first.
|
|
378
|
-
*/
|
|
379
|
-
close(path?: string): Promise<boolean>;
|
|
380
|
-
/** The paths of the open panels, oldest first. Reactive: safe to read in a scope. */
|
|
381
|
-
readonly stack: readonly string[];
|
|
382
|
-
};
|
|
383
|
-
/**
|
|
384
|
-
* Closes the panel `el` sits in, working out which one that is from the DOM.
|
|
385
|
-
* That is what lets a close button work without being handed a `$page`, from
|
|
386
|
-
* any column, whether or not it is on top. Used by `S.box`'s `close: true`.
|
|
387
|
-
*
|
|
388
|
-
* Outside a routed shell (or outside any panel, such as a box in a dialog) there is
|
|
389
|
-
* nothing to close: it warns and resolves `false`.
|
|
390
|
-
*/
|
|
391
|
-
export declare function closeContainingPanel(el: Element | null | undefined): Promise<boolean>;
|
|
392
698
|
export {};
|