staffa 0.13.0 → 0.15.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 +19 -16
- package/dist/components/dialog.js +1 -1
- package/dist/components/main.d.ts +37 -48
- package/dist/components/main.js +25 -61
- package/dist/components/menu.d.ts +8 -0
- package/dist/components/menu.js +40 -6
- package/dist/components/panels.d.ts +114 -82
- package/dist/components/panels.js +198 -230
- package/dist/components/select.js +4 -2
- package/dist/components/tabs.js +5 -4
- package/dist/components/tooltip.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/staffa.esm.js +1 -1
- package/dist/theme.d.ts +13 -65
- package/dist/theme.js +21 -8
- package/package.json +1 -1
- package/skill/FloatingMenuOptions.md +10 -0
- package/skill/MainOptions.md +35 -49
- package/skill/Panel.md +31 -32
- package/skill/PanelStack.md +4 -4
- package/skill/SKILL.md +29 -16
- package/skill/main.md +2 -1
- package/src/components/dialog.ts +1 -1
- package/src/components/main.ts +62 -108
- package/src/components/menu.ts +50 -7
- package/src/components/panels.ts +269 -290
- package/src/components/select.ts +4 -2
- package/src/components/tabs.ts +3 -2
- package/src/components/tooltip.ts +2 -1
- package/src/index.ts +1 -1
- package/src/theme.ts +24 -9
|
@@ -4,20 +4,24 @@ import { type Slot } from "../core.js";
|
|
|
4
4
|
* Routed, multi-column panel navigation for {@link main}.
|
|
5
5
|
*
|
|
6
6
|
* Each route draws one screen of the app, called a *panel*. The open panels
|
|
7
|
-
* form a **stack**,
|
|
7
|
+
* form a **stack**, whose last panel is the **current** one: the panel the URL
|
|
8
8
|
* names, and the rightmost column on screen. As many panels as fit are shown,
|
|
9
9
|
* ending at the current one — on a phone that is one at a time, on a wider
|
|
10
10
|
* screen the panels that would have covered each other sit side by side
|
|
11
11
|
* instead. The app's own code is the same either way.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
13
|
+
* Whatever a navigation lands on becomes the top of that stack, and the same
|
|
14
|
+
* path is never in it twice. Opening a new panel closes everything after the
|
|
15
|
+
* panel it came from; a plain link to a panel that is already open — a
|
|
16
|
+
* breadcrumb, a nav item for the section you are in — returns to its own
|
|
17
|
+
* place, closing whatever was stacked on top, while a `replace` or `open`
|
|
18
|
+
* applies its usual shape, the open panel moving into it alive.
|
|
19
|
+
* Two kinds of panel survive that: the ones the user pinned, which ride along,
|
|
20
|
+
* and the ones holding unsaved work, which no navigation ever tears down.
|
|
21
|
+
* Those wait parked out of sight past the rightmost column, which is the only
|
|
22
|
+
* way a panel ever sits *after* the current one. Escape closes the current
|
|
23
|
+
* panel — or just steps left, when it holds unsaved work or panels sit parked
|
|
24
|
+
* beyond it.
|
|
21
25
|
*
|
|
22
26
|
* Navigation runs through `aberdeen/route`: the URL holds the current panel,
|
|
23
27
|
* and the rest of the arrangement — the panels before it, the ones parked
|
|
@@ -83,6 +87,14 @@ export type AncestorsHandler<P = any> = (params: P, path: string) => readonly st
|
|
|
83
87
|
export type AncestorTable<R> = {
|
|
84
88
|
[K in keyof R & string]?: (params: Prettify<PathParams<K>>, path: string) => readonly string[] | undefined | void;
|
|
85
89
|
};
|
|
90
|
+
/**
|
|
91
|
+
* How wide a panel asks to be — a ceiling the shell never exceeds; see
|
|
92
|
+
* {@link Panel.maxWidth}. `"small"` is the column the content area is divided
|
|
93
|
+
* into; `"medium"` and `"large"` are two and three of those, and `"none"` is
|
|
94
|
+
* the whole area. Each is capped at the content area, so on a narrow window
|
|
95
|
+
* they all come to the same thing.
|
|
96
|
+
*/
|
|
97
|
+
export type PanelSize = "small" | "medium" | "large" | "none";
|
|
86
98
|
/**
|
|
87
99
|
* What a route handler gets: the params from its route, plus everything the
|
|
88
100
|
* shell needs to know about the panel it is drawing. It's an Aberdeen proxy, so
|
|
@@ -153,35 +165,39 @@ export interface Panel<P = Record<string, string | number | string[]>> {
|
|
|
153
165
|
*/
|
|
154
166
|
readonly visible: boolean;
|
|
155
167
|
/**
|
|
156
|
-
* The widest this panel can usefully be
|
|
157
|
-
*
|
|
158
|
-
* *
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* - `"
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
* the
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* else is open, so opening or closing a panel never
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
168
|
+
* The widest this panel can usefully be — a ceiling the shell never
|
|
169
|
+
* exceeds, so the draw function never has to look right past it. It is
|
|
170
|
+
* counted in the shell's *columns*: the content area divides into the
|
|
171
|
+
* narrowest whole number of columns of at least 360px each (three columns
|
|
172
|
+
* of 360 in a 1080px area, four of 380 in 1520px), and a column is never
|
|
173
|
+
* wider than 540 — where the area holds just one, a small centres in it
|
|
174
|
+
* rather than stretching. So an ask never exceeds its column count × 540:
|
|
175
|
+
*
|
|
176
|
+
* - `"small"` — one column, never above 540px: lists, detail forms.
|
|
177
|
+
* - `"medium"` (the default) — two columns, never above 1080px.
|
|
178
|
+
* - `"large"` — three columns, never above 1620px: wide tables.
|
|
179
|
+
* - `"none"` — the whole content area, unbounded: boards, dashboards.
|
|
180
|
+
* Bound it with the shell's own `maxWidth` where that matters.
|
|
181
|
+
*
|
|
182
|
+
* Every size is capped at the content area, so on a phone they all come to
|
|
183
|
+
* the same thing: one screen at a time. And a width depends only on the
|
|
184
|
+
* window, never on what else is open, so opening or closing a panel never
|
|
185
|
+
* resizes another — the run of columns just recentres in the area.
|
|
186
|
+
*
|
|
187
|
+
* The ask is a ceiling only; there is no matching floor, since the window can
|
|
188
|
+
* be any width. Aim your layout at 360px — about the narrowest phone still in
|
|
189
|
+
* common use — and let it degrade gracefully below that.
|
|
190
|
+
*
|
|
191
|
+
* Ask only for what your content can actually use: a panel that would cap
|
|
192
|
+
* its own content narrower than its ask is holding room that would have
|
|
193
|
+
* let another column fit beside it.
|
|
178
194
|
*
|
|
179
195
|
* Set it at the top of your handler and the panel is already that wide when
|
|
180
196
|
* you draw (see {@link Panel.width}); set it later — when your data tells you
|
|
181
197
|
* — and the panel reflows without being redrawn, keeping its state, while
|
|
182
198
|
* the columns beside it move over.
|
|
183
199
|
*/
|
|
184
|
-
maxWidth?:
|
|
200
|
+
maxWidth?: PanelSize;
|
|
185
201
|
/**
|
|
186
202
|
* Set this while you're fetching what the panel needs, and back to `false`
|
|
187
203
|
* when you're done. A new panel waits a moment before sliding in, so it can
|
|
@@ -192,10 +208,10 @@ export interface Panel<P = Record<string, string | number | string[]>> {
|
|
|
192
208
|
loading?: boolean;
|
|
193
209
|
/**
|
|
194
210
|
* Keeps this panel from being closed by navigation happening *elsewhere*.
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* click away. The user toggles it from the crumb's context menu
|
|
211
|
+
* A navigation normally closes everything after the panel it came from (or
|
|
212
|
+
* returned to); a pinned panel survives that, staying in the stack — slotted
|
|
213
|
+
* in beneath the new panel, or parked out of sight when the new panel was
|
|
214
|
+
* already beneath it. Either way it is one crumb click away. The user toggles it from the crumb's context menu
|
|
199
215
|
* (right-click or long-press), which is also where the pin shows; setting
|
|
200
216
|
* it from code does the same thing.
|
|
201
217
|
*
|
|
@@ -207,12 +223,11 @@ export interface Panel<P = Record<string, string | number | string[]>> {
|
|
|
207
223
|
/**
|
|
208
224
|
* Set this while the panel holds work that must not be lost — a dirty form,
|
|
209
225
|
* an upload in flight. An unsaved panel cannot be closed, by anything:
|
|
210
|
-
* navigation that would prune it parks it instead,
|
|
211
|
-
*
|
|
212
|
-
* only parks it. {@link Panel.close} and the crumb menu's Close refuse,
|
|
226
|
+
* navigation that would prune it parks it out of sight instead, wearing a
|
|
227
|
+
* ● in its crumb — even the browser's back button only parks it. {@link Panel.close} and the crumb menu's Close refuse,
|
|
213
228
|
* Escape on it steps left along the stack rather than closing, and closing
|
|
214
|
-
* the browser tab runs into the browser's own are-you-sure
|
|
215
|
-
*
|
|
229
|
+
* the browser tab runs into the browser's own are-you-sure, the unsaved
|
|
230
|
+
* panel brought on screen as the question is raised.
|
|
216
231
|
*
|
|
217
232
|
* Only the app clears it; the user has no toggle. A Save or Discard button
|
|
218
233
|
* clears it and then closes:
|
|
@@ -277,6 +292,7 @@ export interface Panel<P = Record<string, string | number | string[]>> {
|
|
|
277
292
|
*/
|
|
278
293
|
open(href: string, how?: "push" | "replace" | "open"): Promise<boolean>;
|
|
279
294
|
}
|
|
295
|
+
export declare const SMALL_MAX_PX = 540;
|
|
280
296
|
/** Options the stack needs from its shell. */
|
|
281
297
|
export interface PanelStackOptions {
|
|
282
298
|
routes: Routes;
|
|
@@ -287,8 +303,6 @@ export interface PanelStackOptions {
|
|
|
287
303
|
columns?: "auto" | "single";
|
|
288
304
|
/** What a bare link does. See {@link MainOptions.linkNavigation}. */
|
|
289
305
|
linkNavigation?: "push" | "replace" | "open";
|
|
290
|
-
/** How wide a `"full"` panel gets, in px. See {@link MainOptions.fullWidth}. */
|
|
291
|
-
fullWidth: number;
|
|
292
306
|
/** The shell's own title, used as the suffix of `document.title`. */
|
|
293
307
|
title?: unknown;
|
|
294
308
|
/**
|
|
@@ -346,10 +360,10 @@ export interface PanelStack {
|
|
|
346
360
|
* the new panel).
|
|
347
361
|
*
|
|
348
362
|
* The same rules as a link click apply: pushing a path that is already open
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
363
|
+
* returns to it — closing whatever was stacked on top — rather than opening
|
|
364
|
+
* it twice, and a panel holding {@link Panel.unsaved} work is never closed,
|
|
365
|
+
* only parked. That's what a plain link does, and what `data-panel=push`
|
|
366
|
+
* says outright.
|
|
353
367
|
*
|
|
354
368
|
* Note that a link builds on the panel it is *drawn in*, which is the
|
|
355
369
|
* current panel only while no column beside it has the focus. Code
|
|
@@ -584,19 +598,21 @@ export declare class PanelStackController implements PanelStack {
|
|
|
584
598
|
private issue;
|
|
585
599
|
private start;
|
|
586
600
|
/**
|
|
587
|
-
* Make the stack's `index`th panel current
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
601
|
+
* Make the stack's `index`th panel current without closing anything, leaving
|
|
602
|
+
* the panels right of it parked out of sight.
|
|
603
|
+
*
|
|
604
|
+
* Only ever a step around a panel that refuses to close — nothing else is
|
|
605
|
+
* left sitting after the current one — so this is Escape's way past an
|
|
606
|
+
* unsaved panel, and the way back to one. It is a history entry, so the
|
|
607
|
+
* browser's back button returns the focus to where it was.
|
|
592
608
|
*/
|
|
593
609
|
private focusAt;
|
|
594
610
|
/**
|
|
595
611
|
* One step back along the stack — what Escape does (`main()` calls this;
|
|
596
|
-
* it is not {@link PanelStack} API).
|
|
597
|
-
*
|
|
598
|
-
*
|
|
599
|
-
*
|
|
612
|
+
* it is not {@link PanelStack} API). Normally that closes the current panel,
|
|
613
|
+
* which is the stack's end. When it holds {@link Panel.unsaved} work — or
|
|
614
|
+
* panels sit parked beyond it — it stays open instead, and the focus
|
|
615
|
+
* simply moves to the panel on its left.
|
|
600
616
|
* Resolves `false` at the stack's start, where there is no left to go.
|
|
601
617
|
*/
|
|
602
618
|
back(): Promise<boolean>;
|
|
@@ -632,11 +648,14 @@ export declare class PanelStackController implements PanelStack {
|
|
|
632
648
|
* that know it.
|
|
633
649
|
*
|
|
634
650
|
* `how` is the link's `data-panel` attribute (or the caller's word for
|
|
635
|
-
* it)
|
|
636
|
-
*
|
|
637
|
-
*
|
|
638
|
-
* `"open"`
|
|
639
|
-
*
|
|
651
|
+
* it), picking how much of `from`'s context the target keeps: a push (the
|
|
652
|
+
* default, and what unrecognised values fall back to) keeps `from` and
|
|
653
|
+
* builds on it, `"replace"` keeps only what is beneath `from`, and
|
|
654
|
+
* `"open"` keeps nothing — the target arrives with its own stack, the way
|
|
655
|
+
* a nav item's link does. Absent, it is the shell's `linkNavigation`
|
|
656
|
+
* default, like a link without the attribute. A target that is already
|
|
657
|
+
* open is returned to by a push, and *moved* — alive, state intact — by
|
|
658
|
+
* the other two: the stack never holds a path twice.
|
|
640
659
|
*
|
|
641
660
|
* Resolves the way every {@link PanelStack} method does: `true` once the
|
|
642
661
|
* navigation lands, `false` when it doesn't (already there counts as
|
|
@@ -669,21 +688,15 @@ export declare class PanelStackController implements PanelStack {
|
|
|
669
688
|
setColumns(columns: "auto" | "single" | undefined): void;
|
|
670
689
|
/** Adopt a changed `linkNavigation` default; the next click reads it. */
|
|
671
690
|
setLinkNavigation(mode: "push" | "replace" | "open" | undefined): void;
|
|
672
|
-
/**
|
|
673
|
-
* Adopt a changed `fullWidth`: one layout pass, nothing redrawn. A changed
|
|
674
|
-
* `navWidth` needs no counterpart — resizing the sidebar resizes the column
|
|
675
|
-
* region, which the layout engine is already observing.
|
|
676
|
-
*/
|
|
677
|
-
setFullWidth(px: number): void;
|
|
678
691
|
/**
|
|
679
692
|
* The breadcrumb stack, drawn by `main()` into the top bar: every open
|
|
680
693
|
* panel, oldest first, the ones on screen right now in bold, pinned ones
|
|
681
694
|
* wearing their pin. Every crumb but the current panel's is a plain link to
|
|
682
|
-
* that panel, and a link to an open panel
|
|
683
|
-
* so clicking
|
|
684
|
-
*
|
|
685
|
-
*
|
|
686
|
-
*
|
|
695
|
+
* that panel, and a link to an open panel returns to it (see `navigate`) —
|
|
696
|
+
* so clicking a crumb goes back to that panel and closes what was stacked on
|
|
697
|
+
* top of it, pinned and unsaved panels excepted. Right-click (or long-press)
|
|
698
|
+
* offers pinning, and closing just that one panel — the close that splices
|
|
699
|
+
* it out of the middle when it isn't last.
|
|
687
700
|
*/
|
|
688
701
|
drawCrumbs(): void;
|
|
689
702
|
private drawCrumb;
|
|
@@ -702,9 +715,9 @@ export declare class PanelStackController implements PanelStack {
|
|
|
702
715
|
private watchTitle;
|
|
703
716
|
/**
|
|
704
717
|
* While any open panel holds unsaved work, closing the tab — or navigating
|
|
705
|
-
* the whole browser away — runs into the browser's own are-you-sure
|
|
706
|
-
* the
|
|
707
|
-
*
|
|
718
|
+
* the whole browser away — runs into the browser's own are-you-sure, with
|
|
719
|
+
* the unsaved panel brought on screen as the question is raised, so what is
|
|
720
|
+
* holding the tab is in front of the user rather than parked out of sight.
|
|
708
721
|
*/
|
|
709
722
|
private guardTabClose;
|
|
710
723
|
/**
|
|
@@ -726,11 +739,31 @@ export declare class PanelStackController implements PanelStack {
|
|
|
726
739
|
private drawActions;
|
|
727
740
|
scheduleLayout(): void;
|
|
728
741
|
/**
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
|
|
742
|
+
* Run the pending layout pass now, rather than on the frame it is waiting
|
|
743
|
+
* for. For the callers that have to *read* what only the pass knows —
|
|
744
|
+
* `$panel.visible`, `$panel.width` — at a moment when waiting isn't an
|
|
745
|
+
* option. Does nothing when no pass is owed.
|
|
746
|
+
*/
|
|
747
|
+
private flushLayout;
|
|
748
|
+
/**
|
|
749
|
+
* Measure the content area, and with it the width a panel of each size gets.
|
|
750
|
+
*
|
|
751
|
+
* The column region *is* the content area: it takes whatever the shell has
|
|
752
|
+
* left beside the sidebar, capped by the shell's own `maxWidth` — all of it
|
|
753
|
+
* CSS's doing, so there is nothing to add up here and nothing that could
|
|
754
|
+
* drift from the width the bars above and below line up with. Fractional
|
|
755
|
+
* widths throughout: a rounded column edge would drift a pixel away from that
|
|
756
|
+
* chrome.
|
|
757
|
+
*
|
|
758
|
+
* The area divides into the narrowest whole number of columns that keeps each
|
|
759
|
+
* at least {@link SMALL_MIN_PX} wide — the `"small"` unit every other size is
|
|
760
|
+
* a multiple of, capped at the area itself. So 1080px is three columns of 360
|
|
761
|
+
* and 1520px four of 380. An area too narrow for two is a single column,
|
|
762
|
+
* itself capped at {@link SMALL_MAX_PX}: a small centres there instead of
|
|
763
|
+
* stretching toward 720, so its ceiling holds, while the larger sizes still
|
|
764
|
+
* take the whole area. A width is thus a pure function of the window: a panel
|
|
765
|
+
* NEVER resizes because a neighbour came or went, and only a window resize
|
|
766
|
+
* (the snap pass in `layout`) changes one.
|
|
734
767
|
*
|
|
735
768
|
* `undefined` while the shell has no width to speak of (it isn't in a document
|
|
736
769
|
* yet, or it's `display:none`); the next pass tries again.
|
|
@@ -746,8 +779,7 @@ export declare class PanelStackController implements PanelStack {
|
|
|
746
779
|
/** How wide a panel asking for this is, right now; 0 while the shell can't be measured. */
|
|
747
780
|
private roomFor;
|
|
748
781
|
/**
|
|
749
|
-
* Size and position every panel
|
|
750
|
-
* (sidebar + separator + columns) for the shell to centre itself on.
|
|
782
|
+
* Size and position every panel.
|
|
751
783
|
*
|
|
752
784
|
* This is everything CSS can't work out for itself: which panels exist, which
|
|
753
785
|
* of them are visible, how wide each one is and where it sits. All the motion
|