staffa 0.1.0 → 0.2.1

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 (50) hide show
  1. package/README.md +65 -27
  2. package/dist/components/autocomplete.js +20 -20
  3. package/dist/components/box.d.ts +8 -6
  4. package/dist/components/box.js +15 -11
  5. package/dist/components/button.d.ts +10 -33
  6. package/dist/components/button.js +17 -38
  7. package/dist/components/buttonChooser.d.ts +42 -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 -18
  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 +18 -17
  16. package/dist/components/form.d.ts +3 -3
  17. package/dist/components/form.js +4 -4
  18. package/dist/components/main.d.ts +7 -5
  19. package/dist/components/main.js +25 -23
  20. package/dist/components/select.d.ts +1 -1
  21. package/dist/components/select.js +5 -5
  22. package/dist/components/tabs.d.ts +5 -3
  23. package/dist/components/tabs.js +25 -19
  24. package/dist/components/textarea.js +4 -4
  25. package/dist/components/textline.js +1 -1
  26. package/dist/core.d.ts +26 -39
  27. package/dist/core.js +6 -5
  28. package/dist/index.d.ts +10 -8
  29. package/dist/index.js +9 -8
  30. package/dist/staffa.esm.js +1 -0
  31. package/dist/theme.d.ts +9 -75
  32. package/dist/theme.js +178 -82
  33. package/package.json +3 -2
  34. package/src/components/autocomplete.ts +20 -20
  35. package/src/components/box.ts +20 -14
  36. package/src/components/button.ts +24 -72
  37. package/src/components/buttonChooser.ts +66 -0
  38. package/src/components/buttonGroup.ts +18 -18
  39. package/src/components/checkbox.ts +7 -7
  40. package/src/components/dialog.ts +101 -102
  41. package/src/components/field.ts +24 -21
  42. package/src/components/form.ts +7 -7
  43. package/src/components/main.ts +30 -26
  44. package/src/components/select.ts +4 -4
  45. package/src/components/tabs.ts +29 -22
  46. package/src/components/textarea.ts +4 -4
  47. package/src/components/textline.ts +1 -1
  48. package/src/core.ts +26 -40
  49. package/src/index.ts +10 -8
  50. package/src/theme.ts +190 -135
@@ -1,6 +1,8 @@
1
- import { type BaseOptions, type Content, type Slot, type Styling } from "../core.js";
1
+ import { type Content, type Slot, type Attributes } from "../core.js";
2
2
  /** Options for {@link main}. */
3
- export interface MainOptions extends BaseOptions {
3
+ export interface MainOptions {
4
+ /** Aberdeen attr/style string applied to the outermost shell element. */
5
+ attrs?: Attributes;
4
6
  /** App/page title shown in the top bar. */
5
7
  title?: Slot;
6
8
  /** Secondary line under the title. */
@@ -20,9 +22,9 @@ export interface MainOptions extends BaseOptions {
20
22
  */
21
23
  maxWidth?: string;
22
24
  /** Aberdeen attr/style string applied to the content sheet. */
23
- inner?: Styling;
25
+ contentAttrs?: Attributes;
24
26
  /** Aberdeen attr/style string applied to the top bar. */
25
- topbarInner?: Styling;
27
+ topbarAttrs?: Attributes;
26
28
  }
27
29
  /**
28
30
  * An application shell that wires up the things almost every app needs: a sticky
@@ -35,7 +37,7 @@ export interface MainOptions extends BaseOptions {
35
37
  * ```ts
36
38
  * S.main({
37
39
  * icon: "✦",
38
- * title: "Skye Demo",
40
+ * title: "Staffa Demo",
39
41
  * subtitle: "Component playground",
40
42
  * maxWidth: "56rem",
41
43
  * menu: () => S.button({ text: "New", size: "sm" }),
@@ -1,19 +1,21 @@
1
1
  import A from "aberdeen";
2
2
  import { drawSlot } from "../core.js";
3
3
  A.insertGlobalCss({
4
- ".S_main": {
5
- "&": "display:flex flex-direction:column min-height:100vh max-height:100vh bg:$sBg fg:$sFg",
6
- "> header": "display:flex align-items:center gap:$3 padding: $2 $3; bg:$sSurfaceHi border-bottom: 1px solid $sBorder; position:sticky top:0 z-index:10",
7
- "> header .S_icon": "display:flex align-items:center font-size:1.4em",
8
- "> header .S_titles": "display:flex flex-direction:column min-width:0 flex:1",
9
- "> header .S_title": "font-weight:700 font-size:1.1em line-height:1.2 overflow:hidden text-overflow:ellipsis white-space:nowrap",
10
- "> header .S_subtitle": "fg:$sFgMuted font-size:0.85em overflow:hidden text-overflow:ellipsis white-space:nowrap",
11
- "> header .S_menu": "display:flex align-items:center gap:$2",
4
+ ".s-main": {
5
+ // Colours come from the surface classes added in main(): the shell is a
6
+ // filled base, the bars are raised, the framed sheet is a panel.
7
+ "&": "display:flex flex-direction:column min-height:100vh max-height:100vh",
8
+ "> header": "display:flex align-items:center gap:$3 padding: $2 $3; border-bottom: 1px solid $s-border; position:sticky top:0 z-index:10",
9
+ "> header .s-icon": "display:flex align-items:center font-size:1.4em",
10
+ "> header .s-titles": "display:flex flex-direction:column min-width:0 flex:1",
11
+ "> header .s-title": "font-weight:700 font-size:1.1em line-height:1.2 overflow:hidden text-overflow:ellipsis white-space:nowrap",
12
+ "> header .s-subtitle": "fg:$s-fg-muted font-size:0.85em overflow:hidden text-overflow:ellipsis white-space:nowrap",
13
+ "> header .s-menu": "display:flex align-items:center gap:$2",
12
14
  "> main": "flex:1 overflow-y:auto display:flex flex-direction:column",
13
- "> main > .S_content": "width:100% flex:1",
14
- "> main > .S_content.S_framed": "margin: $3 auto; bg:$sSurface border: 1px solid $sBorder; r:$sRadiusLg box-shadow:$sShadow p:$4",
15
- "> main > .S_content.S_plain": "p:$3",
16
- "> footer": "display:flex align-items:center gap:$2 padding: $2 $3; bg:$sSurfaceHi border-top: 1px solid $sBorder; fg:$sFgMuted",
15
+ "> main > .s-content": "width:100% flex:1",
16
+ "> main > .s-content.s-framed": "margin: $3 auto; border: 1px solid $s-border; r:$s-radius-lg box-shadow:$s-shadow p:$4",
17
+ "> main > .s-content.s-plain": "p:$3",
18
+ "> footer": "display:flex align-items:center gap:$2 padding: $2 $3; border-top: 1px solid $s-border; fg:$s-fg-muted",
17
19
  },
18
20
  });
19
21
  /**
@@ -27,7 +29,7 @@ A.insertGlobalCss({
27
29
  * ```ts
28
30
  * S.main({
29
31
  * icon: "✦",
30
- * title: "Skye Demo",
32
+ * title: "Staffa Demo",
31
33
  * subtitle: "Component playground",
32
34
  * maxWidth: "56rem",
33
35
  * menu: () => S.button({ text: "New", size: "sm" }),
@@ -37,44 +39,44 @@ A.insertGlobalCss({
37
39
  * ```
38
40
  */
39
41
  export function main(opts = {}) {
40
- A("div.S_main", opts.root, () => {
42
+ A("div.s-main.s-s.base", opts.attrs, () => {
41
43
  // Top bar — only rendered when there's something to show in it.
42
44
  A(() => {
43
45
  const hasBar = opts.title != null || opts.subtitle != null || opts.icon != null || opts.menu != null;
44
46
  if (!hasBar)
45
47
  return;
46
- A("header", opts.topbarInner, () => {
48
+ A("header.s-s.raised", opts.topbarAttrs, () => {
47
49
  A(() => {
48
50
  if (opts.icon != null)
49
- A("div.S_icon", () => drawSlot(opts.icon));
51
+ A("div.s-icon", () => drawSlot(opts.icon));
50
52
  });
51
- A("div.S_titles", () => {
53
+ A("div.s-titles", () => {
52
54
  A(() => {
53
55
  if (opts.title != null)
54
- A("div.S_title", () => drawSlot(opts.title));
56
+ A("div.s-title", () => drawSlot(opts.title));
55
57
  });
56
58
  A(() => {
57
59
  if (opts.subtitle != null)
58
- A("div.S_subtitle", () => drawSlot(opts.subtitle));
60
+ A("div.s-subtitle", () => drawSlot(opts.subtitle));
59
61
  });
60
62
  });
61
63
  A(() => {
62
64
  if (opts.menu)
63
- A("div.S_menu", () => opts.menu?.());
65
+ A("div.s-menu", () => opts.menu?.());
64
66
  });
65
67
  });
66
68
  });
67
69
  // Scrollable main region with the (optionally framed) content sheet.
68
70
  A("main", () => {
69
- A("div.S_content", opts.inner, () => {
71
+ A("div.s-content", opts.contentAttrs, () => {
70
72
  // Framing applied in its own scope so changing maxWidth doesn't
71
73
  // recreate the content (which holds the whole page).
72
74
  A(() => {
73
75
  const max = opts.maxWidth;
74
76
  if (max != null)
75
- A(".S_framed max-width:", max);
77
+ A(".s-framed.s-s.panel max-width:", max);
76
78
  else
77
- A(".S_plain");
79
+ A(".s-plain");
78
80
  });
79
81
  if (opts.content)
80
82
  opts.content();
@@ -16,7 +16,7 @@ export interface SelectOptions extends FieldOptions {
16
16
  }
17
17
  /**
18
18
  * A single-select dropdown backed by a native `<select>` element. Looks like the
19
- * other Skye inputs but delegates all focus management, keyboard navigation, and
19
+ * other Staffa inputs but delegates all focus management, keyboard navigation, and
20
20
  * mobile-native picker behaviour to the browser.
21
21
  *
22
22
  * @example
@@ -2,15 +2,15 @@ import A from "aberdeen";
2
2
  import { applyControlAttrs, drawField } from "./field.js";
3
3
  // Wrapper provides the chevron via ::after (pseudo-elements on <select> are unreliable).
4
4
  A.insertGlobalCss({
5
- ".S_select_wrap": {
5
+ ".s-select_wrap": {
6
6
  "&": "position:relative display:block",
7
7
  "select": "w:100% cursor:pointer padding-right:2.2em; appearance:none",
8
- "&::after": "content: '▾'; position:absolute right:0.7em top:50%; transform: translateY(-50%); pointer-events:none fg:$sFgMuted font-size:0.85em",
8
+ "&::after": "content: '▾'; position:absolute right:0.7em top:50%; transform: translateY(-50%); pointer-events:none fg:$s-fg-muted font-size:0.85em",
9
9
  },
10
10
  });
11
11
  /**
12
12
  * A single-select dropdown backed by a native `<select>` element. Looks like the
13
- * other Skye inputs but delegates all focus management, keyboard navigation, and
13
+ * other Staffa inputs but delegates all focus management, keyboard navigation, and
14
14
  * mobile-native picker behaviour to the browser.
15
15
  *
16
16
  * @example
@@ -20,8 +20,8 @@ A.insertGlobalCss({
20
20
  */
21
21
  export function select(opts) {
22
22
  drawField(opts, (id, isInvalid) => {
23
- A("div.S_select_wrap", opts.control, () => {
24
- A("select.S_input", () => {
23
+ A("div.s-select_wrap", opts.inputAttrs, () => {
24
+ A("select.s-input", () => {
25
25
  applyControlAttrs(opts, id, isInvalid);
26
26
  A("change=", (e) => {
27
27
  if (opts.bind)
@@ -1,4 +1,4 @@
1
- import { type BaseOptions, type Bindable, type Content, type Slot, type Styling } from "../core.js";
1
+ import { type Bindable, type Content, type Slot, type Attributes } from "../core.js";
2
2
  /** A single tab definition. */
3
3
  export interface Tab {
4
4
  /** Stable id used as the selection value. Falls back to the array index. */
@@ -13,7 +13,9 @@ export interface Tab {
13
13
  disabled?: boolean;
14
14
  }
15
15
  /** Options for {@link tabs}. */
16
- export interface TabsOptions extends BaseOptions {
16
+ export interface TabsOptions {
17
+ /** Aberdeen attr/style string applied to the outermost element. */
18
+ attrs?: Attributes;
17
19
  /** The tabs to display. */
18
20
  tabs: Tab[];
19
21
  /**
@@ -24,7 +26,7 @@ export interface TabsOptions extends BaseOptions {
24
26
  /** Visual style of the tab strip. Defaults to `"underline"`. */
25
27
  variant?: "underline" | "pills";
26
28
  /** Aberdeen attr/style string applied to the active panel. */
27
- inner?: Styling;
29
+ contentAttrs?: Attributes;
28
30
  }
29
31
  /**
30
32
  * A tabbed view. Renders an ARIA `tablist` of buttons and a single live panel
@@ -1,25 +1,27 @@
1
1
  import A from "aberdeen";
2
2
  import { drawSlot, uniqueId } from "../core.js";
3
3
  A.insertGlobalCss({
4
- ".S_tabs": {
4
+ ".s-tabs": {
5
5
  "&": "display:flex flex-direction:column gap:$3",
6
- ".S_tablist": "display:flex gap:$1 align-items:stretch",
7
- ".S_tab": "display:inline-flex align-items:center gap:$2 cursor:pointer background:transparent " +
8
- "border:0 fg:$sFgMuted font-weight:600 padding: 0.6em 0.9em; " +
6
+ ".s-tablist": "display:flex gap:$1 align-items:stretch",
7
+ ".s-tab": "display:inline-flex align-items:center gap:$2 cursor:pointer background:transparent " +
8
+ "border:0 color: $s-fg-muted; font-weight:600 padding: 0.6em 0.9em; " +
9
9
  "transition: color 0.15s, background 0.15s, border-color 0.15s;",
10
- ".S_tab:hover:not(:disabled)": "fg:$sFg",
11
- ".S_tab:disabled": "opacity:0.5 cursor:not-allowed",
12
- ".S_tab:focus-visible": "outline:none box-shadow: 0 0 0 3px $sFocus; r:$sRadius",
13
- // Underline variant.
14
- "&.S_underline .S_tablist": "border-bottom: 1px solid $sBorder;",
15
- "&.S_underline .S_tab": "border-bottom: 2px solid transparent; margin-bottom:-1px",
16
- "&.S_underline .S_tab[aria-selected=true]": "fg:$sFg border-bottom-color:$sPrimary",
17
- // Pills variant.
18
- "&.S_pills .S_tab": "r:$sRadius",
19
- "&.S_pills .S_tab[aria-selected=true]": "fg:$sPrimaryFg background:$sPrimary",
10
+ ".s-tab:hover:not(:disabled)": "color: $s-fg;",
11
+ ".s-tab:disabled": "opacity:0.5 cursor:not-allowed",
12
+ ".s-tab:focus-visible": "outline:none box-shadow: 0 0 0 3px $s-focus; r: $s-radius;",
13
+ // Underline variant — the active marker uses the contextual brand accent.
14
+ "&.s-underline .s-tablist": "border-bottom: 1px solid $s-border;",
15
+ "&.s-underline .s-tab": "border-bottom: 2px solid transparent; margin-bottom:-1px",
16
+ "&.s-underline .s-tab[aria-selected=true]": "color: $s-fg; border-bottom-color: $s-accent;",
17
+ // Pills variant — the active pill gets the `.s-s.primary` surface (added in
18
+ // tabs()), so its `--s-bg`/`--s-fg` are the brand surface's; we just paint
19
+ // (the `.s-tab` rule's transparent background would otherwise win on it).
20
+ "&.s-pills .s-tab": "r: $s-radius;",
21
+ "&.s-pills .s-tab[aria-selected=true]": "background: $s-bg; color: $s-fg;",
20
22
  // The panel has no enclosing box, so no default padding — its content
21
23
  // aligns flush with the tab strip. Callers add padding/flex via `inner`.
22
- ".S_tabpanel": "display:block",
24
+ ".s-tabpanel": "display:block",
23
25
  },
24
26
  });
25
27
  /**
@@ -46,16 +48,20 @@ export function tabs(opts) {
46
48
  return;
47
49
  $sel.value = keyOf(tab, index);
48
50
  };
49
- A(`div.S_tabs.S_${variant}`, opts.root, () => {
50
- A("div.S_tablist role=tablist", () => {
51
+ A(`div.s-tabs.s-${variant}`, opts.attrs, () => {
52
+ A("div.s-tablist role=tablist", () => {
51
53
  opts.tabs.forEach((tab, index) => {
52
54
  const key = keyOf(tab, index);
53
- A("button.S_tab type=button role=tab", () => {
55
+ A("button.s-tab type=button role=tab", () => {
54
56
  A(`id=${groupId}-tab-${key} aria-controls=${groupId}-panel-${key}`);
55
57
  A(() => {
56
58
  const selected = $sel.value === key;
57
59
  A("aria-selected=", selected ? "true" : "false");
58
60
  A("tabindex=", selected ? "0" : "-1");
61
+ // The active pill is a filled brand surface; the classes flip
62
+ // with selection (Aberdeen removes them when this scope re-runs).
63
+ if (selected && variant === "pills")
64
+ A(".s-s.primary");
59
65
  });
60
66
  if (tab.disabled)
61
67
  A("disabled=true");
@@ -66,7 +72,7 @@ export function tabs(opts) {
66
72
  });
67
73
  });
68
74
  });
69
- A("div.S_tabpanel role=tabpanel", opts.inner, () => {
75
+ A("div.s-tabpanel role=tabpanel", opts.contentAttrs, () => {
70
76
  A(() => {
71
77
  const selKey = $sel.value;
72
78
  const index = opts.tabs.findIndex((t, i) => keyOf(t, i) === selKey);
@@ -1,8 +1,8 @@
1
1
  import A from "aberdeen";
2
2
  import { applyControlAttrs, drawField } from "./field.js";
3
3
  A.insertGlobalCss({
4
- "textarea.S_input": "resize:vertical min-height:3em line-height:1.45",
5
- "textarea.S_input.S_autoGrow": "resize:none min-height:2.5em overflow-y:hidden",
4
+ "textarea.s-input": "resize:vertical min-height:3em line-height:1.45",
5
+ "textarea.s-input.s-autoGrow": "resize:none min-height:2.5em overflow-y:hidden",
6
6
  });
7
7
  /**
8
8
  * A multi-line text input. Shares the field chrome and styling of
@@ -16,9 +16,9 @@ A.insertGlobalCss({
16
16
  export function textarea(opts = {}) {
17
17
  const grow = opts.autoGrow !== false;
18
18
  drawField(opts, (id, isInvalid) => {
19
- const el = A("textarea.S_input", opts.control, () => {
19
+ const el = A("textarea.s-input", opts.inputAttrs, () => {
20
20
  if (grow) {
21
- A(".S_autoGrow");
21
+ A(".s-autoGrow");
22
22
  A("input=", (e) => {
23
23
  fitToContent(e.currentTarget);
24
24
  if (opts.input)
@@ -14,7 +14,7 @@ import { applyControlAttrs, drawField } from "./field.js";
14
14
  */
15
15
  export function textline(opts = {}) {
16
16
  drawField(opts, (id, isInvalid) => {
17
- A("input.S_input", opts.control, () => {
17
+ A("input.s-input", opts.inputAttrs, () => {
18
18
  A("type=", opts.type ?? "text");
19
19
  if (opts.placeholder != null)
20
20
  A("placeholder=", opts.placeholder);
package/dist/core.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Shared building blocks for the Skye component library.
2
+ * Shared building blocks for the Staffa component library.
3
3
  *
4
- * Every component in Skye is "just an Aberdeen draw function": a plain function
4
+ * Every component in Staffa is "just an Aberdeen draw function": a plain function
5
5
  * that takes a single, strongly typed options object and emits DOM through
6
6
  * Aberdeen's {@link A} function. This module defines the option-type hierarchy
7
7
  * that all components build on, plus a couple of tiny helpers.
@@ -9,15 +9,19 @@
9
9
  /**
10
10
  * An Aberdeen attribute/style/class string, e.g. `"display:flex gap:$3 .my-class"`.
11
11
  *
12
+ * Common values are our surface modifier classes:
13
+ * - for colors: `.panel` `.raised` `.neutral` `.primary` `.danger` `.success` and `.warning`
14
+ * - for variant: `.filled` `.tonal` and `.outlined`
15
+ *
12
16
  * These strings are passed straight through to {@link A} as positional
13
17
  * arguments, so they accept the full Aberdeen shorthand syntax: CSS shortcuts
14
18
  * (`p`, `mt`, `bg`, `r`, ...), spacing variables (`$1`..`$12`), CSS custom
15
- * properties (`$sPrimary`), classes (`.foo`) and attributes (`aria-label=Hi`).
19
+ * properties (`$s-primary`), classes (`.foo`) and attributes (`aria-label=Hi`).
16
20
  *
17
21
  * Note: because Aberdeen interprets a leading bare word as an element name, write
18
22
  * `display:flex` rather than just `flex`.
19
23
  */
20
- export type Styling = string;
24
+ export type Attributes = string;
21
25
  /** A reactive "value box", such as the result of `A.proxy(x)` or `A.ref(obj, key)`. */
22
26
  export type Bindable<T> = {
23
27
  value: T;
@@ -25,49 +29,32 @@ export type Bindable<T> = {
25
29
  /** A content function. It runs inside the relevant element's reactive scope. */
26
30
  export type Content = () => void;
27
31
  /**
28
- * Something that renders a small piece of content: either a plain string (drawn
29
- * as a text node) or a draw function (for icons, badges, custom markup, ...).
30
- */
31
- export type Slot = string | Content;
32
- /**
33
- * Options shared by *every* Skye component.
32
+ * Something that renders a small piece of content: either a plain string or a
33
+ * draw function (for icons, badges, custom markup, ...).
34
34
  *
35
- * The {@link BaseOptions.root | root} string is applied to the outermost element
36
- * of the widget, letting callers tweak layout, spacing or add classes without
37
- * forking the component.
35
+ * A string is drawn as **rich text** (see {@link drawSlot}): Aberdeen's `rich`
36
+ * markup is applied, so `*italic*`, `**bold**`, `` `code` `` and
37
+ * `[links](/path)` render as inline elements (text is safely escaped).
38
+ *
39
+ * The optional `Args` type parameter lets a slot's draw-function receive
40
+ * arguments — e.g. a dialog body is a `Slot<[close: () => void]>`.
38
41
  */
39
- export interface BaseOptions {
40
- /**
41
- * Aberdeen attr/style string applied to the widget's root element.
42
- *
43
- * It is passed as a positional argument to {@link A}, so a *change* to it on a
44
- * proxied options object re-runs the caller's scope (recreating the widget).
45
- * That's fine for `root` — it rarely changes at runtime.
46
- */
47
- root?: Styling;
48
- }
42
+ export type Slot<Args extends unknown[] = []> = string | ((...args: Args) => void);
49
43
  /**
50
- * Options for components that wrap a single block of caller-provided content.
51
- *
52
- * Such components render an *inner* element (the one that actually holds the
53
- * children) which is given sensible default padding and `gap` in CSS. Override
54
- * those via {@link ContentOptions.inner | inner}, whose declarations win because
55
- * they're applied as inline styles.
44
+ * Options for components that wrap a single block of caller-provided content,
45
+ * with an `attrs` escape hatch on the outermost element.
56
46
  */
57
- export interface ContentOptions extends BaseOptions {
47
+ export interface ContentOptions {
48
+ /** Aberdeen attr/style string applied to the widget's outermost element. */
49
+ attrs?: Attributes;
58
50
  /** Draws the children of this component. */
59
51
  content?: Content;
60
- /**
61
- * Aberdeen attr/style string applied to the inner (content-holding) element.
62
- * Add `display:flex` here if you want the children laid out as a flex
63
- * row/column.
64
- */
65
- inner?: Styling;
66
52
  }
67
53
  /** Generates a process-unique id, used to wire `<label for>` to its control. */
68
54
  export declare function uniqueId(prefix?: string): string;
69
55
  /**
70
- * Draw a {@link Slot} into the current element: call it if it's a function,
71
- * otherwise emit it as a text node.
56
+ * Draw a {@link Slot} into the current element: call it (with any extra `args`)
57
+ * if it's a function, otherwise emit the string as **rich text** via Aberdeen's
58
+ * `rich` markup (`*italic*`, `**bold**`, `` `code` ``, `[link](/path)`).
72
59
  */
73
- export declare function drawSlot(slot: Slot | undefined): void;
60
+ export declare function drawSlot<Args extends unknown[] = []>(slot: Slot<Args> | undefined, ...args: Args): void;
package/dist/core.js CHANGED
@@ -5,14 +5,15 @@ export function uniqueId(prefix = "s") {
5
5
  return `${prefix}-${++idCounter}`;
6
6
  }
7
7
  /**
8
- * Draw a {@link Slot} into the current element: call it if it's a function,
9
- * otherwise emit it as a text node.
8
+ * Draw a {@link Slot} into the current element: call it (with any extra `args`)
9
+ * if it's a function, otherwise emit the string as **rich text** via Aberdeen's
10
+ * `rich` markup (`*italic*`, `**bold**`, `` `code` ``, `[link](/path)`).
10
11
  */
11
- export function drawSlot(slot) {
12
+ export function drawSlot(slot, ...args) {
12
13
  if (slot == null)
13
14
  return;
14
15
  if (typeof slot === "function")
15
- slot();
16
+ slot(...args);
16
17
  else
17
- A("#", slot);
18
+ A("rich=", slot);
18
19
  }
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Skye — a small, opinionated component library for the
2
+ * Staffa — a small, opinionated component library for the
3
3
  * {@link https://aberdeenjs.org | Aberdeen} reactive UI library.
4
4
  *
5
5
  * Import the default `S` object and call its component functions:
6
6
  *
7
7
  * ```ts
8
- * import S from "skye";
8
+ * import S from "staffa";
9
9
  *
10
10
  * S.main({
11
11
  * title: "Hello",
@@ -33,6 +33,7 @@ import { setDarkMode, getDarkMode } from "./theme.js";
33
33
  import { autocomplete } from "./components/autocomplete.js";
34
34
  import { box } from "./components/box.js";
35
35
  import { button } from "./components/button.js";
36
+ import { buttonChooser } from "./components/buttonChooser.js";
36
37
  import { buttonGroup } from "./components/buttonGroup.js";
37
38
  import { checkbox } from "./components/checkbox.js";
38
39
  import { form } from "./components/form.js";
@@ -42,7 +43,7 @@ import { select } from "./components/select.js";
42
43
  import { tabs } from "./components/tabs.js";
43
44
  import { textarea } from "./components/textarea.js";
44
45
  import { textline } from "./components/textline.js";
45
- /** The Skye component namespace. */
46
+ /** The Staffa component namespace. */
46
47
  export declare const S: {
47
48
  main: typeof main;
48
49
  box: typeof box;
@@ -56,17 +57,17 @@ export declare const S: {
56
57
  checkbox: typeof checkbox;
57
58
  tabs: typeof tabs;
58
59
  button: typeof button;
60
+ buttonChooser: typeof buttonChooser;
59
61
  buttonGroup: typeof buttonGroup;
60
62
  autocomplete: typeof autocomplete;
61
63
  select: typeof select;
62
- darkTheme: import("./theme.js").Theme;
63
- lightTheme: import("./theme.js").Theme;
64
64
  setDarkMode: typeof setDarkMode;
65
65
  getDarkMode: typeof getDarkMode;
66
66
  };
67
67
  export default S;
68
- export { type Theme, darkTheme, lightTheme, setDarkMode, getDarkMode } from "./theme.js";
69
- export type { BaseOptions, ContentOptions, Bindable, Content, Slot, Styling, } from "./core.js";
68
+ export { setDarkMode, getDarkMode } from "./theme.js";
69
+ export type { SurfaceRole, Variant } from "./theme.js";
70
+ export type { ContentOptions, Bindable, Content, Slot, Attributes as Styling, } from "./core.js";
70
71
  export { drawSlot, uniqueId } from "./core.js";
71
72
  export type { FieldOptions } from "./components/field.js";
72
73
  export type { MainOptions } from "./components/main.js";
@@ -76,7 +77,8 @@ export type { TextlineOptions, TextlineType } from "./components/textline.js";
76
77
  export type { TextareaOptions } from "./components/textarea.js";
77
78
  export type { CheckboxOptions } from "./components/checkbox.js";
78
79
  export type { Tab, TabsOptions } from "./components/tabs.js";
79
- export type { ButtonOptions, ButtonVariant, ButtonColor } from "./components/button.js";
80
+ export type { ButtonOptions } from "./components/button.js";
81
+ export type { ButtonChooserOptions } from "./components/buttonChooser.js";
80
82
  export type { ButtonGroupOptions } from "./components/buttonGroup.js";
81
83
  export type { AutocompleteOptions, AutocompleteOptionInput } from "./components/autocomplete.js";
82
84
  export type { SelectOptions, SelectOptionInput } from "./components/select.js";
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Skye — a small, opinionated component library for the
2
+ * Staffa — a small, opinionated component library for the
3
3
  * {@link https://aberdeenjs.org | Aberdeen} reactive UI library.
4
4
  *
5
5
  * Import the default `S` object and call its component functions:
6
6
  *
7
7
  * ```ts
8
- * import S from "skye";
8
+ * import S from "staffa";
9
9
  *
10
10
  * S.main({
11
11
  * title: "Hello",
@@ -30,11 +30,13 @@
30
30
  * mutate it. See `AGENTS.md` for the design philosophy.
31
31
  */
32
32
  // Importing the theme module installs spacing vars, the reactive theme and the
33
- // base stylesheet. Customise by mutating S.darkTheme / S.lightTheme.
34
- import { darkTheme, lightTheme, setDarkMode, getDarkMode } from "./theme.js";
33
+ // base stylesheet. Customise it from your app with A.insertGlobalCss (see
34
+ // theme.ts); toggle modes with setDarkMode / getDarkMode.
35
+ import { setDarkMode, getDarkMode } from "./theme.js";
35
36
  import { autocomplete } from "./components/autocomplete.js";
36
37
  import { box } from "./components/box.js";
37
38
  import { button } from "./components/button.js";
39
+ import { buttonChooser } from "./components/buttonChooser.js";
38
40
  import { buttonGroup } from "./components/buttonGroup.js";
39
41
  import { checkbox } from "./components/checkbox.js";
40
42
  import { form } from "./components/form.js";
@@ -44,7 +46,7 @@ import { select } from "./components/select.js";
44
46
  import { tabs } from "./components/tabs.js";
45
47
  import { textarea } from "./components/textarea.js";
46
48
  import { textline } from "./components/textline.js";
47
- /** The Skye component namespace. */
49
+ /** The Staffa component namespace. */
48
50
  export const S = {
49
51
  main,
50
52
  box,
@@ -58,15 +60,14 @@ export const S = {
58
60
  checkbox,
59
61
  tabs,
60
62
  button,
63
+ buttonChooser,
61
64
  buttonGroup,
62
65
  autocomplete,
63
66
  select,
64
- darkTheme,
65
- lightTheme,
66
67
  setDarkMode,
67
68
  getDarkMode,
68
69
  };
69
70
  export default S;
70
71
  // Re-export theming and shared types for advanced use.
71
- export { darkTheme, lightTheme, setDarkMode, getDarkMode } from "./theme.js";
72
+ export { setDarkMode, getDarkMode } from "./theme.js";
72
73
  export { drawSlot, uniqueId } from "./core.js";
@@ -0,0 +1 @@
1
+ import S from"aberdeen";var K="staffa:darkMode",U=S.proxy({value:le()});function le(){try{let e=localStorage.getItem(K);if(e==="dark")return!0;if(e==="light")return!1}catch{}}function P(e){U.value=e;try{e===void 0?localStorage.removeItem(K):localStorage.setItem(K,e?"dark":"light")}catch{}}function M(e=!1){let t=U.value;return t===void 0&&!e?S.darkMode():t}S(()=>{M()?S.insertGlobalCss({":root":"--s-primary:#8b7bff --s-danger:#ff6b6b --s-success:#46d39a --s-warning:#fbbf24 --s-neutral:#3c4352 --s-page:#0e1015 --s-panel:#181b22 --s-raised:#222632 --s-ink:#e8eaf0 --s-on-accent:#0c0a14 --s-focus:rgba(139,123,255,0.45) --s-radius:10px --s-radius-lg:16px --s-shadow: 0 8px 30px rgba(0,0,0,0.45);",":root, .s-s.base, .s-s.panel, .s-s.raised, .s-s.neutral":"--s-link:#6db3ff"}):S.insertGlobalCss({":root":"--s-primary:#6c5ce7 --s-danger:#e23b3b --s-success:#1f9d6b --s-warning:#d97706 --s-neutral:#c7ccda --s-page:#f3f4f8 --s-panel:#ffffff --s-raised:#eceef4 --s-ink:#1b1e27 --s-on-accent:#ffffff --s-focus:rgba(108,92,231,0.35) --s-radius:10px --s-radius-lg:16px --s-shadow: 0 6px 24px rgba(20,24,40,0.12);",":root, .s-s.base, .s-s.panel, .s-s.raised, .s-s.neutral":"--s-link:#2563eb"})});S.setSpacingCssVars();S.insertGlobalCss({":root, .s-s.base":"--s-a:$s-ink --s-b:$s-page",".s-s.panel":"--s-a:$s-ink --s-b:$s-panel",".s-s.raised":"--s-a:$s-ink --s-b:$s-raised",".s-s.neutral":"--s-a:$s-ink --s-b:$s-neutral",".s-s.primary":"--s-a:$s-on-accent --s-b:$s-primary",".s-s.danger":"--s-a:$s-on-accent --s-b:$s-danger",".s-s.success":"--s-a:$s-on-accent --s-b:$s-success",".s-s.warning":"--s-a:$s-on-accent --s-b:$s-warning",":root, .s-s":"--s-fg:$s-a --s-bg:$s-b --s-fg-muted: color-mix(in oklab, $s-fg, $s-bg 42%); --s-fg-faint: color-mix(in oklab, $s-fg, $s-bg 64%); --s-border: color-mix(in oklab, $s-fg, $s-bg 82%); --s-border-strong: color-mix(in oklab, $s-fg, $s-bg 68%); background:$s-bg color:$s-fg",".s-s.tonal":"--s-fg:$s-b --s-bg: color-mix(in srgb, $s-b 16%, transparent);",".s-s.outlined":"--s-fg:$s-b --s-bg:inherit background:transparent --s-border: color-mix(in srgb, $s-fg 55%, $s-bg);",".s-s.filled":"--s-fg:$s-a --s-bg:$s-b background:$s-bg;",":root, .s-s.base, .s-s.panel, .s-s.raised, .s-s.neutral":"--s-accent:$s-primary",".s-s.primary, .s-s.danger, .s-s.success, .s-s.warning":"--s-accent:$s-fg --s-link:$s-fg"});S.insertGlobalCss({"*, *::before, *::after":"box-sizing:border-box",html:"text-size-adjust:100%",body:"m:0 line-height:1.5 font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; -webkit-font-smoothing:antialiased",a:"color: $s-link; text-decoration:underline text-underline-offset:2px","a:hover":"filter: brightness(1.15)","input, button, textarea, select":"font:inherit color:inherit","code, kbd, samp, pre":"font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;",code:"background: color-mix(in oklab, $s-fg, $s-bg 86%); padding: 0.12em 0.34em; r:4px font-size:0.9em",pre:"background: color-mix(in oklab, $s-fg, $s-bg 92%); p:$3 r: $s-radius; overflow:auto","pre code":"background:transparent p:0","img, svg, video, canvas":"max-width:100% h:auto",hr:"border:0 border-top: 1px solid $s-border; margin: $3 0;","::placeholder":"color: $s-fg-faint; opacity:1",":focus-visible":"outline: 2px solid $s-focus; outline-offset:2px",small:"color:$s-fg-muted font-size:0.9em"});import o from"aberdeen";import de from"aberdeen";var ce=0;function O(e="s"){return`${e}-${++ce}`}function c(e,...t){e!=null&&(typeof e=="function"?e(...t):de("rich=",e))}import g from"aberdeen";g.insertGlobalCss({".s-field":{"&":"display:flex flex-direction:column gap:$1","> label":"font-weight:600 font-size:0.9em fg:$s-fg user-select:none"},".s-req":"fg:$s-danger margin-left:2px",".s-help":"font-size:0.82em fg:$s-fg-muted",".s-error":"font-size:0.82em fg:$s-danger",".s-input":{"&":"w:100% bg:$s-panel fg:$s-ink border: 1px solid $s-border; r:$s-radius padding: 0.55em 0.7em; transition: border-color 0.15s, box-shadow 0.15s;","&:hover:not(:disabled)":"border-color:$s-border-strong","&:focus-visible":"border-color:$s-accent box-shadow: 0 0 0 3px $s-focus; outline:none","&:disabled":"opacity:0.6 cursor:not-allowed","&[aria-invalid=true]":"border-color:$s-danger"}});function T(e,t){let n=e.id??O("field"),i=()=>!!e.error;g("div.s-field",e.attrs,()=>{g(()=>{e.label!=null&&g(`label for=${n}`,()=>{c(e.label),e.required&&g("span.s-req aria-hidden=true #*")})}),t(n,i),g(()=>{e.help!=null&&!e.error&&g("div.s-help",()=>c(e.help))}),g(()=>{e.error&&g("div.s-error role=alert #",e.error)})})}function F(e,t,n,i){g(`id=${t}`),e.name&&g(`name=${e.name}`),g(()=>{e.disabled&&g("disabled=true")}),g(()=>{e.required&&g("aria-required=true")}),g(()=>g("aria-invalid=",n()?"true":"false")),i&&g("bind=",i)}o.insertGlobalCss({".s-ac":{"&":"position:relative","> .s-control":"display:flex flex-wrap:wrap align-items:center gap:$1 bg:$s-panel fg:$s-ink border: 1px solid $s-border; r:$s-radius padding: 0.3em 0.4em; cursor:text; transition: border-color 0.15s, box-shadow 0.15s;","> .s-control:hover":"border-color:$s-border-strong","> .s-control:focus-within":"border-color:$s-accent box-shadow: 0 0 0 3px $s-focus;","&[aria-invalid=true] > .s-control":"border-color:$s-danger",".s-chip":"display:inline-flex align-items:center gap:$1 font-size:0.85em bg:$s-raised border: 1px solid $s-border; r:$s-radius padding: 0.1em 0.2em 0.1em 0.5em;",".s-chip > button":"cursor:pointer border:0 background:transparent fg:$s-fg-muted font-size:1.1em line-height:1 padding: 0 0.2em; r:4px",".s-chip > button:hover":"fg:$s-fg background:$s-border",input:"flex:1 min-width:6ch border:0 background:transparent color:inherit outline:none padding:0.25em","> .s-menu":"position:absolute top:100% left:0 right:0 z-index:20 margin-top:4px max-height:15rem overflow-y:auto list-style:none p:$1 margin-bottom:0 bg:$s-panel border: 1px solid $s-border; r:$s-radius box-shadow:$s-shadow",".s-option":"padding: 0.45em 0.6em; r:6px cursor:pointer",".s-option[aria-selected=true]":"background:$s-raised",".s-add":"fg:$s-accent font-style:italic",".s-empty":"padding: 0.45em 0.6em; fg:$s-fg-muted"}});function ue(e){return typeof e=="string"?{value:e,label:e}:{value:e.value,label:e.label??e.value}}function _(e){let t=O("ac-menu"),n=o.proxy({query:"",open:!1,active:0}),i=()=>(typeof e.options=="function"?e.options():e.options).map(ue),r=()=>{let s=e.bind?.value;return s==null||s===""?[]:Array.isArray(s)?s:[s]},l=s=>i().find(x=>x.value===s)?.label??s;if(!e.multi){let s=e.bind?o.peek(e.bind,"value"):void 0;typeof s=="string"&&s&&(n.query=o.peek(()=>l(s)))}let a=()=>{let s=new Set(r()),x=i();e.multi&&(x=x.filter(m=>!s.has(m.value)));let b=n.query.trim().toLowerCase();return b&&(x=x.filter(m=>m.label.toLowerCase().includes(b))),x},u=(s,x)=>{if(e.multi){let b=Array.isArray(e.bind?.value)?[...e.bind.value]:[];b.includes(s)||b.push(s),e.bind&&(e.bind.value=b),n.query=""}else e.bind&&(e.bind.value=s),n.query=l(s),n.open=!1;n.active=0,x?.focus()},$=s=>{if(!e.bind)return;let x=e.bind.value??[];e.bind.value=x.filter(b=>b!==s)};T(e,(s,x)=>{o("div.s-ac",e.inputAttrs,()=>{o(()=>o("aria-invalid=",x()?"true":"false"));let b;o("div.s-control",()=>{o("click=",()=>b?.focus()),o(()=>{if(e.multi)for(let m of r())o("span.s-chip",()=>{o("span #",o.peek(()=>l(m))),o("button type=button aria-label=",`Remove ${m}`,()=>{o("#\xD7"),o("click=",v=>{v.stopPropagation(),$(m),b?.focus()})})})}),b=o("input type=text role=combobox autocomplete=off",()=>{o(`id=${s} aria-controls=${t} aria-autocomplete=list`),e.placeholder!=null&&o("placeholder=",e.placeholder),e.disabled&&o("disabled=true"),e.required&&o("aria-required=true"),o("bind=",o.ref(n,"query")),o(()=>o("aria-expanded=",n.open?"true":"false")),o(()=>{let v=a()[n.active];o("aria-activedescendant=",n.open&&v?`${t}-opt-${n.active}`:"")}),o("input=",()=>{n.open=!0,n.active=0}),o("focus=",()=>{n.open=!0}),o("blur=",()=>{setTimeout(()=>L(),150)}),o("keydown=",m=>A(m,b))})}),o(()=>{if(!n.open)return;let m=a(),v=n.query.trim(),W=e.allowCustom!==!1&&v!==""&&!m.some(G=>G.label.toLowerCase()===v.toLowerCase());o("ul.s-menu role=listbox",`id=${t}`,()=>{m.forEach((G,H)=>{o("li.s-option role=option",`id=${t}-opt-${H}`,()=>{o(()=>o("aria-selected=",n.active===H?"true":"false")),o("#",G.label),o("mousedown=",ae=>ae.preventDefault()),o("click=",()=>u(G.value,b)),o("mousemove=",()=>{n.active=H})})}),W&&o("li.s-option.s-add role=option",()=>{o("#",`Add "${v}"`),o("mousedown=",G=>G.preventDefault()),o("click=",()=>u(v,b))}),m.length===0&&!W&&o("li.s-empty #No matches")})}),o(()=>{if(e.name)if(e.multi)for(let m of r())o("input type=hidden",()=>{o("name=",e.name),o("value=",m)});else o("input type=hidden",()=>{o("name=",e.name),o("value=",r()[0]??"")})})})});function A(s,x){let b=a(),m=b.length-1;if(s.key==="ArrowDown")s.preventDefault(),n.open=!0,n.active=Math.min(m,n.active+1);else if(s.key==="ArrowUp")s.preventDefault(),n.active=Math.max(0,n.active-1);else if(s.key==="Enter"){s.preventDefault();let v=b[n.active];v?u(v.value,x):e.allowCustom!==!1&&n.query.trim()?u(n.query.trim(),x):n.open&&(n.open=!1)}else if(s.key==="Escape")n.open=!1,e.multi||(n.query=l(r()[0]??""));else if(s.key==="Backspace"&&e.multi&&n.query===""){let v=r();v.length&&$(v[v.length-1])}}function L(){n.open=!1,e.multi?n.query="":e.allowCustom!==!1&&n.query.trim()?u(n.query.trim()):n.query=l(r()[0]??"")}}import B from"aberdeen";B.insertGlobalCss({".s-box":{"&":"display:flex flex-direction:column border: 1px solid $s-border; r: $s-radius; overflow:hidden","> header":"display:flex align-items:center gap:$2 padding: $2 $3; border-bottom: 1px solid $s-border; font-weight:600","> footer":"display:flex align-items:center gap:$2 padding: $2 $3; border-top: 1px solid $s-border;","> div":"p:$3 gap:$3"}});function Y(e={}){let t=typeof e=="function"?{content:e}:e;B("section.s-box.s-s.panel",t.attrs,()=>{B(()=>{t.header!=null&&B("header.s-s.raised",t.headerAttrs,()=>c(t.header))}),B("div",t.contentAttrs,()=>{t.content&&t.content()}),B(()=>{t.footer!=null&&B("footer.s-s.raised",t.footerAttrs,()=>c(t.footer))})})}import C from"aberdeen";C.insertGlobalCss({".s-btn":{"&":"display:inline-flex align-items:center justify-content:center gap:$2 font-weight:600 line-height:1.2 white-space:nowrap cursor:pointer text-decoration:none border: 1px solid $s-border; r: $s-radius; padding: 0.5em 1em; transition: background 0.15s, border-color 0.15s, filter 0.15s, box-shadow 0.15s;","&:focus-visible":"outline:none box-shadow: 0 0 0 3px $s-focus;","&:disabled, &[aria-disabled=true]":"opacity:0.45 cursor:not-allowed pointer-events:none filter:saturate(0.6)","&:hover":"filter: brightness(1.08)","&.tonal:hover, &.outlined:hover":"background: color-mix(in srgb, $s-b 26%, transparent);","&.s-sm":"padding: 0.32em 0.7em; font-size:0.85em","&.s-lg":"padding: 0.66em 1.3em; font-size:1.1em"}});function w(e={}){let t=typeof e=="string"?{text:e}:typeof e=="function"?{content:e}:e,n=t.href!=null?"a":"button",i=t.size!=null?`.s-${t.size}`:"";C(`${n}.s-btn.s-s.primary${i}`,t.attrs,()=>{t.href!=null?(C(`href=${t.href} role=button`),t.disabled&&C("aria-disabled=true")):(C("type=",t.type??"button"),t.disabled&&C("disabled=true")),t.ariaLabel&&C("aria-label=",t.ariaLabel),t.click&&C("click=",t.click),c(t.icon),t.content?t.content():t.text!=null&&C("#",t.text)})}import R from"aberdeen";import N from"aberdeen";N.insertGlobalCss({".s-bgroup":{"&":"display:inline-flex align-items:stretch","&.s-spaced":"gap:$2 flex-wrap:wrap","&.s-vertical":"flex-direction:column","&.s-attached":"gap:0","&.s-attached:not(.s-vertical) > .s-btn:not(:first-child)":"margin-left:-1px","&.s-attached:not(.s-vertical) > .s-btn:not(:first-child):not(:last-child)":"r:0","&.s-attached:not(.s-vertical) > .s-btn:first-child:not(:last-child)":"border-top-right-radius:0 border-bottom-right-radius:0","&.s-attached:not(.s-vertical) > .s-btn:last-child:not(:first-child)":"border-top-left-radius:0 border-bottom-left-radius:0","&.s-attached.s-vertical > .s-btn:not(:first-child)":"margin-top:-1px","&.s-attached.s-vertical > .s-btn:not(:first-child):not(:last-child)":"r:0","&.s-attached.s-vertical > .s-btn:first-child:not(:last-child)":"border-bottom-left-radius:0 border-bottom-right-radius:0","&.s-attached.s-vertical > .s-btn:last-child:not(:first-child)":"border-top-left-radius:0 border-top-right-radius:0","&.s-attached > .s-btn:hover, &.s-attached > .s-btn:focus-visible":"z-index:1"}});function E(e={}){let n=`.s-${e.layout??"attached"}${e.vertical?".s-vertical":""}`;N(`div.s-bgroup${n} role=group`,e.attrs,()=>{if(e.buttons)for(let i of e.buttons)w(i);e.content&&e.content()})}function J(e){R(()=>{let t=e.bind.value;E({attrs:e.attrs,buttons:Object.entries(e.options).map(([n,i])=>({text:i,size:e.size,attrs:t===n?".primary":".neutral .outlined",click:()=>{e.bind.value=e.allowDeselect&&t===n?null:n}}))})}),e.name&&R(()=>R(`input type=hidden name=${e.name} value=`,e.bind.value??""))}import p from"aberdeen";p.insertGlobalCss({".s-check":{"&":"display:flex flex-direction:column gap:$1","> label":"display:flex align-items:center gap:$2 cursor:pointer user-select:none","> label:has(input:disabled)":"cursor:not-allowed opacity:0.6",input:"width:1.15em height:1.15em accent-color:$s-accent cursor:inherit m:0"}});function Q(e={}){let t=e.id??O("check");p("div.s-check",e.attrs,()=>{p(`label for=${t}`,()=>{p("input type=checkbox",e.inputAttrs,()=>{p(`id=${t}`),e.name&&p(`name=${e.name}`),e.checked&&!e.bind&&p("checked=true"),e.change&&p("change=",e.change),p(()=>{e.disabled&&p("disabled=true")}),p(()=>{e.required&&p("aria-required=true")}),e.bind&&p("bind=",e.bind)}),p(()=>{e.label!=null&&c(e.label),e.required&&p("span.s-req aria-hidden=true #*")})}),p(()=>{e.help!=null&&!e.error&&p("div.s-help",()=>c(e.help))}),p(()=>{e.error&&p("div.s-error role=alert #",e.error)})})}import q from"aberdeen";q.insertGlobalCss({".s-form":{"&":"display:flex flex-direction:column gap:$3","&.grid":"display:grid grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap:$3","&.grid > .s-wide, &.grid > footer":"grid-column: 1 / -1;","> footer":"display:flex align-items:center gap:$2 flex-wrap:wrap margin-top:$1"}});function X(e={}){let t=typeof e=="function"?{content:e}:e;q("form.s-form",t.attrs,()=>{q(()=>{q(".grid=",t.layout==="grid")}),q("submit=",n=>{if(n.preventDefault(),t.submit){let i=new FormData(n.target),r={};for(let l of new Set(i.keys())){let a=i.getAll(l);r[l]=a.length===1?a[0]:a}t.submit(r,n)}}),t.content&&t.content(),q(()=>{t.actions&&q("footer",t.actionsAttrs,()=>t.actions?.())})})}import f from"aberdeen";f.insertGlobalCss({".s-main":{"&":"display:flex flex-direction:column min-height:100vh max-height:100vh","> header":"display:flex align-items:center gap:$3 padding: $2 $3; border-bottom: 1px solid $s-border; position:sticky top:0 z-index:10","> header .s-icon":"display:flex align-items:center font-size:1.4em","> header .s-titles":"display:flex flex-direction:column min-width:0 flex:1","> header .s-title":"font-weight:700 font-size:1.1em line-height:1.2 overflow:hidden text-overflow:ellipsis white-space:nowrap","> header .s-subtitle":"fg:$s-fg-muted font-size:0.85em overflow:hidden text-overflow:ellipsis white-space:nowrap","> header .s-menu":"display:flex align-items:center gap:$2","> main":"flex:1 overflow-y:auto display:flex flex-direction:column","> main > .s-content":"width:100% flex:1","> main > .s-content.s-framed":"margin: $3 auto; border: 1px solid $s-border; r:$s-radius-lg box-shadow:$s-shadow p:$4","> main > .s-content.s-plain":"p:$3","> footer":"display:flex align-items:center gap:$2 padding: $2 $3; border-top: 1px solid $s-border; fg:$s-fg-muted"}});function Z(e={}){f("div.s-main.s-s.base",e.attrs,()=>{f(()=>{(e.title!=null||e.subtitle!=null||e.icon!=null||e.menu!=null)&&f("header.s-s.raised",e.topbarAttrs,()=>{f(()=>{e.icon!=null&&f("div.s-icon",()=>c(e.icon))}),f("div.s-titles",()=>{f(()=>{e.title!=null&&f("div.s-title",()=>c(e.title))}),f(()=>{e.subtitle!=null&&f("div.s-subtitle",()=>c(e.subtitle))})}),f(()=>{e.menu&&f("div.s-menu",()=>e.menu?.())})})}),f("main",()=>{f("div.s-content",e.contentAttrs,()=>{f(()=>{let t=e.maxWidth;t!=null?f(".s-framed.s-s.panel max-width:",t):f(".s-plain")}),e.content&&e.content()})}),f(()=>{e.footer!=null&&f("footer",()=>c(e.footer))})})}import d from"aberdeen";import z from"aberdeen";function j(e={}){T(e,(t,n)=>{z("input.s-input",e.inputAttrs,()=>{z("type=",e.type??"text"),e.placeholder!=null&&z("placeholder=",e.placeholder),e.autocomplete!=null&&z("autocomplete=",e.autocomplete),e.value!=null&&!e.bind&&z("value=",e.value),e.input&&z("input=",e.input),e.change&&z("change=",e.change),F(e,t,n,e.bind)})})}d.insertGlobalCss({".s-backdrop":{"&":"position:fixed inset:0 z-index:200 display:block background: rgba(0,0,0,0.55); transition: opacity 0.4s ease-in-out;","&.hidden":"opacity:0 pointer-events:none"},".s-dialog":{"&":"position:fixed z-index:200 top:50% left:50% display:flex flex-direction:column transform:translate(-50%,-50%) min-width:20rem max-width:min(90vw,44rem) max-height:min(88vh,800px) border: 1px solid $s-border; r: $s-radius-lg; box-shadow: $s-shadow; overflow:hidden transition: opacity 0.2s ease-out, transform 0.2s ease-out;","> header":"display:flex align-items:center gap:$2 padding: $2 $3; border-bottom: 1px solid $s-border; font-weight:600 flex-shrink:0","> footer":"display:flex align-items:center gap:$2 padding: $2 $3; border-top: 1px solid $s-border; flex-shrink:0","> div":"p:$3 gap:$3 display:flex flex-direction:column overflow-y:auto flex:1 min-height:0","&.hidden":"opacity:0 pointer-events:none transform: translate(-50%, calc(-50% + 20px)); pointer-events:none"}});var I=d.proxy({}),V=0,fe=d.derive(()=>{let e=Object.keys(I);if(e.length)return e[e.length-1]});d.mount(document.body,()=>{d.onEach(I,({resolve:e,opts:t},n)=>{let i=()=>{delete I[n]};d.clean(()=>{t.onClose?.(),e()});let r=d.derive(()=>fe.value!=n);d("div.s-backdrop create=hidden destroy=hidden .hidden=",r,"click=",()=>{t.allowCancel!==!1&&i()}),d("div.s-dialog.s-s.panel create=hidden destroy=hidden",t.attrs,()=>{d(()=>{t.header!=null&&d("header.s-s.raised",t.headerAttrs,()=>c(t.header))}),d("div",t.contentAttrs,()=>{c(t.content,i)}),d(()=>{t.footer!=null&&d("footer.s-s.raised",t.footerAttrs,()=>c(t.footer))})})})});function D(e){V||document.addEventListener("keydown",n=>{if(n.key==="Escape"&&e.allowCancel!==!1){let i=d.unproxy(I);for(let r=V;r>0;r--)if(i[r]){i[r].opts.allowCancel!==!1&&delete I[r];break}}});let t=++V;return e.cancelWithScope!==!1&&d.clean(()=>{delete I[t]}),new Promise(n=>{I[t]={resolve:n,opts:e}})}function ee(e,t={}){return D({header:"Alert",allowCancel:!0,content:n=>{d("p",()=>{d("#",e)}),E({layout:"spaced",attrs:"align-self:flex-end",content:()=>{w({text:"OK",click:n})}})},...t})}function te(e,t={}){return new Promise(n=>{let i=!1;D({header:"Confirm",allowCancel:!0,content:r=>{d("p",()=>{d("#",e)}),E({layout:"spaced",attrs:"align-self:flex-end",content:()=>{w({text:"Cancel",attrs:".neutral .outlined",click:r}),w({text:"OK",click:()=>{i=!0,r()}})}})},...t,onClose:()=>{n(i),t.onClose?.()}})})}function ne(e,t="",n={}){return new Promise(i=>{let r=null;D({header:"Input",allowCancel:!0,content:l=>{d("p",()=>{d("#",e)});let a=d.proxy({value:t});d("form display:contents",()=>{d("submit=",u=>{u.preventDefault(),r=a.value,l()}),j({bind:d.ref(a,"value")}),E({layout:"spaced",attrs:"align-self:flex-end",content:()=>{w({text:"Cancel",attrs:".neutral .outlined",type:"button",click:l}),w({text:"OK",type:"submit"})}})})},...n,onClose:()=>{i(r),n.onClose?.()}})})}import y from"aberdeen";y.insertGlobalCss({".s-select_wrap":{"&":"position:relative display:block",select:"w:100% cursor:pointer padding-right:2.2em; appearance:none","&::after":"content: '\u25BE'; position:absolute right:0.7em top:50%; transform: translateY(-50%); pointer-events:none fg:$s-fg-muted font-size:0.85em"}});function oe(e){T(e,(t,n)=>{y("div.s-select_wrap",e.inputAttrs,()=>{y("select.s-input",()=>{F(e,t,n),y("change=",i=>{e.bind&&(e.bind.value=i.target.value)}),y(()=>{let i=typeof e.options=="function"?e.options():e.options,r=e.bind?.value??"";e.placeholder!=null&&y("option",()=>{y("value= disabled=true hidden=true"),r||y("selected=true"),y("#",e.placeholder)});for(let l of i){let a=typeof l=="string"?{value:l,label:l}:{value:l.value,label:l.label??l.value};y("option",()=>{y("value=",a.value),a.value===r&&y("selected=true"),y("#",a.label)})}})})})})}import h from"aberdeen";h.insertGlobalCss({".s-tabs":{"&":"display:flex flex-direction:column gap:$3",".s-tablist":"display:flex gap:$1 align-items:stretch",".s-tab":"display:inline-flex align-items:center gap:$2 cursor:pointer background:transparent border:0 color: $s-fg-muted; font-weight:600 padding: 0.6em 0.9em; transition: color 0.15s, background 0.15s, border-color 0.15s;",".s-tab:hover:not(:disabled)":"color: $s-fg;",".s-tab:disabled":"opacity:0.5 cursor:not-allowed",".s-tab:focus-visible":"outline:none box-shadow: 0 0 0 3px $s-focus; r: $s-radius;","&.s-underline .s-tablist":"border-bottom: 1px solid $s-border;","&.s-underline .s-tab":"border-bottom: 2px solid transparent; margin-bottom:-1px","&.s-underline .s-tab[aria-selected=true]":"color: $s-fg; border-bottom-color: $s-accent;","&.s-pills .s-tab":"r: $s-radius;","&.s-pills .s-tab[aria-selected=true]":"background: $s-bg; color: $s-fg;",".s-tabpanel":"display:block"}});function re(e){let t=e.variant??"underline",n=O("tabs"),i=(a,u)=>a.id??String(u),r=e.bind??h.proxy(i(e.tabs[0]??{label:""},0)),l=(a,u)=>{a.disabled||(r.value=i(a,u))};h(`div.s-tabs.s-${t}`,e.attrs,()=>{h("div.s-tablist role=tablist",()=>{e.tabs.forEach((a,u)=>{let $=i(a,u);h("button.s-tab type=button role=tab",()=>{h(`id=${n}-tab-${$} aria-controls=${n}-panel-${$}`),h(()=>{let A=r.value===$;h("aria-selected=",A?"true":"false"),h("tabindex=",A?"0":"-1"),A&&t==="pills"&&h(".s-s.primary")}),a.disabled&&h("disabled=true"),h("click=",()=>l(a,u)),h("keydown=",A=>pe(A,e.tabs,u,l)),c(a.icon),c(a.label)})})}),h("div.s-tabpanel role=tabpanel",e.contentAttrs,()=>{h(()=>{let a=r.value,u=e.tabs.findIndex((A,L)=>i(A,L)===a),$=e.tabs[u]??e.tabs[0];$&&(h(`id=${n}-panel-${i($,u)} aria-labelledby=${n}-tab-${i($,u)}`),$.content?.())})})})}function pe(e,t,n,i){let r=n;if(e.key==="ArrowRight"||e.key==="ArrowDown")r=(n+1)%t.length;else if(e.key==="ArrowLeft"||e.key==="ArrowUp")r=(n-1+t.length)%t.length;else if(e.key==="Home")r=0;else if(e.key==="End")r=t.length-1;else return;e.preventDefault();let l=r>=n?1:-1;for(let a=0;a<t.length;a++){let u=t[r];if(u&&!u.disabled){i(u,r),e.currentTarget?.parentElement?.children[r]?.focus();return}r=(r+l+t.length)%t.length}}import k from"aberdeen";k.insertGlobalCss({"textarea.s-input":"resize:vertical min-height:3em line-height:1.45","textarea.s-input.s-autoGrow":"resize:none min-height:2.5em overflow-y:hidden"});function se(e={}){let t=e.autoGrow!==!1;T(e,(n,i)=>{let r=k("textarea.s-input",e.inputAttrs,()=>{t?(k(".s-autoGrow"),k("input=",l=>{ie(l.currentTarget),e.input&&e.input(l)})):(k("rows=",e.rows??4),k("resize:",e.resize??"vertical"),e.input&&k("input=",e.input)),e.placeholder!=null&&k("placeholder=",e.placeholder),e.value!=null&&!e.bind&&k("value=",e.value),e.change&&k("change=",e.change),F(e,n,i,e.bind)});t&&requestAnimationFrame(()=>ie(r))})}function ie(e){e.style.height="auto",e.style.height=`${e.scrollHeight}px`}var be={main:Z,box:Y,dialog:D,alert:ee,confirm:te,prompt:ne,form:X,textline:j,textarea:se,checkbox:Q,tabs:re,button:w,buttonChooser:J,buttonGroup:E,autocomplete:_,select:oe,setDarkMode:P,getDarkMode:M},At=be;export{be as S,At as default,c as drawSlot,M as getDarkMode,P as setDarkMode,O as uniqueId};