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.
- package/README.md +161 -97
- package/dist/components/autocomplete.js +21 -20
- package/dist/components/box.d.ts +8 -6
- package/dist/components/box.js +16 -12
- package/dist/components/button.d.ts +21 -35
- package/dist/components/button.js +46 -39
- package/dist/components/buttonChooser.d.ts +41 -0
- package/dist/components/buttonChooser.js +38 -0
- package/dist/components/buttonGroup.d.ts +3 -3
- package/dist/components/buttonGroup.js +18 -21
- package/dist/components/checkbox.js +7 -7
- package/dist/components/dialog.d.ts +20 -25
- package/dist/components/dialog.js +81 -91
- package/dist/components/field.d.ts +8 -6
- package/dist/components/field.js +16 -18
- package/dist/components/form.d.ts +3 -3
- package/dist/components/form.js +4 -4
- package/dist/components/main.d.ts +41 -14
- package/dist/components/main.js +193 -53
- package/dist/components/menu.d.ts +118 -0
- package/dist/components/menu.js +218 -0
- package/dist/components/select.d.ts +1 -1
- package/dist/components/select.js +5 -5
- package/dist/components/tabs.d.ts +5 -5
- package/dist/components/tabs.js +15 -22
- package/dist/components/textarea.js +4 -4
- package/dist/components/textline.js +1 -1
- package/dist/components/toast.d.ts +37 -0
- package/dist/components/toast.js +79 -0
- package/dist/components/tooltip.d.ts +32 -0
- package/dist/components/tooltip.js +130 -0
- package/dist/core.d.ts +26 -39
- package/dist/core.js +6 -5
- package/dist/icons-helpers.d.ts +46 -0
- package/dist/icons-helpers.js +44 -0
- package/dist/icons.d.ts +1960 -0
- package/dist/icons.js +1972 -0
- package/dist/index.d.ts +21 -8
- package/dist/index.js +17 -8
- package/dist/staffa.esm.js +1 -0
- package/dist/theme.d.ts +9 -75
- package/dist/theme.js +279 -82
- package/package.json +12 -5
- package/src/components/autocomplete.ts +21 -20
- package/src/components/box.ts +21 -15
- package/src/components/button.ts +59 -75
- package/src/components/buttonChooser.ts +65 -0
- package/src/components/buttonGroup.ts +18 -21
- package/src/components/checkbox.ts +7 -7
- package/src/components/dialog.ts +101 -102
- package/src/components/field.ts +22 -22
- package/src/components/form.ts +7 -7
- package/src/components/main.ts +212 -52
- package/src/components/menu.ts +288 -0
- package/src/components/select.ts +4 -4
- package/src/components/tabs.ts +20 -27
- package/src/components/textarea.ts +4 -4
- package/src/components/textline.ts +1 -1
- package/src/components/toast.ts +115 -0
- package/src/components/tooltip.ts +139 -0
- package/src/core.ts +26 -40
- package/src/icons-helpers.ts +90 -0
- package/src/icons.ts +1977 -0
- package/src/index.ts +21 -8
- package/src/theme.ts +300 -135
- package/dist/components/modal.d.ts +0 -2
- package/dist/components/modal.js +0 -2
- package/dist/skye.esm.js +0 -1
package/src/components/button.ts
CHANGED
|
@@ -1,33 +1,8 @@
|
|
|
1
1
|
import A from "aberdeen";
|
|
2
|
-
import { type
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Visual weight of a button.
|
|
6
|
-
* - `filled`: solid background, highest emphasis.
|
|
7
|
-
* - `tonal`: soft tinted background, medium emphasis.
|
|
8
|
-
* - `outlined`: bordered, transparent background, lowest emphasis.
|
|
9
|
-
*
|
|
10
|
-
* Every variant carries at least a visible border, per Staffa's "everything is
|
|
11
|
-
* legible at a glance" principle.
|
|
12
|
-
*/
|
|
13
|
-
export type ButtonVariant = "filled" | "tonal" | "outlined";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Color of a button.
|
|
17
|
-
*
|
|
18
|
-
* The four named **semantic roles** map to theme colours and are offered as
|
|
19
|
-
* autocomplete suggestions. You may also pass *any* CSS colour the browser
|
|
20
|
-
* understands and it becomes the button's accent directly: a literal like
|
|
21
|
-
* `"#ef6b00"` / `"rgb(255 107 0)"`, or a theme custom-property reference like
|
|
22
|
-
* `"$sWarning"` (Aberdeen's `$name` shorthand for `var(--name)`).
|
|
23
|
-
*
|
|
24
|
-
* The `(string & {})` member is what keeps the literal suggestions visible while
|
|
25
|
-
* still allowing arbitrary strings — TypeScript only widens to `string` lazily.
|
|
26
|
-
*/
|
|
27
|
-
export type ButtonColor = "primary" | "neutral" | "danger" | "success" | (string & {});
|
|
2
|
+
import { type Content, type Slot, type Attributes, drawSlot } from "../core.js";
|
|
28
3
|
|
|
29
4
|
/** Options for {@link button}. */
|
|
30
|
-
export interface ButtonOptions
|
|
5
|
+
export interface ButtonOptions {
|
|
31
6
|
/** Button label text. */
|
|
32
7
|
text?: string;
|
|
33
8
|
/** Custom content (overrides {@link ButtonOptions.text | text}). */
|
|
@@ -36,12 +11,6 @@ export interface ButtonOptions extends BaseOptions {
|
|
|
36
11
|
icon?: Slot;
|
|
37
12
|
/** Click handler. */
|
|
38
13
|
click?: (event: Event) => void;
|
|
39
|
-
/** Visual weight. Defaults to `"filled"`. */
|
|
40
|
-
variant?: ButtonVariant;
|
|
41
|
-
/** Color role. Defaults to `"primary"`. */
|
|
42
|
-
color?: ButtonColor;
|
|
43
|
-
/** Size. Defaults to `"md"`. */
|
|
44
|
-
size?: "sm" | "md" | "lg";
|
|
45
14
|
/** Disables the button. */
|
|
46
15
|
disabled?: boolean;
|
|
47
16
|
/** Native button behaviour. Defaults to `"button"`. */
|
|
@@ -50,50 +19,76 @@ export interface ButtonOptions extends BaseOptions {
|
|
|
50
19
|
href?: string;
|
|
51
20
|
/** Accessible label, when the button has only an icon. */
|
|
52
21
|
ariaLabel?: string;
|
|
53
|
-
/**
|
|
54
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Aberdeen attr/style string applied to the button. A button is a surface, so
|
|
24
|
+
* pass surface modifier classes here to restyle it, e.g. `".danger"`,
|
|
25
|
+
* `".neutral .outlined"`. Defaults to a filled `.primary` surface.
|
|
26
|
+
*
|
|
27
|
+
* Size is set here too, with `.small` or `.large` (medium is the default and
|
|
28
|
+
* needs no class), e.g. `".danger .small"`. A `.small`/`.large` parent (such
|
|
29
|
+
* as a {@link buttonGroup}) also sizes its buttons, so you can set it once.
|
|
30
|
+
*/
|
|
31
|
+
attrs?: Attributes;
|
|
55
32
|
}
|
|
56
33
|
|
|
57
|
-
// The
|
|
58
|
-
//
|
|
34
|
+
// The button is a `.s-s` surface (defaulting to `.primary` in button() below), so
|
|
35
|
+
// its colours come from the surface classes in theme.ts. This rule only handles
|
|
36
|
+
// layout, border, focus, hover and sizing.
|
|
59
37
|
A.insertGlobalCss({
|
|
60
|
-
".
|
|
38
|
+
".s-btn": {
|
|
61
39
|
"&":
|
|
62
|
-
"--c:$sPrimary --cfg:$sPrimaryFg " +
|
|
63
40
|
"display:inline-flex align-items:center justify-content:center gap:$2 " +
|
|
64
41
|
"font-weight:600 line-height:1.2 white-space:nowrap cursor:pointer text-decoration:none " +
|
|
65
|
-
"border: 1px solid
|
|
66
|
-
"transition: background 0.15s, border-color 0.15s, filter 0.15s, box-shadow 0.15s;",
|
|
67
|
-
"&:focus-visible": "outline:none box-shadow: 0 0 0 3px $
|
|
42
|
+
"border: 1px solid $s-border; r: $s-radius; padding: 0.5em 1em; " +
|
|
43
|
+
"transition: background 0.15s, border-color 0.15s, color 0.15s, filter 0.15s, box-shadow 0.15s, transform 0.08s;",
|
|
44
|
+
"&:focus-visible": "outline:none box-shadow: 0 0 0 3px $s-focus;",
|
|
68
45
|
"&:disabled, &[aria-disabled=true]": "opacity:0.45 cursor:not-allowed pointer-events:none filter:saturate(0.6)",
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
"&.
|
|
82
|
-
"&.
|
|
46
|
+
// Every button lifts a little toward the cursor on hover (the transform is in
|
|
47
|
+
// the transition list above). The filled `.gradient` CTA below layers a deeper
|
|
48
|
+
// shadow on top of the same lift, so it still reads as the signature action.
|
|
49
|
+
"&:hover": "filter: brightness(1.08); transform: translateY(-1px)",
|
|
50
|
+
"&.tonal:hover, &.outlined:hover": "background: color-mix(in srgb, $s-b 26%, transparent);",
|
|
51
|
+
// A filled `.gradient` button (the default) is the app's signature call to
|
|
52
|
+
// action: a borderless gradient with a soft glow that lifts on hover. The
|
|
53
|
+
// gradient fill itself comes from the `.s-s.gradient` surface rule in theme.ts.
|
|
54
|
+
// No border: a filled gradient reads as one solid shape. Dropping the border
|
|
55
|
+
// (rather than making it transparent) also sidesteps a Chromium artifact where
|
|
56
|
+
// a gradient clipped to a transparent rounded border fringes the edge with the
|
|
57
|
+
// gradient's far colour.
|
|
58
|
+
"&.gradient:not(.tonal):not(.outlined)": "border:0 box-shadow: $s-glow;",
|
|
59
|
+
"&.gradient:not(.tonal):not(.outlined):hover":
|
|
60
|
+
"filter: brightness(1.06); box-shadow: 0 10px 28px color-mix(in srgb, $s-primary 42%, transparent); transform: translateY(-1px);",
|
|
61
|
+
// Subtle press feedback.
|
|
62
|
+
"&:active:not(:disabled):not([aria-disabled=true])": "transform: translateY(1px)",
|
|
63
|
+
// Size: set on the button itself, or inherited from a `.small`/`.large`
|
|
64
|
+
// parent (e.g. a buttonGroup), so a container can size all its buttons at once.
|
|
65
|
+
"&.small, .small > &": "padding: 0.32em 0.7em; font-size:0.85em",
|
|
66
|
+
"&.large, .large > &": "padding: 0.66em 1.3em; font-size:1.1em",
|
|
83
67
|
},
|
|
84
68
|
});
|
|
85
69
|
|
|
70
|
+
// Surface-role classes a caller may pass in `attrs`. When one is present we skip
|
|
71
|
+
// the default `.gradient` base so the two roles don't stack on one element.
|
|
72
|
+
const ROLE_CLASS = /\.(gradient|primary|secondary|neutral|danger|success|warning|base|panel|raised)(\.|\s|$)/;
|
|
73
|
+
|
|
86
74
|
/**
|
|
87
75
|
* A button. Always carries at least a visible border so its affordance is
|
|
88
|
-
* obvious at a glance
|
|
76
|
+
* obvious at a glance.
|
|
89
77
|
*
|
|
90
78
|
* Shortcut: pass a string to use it as the label, or a function for custom
|
|
91
79
|
* content.
|
|
92
80
|
*
|
|
81
|
+
* **Tip:** pair `href` with Aberdeen's `interceptLinks()` (called once at app
|
|
82
|
+
* startup) for SPA-style navigation without manual click handlers:
|
|
83
|
+
* ```ts
|
|
84
|
+
* interceptLinks(); // once at root
|
|
85
|
+
* S.button({ href: "/dashboard", text: "Dashboard" }); // navigates via router
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
93
88
|
* @example
|
|
94
89
|
* ```ts
|
|
95
90
|
* S.button({ text: "Save", click: save });
|
|
96
|
-
* S.button({ text: "Delete",
|
|
91
|
+
* S.button({ text: "Delete", attrs: ".danger .outlined", click: del });
|
|
97
92
|
* S.button("Cancel"); // shorthand for { text: "Cancel" }
|
|
98
93
|
* S.button({ href: "/docs", text: "Docs" }); // renders an <a role=button>
|
|
99
94
|
* ```
|
|
@@ -102,17 +97,13 @@ export function button(opts: ButtonOptions | string | Content = {}): void {
|
|
|
102
97
|
const o: ButtonOptions = typeof opts === "string" ? { text: opts } : typeof opts === "function" ? { content: opts } : opts;
|
|
103
98
|
|
|
104
99
|
const tag = o.href != null ? "a" : "button";
|
|
105
|
-
const variant = o.variant ?? "filled";
|
|
106
|
-
const color = o.color ?? "primary";
|
|
107
|
-
const size = o.size === "sm" || o.size === "lg" ? `.S_${o.size}` : "";
|
|
108
|
-
|
|
109
|
-
// Semantic roles select a colour class (the CSS sets `--c`/`--cfg`); any other
|
|
110
|
-
// value is a raw CSS colour we assign to `--c`, which the variant rules consume
|
|
111
|
-
// via `var(--c)`. (`primary` is the base default — its class is a no-op.)
|
|
112
|
-
const semantic = color === "primary" || color === "neutral" || color === "danger" || color === "success";
|
|
113
|
-
const colorCls = semantic ? `.S_${color}` : "";
|
|
114
100
|
|
|
115
|
-
|
|
101
|
+
// A filled `.gradient` surface by default — the signature CTA. If the caller's
|
|
102
|
+
// `attrs` already names a surface role we omit the default, so `.danger`,
|
|
103
|
+
// `.neutral .outlined`, etc. fully take over (rather than stacking two roles).
|
|
104
|
+
// A bare variant/size (`.outlined`, `.small`) keeps the gradient base.
|
|
105
|
+
const role = o.attrs && ROLE_CLASS.test(o.attrs) ? "" : ".gradient";
|
|
106
|
+
A(`${tag}.s-btn.s-s${role}`, o.attrs, () => {
|
|
116
107
|
if (o.href != null) {
|
|
117
108
|
A(`href=${o.href} role=button`);
|
|
118
109
|
if (o.disabled) A("aria-disabled=true");
|
|
@@ -127,11 +118,4 @@ export function button(opts: ButtonOptions | string | Content = {}): void {
|
|
|
127
118
|
if (o.content) o.content();
|
|
128
119
|
else if (o.text != null) A("#", o.text);
|
|
129
120
|
});
|
|
130
|
-
|
|
131
|
-
// Aberdeen's inline styler doesn't set CSS custom properties, so assign the
|
|
132
|
-
// custom accent on the element directly. A leading `$` is Aberdeen's shorthand
|
|
133
|
-
// for a CSS variable reference, so expand it to `var(--name)`.
|
|
134
|
-
if (!semantic && el instanceof HTMLElement) {
|
|
135
|
-
el.style.setProperty("--c", color.startsWith("$") ? `var(--${color.slice(1)})` : color);
|
|
136
|
-
}
|
|
137
121
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import A from "aberdeen";
|
|
2
|
+
import { type Bindable, type Attributes, type Slot } from "../core.js";
|
|
3
|
+
import { buttonGroup } from "./buttonGroup.js";
|
|
4
|
+
|
|
5
|
+
/** Options for {@link buttonChooser}. */
|
|
6
|
+
export interface ButtonChooserOptions {
|
|
7
|
+
/** Aberdeen attr/style string applied to the button group. */
|
|
8
|
+
attrs?: Attributes;
|
|
9
|
+
/**
|
|
10
|
+
* The options to display, as a plain object mapping id → display label.
|
|
11
|
+
* Buttons appear in insertion order. A label may be a plain (rich-text)
|
|
12
|
+
* string, or a draw-function for custom content such as an icon.
|
|
13
|
+
*/
|
|
14
|
+
options: Record<string, Slot>;
|
|
15
|
+
/**
|
|
16
|
+
* Two-way binding for the selected id, or `null` when nothing is selected.
|
|
17
|
+
* Use an `A.proxy` or `A.ref`.
|
|
18
|
+
*/
|
|
19
|
+
bind: Bindable<string | null>;
|
|
20
|
+
/**
|
|
21
|
+
* When `true`, clicking the already-selected button deselects it, setting
|
|
22
|
+
* `bind.value` to `null`. Useful for "none / auto" states.
|
|
23
|
+
*/
|
|
24
|
+
allowDeselect?: boolean;
|
|
25
|
+
/** Name attribute for the hidden `<input>`, enabling form submission. */
|
|
26
|
+
name?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A single-selection segmented control: an attached button group where exactly
|
|
31
|
+
* one button is active at a time. Optionally allows deselecting back to `null`.
|
|
32
|
+
*
|
|
33
|
+
* Renders a hidden `<input>` alongside (when `name` is set) so the selected
|
|
34
|
+
* value is included in native form submission.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const $view = A.proxy({ value: "day" as string | null });
|
|
39
|
+
* S.buttonChooser({
|
|
40
|
+
* options: { day: "Day", week: "Week", month: "Month" },
|
|
41
|
+
* bind: $view,
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function buttonChooser(opts: ButtonChooserOptions): void {
|
|
46
|
+
A(() => {
|
|
47
|
+
const selected = opts.bind.value;
|
|
48
|
+
buttonGroup({
|
|
49
|
+
attrs: opts.attrs,
|
|
50
|
+
buttons: Object.entries(opts.options).map(([id, label]) => ({
|
|
51
|
+
text: typeof label === "string" ? label : undefined,
|
|
52
|
+
content: typeof label === "function" ? label : undefined,
|
|
53
|
+
attrs: selected === id ? ".primary" : ".neutral .outlined",
|
|
54
|
+
click: () => {
|
|
55
|
+
opts.bind.value = (opts.allowDeselect && selected === id) ? null : id;
|
|
56
|
+
},
|
|
57
|
+
})),
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (opts.name) {
|
|
62
|
+
// Hidden input carries the value into native form submission.
|
|
63
|
+
A(() => A(`input type=hidden name=${opts.name} value=`, opts.bind.value ?? ""));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -19,23 +19,20 @@ export interface ButtonGroupOptions extends ContentOptions {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
A.insertGlobalCss({
|
|
22
|
-
".
|
|
22
|
+
".s-bgroup": {
|
|
23
23
|
"&": "display:inline-flex align-items:stretch",
|
|
24
|
-
"&.
|
|
25
|
-
"&.
|
|
26
|
-
"&.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"&.
|
|
30
|
-
"&.
|
|
31
|
-
"&.
|
|
32
|
-
"&.
|
|
33
|
-
"&.
|
|
34
|
-
"&.
|
|
35
|
-
"&.
|
|
36
|
-
"&.S_attached.S_vertical > .S_btn:last-child:not(:first-child)": "border-top-left-radius:0 border-top-right-radius:0",
|
|
37
|
-
// Keep the hovered/focused button's border above its neighbours.
|
|
38
|
-
"&.S_attached > .S_btn:hover, &.S_attached > .S_btn:focus-visible": "z-index:1",
|
|
24
|
+
"&.s-spaced": "gap:$2 flex-wrap:wrap",
|
|
25
|
+
"&.s-vertical": "flex-direction:column",
|
|
26
|
+
"&.s-attached": "gap:0",
|
|
27
|
+
"&.s-attached:not(.s-vertical) > .s-btn:not(:first-child)": "margin-left:-1px",
|
|
28
|
+
"&.s-attached:not(.s-vertical) > .s-btn:not(:first-child):not(:last-child)": "r:0",
|
|
29
|
+
"&.s-attached:not(.s-vertical) > .s-btn:first-child:not(:last-child)": "border-top-right-radius:0 border-bottom-right-radius:0",
|
|
30
|
+
"&.s-attached:not(.s-vertical) > .s-btn:last-child:not(:first-child)": "border-top-left-radius:0 border-bottom-left-radius:0",
|
|
31
|
+
"&.s-attached.s-vertical > .s-btn:not(:first-child)": "margin-top:-1px",
|
|
32
|
+
"&.s-attached.s-vertical > .s-btn:not(:first-child):not(:last-child)": "r:0",
|
|
33
|
+
"&.s-attached.s-vertical > .s-btn:first-child:not(:last-child)": "border-bottom-left-radius:0 border-bottom-right-radius:0",
|
|
34
|
+
"&.s-attached.s-vertical > .s-btn:last-child:not(:first-child)": "border-top-left-radius:0 border-top-right-radius:0",
|
|
35
|
+
"&.s-attached > .s-btn:hover, &.s-attached > .s-btn:focus-visible": "z-index:1",
|
|
39
36
|
},
|
|
40
37
|
});
|
|
41
38
|
|
|
@@ -46,17 +43,17 @@ A.insertGlobalCss({
|
|
|
46
43
|
* @example
|
|
47
44
|
* ```ts
|
|
48
45
|
* S.buttonGroup({ buttons: [
|
|
49
|
-
* { text: "Day",
|
|
50
|
-
* { text: "Week",
|
|
51
|
-
* { text: "Month",
|
|
46
|
+
* { text: "Day", attrs: ".neutral .outlined" },
|
|
47
|
+
* { text: "Week", attrs: ".neutral .outlined" },
|
|
48
|
+
* { text: "Month", attrs: ".neutral .outlined" },
|
|
52
49
|
* ]});
|
|
53
50
|
* ```
|
|
54
51
|
*/
|
|
55
52
|
export function buttonGroup(opts: ButtonGroupOptions = {}): void {
|
|
56
53
|
const layout = opts.layout ?? "attached";
|
|
57
|
-
const cls = `.
|
|
54
|
+
const cls = `.s-${layout}${opts.vertical ? ".s-vertical" : ""}`;
|
|
58
55
|
|
|
59
|
-
A(`div.
|
|
56
|
+
A(`div.s-bgroup${cls} role=group`, opts.attrs, () => {
|
|
60
57
|
if (opts.buttons) for (const b of opts.buttons) button(b);
|
|
61
58
|
if (opts.content) opts.content();
|
|
62
59
|
});
|
|
@@ -15,12 +15,12 @@ export interface CheckboxOptions extends Omit<FieldOptions, "label"> {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
A.insertGlobalCss({
|
|
18
|
-
".
|
|
18
|
+
".s-check": {
|
|
19
19
|
"&": "display:flex flex-direction:column gap:$1",
|
|
20
20
|
"> label": "display:flex align-items:center gap:$2 cursor:pointer user-select:none",
|
|
21
21
|
"> label:has(input:disabled)": "cursor:not-allowed opacity:0.6",
|
|
22
22
|
// Native control styled with accent-color: accessible and zero-fuss.
|
|
23
|
-
"input": "width:1.15em height:1.15em accent-color:$
|
|
23
|
+
"input": "width:1.15em height:1.15em accent-color:$s-accent cursor:inherit m:0",
|
|
24
24
|
},
|
|
25
25
|
});
|
|
26
26
|
|
|
@@ -37,9 +37,9 @@ A.insertGlobalCss({
|
|
|
37
37
|
export function checkbox(opts: CheckboxOptions = {}): void {
|
|
38
38
|
const id = opts.id ?? uniqueId("check");
|
|
39
39
|
|
|
40
|
-
A("div.
|
|
40
|
+
A("div.s-check", opts.attrs, () => {
|
|
41
41
|
A(`label for=${id}`, () => {
|
|
42
|
-
A("input type=checkbox", opts.
|
|
42
|
+
A("input type=checkbox", opts.inputAttrs, () => {
|
|
43
43
|
A(`id=${id}`);
|
|
44
44
|
if (opts.name) A(`name=${opts.name}`);
|
|
45
45
|
// `checked` is a boolean attribute: only set it when actually true.
|
|
@@ -56,15 +56,15 @@ export function checkbox(opts: CheckboxOptions = {}): void {
|
|
|
56
56
|
// Own scope so the label text/required marker don't recreate the input.
|
|
57
57
|
A(() => {
|
|
58
58
|
if (opts.label != null) drawSlot(opts.label);
|
|
59
|
-
if (opts.required) A("span.
|
|
59
|
+
if (opts.required) A("span.s-req aria-hidden=true #*");
|
|
60
60
|
});
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
A(() => {
|
|
64
|
-
if (opts.help != null && !opts.error) A("div.
|
|
64
|
+
if (opts.help != null && !opts.error) A("div.s-help", () => drawSlot(opts.help));
|
|
65
65
|
});
|
|
66
66
|
A(() => {
|
|
67
|
-
if (opts.error) A("div.
|
|
67
|
+
if (opts.error) A("div.s-error role=alert #", opts.error);
|
|
68
68
|
});
|
|
69
69
|
});
|
|
70
70
|
}
|