staffa 0.14.0 → 0.16.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 +99 -271
- package/dist/components/autocomplete.js +4 -5
- package/dist/components/box.js +11 -21
- package/dist/components/button.d.ts +20 -5
- package/dist/components/button.js +55 -47
- package/dist/components/buttonChooser.js +1 -3
- package/dist/components/checkbox.js +1 -2
- package/dist/components/dialog.d.ts +9 -2
- package/dist/components/dialog.js +29 -35
- package/dist/components/field.d.ts +5 -8
- package/dist/components/field.js +4 -6
- package/dist/components/form.d.ts +5 -7
- package/dist/components/form.js +6 -9
- package/dist/components/keyhelp.d.ts +22 -0
- package/dist/components/keyhelp.js +91 -0
- package/dist/components/main.js +190 -318
- package/dist/components/menu.d.ts +36 -9
- package/dist/components/menu.js +193 -144
- package/dist/components/panels.d.ts +152 -232
- package/dist/components/panels.js +341 -556
- package/dist/components/select.js +1 -3
- package/dist/components/tabs.d.ts +10 -13
- package/dist/components/tabs.js +40 -63
- package/dist/components/textline.d.ts +3 -5
- package/dist/components/textline.js +3 -5
- package/dist/components/toast.d.ts +1 -3
- package/dist/components/toast.js +3 -6
- package/dist/components/tooltip.d.ts +4 -5
- package/dist/components/tooltip.js +13 -22
- package/dist/core.d.ts +17 -39
- package/dist/core.js +13 -35
- package/dist/icons-helpers.d.ts +3 -3
- package/dist/icons-helpers.js +6 -11
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -4
- package/dist/keys.d.ts +92 -0
- package/dist/keys.js +279 -0
- package/dist/staffa.esm.js +1 -1
- package/dist/theme.d.ts +4 -10
- package/dist/theme.js +58 -123
- package/package.json +2 -2
- package/skill/ButtonOptions.md +12 -0
- package/skill/DialogOptions.md +11 -2
- package/skill/FieldOptions.md +3 -5
- package/skill/IconButtonOptions.md +8 -0
- package/skill/MenuItem.md +22 -3
- package/skill/Panel.md +8 -0
- package/skill/SKILL.md +161 -294
- package/skill/addTooltip.md +4 -5
- package/skill/bindKey.md +51 -0
- package/skill/box.md +1 -1
- package/skill/form.md +5 -7
- package/skill/formatKey.md +21 -0
- package/skill/iconButton.md +4 -5
- package/skill/scrollStrip.md +7 -9
- package/skill/showFloatingMenu.md +2 -2
- package/skill/showKeyHelp.md +17 -0
- package/skill/tabs.md +3 -4
- package/skill/textline.md +3 -5
- package/src/components/autocomplete.ts +4 -5
- package/src/components/box.ts +11 -21
- package/src/components/button.ts +70 -47
- package/src/components/buttonChooser.ts +1 -3
- package/src/components/checkbox.ts +1 -2
- package/src/components/dialog.ts +39 -37
- package/src/components/field.ts +7 -11
- package/src/components/form.ts +6 -9
- package/src/components/keyhelp.ts +96 -0
- package/src/components/main.ts +194 -318
- package/src/components/menu.ts +209 -146
- package/src/components/panels.ts +389 -618
- package/src/components/select.ts +1 -3
- package/src/components/tabs.ts +40 -63
- package/src/components/textline.ts +3 -5
- package/src/components/toast.ts +4 -9
- package/src/components/tooltip.ts +13 -22
- package/src/core.ts +17 -43
- package/src/icons-helpers.ts +6 -11
- package/src/index.ts +5 -4
- package/src/keys.ts +300 -0
- package/src/theme.ts +58 -123
- package/skill/Attributes.md +0 -10
package/dist/theme.js
CHANGED
|
@@ -2,54 +2,23 @@ import A from "aberdeen";
|
|
|
2
2
|
/**
|
|
3
3
|
* Theming and global base styles for Staffa.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* A Staffa app is a tree of **surfaces** (`.s-s`), in two families:
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* - **Neutral** — `.neutral`, and the implicit page at `:root`. Its shade steps
|
|
8
|
+
* with nesting depth (capped). No `tonal`/`outlined` variants.
|
|
9
|
+
* - **Accent** — `.primary`, `.danger`, `.success`, `.warning`, `.link` (a bare
|
|
10
|
+
* `.s-s` is primary): a bright fill with white ink, `.tonal`/`.outlined`
|
|
11
|
+
* variants supported. A surface nested *inside* one is forced back to filled,
|
|
12
|
+
* so it can't bleed into the vivid parent.
|
|
10
13
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* is always rendered filled, so it can't bleed into the vivid parent.
|
|
20
|
-
*
|
|
21
|
-
* # Contextual tokens
|
|
22
|
-
*
|
|
23
|
-
* Inside any surface (including `:root`) these inherited custom properties are
|
|
24
|
-
* defined, so widgets adapt to wherever they're nested:
|
|
25
|
-
*
|
|
26
|
-
* | token | meaning |
|
|
27
|
-
* | ------------ | --------------------------------------------------------- |
|
|
28
|
-
* | `--s-bg` | the surface background |
|
|
29
|
-
* | `--s-text` | default ink (also applied as `color`) |
|
|
30
|
-
* | `--s-muted` | secondary text (subtitles, help) |
|
|
31
|
-
* | `--s-accent` | the surface's "pop" — the brand primary on neutral surfaces, the ink on accent surfaces |
|
|
32
|
-
* | `--s-faint` | hairline, derived from text/bg |
|
|
33
|
-
*
|
|
34
|
-
* The brand/semantic colours are mode-independent and settable: `--s-primary`
|
|
35
|
-
* (the one brand colour — it tints the neutrals and defines `.s-s.primary`),
|
|
36
|
-
* `--s-danger`, `--s-success`, `--s-warning`, and `--s-link` (the link colour,
|
|
37
|
-
* also the fill of the `.s-s.link` surface). Links render in `--s-link` on
|
|
38
|
-
* neutral surfaces and in the ink on accent surfaces.
|
|
39
|
-
*
|
|
40
|
-
* # Borders & shadows
|
|
41
|
-
*
|
|
42
|
-
* Neutral surfaces carry a subtle hairline border by default (so a card reads as a
|
|
43
|
-
* card with no component help); it's applied through `:where()`, so a bar that
|
|
44
|
-
* wants only a divider overrides it trivially. Any surface can opt into elevation
|
|
45
|
-
* with `.shadow` or `.extra-shadow`, or drop a component's built-in shadow with
|
|
46
|
-
* `.no-shadow` (e.g. `S.button({ attrs: ".no-shadow" })`).
|
|
47
|
-
*
|
|
48
|
-
* # Customising
|
|
49
|
-
*
|
|
50
|
-
* Re-skin by overriding the colour tokens (e.g. `--s-primary`). To add your own
|
|
51
|
-
* accent surface, just set `--s-bg` (and, if needed, `--s-text`) — the gradient
|
|
52
|
-
* and the rest of the tokens follow automatically:
|
|
14
|
+
* Every surface (and `:root`) defines the inherited tokens widgets style against,
|
|
15
|
+
* so they adapt to wherever they're nested: `--s-bg`, `--s-text` (also applied as
|
|
16
|
+
* `color`), `--s-muted` (secondary text), `--s-accent` (the surface's "pop" — the
|
|
17
|
+
* brand primary on neutral surfaces, the ink on accent ones) and `--s-faint`
|
|
18
|
+
* (hairline). The brand/semantic colours — `--s-primary`, `--s-danger`,
|
|
19
|
+
* `--s-success`, `--s-warning`, `--s-link` — are mode-independent and settable;
|
|
20
|
+
* overriding them re-skins the app. A custom accent surface needs only `--s-bg`
|
|
21
|
+
* (and, if needed, `--s-text`); gradient and tokens follow:
|
|
53
22
|
*
|
|
54
23
|
* ```ts
|
|
55
24
|
* A.insertGlobalCss({ ".s-s.brand": "--s-bg:#ef6b00 --s-text:#fff" });
|
|
@@ -58,25 +27,18 @@ import A from "aberdeen";
|
|
|
58
27
|
*/
|
|
59
28
|
/**
|
|
60
29
|
* The subtle single-colour wash a surface is painted with, as a `background:`
|
|
61
|
-
* declaration, at the given angle.
|
|
62
|
-
* property
|
|
63
|
-
*
|
|
64
|
-
* with the page's wash instead of its own `$s-bg`'s.
|
|
30
|
+
* declaration, at the given angle. A shared constant rather than a `--s-sheen`
|
|
31
|
+
* custom property: `var()`s inside a custom property resolve where it is
|
|
32
|
+
* *defined*, so every surface would get the page's wash instead of its own.
|
|
65
33
|
*/
|
|
66
34
|
const sheen = (angle) => `background: linear-gradient(${angle}, color-mix(in oklab, $s-bg, white 9%), color-mix(in oklab, $s-bg, black 9%));`;
|
|
67
35
|
/** The surface sheen: the wash every surface (and the page) is painted with. */
|
|
68
36
|
export const SURFACE_SHEEN = sheen("170deg");
|
|
69
37
|
/**
|
|
70
|
-
* The same wash, straight down — for panels.ts, where the routed columns and
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* height, so a 430px column and the 1720px region behind it would paint
|
|
75
|
-
* different slices of the same wash and meet at a visible step. Straight down,
|
|
76
|
-
* the extent is the height alone — which every column shares exactly with the
|
|
77
|
-
* region (a column is `top:0 bottom:0` in it) and so with every other column.
|
|
78
|
-
* The 10° of tilt is worth losing there; it buys the one place in the app where
|
|
79
|
-
* boxes of *different widths* must be seamless.
|
|
38
|
+
* The same wash, straight down — for panels.ts, where the routed columns and the
|
|
39
|
+
* ground beside them have to look like one continuous surface. An angled gradient
|
|
40
|
+
* takes its extent from the box's *width* as well as its height, so boxes of
|
|
41
|
+
* different widths would paint different slices of it and meet at a visible step.
|
|
80
42
|
*/
|
|
81
43
|
export const PANEL_SHEEN = sheen("180deg");
|
|
82
44
|
const STORAGE_KEY = "staffa:darkMode";
|
|
@@ -124,13 +86,9 @@ export function getDarkMode(allowAuto = false) {
|
|
|
124
86
|
const v = $override.value;
|
|
125
87
|
return v === undefined && !allowAuto ? A.darkMode() : v;
|
|
126
88
|
}
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
// the page (depth 0); each nested `.neutral` steps one shade up, capped at the
|
|
131
|
-
// `.neutral .neutral` rule. The accent (coloured) surfaces and everything else are
|
|
132
|
-
// mode-independent and live in the static block below.
|
|
133
|
-
// ---------------------------------------------------------------------------
|
|
89
|
+
// The only mode-dependent thing: the neutral shades and their ink, written
|
|
90
|
+
// straight onto the surfaces (no intermediate palette vars). `:root` is the page
|
|
91
|
+
// (depth 0); each nested `.neutral` steps a shade up, capped at the second level.
|
|
134
92
|
A(() => {
|
|
135
93
|
if (getDarkMode()) {
|
|
136
94
|
A.insertGlobalCss({
|
|
@@ -147,34 +105,26 @@ A(() => {
|
|
|
147
105
|
});
|
|
148
106
|
}
|
|
149
107
|
});
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
// tokens are single values kept only because they're reused across components.
|
|
154
|
-
// Rule order matters: role fills come after the `:not(.neutral)` default, so a
|
|
155
|
-
// caller's `attrs` override wins at equal specificity.
|
|
156
|
-
// ---------------------------------------------------------------------------
|
|
108
|
+
// Static structure — inserted once, mode-independent. Rule order matters: role
|
|
109
|
+
// fills come after the `:not(.neutral)` default, so a caller's `attrs` override
|
|
110
|
+
// wins at equal specificity.
|
|
157
111
|
A.setSpacingCssVars(1.1);
|
|
158
112
|
A.insertGlobalCss({
|
|
159
|
-
//
|
|
113
|
+
// A lightweight reset: bare semantic HTML, with less ugly defaults.
|
|
160
114
|
"*, *::before, *::after": "box-sizing:border-box",
|
|
161
115
|
html: "text-size-adjust:100%",
|
|
162
116
|
body: "m:0 p:$3 line-height:1.5 font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; -webkit-font-smoothing:antialiased background-color:$s-bg text:$s-text",
|
|
163
|
-
//
|
|
164
|
-
// surfaces, the ink on accent surfaces.
|
|
117
|
+
// The contextual link colour: `--s-link` on neutral surfaces, the ink on accent ones.
|
|
165
118
|
a: "color: $s-link-fg; text-decoration:underline text-underline-offset:2px; transition: color 0.12s, filter 0.12s;",
|
|
166
119
|
"a:hover": "filter: brightness(1.15)",
|
|
167
120
|
"input, button, textarea, select, optgroup": "font:inherit color:inherit",
|
|
168
|
-
// Bare text-like fields get a calm bordered box derived from the surface.
|
|
169
|
-
//
|
|
170
|
-
// higher specificity, so this only governs otherwise-unstyled HTML. `:where()`
|
|
171
|
-
// keeps it at element specificity, so a component class always wins.
|
|
121
|
+
// Bare text-like fields get a calm bordered box derived from the surface.
|
|
122
|
+
// `:where()` keeps it at element specificity, so a component class always wins.
|
|
172
123
|
"input:where(:not([type=checkbox],[type=radio],[type=range],[type=file],[type=color],[type=image],[type=submit],[type=button],[type=reset],[type=hidden])), textarea, select": "background:$s-bg border: 1px solid $s-faint; r:$s-radius-sm padding: 0.45em 0.65em; max-width:100%",
|
|
173
|
-
// Checkboxes/radios: a touch larger with a pointer cursor. (
|
|
174
|
-
//
|
|
124
|
+
// Checkboxes/radios: a touch larger, with a pointer cursor. (`accent-color` is
|
|
125
|
+
// inherited from the surface, see the `:root, .s-s` rule.)
|
|
175
126
|
"input:where([type=checkbox],[type=radio])": "width:1.15em height:1.15em cursor:pointer",
|
|
176
|
-
// Range: a thin pill track
|
|
177
|
-
// with a round brand thumb — no native groove/outline.
|
|
127
|
+
// Range: a thin faint pill track with a round brand thumb — no native groove.
|
|
178
128
|
"input[type=range]": "appearance:none background:transparent cursor:pointer vertical-align:middle",
|
|
179
129
|
"input[type=range]::-webkit-slider-runnable-track": "height:4px r:99px background:$s-faint",
|
|
180
130
|
"input[type=range]::-moz-range-track": "height:4px r:99px background:$s-faint",
|
|
@@ -213,11 +163,10 @@ A.insertGlobalCss({
|
|
|
213
163
|
"--s-focus: color-mix(in srgb, $s-primary 38%, transparent); " +
|
|
214
164
|
// Brand sweep for the headline mark, the active nav pill, the selected tab.
|
|
215
165
|
"--s-gradient: linear-gradient(135deg, color-mix(in oklab, $s-primary, white 16%), color-mix(in oklab, $s-primary, black 14%));",
|
|
216
|
-
// Neutral surfaces (and the page)
|
|
217
|
-
// links use the link colour. (The bg/ink come from the mode block above.)
|
|
166
|
+
// Neutral surfaces (and the page); their bg/ink come from the mode block above.
|
|
218
167
|
":root, .s-s.neutral": "--s-accent:$s-primary --s-link-fg:$s-link",
|
|
219
|
-
// Accent surfaces: bright fill, white ink. The bare `:not(.neutral)`
|
|
220
|
-
// shared defaults
|
|
168
|
+
// Accent surfaces: bright fill, white ink. The bare `:not(.neutral)` holds the
|
|
169
|
+
// shared defaults; each role below names its own fill.
|
|
221
170
|
".s-s:not(.neutral)": "--s-bg:$s-primary " + // Default .s-s to .primary
|
|
222
171
|
"border:0 " +
|
|
223
172
|
"--s-text:#eee --s-accent:#fff --s-link-fg:#eef " +
|
|
@@ -228,55 +177,44 @@ A.insertGlobalCss({
|
|
|
228
177
|
".s-s.warning": "--s-bg:$s-warning",
|
|
229
178
|
".s-s.link": "--s-bg:$s-link",
|
|
230
179
|
".s-s.primary": "--s-bg:$s-primary",
|
|
231
|
-
//
|
|
232
|
-
//
|
|
180
|
+
// Every surface (and the page) derives its muted ink + hairline from the
|
|
181
|
+
// text/bg pair it resolved to above.
|
|
233
182
|
":root, .s-s": "--s-muted: color-mix(in oklab, $s-text, $s-bg 42%); " +
|
|
234
183
|
"--s-faint: color-mix(in oklab, $s-text, $s-bg 80%); " +
|
|
235
184
|
"color:$s-text accent-color:$s-accent scrollbar-width:thin scrollbar-color: $s-faint transparent;",
|
|
236
185
|
// Subtle single-colour gradient sheen, painted on every surface (and the page).
|
|
237
186
|
".s-s, body": SURFACE_SHEEN,
|
|
238
187
|
".s-s": "r:$s-radius",
|
|
239
|
-
//
|
|
240
|
-
// any component help
|
|
241
|
-
//
|
|
242
|
-
// it with a single plain rule. Accent (filled) surfaces don't get it — their fill
|
|
243
|
-
// is the edge. Buttons keep their own `border:0`.
|
|
188
|
+
// A neutral surface owns a hairline border, so a card reads as a card without
|
|
189
|
+
// any component help. `:where()` keeps it zero-specificity, so a bar that wants
|
|
190
|
+
// only a divider overrides it with a single plain rule.
|
|
244
191
|
":where(.s-s.neutral)": "border: 1px solid $s-faint;",
|
|
245
192
|
".s-s::-webkit-scrollbar, .s-s ::-webkit-scrollbar": "width:10px height:10px",
|
|
246
193
|
".s-s::-webkit-scrollbar-track, .s-s ::-webkit-scrollbar-track": "background:transparent",
|
|
247
194
|
".s-s::-webkit-scrollbar-thumb, .s-s ::-webkit-scrollbar-thumb": "background:$s-faint border-radius:99px border: 2px solid transparent; background-clip:padding-box",
|
|
248
|
-
// Elevation
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
// • tonal/outlined surface → ignored (a translucent body has nothing to lift)
|
|
253
|
-
// A `.neutral` button stays flat (so segmented groups gain no stray
|
|
254
|
-
// shadows). `.no-shadow` is a hard override of any of the above — place it last
|
|
255
|
-
// and make it !important so it beats the higher-specificity glow rule.
|
|
195
|
+
// Elevation: `.shadow`/`.extra-shadow` give a neutral surface a drop shadow and
|
|
196
|
+
// an accent one a self-coloured glow; tonal/outlined have nothing to lift. A
|
|
197
|
+
// `.neutral` button stays flat, so segmented groups gain no stray shadows.
|
|
198
|
+
// `.no-shadow` comes last and needs `!important` to beat the glow rules.
|
|
256
199
|
".s-s.shadow.neutral:not(.s-btn)": "box-shadow: 0 4px 14px rgba(0,0,0,0.13);",
|
|
257
200
|
".s-s.extra-shadow.neutral:not(.s-btn)": "box-shadow: 0 18px 50px rgba(0,0,0,0.28);",
|
|
258
201
|
".s-s.shadow:not(.neutral):not(.tonal):not(.outlined)": "box-shadow: 0 4px 14px color-mix(in srgb, $s-bg 30%, transparent);",
|
|
259
202
|
".s-s.extra-shadow:not(.neutral):not(.tonal):not(.outlined)": "box-shadow: 0 14px 40px color-mix(in srgb, $s-bg 40%, transparent);",
|
|
260
203
|
".s-s.no-shadow": "box-shadow: none !important;",
|
|
261
|
-
// Accent variants
|
|
262
|
-
//
|
|
263
|
-
// with a colour edge. (Neutral surfaces ignore these.)
|
|
204
|
+
// Accent variants: the fill colour becomes the ink, over a soft self-tint
|
|
205
|
+
// (`tonal`) or a transparent body with a colour edge (`outlined`).
|
|
264
206
|
".s-s:not(.neutral).tonal, .s-s:not(.neutral).outlined": "--s-text:$s-bg --s-accent:$s-bg --s-link-fg:$s-bg --s-faint: color-mix(in srgb, $s-bg 30%, transparent); --s-muted: color-mix(in srgb, $s-bg 70%, transparent);",
|
|
265
207
|
".s-s:not(.neutral).tonal": "background: color-mix(in srgb, $s-bg 15%, transparent); border: 1px solid $s-faint;",
|
|
266
208
|
".s-s:not(.neutral).outlined": "background: transparent; border: 1px solid color-mix(in srgb, $s-bg 45%, transparent);",
|
|
267
|
-
// A surface
|
|
268
|
-
//
|
|
269
|
-
// Specificity (4 classes) beats the 2-class variant rules — no !important.
|
|
209
|
+
// A surface inside an accent surface is forced back to filled: a translucent
|
|
210
|
+
// body would bleed into the vivid parent. 4 classes beats the variant rules.
|
|
270
211
|
".s-s:not(.neutral) .s-s.tonal, .s-s:not(.neutral) .s-s.outlined": "--s-text:#fff --s-accent:#fff --s-link-fg:#fff " +
|
|
271
212
|
SURFACE_SHEEN + " border-color: transparent;",
|
|
272
213
|
});
|
|
273
214
|
// ── Suppress transitions during the initial load ─────────────────────────────
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
// default (unstyled) colours into the theme. Tag <html> until the first frame
|
|
278
|
-
// has painted and hard-disable transitions under that tag, so the initial render
|
|
279
|
-
// snaps straight to the right colours.
|
|
215
|
+
// Colour transitions make a light↔dark switch smooth, but on a cold load they'd
|
|
216
|
+
// animate from the unstyled colours into the theme. Tag <html> until the first
|
|
217
|
+
// frame has painted, so the initial render snaps to the right colours.
|
|
280
218
|
A.insertGlobalCss({
|
|
281
219
|
".s-preload, .s-preload *, .s-preload *::before, .s-preload *::after": "transition: none !important; animation: none !important;",
|
|
282
220
|
});
|
|
@@ -286,18 +224,15 @@ if (typeof document !== "undefined" && typeof requestAnimationFrame === "functio
|
|
|
286
224
|
requestAnimationFrame(() => requestAnimationFrame(() => root.classList.remove("s-preload")));
|
|
287
225
|
}
|
|
288
226
|
// ── Disabled region ───────────────────────────────────────────────────────────
|
|
289
|
-
// aria-disabled="true" on any container dims it and blocks pointer events on
|
|
290
|
-
//
|
|
291
|
-
// access to focusable descendants is unaffected — add the `inert` attribute too
|
|
292
|
-
// if you need that.
|
|
227
|
+
// aria-disabled="true" on any container dims it and blocks pointer events on it
|
|
228
|
+
// and all descendants. Keyboard access is unaffected — add `inert` for that.
|
|
293
229
|
A.insertGlobalCss({
|
|
294
230
|
":disabled, [aria-disabled=true]": "opacity:0.45 filter:saturate(0.6) user-select:none",
|
|
295
231
|
":disabled, [aria-disabled=true], :disabled *, [aria-disabled=true] *": "pointer-events:none cursor:not-allowed",
|
|
296
232
|
});
|
|
297
233
|
// ── Flow content: vertical rhythm & light typography ─────────────────────────
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
// give every block a *top* margin only when it isn't its parent's first child.
|
|
234
|
+
// Block defaults for *any* content, markdown-rendered or your own: no browser
|
|
235
|
+
// block margins, but a *top* margin unless the block is its parent's first child.
|
|
301
236
|
const BLOCK = "p, ul, ol, dl, blockquote, pre, table, figure, hr, h1, h2, h3, h4, h5, h6";
|
|
302
237
|
A.insertGlobalCss({
|
|
303
238
|
[`${BLOCK}`]: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "staffa",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "An opinionated component library for the Aberdeen reactive UI library.",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"jsdom": "^29.1.1",
|
|
42
42
|
"lucide-static": "^1.17.0",
|
|
43
43
|
"readme-tsdoc": "^1.2.1",
|
|
44
|
-
"shotest": "^2.
|
|
44
|
+
"shotest": "^2.4.0",
|
|
45
45
|
"typescript": "^5.9.3"
|
|
46
46
|
},
|
|
47
47
|
"allowScripts": {
|
package/skill/ButtonOptions.md
CHANGED
|
@@ -44,6 +44,18 @@ Accessible label, when the button has only an icon.
|
|
|
44
44
|
|
|
45
45
|
**Type:** `string`
|
|
46
46
|
|
|
47
|
+
### buttonOptions.key · member
|
|
48
|
+
|
|
49
|
+
A keyboard shortcut that presses this button: `"mod+s"`, `"f2"` — see
|
|
50
|
+
`bindKey` for the spelling and which keystrokes are yours to take.
|
|
51
|
+
It works from anywhere while the button is drawn, shows in a tooltip,
|
|
52
|
+
reaches screen readers as `aria-keyshortcuts`, and does exactly what a
|
|
53
|
+
click does: a `type: "submit"` submits its form, an `href` navigates. The
|
|
54
|
+
`?` overview (`showKeyHelp`) lists it under the button's text (or
|
|
55
|
+
its `ariaLabel`).
|
|
56
|
+
|
|
57
|
+
**Type:** `string`
|
|
58
|
+
|
|
47
59
|
### buttonOptions.attrs · member
|
|
48
60
|
|
|
49
61
|
Aberdeen attr/style string applied to the button. A button is a surface, so
|
package/skill/DialogOptions.md
CHANGED
|
@@ -48,8 +48,17 @@ May be changed on a proxied options object while the dialog is open
|
|
|
48
48
|
|
|
49
49
|
### dialogOptions.cancelWithScope · member
|
|
50
50
|
|
|
51
|
-
When
|
|
52
|
-
|
|
51
|
+
When `true` (default), the dialog is destroyed when the scope that called
|
|
52
|
+
`dialog()` is destroyed.
|
|
53
|
+
|
|
54
|
+
**Type:** `boolean`
|
|
55
|
+
|
|
56
|
+
### dialogOptions.keyboardTransparent · member
|
|
57
|
+
|
|
58
|
+
Leave the keyboard shortcuts behind this dialog working, instead of owning
|
|
59
|
+
the keyboard the way a modal does. For overlays that inform rather than
|
|
60
|
+
interrupt — the `?` shortcut overview is one. Esc still closes it first,
|
|
61
|
+
whatever it meant before.
|
|
53
62
|
|
|
54
63
|
**Type:** `boolean`
|
|
55
64
|
|
package/skill/FieldOptions.md
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
## FieldOptions · interface
|
|
2
2
|
|
|
3
3
|
Options shared by all *form field* components (textline, textarea, checkbox,
|
|
4
|
-
autocomplete, ...).
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
itself, and optional help/error text below it. `form` relies on this
|
|
8
|
-
shared structure to align groups of fields.
|
|
4
|
+
autocomplete, ...). Every field lays out the same way — optional label,
|
|
5
|
+
control, optional help/error below — which is what lets `form` align
|
|
6
|
+
groups of them.
|
|
9
7
|
|
|
10
8
|
### fieldOptions.attrs · member
|
|
11
9
|
|
|
@@ -20,6 +20,14 @@ Click handler.
|
|
|
20
20
|
|
|
21
21
|
**Type:** `(event: Event) => void`
|
|
22
22
|
|
|
23
|
+
### iconButtonOptions.key · member
|
|
24
|
+
|
|
25
|
+
A keyboard shortcut that presses this button — see `ButtonOptions.key`.
|
|
26
|
+
The tooltip shows it after the `ariaLabel` (a glyph is worth naming there
|
|
27
|
+
anyway), and the `?` overview lists it under that label too.
|
|
28
|
+
|
|
29
|
+
**Type:** `string`
|
|
30
|
+
|
|
23
31
|
### iconButtonOptions.href · member
|
|
24
32
|
|
|
25
33
|
Render as a link (`<a role=button>`) pointing here instead of a `<button>`.
|
package/skill/MenuItem.md
CHANGED
|
@@ -38,6 +38,26 @@ columns rather than on top of whatever panel the menu was drawn in. Pass
|
|
|
38
38
|
|
|
39
39
|
**Type:** `string`
|
|
40
40
|
|
|
41
|
+
### menuItem.key · member
|
|
42
|
+
|
|
43
|
+
A keyboard shortcut that activates this item: `"mod+k"`, `"f2"`, a bare
|
|
44
|
+
`"?"`. The spelling, and which keystrokes are yours to take, are
|
|
45
|
+
documented on `bindKey`. The combination shows at the right end of
|
|
46
|
+
the row (not on a touch device) and reaches screen readers as
|
|
47
|
+
`aria-keyshortcuts`; the `?` overview (`showKeyHelp`) lists it
|
|
48
|
+
under the item's label. Activating runs `click` with the `KeyboardEvent`
|
|
49
|
+
and follows `href` as a fresh navigation to it — the target getting its
|
|
50
|
+
own panel stack, as a nav item's does.
|
|
51
|
+
|
|
52
|
+
The shortcut works with the menu shut — rather the point of one on a
|
|
53
|
+
dropdown or context menu — for as long as whatever owns the items is
|
|
54
|
+
drawn: the `menu`, `menuButton` or `addContextMenu`
|
|
55
|
+
call, or `S.main`'s `nav`. (The bare `showFloatingMenu` binds
|
|
56
|
+
nothing: its menu exists only while it is up.) A disabled item's key is
|
|
57
|
+
not bound.
|
|
58
|
+
|
|
59
|
+
**Type:** `string`
|
|
60
|
+
|
|
41
61
|
### menuItem.match · member
|
|
42
62
|
|
|
43
63
|
Pages this item claims *beyond* its own `href`: a string claims that path
|
|
@@ -45,9 +65,8 @@ and everything under it (`"/mail"` claims `/mail/…`, not `/mailbox`), a
|
|
|
45
65
|
function is asked with the current path. While a claimed page is current,
|
|
46
66
|
the item is highlighted and the branches above it stay unfolded — for the
|
|
47
67
|
detail screens a menu has no row of their own: the `/thread/[id]` a
|
|
48
|
-
notification lands on, an icon's page under the gallery's row. Claims
|
|
49
|
-
|
|
50
|
-
which no amount of fold-state keeping can.
|
|
68
|
+
notification lands on, an icon's page under the gallery's row. Claims work
|
|
69
|
+
from the first paint, so cold deep links are covered too.
|
|
51
70
|
|
|
52
71
|
**Type:** `string | ((path: string) => boolean)`
|
|
53
72
|
|
package/skill/Panel.md
CHANGED
|
@@ -20,6 +20,10 @@ The params matched from this panel's path, typed per its route key:
|
|
|
20
20
|
`[x]` is a `string`, `[x=integer]` a `number`, `[...x]` a `string`.
|
|
21
21
|
Read-only.
|
|
22
22
|
|
|
23
|
+
Single-segment params are percent-decoded; `[...x]` is not, since decoding
|
|
24
|
+
it would make an encoded slash indistinguishable from a separator. Split it
|
|
25
|
+
yourself: `x.split("/").map(decodeURIComponent)`.
|
|
26
|
+
|
|
23
27
|
**Type:** `P`
|
|
24
28
|
|
|
25
29
|
### panel.stack · member
|
|
@@ -108,6 +112,10 @@ the same thing: one screen at a time. And a width depends only on the
|
|
|
108
112
|
window, never on what else is open, so opening or closing a panel never
|
|
109
113
|
resizes another — the run of columns just recentres in the area.
|
|
110
114
|
|
|
115
|
+
The ask is a ceiling only; there is no matching floor, since the window can
|
|
116
|
+
be any width. Aim your layout at 360px — about the narrowest phone still in
|
|
117
|
+
common use — and let it degrade gracefully below that.
|
|
118
|
+
|
|
111
119
|
Ask only for what your content can actually use: a panel that would cap
|
|
112
120
|
its own content narrower than its ask is holding room that would have
|
|
113
121
|
let another column fit beside it.
|