staffa 0.1.0 → 0.3.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.
Files changed (68) hide show
  1. package/README.md +161 -97
  2. package/dist/components/autocomplete.js +21 -20
  3. package/dist/components/box.d.ts +8 -6
  4. package/dist/components/box.js +16 -12
  5. package/dist/components/button.d.ts +21 -35
  6. package/dist/components/button.js +46 -39
  7. package/dist/components/buttonChooser.d.ts +41 -0
  8. package/dist/components/buttonChooser.js +38 -0
  9. package/dist/components/buttonGroup.d.ts +3 -3
  10. package/dist/components/buttonGroup.js +18 -21
  11. package/dist/components/checkbox.js +7 -7
  12. package/dist/components/dialog.d.ts +20 -25
  13. package/dist/components/dialog.js +81 -91
  14. package/dist/components/field.d.ts +8 -6
  15. package/dist/components/field.js +16 -18
  16. package/dist/components/form.d.ts +3 -3
  17. package/dist/components/form.js +4 -4
  18. package/dist/components/main.d.ts +41 -14
  19. package/dist/components/main.js +193 -53
  20. package/dist/components/menu.d.ts +118 -0
  21. package/dist/components/menu.js +218 -0
  22. package/dist/components/select.d.ts +1 -1
  23. package/dist/components/select.js +5 -5
  24. package/dist/components/tabs.d.ts +5 -5
  25. package/dist/components/tabs.js +15 -22
  26. package/dist/components/textarea.js +4 -4
  27. package/dist/components/textline.js +1 -1
  28. package/dist/components/toast.d.ts +37 -0
  29. package/dist/components/toast.js +79 -0
  30. package/dist/components/tooltip.d.ts +32 -0
  31. package/dist/components/tooltip.js +130 -0
  32. package/dist/core.d.ts +26 -39
  33. package/dist/core.js +6 -5
  34. package/dist/icons-helpers.d.ts +46 -0
  35. package/dist/icons-helpers.js +44 -0
  36. package/dist/icons.d.ts +1960 -0
  37. package/dist/icons.js +1972 -0
  38. package/dist/index.d.ts +21 -8
  39. package/dist/index.js +17 -8
  40. package/dist/staffa.esm.js +1 -0
  41. package/dist/theme.d.ts +9 -75
  42. package/dist/theme.js +279 -82
  43. package/package.json +12 -5
  44. package/src/components/autocomplete.ts +21 -20
  45. package/src/components/box.ts +21 -15
  46. package/src/components/button.ts +59 -75
  47. package/src/components/buttonChooser.ts +65 -0
  48. package/src/components/buttonGroup.ts +18 -21
  49. package/src/components/checkbox.ts +7 -7
  50. package/src/components/dialog.ts +101 -102
  51. package/src/components/field.ts +22 -22
  52. package/src/components/form.ts +7 -7
  53. package/src/components/main.ts +212 -52
  54. package/src/components/menu.ts +288 -0
  55. package/src/components/select.ts +4 -4
  56. package/src/components/tabs.ts +20 -27
  57. package/src/components/textarea.ts +4 -4
  58. package/src/components/textline.ts +1 -1
  59. package/src/components/toast.ts +115 -0
  60. package/src/components/tooltip.ts +139 -0
  61. package/src/core.ts +26 -40
  62. package/src/icons-helpers.ts +90 -0
  63. package/src/icons.ts +1977 -0
  64. package/src/index.ts +21 -8
  65. package/src/theme.ts +300 -135
  66. package/dist/components/modal.d.ts +0 -2
  67. package/dist/components/modal.js +0 -2
  68. package/dist/skye.esm.js +0 -1
@@ -1,43 +1,59 @@
1
1
  import A from "aberdeen";
2
2
  import { drawSlot } from "../core.js";
3
- // The color role sets a local `--c` (and `--cfg` for text on filled); the
4
- // variant rules consume them, so we avoid writing colour×variant rules.
3
+ // The button is a `.s-s` surface (defaulting to `.primary` in button() below), so
4
+ // its colours come from the surface classes in theme.ts. This rule only handles
5
+ // layout, border, focus, hover and sizing.
5
6
  A.insertGlobalCss({
6
- ".S_btn": {
7
- "&": "--c:$sPrimary --cfg:$sPrimaryFg " +
8
- "display:inline-flex align-items:center justify-content:center gap:$2 " +
7
+ ".s-btn": {
8
+ "&": "display:inline-flex align-items:center justify-content:center gap:$2 " +
9
9
  "font-weight:600 line-height:1.2 white-space:nowrap cursor:pointer text-decoration:none " +
10
- "border: 1px solid transparent; r:$sRadius padding: 0.5em 1em; " +
11
- "transition: background 0.15s, border-color 0.15s, filter 0.15s, box-shadow 0.15s;",
12
- "&:focus-visible": "outline:none box-shadow: 0 0 0 3px $sFocus;",
10
+ "border: 1px solid $s-border; r: $s-radius; padding: 0.5em 1em; " +
11
+ "transition: background 0.15s, border-color 0.15s, color 0.15s, filter 0.15s, box-shadow 0.15s, transform 0.08s;",
12
+ "&:focus-visible": "outline:none box-shadow: 0 0 0 3px $s-focus;",
13
13
  "&:disabled, &[aria-disabled=true]": "opacity:0.45 cursor:not-allowed pointer-events:none filter:saturate(0.6)",
14
- // Colour roles.
15
- "&.S_neutral": "--c:$sBorderStrong --cfg:$sFg",
16
- "&.S_danger": "--c:$sDanger --cfg:#fff",
17
- "&.S_success": "--c:$sSuccess --cfg:#08110d",
18
- // Variants.
19
- "&.S_filled": "background:$c color:$cfg border-color:$c",
20
- "&.S_filled:hover": "filter:brightness(1.1)",
21
- "&.S_tonal": "color:$c background: color-mix(in srgb, $c 20%, transparent); border-color: color-mix(in srgb, $c 30%, transparent);",
22
- "&.S_tonal:hover": "background: color-mix(in srgb, $c 30%, transparent);",
23
- "&.S_outlined": "color:$c background:transparent border-color: color-mix(in srgb, $c 55%, $sBorder);",
24
- "&.S_outlined:hover": "background: color-mix(in srgb, $c 12%, transparent);",
25
- // Sizes.
26
- "&.S_sm": "padding: 0.32em 0.7em; font-size:0.85em",
27
- "&.S_lg": "padding: 0.66em 1.3em; font-size:1.1em",
14
+ // Every button lifts a little toward the cursor on hover (the transform is in
15
+ // the transition list above). The filled `.gradient` CTA below layers a deeper
16
+ // shadow on top of the same lift, so it still reads as the signature action.
17
+ "&:hover": "filter: brightness(1.08); transform: translateY(-1px)",
18
+ "&.tonal:hover, &.outlined:hover": "background: color-mix(in srgb, $s-b 26%, transparent);",
19
+ // A filled `.gradient` button (the default) is the app's signature call to
20
+ // action: a borderless gradient with a soft glow that lifts on hover. The
21
+ // gradient fill itself comes from the `.s-s.gradient` surface rule in theme.ts.
22
+ // No border: a filled gradient reads as one solid shape. Dropping the border
23
+ // (rather than making it transparent) also sidesteps a Chromium artifact where
24
+ // a gradient clipped to a transparent rounded border fringes the edge with the
25
+ // gradient's far colour.
26
+ "&.gradient:not(.tonal):not(.outlined)": "border:0 box-shadow: $s-glow;",
27
+ "&.gradient:not(.tonal):not(.outlined):hover": "filter: brightness(1.06); box-shadow: 0 10px 28px color-mix(in srgb, $s-primary 42%, transparent); transform: translateY(-1px);",
28
+ // Subtle press feedback.
29
+ "&:active:not(:disabled):not([aria-disabled=true])": "transform: translateY(1px)",
30
+ // Size: set on the button itself, or inherited from a `.small`/`.large`
31
+ // parent (e.g. a buttonGroup), so a container can size all its buttons at once.
32
+ "&.small, .small > &": "padding: 0.32em 0.7em; font-size:0.85em",
33
+ "&.large, .large > &": "padding: 0.66em 1.3em; font-size:1.1em",
28
34
  },
29
35
  });
36
+ // Surface-role classes a caller may pass in `attrs`. When one is present we skip
37
+ // the default `.gradient` base so the two roles don't stack on one element.
38
+ const ROLE_CLASS = /\.(gradient|primary|secondary|neutral|danger|success|warning|base|panel|raised)(\.|\s|$)/;
30
39
  /**
31
40
  * A button. Always carries at least a visible border so its affordance is
32
- * obvious at a glance, regardless of {@link ButtonVariant | variant}.
41
+ * obvious at a glance.
33
42
  *
34
43
  * Shortcut: pass a string to use it as the label, or a function for custom
35
44
  * content.
36
45
  *
46
+ * **Tip:** pair `href` with Aberdeen's `interceptLinks()` (called once at app
47
+ * startup) for SPA-style navigation without manual click handlers:
48
+ * ```ts
49
+ * interceptLinks(); // once at root
50
+ * S.button({ href: "/dashboard", text: "Dashboard" }); // navigates via router
51
+ * ```
52
+ *
37
53
  * @example
38
54
  * ```ts
39
55
  * S.button({ text: "Save", click: save });
40
- * S.button({ text: "Delete", color: "danger", variant: "outlined", click: del });
56
+ * S.button({ text: "Delete", attrs: ".danger .outlined", click: del });
41
57
  * S.button("Cancel"); // shorthand for { text: "Cancel" }
42
58
  * S.button({ href: "/docs", text: "Docs" }); // renders an <a role=button>
43
59
  * ```
@@ -45,15 +61,12 @@ A.insertGlobalCss({
45
61
  export function button(opts = {}) {
46
62
  const o = typeof opts === "string" ? { text: opts } : typeof opts === "function" ? { content: opts } : opts;
47
63
  const tag = o.href != null ? "a" : "button";
48
- const variant = o.variant ?? "filled";
49
- const color = o.color ?? "primary";
50
- const size = o.size === "sm" || o.size === "lg" ? `.S_${o.size}` : "";
51
- // Semantic roles select a colour class (the CSS sets `--c`/`--cfg`); any other
52
- // value is a raw CSS colour we assign to `--c`, which the variant rules consume
53
- // via `var(--c)`. (`primary` is the base default — its class is a no-op.)
54
- const semantic = color === "primary" || color === "neutral" || color === "danger" || color === "success";
55
- const colorCls = semantic ? `.S_${color}` : "";
56
- const el = A(`${tag}.S_btn.S_${variant}${colorCls}${size}`, o.root, o.inner, () => {
64
+ // A filled `.gradient` surface by default — the signature CTA. If the caller's
65
+ // `attrs` already names a surface role we omit the default, so `.danger`,
66
+ // `.neutral .outlined`, etc. fully take over (rather than stacking two roles).
67
+ // A bare variant/size (`.outlined`, `.small`) keeps the gradient base.
68
+ const role = o.attrs && ROLE_CLASS.test(o.attrs) ? "" : ".gradient";
69
+ A(`${tag}.s-btn.s-s${role}`, o.attrs, () => {
57
70
  if (o.href != null) {
58
71
  A(`href=${o.href} role=button`);
59
72
  if (o.disabled)
@@ -74,10 +87,4 @@ export function button(opts = {}) {
74
87
  else if (o.text != null)
75
88
  A("#", o.text);
76
89
  });
77
- // Aberdeen's inline styler doesn't set CSS custom properties, so assign the
78
- // custom accent on the element directly. A leading `$` is Aberdeen's shorthand
79
- // for a CSS variable reference, so expand it to `var(--name)`.
80
- if (!semantic && el instanceof HTMLElement) {
81
- el.style.setProperty("--c", color.startsWith("$") ? `var(--${color.slice(1)})` : color);
82
- }
83
90
  }
@@ -0,0 +1,41 @@
1
+ import { type Bindable, type Attributes, type Slot } from "../core.js";
2
+ /** Options for {@link buttonChooser}. */
3
+ export interface ButtonChooserOptions {
4
+ /** Aberdeen attr/style string applied to the button group. */
5
+ attrs?: Attributes;
6
+ /**
7
+ * The options to display, as a plain object mapping id → display label.
8
+ * Buttons appear in insertion order. A label may be a plain (rich-text)
9
+ * string, or a draw-function for custom content such as an icon.
10
+ */
11
+ options: Record<string, Slot>;
12
+ /**
13
+ * Two-way binding for the selected id, or `null` when nothing is selected.
14
+ * Use an `A.proxy` or `A.ref`.
15
+ */
16
+ bind: Bindable<string | null>;
17
+ /**
18
+ * When `true`, clicking the already-selected button deselects it, setting
19
+ * `bind.value` to `null`. Useful for "none / auto" states.
20
+ */
21
+ allowDeselect?: boolean;
22
+ /** Name attribute for the hidden `<input>`, enabling form submission. */
23
+ name?: string;
24
+ }
25
+ /**
26
+ * A single-selection segmented control: an attached button group where exactly
27
+ * one button is active at a time. Optionally allows deselecting back to `null`.
28
+ *
29
+ * Renders a hidden `<input>` alongside (when `name` is set) so the selected
30
+ * value is included in native form submission.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const $view = A.proxy({ value: "day" as string | null });
35
+ * S.buttonChooser({
36
+ * options: { day: "Day", week: "Week", month: "Month" },
37
+ * bind: $view,
38
+ * });
39
+ * ```
40
+ */
41
+ export declare function buttonChooser(opts: ButtonChooserOptions): void;
@@ -0,0 +1,38 @@
1
+ import A from "aberdeen";
2
+ import { buttonGroup } from "./buttonGroup.js";
3
+ /**
4
+ * A single-selection segmented control: an attached button group where exactly
5
+ * one button is active at a time. Optionally allows deselecting back to `null`.
6
+ *
7
+ * Renders a hidden `<input>` alongside (when `name` is set) so the selected
8
+ * value is included in native form submission.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const $view = A.proxy({ value: "day" as string | null });
13
+ * S.buttonChooser({
14
+ * options: { day: "Day", week: "Week", month: "Month" },
15
+ * bind: $view,
16
+ * });
17
+ * ```
18
+ */
19
+ export function buttonChooser(opts) {
20
+ A(() => {
21
+ const selected = opts.bind.value;
22
+ buttonGroup({
23
+ attrs: opts.attrs,
24
+ buttons: Object.entries(opts.options).map(([id, label]) => ({
25
+ text: typeof label === "string" ? label : undefined,
26
+ content: typeof label === "function" ? label : undefined,
27
+ attrs: selected === id ? ".primary" : ".neutral .outlined",
28
+ click: () => {
29
+ opts.bind.value = (opts.allowDeselect && selected === id) ? null : id;
30
+ },
31
+ })),
32
+ });
33
+ });
34
+ if (opts.name) {
35
+ // Hidden input carries the value into native form submission.
36
+ A(() => A(`input type=hidden name=${opts.name} value=`, opts.bind.value ?? ""));
37
+ }
38
+ }
@@ -22,9 +22,9 @@ export interface ButtonGroupOptions extends ContentOptions {
22
22
  * @example
23
23
  * ```ts
24
24
  * S.buttonGroup({ buttons: [
25
- * { text: "Day", variant: "outlined", color: "neutral" },
26
- * { text: "Week", variant: "outlined", color: "neutral" },
27
- * { text: "Month", variant: "outlined", color: "neutral" },
25
+ * { text: "Day", attrs: ".neutral .outlined" },
26
+ * { text: "Week", attrs: ".neutral .outlined" },
27
+ * { text: "Month", attrs: ".neutral .outlined" },
28
28
  * ]});
29
29
  * ```
30
30
  */
@@ -1,23 +1,20 @@
1
1
  import A from "aberdeen";
2
2
  import { button } from "./button.js";
3
3
  A.insertGlobalCss({
4
- ".S_bgroup": {
4
+ ".s-bgroup": {
5
5
  "&": "display:inline-flex align-items:stretch",
6
- "&.S_spaced": "gap:$2 flex-wrap:wrap",
7
- "&.S_vertical": "flex-direction:column",
8
- "&.S_attached": "gap:0",
9
- // When attached, collapse the shared border and square off the touching
10
- // corners, keeping only the outer ends of the group rounded.
11
- "&.S_attached:not(.S_vertical) > .S_btn:not(:first-child)": "margin-left:-1px",
12
- "&.S_attached:not(.S_vertical) > .S_btn:not(:first-child):not(:last-child)": "r:0",
13
- "&.S_attached:not(.S_vertical) > .S_btn:first-child:not(:last-child)": "border-top-right-radius:0 border-bottom-right-radius:0",
14
- "&.S_attached:not(.S_vertical) > .S_btn:last-child:not(:first-child)": "border-top-left-radius:0 border-bottom-left-radius:0",
15
- "&.S_attached.S_vertical > .S_btn:not(:first-child)": "margin-top:-1px",
16
- "&.S_attached.S_vertical > .S_btn:not(:first-child):not(:last-child)": "r:0",
17
- "&.S_attached.S_vertical > .S_btn:first-child:not(:last-child)": "border-bottom-left-radius:0 border-bottom-right-radius:0",
18
- "&.S_attached.S_vertical > .S_btn:last-child:not(:first-child)": "border-top-left-radius:0 border-top-right-radius:0",
19
- // Keep the hovered/focused button's border above its neighbours.
20
- "&.S_attached > .S_btn:hover, &.S_attached > .S_btn:focus-visible": "z-index:1",
6
+ "&.s-spaced": "gap:$2 flex-wrap:wrap",
7
+ "&.s-vertical": "flex-direction:column",
8
+ "&.s-attached": "gap:0",
9
+ "&.s-attached:not(.s-vertical) > .s-btn:not(:first-child)": "margin-left:-1px",
10
+ "&.s-attached:not(.s-vertical) > .s-btn:not(:first-child):not(:last-child)": "r:0",
11
+ "&.s-attached:not(.s-vertical) > .s-btn:first-child:not(:last-child)": "border-top-right-radius:0 border-bottom-right-radius:0",
12
+ "&.s-attached:not(.s-vertical) > .s-btn:last-child:not(:first-child)": "border-top-left-radius:0 border-bottom-left-radius:0",
13
+ "&.s-attached.s-vertical > .s-btn:not(:first-child)": "margin-top:-1px",
14
+ "&.s-attached.s-vertical > .s-btn:not(:first-child):not(:last-child)": "r:0",
15
+ "&.s-attached.s-vertical > .s-btn:first-child:not(:last-child)": "border-bottom-left-radius:0 border-bottom-right-radius:0",
16
+ "&.s-attached.s-vertical > .s-btn:last-child:not(:first-child)": "border-top-left-radius:0 border-top-right-radius:0",
17
+ "&.s-attached > .s-btn:hover, &.s-attached > .s-btn:focus-visible": "z-index:1",
21
18
  },
22
19
  });
23
20
  /**
@@ -27,16 +24,16 @@ A.insertGlobalCss({
27
24
  * @example
28
25
  * ```ts
29
26
  * S.buttonGroup({ buttons: [
30
- * { text: "Day", variant: "outlined", color: "neutral" },
31
- * { text: "Week", variant: "outlined", color: "neutral" },
32
- * { text: "Month", variant: "outlined", color: "neutral" },
27
+ * { text: "Day", attrs: ".neutral .outlined" },
28
+ * { text: "Week", attrs: ".neutral .outlined" },
29
+ * { text: "Month", attrs: ".neutral .outlined" },
33
30
  * ]});
34
31
  * ```
35
32
  */
36
33
  export function buttonGroup(opts = {}) {
37
34
  const layout = opts.layout ?? "attached";
38
- const cls = `.S_${layout}${opts.vertical ? ".S_vertical" : ""}`;
39
- A(`div.S_bgroup${cls} role=group`, opts.root, opts.inner, () => {
35
+ const cls = `.s-${layout}${opts.vertical ? ".s-vertical" : ""}`;
36
+ A(`div.s-bgroup${cls} role=group`, opts.attrs, () => {
40
37
  if (opts.buttons)
41
38
  for (const b of opts.buttons)
42
39
  button(b);
@@ -1,12 +1,12 @@
1
1
  import A from "aberdeen";
2
2
  import { drawSlot, uniqueId } from "../core.js";
3
3
  A.insertGlobalCss({
4
- ".S_check": {
4
+ ".s-check": {
5
5
  "&": "display:flex flex-direction:column gap:$1",
6
6
  "> label": "display:flex align-items:center gap:$2 cursor:pointer user-select:none",
7
7
  "> label:has(input:disabled)": "cursor:not-allowed opacity:0.6",
8
8
  // Native control styled with accent-color: accessible and zero-fuss.
9
- "input": "width:1.15em height:1.15em accent-color:$sPrimary cursor:inherit m:0",
9
+ "input": "width:1.15em height:1.15em accent-color:$s-accent cursor:inherit m:0",
10
10
  },
11
11
  });
12
12
  /**
@@ -21,9 +21,9 @@ A.insertGlobalCss({
21
21
  */
22
22
  export function checkbox(opts = {}) {
23
23
  const id = opts.id ?? uniqueId("check");
24
- A("div.S_check", opts.root, () => {
24
+ A("div.s-check", opts.attrs, () => {
25
25
  A(`label for=${id}`, () => {
26
- A("input type=checkbox", opts.control, () => {
26
+ A("input type=checkbox", opts.inputAttrs, () => {
27
27
  A(`id=${id}`);
28
28
  if (opts.name)
29
29
  A(`name=${opts.name}`);
@@ -48,16 +48,16 @@ export function checkbox(opts = {}) {
48
48
  if (opts.label != null)
49
49
  drawSlot(opts.label);
50
50
  if (opts.required)
51
- A("span.S_req aria-hidden=true #*");
51
+ A("span.s-req aria-hidden=true #*");
52
52
  });
53
53
  });
54
54
  A(() => {
55
55
  if (opts.help != null && !opts.error)
56
- A("div.S_help", () => drawSlot(opts.help));
56
+ A("div.s-help", () => drawSlot(opts.help));
57
57
  });
58
58
  A(() => {
59
59
  if (opts.error)
60
- A("div.S_error role=alert #", opts.error);
60
+ A("div.s-error role=alert #", opts.error);
61
61
  });
62
62
  });
63
63
  }
@@ -1,18 +1,18 @@
1
- import { type Slot, type Styling } from "../core.js";
1
+ import { type Slot, type Attributes } from "../core.js";
2
2
  /** Options for {@link dialog}. */
3
3
  export interface DialogOptions {
4
4
  /** Slot rendered in the styled header bar. */
5
5
  header?: Slot;
6
6
  /** Slot rendered in the styled footer bar. */
7
7
  footer?: Slot;
8
+ /** Aberdeen attr/style string applied to the dialog panel. A surface — pass modifier classes (e.g. `".warning"`) to recolour it. */
9
+ attrs?: Attributes;
8
10
  /** Aberdeen attr/style string applied to the header bar. */
9
- headerInner?: Styling;
11
+ headerAttrs?: Attributes;
10
12
  /** Aberdeen attr/style string applied to the footer bar. */
11
- footerInner?: Styling;
13
+ footerAttrs?: Attributes;
12
14
  /** Aberdeen attr/style string applied to the scrollable content `<div>`. */
13
- inner?: Styling;
14
- /** Aberdeen attr/style string applied to the dialog panel itself. */
15
- root?: Styling;
15
+ contentAttrs?: Attributes;
16
16
  /**
17
17
  * Allow closing via Esc or clicking the backdrop. Defaults to `true`.
18
18
  * May be changed on a proxied options object while the dialog is open
@@ -20,10 +20,16 @@ export interface DialogOptions {
20
20
  */
21
21
  allowCancel?: boolean;
22
22
  /**
23
- * Dialog body. Receives a `close()` function call it to dismiss the dialog
24
- * programmatically.
23
+ * When set to `true` (default) the model will be destroyed when the `dialog()`-calling
24
+ * scope is destroyed.
25
25
  */
26
- content?: (close: () => void) => void;
26
+ cancelWithScope?: boolean;
27
+ /**
28
+ * Dialog body. A {@link Slot} whose draw-function receives a `close()` function
29
+ * — call it to dismiss the dialog programmatically. (A plain string renders as
30
+ * rich text.)
31
+ */
32
+ content?: Slot<[close: () => void]>;
27
33
  /**
28
34
  * Called when the dialog closes for any reason (explicit `close()`, Esc, or
29
35
  * backdrop click). Useful when you want a side-effect on close but don't need
@@ -37,11 +43,8 @@ export interface DialogOptions {
37
43
  * closes. Lifecycle is also tied to the parent reactive scope — when that scope
38
44
  * is cleaned up the dialog disappears and the promise resolves.
39
45
  *
40
- * Only the **last** open dialog (and its backdrop) is visible; earlier pairs are
41
- * hidden via the CSS `+` selector, so nested dialogs stack correctly.
42
- *
43
- * The header and footer are pinned; only the body content scrolls when it is
44
- * taller than `88vh`.
46
+ * Multiple dialogs stack correctly: each new pair (backdrop + dialog) has a
47
+ * higher z-index, while older dialogs are pushed behind their covering backdrop.
45
48
  *
46
49
  * @example
47
50
  * ```ts
@@ -50,7 +53,7 @@ export interface DialogOptions {
50
53
  * content: (close) => {
51
54
  * A("p #Are you sure?");
52
55
  * S.button({ text: "Yes", click: () => { doIt(); close(); } });
53
- * S.button({ text: "Cancel", variant: "outlined", click: close });
56
+ * S.button({ text: "Cancel", attrs: ".neutral .outlined", click: close });
54
57
  * },
55
58
  * });
56
59
  * ```
@@ -60,8 +63,6 @@ export declare function dialog(opts: DialogOptions): Promise<void>;
60
63
  * Shows a message dialog with a single OK button. Returns a `Promise<void>`
61
64
  * that resolves when the user dismisses it.
62
65
  *
63
- * All properties of `opts` override the defaults, including `content`.
64
- *
65
66
  * @example
66
67
  * ```ts
67
68
  * await S.alert("File saved successfully.");
@@ -70,10 +71,7 @@ export declare function dialog(opts: DialogOptions): Promise<void>;
70
71
  export declare function alert(message: string, opts?: Partial<DialogOptions>): Promise<void>;
71
72
  /**
72
73
  * Shows a confirmation dialog with Cancel and OK buttons. Returns a
73
- * `Promise<boolean>` — `true` if the user clicked OK, `false` otherwise
74
- * (including Esc / backdrop click when `allowCancel` is not `false`).
75
- *
76
- * All properties of `opts` override the defaults, including `content`.
74
+ * `Promise<boolean>` — `true` if the user clicked OK, `false` otherwise.
77
75
  *
78
76
  * @example
79
77
  * ```ts
@@ -83,10 +81,7 @@ export declare function alert(message: string, opts?: Partial<DialogOptions>): P
83
81
  export declare function confirm(message: string, opts?: Partial<DialogOptions>): Promise<boolean>;
84
82
  /**
85
83
  * Shows a prompt dialog with a text input. Returns a `Promise<string | null>` —
86
- * the entered string if the user confirmed, or `null` if cancelled (Esc /
87
- * backdrop click / Cancel button).
88
- *
89
- * All properties of `opts` override the defaults, including `content`.
84
+ * the entered string if the user confirmed, or `null` if cancelled.
90
85
  *
91
86
  * @example
92
87
  * ```ts