@fungi.computer/caps 0.6.0 → 0.7.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.
- package/dist/components/data-list.d.ts +19 -0
- package/dist/components/data-list.js +7 -0
- package/dist/components/field.d.ts +10 -0
- package/dist/components/field.js +2 -2
- package/dist/components/hold-button.js +17 -8
- package/dist/components/icon-checkbox.d.ts +24 -1
- package/dist/components/icon-checkbox.js +19 -2
- package/dist/components/icon-radio.d.ts +27 -0
- package/dist/components/icon-radio.js +9 -2
- package/dist/components/keycap.d.ts +10 -0
- package/dist/components/keycap.js +2 -2
- package/dist/form-fields.js +7 -5
- package/dist/styles.css +237 -2
- package/package.json +4 -7
- package/src/styles.css +319 -4
|
@@ -9,6 +9,25 @@ export declare const DataList: import("react").ForwardRefExoticComponent<HTMLAtt
|
|
|
9
9
|
} & import("react").RefAttributes<HTMLUListElement>>;
|
|
10
10
|
/** A list row for a caller-provided label and value. */
|
|
11
11
|
export declare const DataRow: import("react").ForwardRefExoticComponent<LiHTMLAttributes<HTMLLIElement> & import("react").RefAttributes<HTMLLIElement>>;
|
|
12
|
+
/** Props accepted by FactRow. */
|
|
13
|
+
export type FactRowProps = Omit<LiHTMLAttributes<HTMLLIElement>, "children"> & {
|
|
14
|
+
/** A 16px Sigil before the label; decorative. */
|
|
15
|
+
icon?: ReactNode;
|
|
16
|
+
label: ReactNode;
|
|
17
|
+
/** The fact; long values wrap in their own column. */
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* One details-panel fact in a compact DataList: a fixed label column (Sigil
|
|
22
|
+
* and quiet name) and a wrapping value, as in a Print's Image info.
|
|
23
|
+
*/
|
|
24
|
+
export declare const FactRow: import("react").ForwardRefExoticComponent<Omit<LiHTMLAttributes<HTMLLIElement>, "children"> & {
|
|
25
|
+
/** A 16px Sigil before the label; decorative. */
|
|
26
|
+
icon?: ReactNode;
|
|
27
|
+
label: ReactNode;
|
|
28
|
+
/** The fact; long values wrap in their own column. */
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
} & import("react").RefAttributes<HTMLLIElement>>;
|
|
12
31
|
/** Props accepted by DataListHeading. */
|
|
13
32
|
export type DataListHeadingProps = HTMLAttributes<HTMLHeadingElement> & {
|
|
14
33
|
/** Heading level; the host keeps its document outline. */
|
|
@@ -9,6 +9,13 @@ export const DataList = forwardRef(function DataList({ className, density = "reg
|
|
|
9
9
|
export const DataRow = forwardRef(function DataRow({ className, ...props }, ref) {
|
|
10
10
|
return _jsx("li", { ...props, className: cn("list-row", className), ref: ref });
|
|
11
11
|
});
|
|
12
|
+
/**
|
|
13
|
+
* One details-panel fact in a compact DataList: a fixed label column (Sigil
|
|
14
|
+
* and quiet name) and a wrapping value, as in a Print's Image info.
|
|
15
|
+
*/
|
|
16
|
+
export const FactRow = forwardRef(function FactRow({ className, icon, label, children, ...props }, ref) {
|
|
17
|
+
return (_jsxs("li", { ...props, className: cn("list-row caps-fact", className), ref: ref, children: [_jsxs("span", { className: "caps-fact-label", children: [icon ? (_jsx("span", { className: "caps-fact-icon", "aria-hidden": "true", children: icon })) : null, label] }), _jsx("span", { className: "caps-fact-value", children: children })] }));
|
|
18
|
+
});
|
|
12
19
|
/** A quiet small-caps section label that groups the rows below it. */
|
|
13
20
|
export const DataListHeading = forwardRef(function DataListHeading({ aside, children, className, level = 2, ...props }, ref) {
|
|
14
21
|
const Heading = level === 3 ? "h3" : "h2";
|
|
@@ -57,6 +57,11 @@ export declare const FormSection: import("react").ForwardRefExoticComponent<{
|
|
|
57
57
|
/** Props accepted by FieldSet. */
|
|
58
58
|
export type FieldSetProps = {
|
|
59
59
|
legend: ReactNode;
|
|
60
|
+
/**
|
|
61
|
+
* A decorative Sigil or App icon before the legend, as a Field's `icon`
|
|
62
|
+
* marks its label. Hidden from assistive tech.
|
|
63
|
+
*/
|
|
64
|
+
icon?: ReactNode;
|
|
60
65
|
/** Full legend on hover when a columns form clips it to one line. */
|
|
61
66
|
legendTitle?: string;
|
|
62
67
|
description?: ReactNode;
|
|
@@ -72,6 +77,11 @@ export type FieldSetProps = {
|
|
|
72
77
|
*/
|
|
73
78
|
export declare const FieldSet: import("react").ForwardRefExoticComponent<{
|
|
74
79
|
legend: ReactNode;
|
|
80
|
+
/**
|
|
81
|
+
* A decorative Sigil or App icon before the legend, as a Field's `icon`
|
|
82
|
+
* marks its label. Hidden from assistive tech.
|
|
83
|
+
*/
|
|
84
|
+
icon?: ReactNode;
|
|
75
85
|
/** Full legend on hover when a columns form clips it to one line. */
|
|
76
86
|
legendTitle?: string;
|
|
77
87
|
description?: ReactNode;
|
package/dist/components/field.js
CHANGED
|
@@ -39,7 +39,7 @@ export const FormSection = forwardRef(function FormSection({ children, className
|
|
|
39
39
|
* A legend-named group of related choices, such as a list of inline
|
|
40
40
|
* checkbox Fields. `disabled` disables every control inside.
|
|
41
41
|
*/
|
|
42
|
-
export const FieldSet = forwardRef(function FieldSet({ children, className, description, error, layout, legend, legendTitle, ...props }, ref) {
|
|
42
|
+
export const FieldSet = forwardRef(function FieldSet({ children, className, description, error, icon, layout, legend, legendTitle, ...props }, ref) {
|
|
43
43
|
const id = useId();
|
|
44
44
|
const describedBy = [
|
|
45
45
|
description ? `${id}-description` : props["aria-describedby"],
|
|
@@ -47,7 +47,7 @@ export const FieldSet = forwardRef(function FieldSet({ children, className, desc
|
|
|
47
47
|
]
|
|
48
48
|
.filter(Boolean)
|
|
49
49
|
.join(" ") || undefined;
|
|
50
|
-
return (_jsxs("fieldset", { ...props, "aria-describedby": describedBy, "aria-invalid": error ? true : props["aria-invalid"], className: cn("caps-fieldset", className), "data-layout": layout, ref: ref, children: [
|
|
50
|
+
return (_jsxs("fieldset", { ...props, "aria-describedby": describedBy, "aria-invalid": error ? true : props["aria-invalid"], className: cn("caps-fieldset", className), "data-layout": layout, ref: ref, children: [_jsxs("legend", { className: "caps-fieldset-legend", title: legendTitle, children: [icon ? (_jsx("span", { className: "caps-field-icon", "aria-hidden": "true", children: icon })) : null, legend] }), description ? (_jsx("p", { className: "caps-fieldset-description", id: `${id}-description`, children: description })) : null, _jsx("div", { className: "caps-fieldset-fields", children: children }), error === undefined ? null : (_jsx(FieldError, { id: `${id}-error`, children: error }))] }));
|
|
51
51
|
});
|
|
52
52
|
/** The labelled control inside an InputGroup: its first non-addon element. */
|
|
53
53
|
function groupControl(group) {
|
|
@@ -14,6 +14,7 @@ export const HoldButton = forwardRef(function HoldButton({ children, onConfirm,
|
|
|
14
14
|
const fill = useRef(null);
|
|
15
15
|
const rising = useRef(null);
|
|
16
16
|
const timer = useRef(null);
|
|
17
|
+
const holder = useRef(null);
|
|
17
18
|
const confirm = useRef(onConfirm);
|
|
18
19
|
const hint = useId();
|
|
19
20
|
useEffect(() => {
|
|
@@ -27,6 +28,7 @@ export const HoldButton = forwardRef(function HoldButton({ children, onConfirm,
|
|
|
27
28
|
const finish = () => {
|
|
28
29
|
rising.current = null;
|
|
29
30
|
timer.current = null;
|
|
31
|
+
holder.current = null;
|
|
30
32
|
confirm.current();
|
|
31
33
|
const element = fill.current;
|
|
32
34
|
if (!element || !canAnimate(element))
|
|
@@ -37,7 +39,7 @@ export const HoldButton = forwardRef(function HoldButton({ children, onConfirm,
|
|
|
37
39
|
easing: motionEasing(motion, motion.easeExit),
|
|
38
40
|
});
|
|
39
41
|
};
|
|
40
|
-
const start = () => {
|
|
42
|
+
const start = (by) => {
|
|
41
43
|
const element = fill.current;
|
|
42
44
|
if (disabled === true ||
|
|
43
45
|
loading ||
|
|
@@ -45,6 +47,7 @@ export const HoldButton = forwardRef(function HoldButton({ children, onConfirm,
|
|
|
45
47
|
rising.current ||
|
|
46
48
|
timer.current)
|
|
47
49
|
return;
|
|
50
|
+
holder.current = by;
|
|
48
51
|
if (!canAnimate(element)) {
|
|
49
52
|
timer.current = setTimeout(finish, holdMs);
|
|
50
53
|
return;
|
|
@@ -59,7 +62,13 @@ export const HoldButton = forwardRef(function HoldButton({ children, onConfirm,
|
|
|
59
62
|
animation.addEventListener("finish", finish);
|
|
60
63
|
rising.current = animation;
|
|
61
64
|
};
|
|
62
|
-
|
|
65
|
+
// Each input ends only the hold it started: a mouse leaving the button
|
|
66
|
+
// (or the button moving away from a resting mouse) never cancels a held
|
|
67
|
+
// key. Blur and Escape end any hold.
|
|
68
|
+
const release = (by) => {
|
|
69
|
+
if (by !== undefined && holder.current !== by)
|
|
70
|
+
return;
|
|
71
|
+
holder.current = null;
|
|
63
72
|
if (timer.current !== null) {
|
|
64
73
|
clearTimeout(timer.current);
|
|
65
74
|
timer.current = null;
|
|
@@ -86,16 +95,16 @@ export const HoldButton = forwardRef(function HoldButton({ children, onConfirm,
|
|
|
86
95
|
}, onPointerDown: (event) => {
|
|
87
96
|
onPointerDown?.(event);
|
|
88
97
|
if (event.button === 0)
|
|
89
|
-
start();
|
|
98
|
+
start(event.pointerId);
|
|
90
99
|
}, onPointerUp: (event) => {
|
|
91
100
|
onPointerUp?.(event);
|
|
92
|
-
release();
|
|
101
|
+
release(event.pointerId);
|
|
93
102
|
}, onPointerLeave: (event) => {
|
|
94
103
|
onPointerLeave?.(event);
|
|
95
|
-
release();
|
|
104
|
+
release(event.pointerId);
|
|
96
105
|
}, onPointerCancel: (event) => {
|
|
97
106
|
onPointerCancel?.(event);
|
|
98
|
-
release();
|
|
107
|
+
release(event.pointerId);
|
|
99
108
|
}, onKeyDown: (event) => {
|
|
100
109
|
onKeyDown?.(event);
|
|
101
110
|
if (event.key === "Escape")
|
|
@@ -104,12 +113,12 @@ export const HoldButton = forwardRef(function HoldButton({ children, onConfirm,
|
|
|
104
113
|
return;
|
|
105
114
|
event.preventDefault();
|
|
106
115
|
if (!event.repeat)
|
|
107
|
-
start();
|
|
116
|
+
start("key");
|
|
108
117
|
}, onKeyUp: (event) => {
|
|
109
118
|
onKeyUp?.(event);
|
|
110
119
|
if (!confirmKeys.has(event.key))
|
|
111
120
|
return;
|
|
112
121
|
event.preventDefault();
|
|
113
|
-
release();
|
|
122
|
+
release("key");
|
|
114
123
|
}, children: [_jsx("span", { ref: fill, "aria-hidden": "true", className: "caps-hold-fill" }), children] }), _jsx(VisuallyHidden, { id: hint, children: holdHint })] }));
|
|
115
124
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ReactNode } from "react";
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from "react";
|
|
2
2
|
import { type CheckboxProps } from "./checkbox.js";
|
|
3
3
|
/** Props accepted by IconCheckbox. The full label names the native input. */
|
|
4
4
|
export type IconCheckboxProps = {
|
|
@@ -18,3 +18,26 @@ export declare const IconCheckbox: import("react").ForwardRefExoticComponent<{
|
|
|
18
18
|
/** Compact instruments show the icon and caption without a tile or lamp. */
|
|
19
19
|
appearance?: "default" | "compact";
|
|
20
20
|
} & Omit<CheckboxProps, "children" | "indicator"> & import("react").RefAttributes<HTMLInputElement>>;
|
|
21
|
+
/** Props accepted by CheckKey. The label names the native input. */
|
|
22
|
+
export type CheckKeyProps = {
|
|
23
|
+
label: string;
|
|
24
|
+
/** A Sigil or App icon before the label; decorative. */
|
|
25
|
+
icon?: ReactNode;
|
|
26
|
+
/** One quiet line under the label, read as the input's description. */
|
|
27
|
+
hint?: ReactNode;
|
|
28
|
+
} & Omit<CheckboxProps, "children" | "indicator" | "appearance">;
|
|
29
|
+
/**
|
|
30
|
+
* One choice in a short multi-select, drawn as a wide Caps key: Sigil, label
|
|
31
|
+
* and an optional hint, a lit lamp and selected face when checked. The native
|
|
32
|
+
* checkbox covers the whole key, so it keeps form submission, keyboard and
|
|
33
|
+
* focus behaviour.
|
|
34
|
+
*/
|
|
35
|
+
export declare const CheckKey: import("react").ForwardRefExoticComponent<{
|
|
36
|
+
label: string;
|
|
37
|
+
/** A Sigil or App icon before the label; decorative. */
|
|
38
|
+
icon?: ReactNode;
|
|
39
|
+
/** One quiet line under the label, read as the input's description. */
|
|
40
|
+
hint?: ReactNode;
|
|
41
|
+
} & Omit<CheckboxProps, "children" | "appearance" | "indicator"> & import("react").RefAttributes<HTMLInputElement>>;
|
|
42
|
+
/** Lays out a group of CheckKeys: a wrapping grid of equal keys. */
|
|
43
|
+
export declare function CheckKeys({ className, ...props }: HTMLAttributes<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef } from "react";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useId } from "react";
|
|
3
3
|
import { cn } from "../lib/cn.js";
|
|
4
4
|
import { Checkbox } from "./checkbox.js";
|
|
5
5
|
import { IconChoiceMark } from "./icon-choice-mark.js";
|
|
@@ -7,3 +7,20 @@ import { IconChoiceMark } from "./icon-choice-mark.js";
|
|
|
7
7
|
export const IconCheckbox = forwardRef(function IconCheckbox({ label, icon, caption, appearance, className, ...props }, ref) {
|
|
8
8
|
return (_jsx("label", { className: "caps-icon-choice", "data-appearance": appearance === "compact" ? appearance : undefined, children: _jsx(Checkbox, { ...props, appearance: appearance ?? "default", "aria-label": label, className: cn("caps-icon-choice-input", className), indicator: _jsx(IconChoiceMark, { icon: icon, caption: caption }), ref: ref }) }));
|
|
9
9
|
});
|
|
10
|
+
/**
|
|
11
|
+
* One choice in a short multi-select, drawn as a wide Caps key: Sigil, label
|
|
12
|
+
* and an optional hint, a lit lamp and selected face when checked. The native
|
|
13
|
+
* checkbox covers the whole key, so it keeps form submission, keyboard and
|
|
14
|
+
* focus behaviour.
|
|
15
|
+
*/
|
|
16
|
+
export const CheckKey = forwardRef(function CheckKey({ label, icon, hint, className, ...props }, ref) {
|
|
17
|
+
const hintId = useId();
|
|
18
|
+
const describedBy = [props["aria-describedby"], hint ? hintId : null]
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.join(" ") || undefined;
|
|
21
|
+
return (_jsx("label", { className: "caps-check-key", children: _jsx(Checkbox, { ...props, "aria-label": label, "aria-describedby": describedBy, className: cn("caps-check-key-input", className), indicator: _jsxs("span", { className: "caps-check-key-face", "aria-hidden": "true", children: [icon ? (_jsx("span", { className: "caps-check-key-icon", children: icon })) : null, _jsxs("span", { className: "caps-check-key-copy", children: [_jsx("span", { className: "caps-check-key-label", children: label }), hint ? (_jsx("span", { className: "caps-check-key-hint", id: hintId, children: hint })) : null] }), _jsx("span", { className: "caps-check-key-lamp" })] }), ref: ref }) }));
|
|
22
|
+
});
|
|
23
|
+
/** Lays out a group of CheckKeys: a wrapping grid of equal keys. */
|
|
24
|
+
export function CheckKeys({ className, ...props }) {
|
|
25
|
+
return _jsx("div", { ...props, className: cn("caps-check-keys", className) });
|
|
26
|
+
}
|
|
@@ -6,6 +6,11 @@ export type IconRadioProps = {
|
|
|
6
6
|
icon: ReactNode;
|
|
7
7
|
/** A short visible legend; it does not replace the accessible label. */
|
|
8
8
|
caption?: string;
|
|
9
|
+
/**
|
|
10
|
+
* `color` keeps an off key's art in full colour, for choices whose colour
|
|
11
|
+
* is the information (a character, a palette). Icons dim by default.
|
|
12
|
+
*/
|
|
13
|
+
art?: "dim" | "color";
|
|
9
14
|
} & Omit<RadioGroupItemProps, "children" | "indicator">;
|
|
10
15
|
/** One icon choice in a native, mutually exclusive Caps RadioGroup. */
|
|
11
16
|
export declare const IconRadio: import("react").ForwardRefExoticComponent<{
|
|
@@ -13,4 +18,26 @@ export declare const IconRadio: import("react").ForwardRefExoticComponent<{
|
|
|
13
18
|
icon: ReactNode;
|
|
14
19
|
/** A short visible legend; it does not replace the accessible label. */
|
|
15
20
|
caption?: string;
|
|
21
|
+
/**
|
|
22
|
+
* `color` keeps an off key's art in full colour, for choices whose colour
|
|
23
|
+
* is the information (a character, a palette). Icons dim by default.
|
|
24
|
+
*/
|
|
25
|
+
art?: "dim" | "color";
|
|
26
|
+
} & Omit<RadioGroupItemProps, "children" | "indicator"> & import("react").RefAttributes<HTMLInputElement>>;
|
|
27
|
+
/** Props accepted by SwatchRadio. Use it inside the existing RadioGroup. */
|
|
28
|
+
export type SwatchRadioProps = {
|
|
29
|
+
/** The colour's name: the accessible label and the hover title. */
|
|
30
|
+
label: string;
|
|
31
|
+
/** Any CSS colour, drawn as the key's dot. */
|
|
32
|
+
color: string;
|
|
33
|
+
} & Omit<RadioGroupItemProps, "children" | "indicator">;
|
|
34
|
+
/**
|
|
35
|
+
* One colour choice: a small icon key whose dot keeps its colour while off,
|
|
36
|
+
* so every choice reads before it is picked. The lamp marks the choice.
|
|
37
|
+
*/
|
|
38
|
+
export declare const SwatchRadio: import("react").ForwardRefExoticComponent<{
|
|
39
|
+
/** The colour's name: the accessible label and the hover title. */
|
|
40
|
+
label: string;
|
|
41
|
+
/** Any CSS colour, drawn as the key's dot. */
|
|
42
|
+
color: string;
|
|
16
43
|
} & Omit<RadioGroupItemProps, "children" | "indicator"> & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -4,6 +4,13 @@ import { cn } from "../lib/cn.js";
|
|
|
4
4
|
import { RadioGroupItem } from "./radio-group.js";
|
|
5
5
|
import { IconChoiceMark } from "./icon-choice-mark.js";
|
|
6
6
|
/** One icon choice in a native, mutually exclusive Caps RadioGroup. */
|
|
7
|
-
export const IconRadio = forwardRef(function IconRadio({ label, icon, caption, className, ...props }, ref) {
|
|
8
|
-
return (_jsx("label", { className: "caps-icon-choice", children: _jsx(RadioGroupItem, { ...props, "aria-label": label, className: cn("caps-icon-choice-input", className), indicator: _jsx(IconChoiceMark, { icon: icon, caption: caption }), ref: ref }) }));
|
|
7
|
+
export const IconRadio = forwardRef(function IconRadio({ label, icon, caption, art = "dim", className, ...props }, ref) {
|
|
8
|
+
return (_jsx("label", { className: "caps-icon-choice", "data-art": art === "color" ? art : undefined, children: _jsx(RadioGroupItem, { ...props, "aria-label": label, className: cn("caps-icon-choice-input", className), indicator: _jsx(IconChoiceMark, { icon: icon, caption: caption }), ref: ref }) }));
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* One colour choice: a small icon key whose dot keeps its colour while off,
|
|
12
|
+
* so every choice reads before it is picked. The lamp marks the choice.
|
|
13
|
+
*/
|
|
14
|
+
export const SwatchRadio = forwardRef(function SwatchRadio({ label, color, className, ...props }, ref) {
|
|
15
|
+
return (_jsx("label", { className: "caps-icon-choice", "data-art": "color", "data-swatch": "", title: label, children: _jsx(RadioGroupItem, { ...props, "aria-label": label, className: cn("caps-icon-choice-input", className), indicator: _jsx(IconChoiceMark, { icon: _jsx("span", { className: "caps-swatch-dot", style: { backgroundColor: color } }) }), ref: ref }) }));
|
|
9
16
|
});
|
|
@@ -37,6 +37,11 @@ export type KeycapButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "c
|
|
|
37
37
|
/** Extra readout after the label, such as a PixelNumber count. */
|
|
38
38
|
readout?: ReactNode;
|
|
39
39
|
tone?: KeycapTone;
|
|
40
|
+
/**
|
|
41
|
+
* `compact` is the details-panel key: 2.25rem high, centred `sm` label.
|
|
42
|
+
* It sizes to its legend in a row and fills its cell in a grid.
|
|
43
|
+
*/
|
|
44
|
+
density?: "regular" | "compact";
|
|
40
45
|
/**
|
|
41
46
|
* Toggle state for mode keys. `undefined` is a momentary key; a boolean adds
|
|
42
47
|
* `aria-pressed` and lights the key's indicator.
|
|
@@ -66,6 +71,11 @@ export declare const KeycapButton: import("react").ForwardRefExoticComponent<Omi
|
|
|
66
71
|
/** Extra readout after the label, such as a PixelNumber count. */
|
|
67
72
|
readout?: ReactNode;
|
|
68
73
|
tone?: KeycapTone;
|
|
74
|
+
/**
|
|
75
|
+
* `compact` is the details-panel key: 2.25rem high, centred `sm` label.
|
|
76
|
+
* It sizes to its legend in a row and fills its cell in a grid.
|
|
77
|
+
*/
|
|
78
|
+
density?: "regular" | "compact";
|
|
69
79
|
/**
|
|
70
80
|
* Toggle state for mode keys. `undefined` is a momentary key; a boolean adds
|
|
71
81
|
* `aria-pressed` and lights the key's indicator.
|
|
@@ -12,7 +12,7 @@ export const Keycap = forwardRef(function Keycap({ tone = "neutral", size = "md"
|
|
|
12
12
|
* A chunky action key for game docks and toy panels. The host owns the action
|
|
13
13
|
* and the keyboard binding; `shortcut` only labels it.
|
|
14
14
|
*/
|
|
15
|
-
export const KeycapButton = forwardRef(function KeycapButton({ label, shortcut, icon, readout, tone = "neutral", pressed, expanded, className, type = "button", ...props }, ref) {
|
|
15
|
+
export const KeycapButton = forwardRef(function KeycapButton({ label, shortcut, icon, readout, tone = "neutral", density = "regular", pressed, expanded, className, type = "button", ...props }, ref) {
|
|
16
16
|
const lamp = pressed ?? expanded;
|
|
17
|
-
return (_jsxs("button", { ...props, ref: ref, type: type, className: cn("caps-keycap-button", className), "data-tone": tone, "aria-pressed": pressed, "aria-expanded": expanded, "aria-keyshortcuts": props["aria-keyshortcuts"] ?? shortcut, children: [lamp === undefined ? null : (_jsx("span", { className: "caps-keycap-led", "data-lit": lamp, "aria-hidden": "true" })), icon ? (_jsx("span", { className: "caps-keycap-button-icon", "aria-hidden": "true", children: icon })) : null, label === null ? null : (_jsx("span", { className: "caps-keycap-button-label", children: label })), readout, shortcut ? (_jsx(Keycap, { size: "sm", tone: "neutral", "aria-hidden": "true", children: shortcut })) : null] }));
|
|
17
|
+
return (_jsxs("button", { ...props, ref: ref, type: type, className: cn("caps-keycap-button", className), "data-tone": tone, "data-density": density === "compact" ? density : undefined, "aria-pressed": pressed, "aria-expanded": expanded, "aria-keyshortcuts": props["aria-keyshortcuts"] ?? shortcut, children: [lamp === undefined ? null : (_jsx("span", { className: "caps-keycap-led", "data-lit": lamp, "aria-hidden": "true" })), icon ? (_jsx("span", { className: "caps-keycap-button-icon", "aria-hidden": "true", children: icon })) : null, label === null ? null : (_jsx("span", { className: "caps-keycap-button-label", children: label })), readout, shortcut ? (_jsx(Keycap, { size: "sm", tone: "neutral", "aria-hidden": "true", children: shortcut })) : null] }));
|
|
18
18
|
});
|
package/dist/form-fields.js
CHANGED
|
@@ -8,7 +8,7 @@ import { UploadQueue } from "./blocks/upload-queue.js";
|
|
|
8
8
|
import { Checkbox } from "./components/checkbox.js";
|
|
9
9
|
import { Combobox, ComboboxTrigger } from "./components/combobox.js";
|
|
10
10
|
import { Field, FieldSet, FormSection, NumberField, } from "./components/field.js";
|
|
11
|
-
import { IconCheckbox } from "./components/icon-checkbox.js";
|
|
11
|
+
import { CheckKey, CheckKeys, IconCheckbox, } from "./components/icon-checkbox.js";
|
|
12
12
|
import { IconRadio } from "./components/icon-radio.js";
|
|
13
13
|
import { Input } from "./components/input.js";
|
|
14
14
|
import { InputGroup, InputGroupAddon } from "./components/input-group.js";
|
|
@@ -169,16 +169,18 @@ function SpecGroup({ spec, field, locked, error, layout, }) {
|
|
|
169
169
|
? [...selected, value]
|
|
170
170
|
: selected.filter((current) => current !== value));
|
|
171
171
|
};
|
|
172
|
-
return (_jsx(FieldSet, { legend: legend, layout: layout, ...(layout === "columns" ? { legendTitle: legend } : {}), description: spec.help, disabled: disabled, error: error ?? null, children: spec.kind === "icon-checkboxes" ? (_jsx("div", { className: "caps-field-icon-choices", children: spec.options.map((option) => (_jsx(IconCheckbox, { name: spec.name, value: option.value, label: option.label, ...(option.caption === undefined
|
|
172
|
+
return (_jsx(FieldSet, { legend: legend, icon: spec.icon, layout: layout, ...(layout === "columns" ? { legendTitle: legend } : {}), description: spec.help, disabled: disabled, error: error ?? null, children: spec.kind === "icon-checkboxes" ? (_jsx("div", { className: "caps-field-icon-choices", children: spec.options.map((option) => (_jsx(IconCheckbox, { name: spec.name, value: option.value, label: option.label, ...(option.caption === undefined
|
|
173
173
|
? {}
|
|
174
174
|
: { caption: option.caption }), icon: option.icon, disabled: option.disabled, checked: selected.includes(option.value), onBlur: field.handleBlur, onChange: (event) => {
|
|
175
175
|
toggle(option.value, event.currentTarget.checked);
|
|
176
|
-
} }, option.value))) })) : (
|
|
176
|
+
} }, option.value))) })) : (
|
|
177
|
+
// A short multi-select reads as one row of check keys.
|
|
178
|
+
_jsx(CheckKeys, { children: spec.options.map((option) => (_jsx(CheckKey, { name: spec.name, value: option.value, label: option.label, icon: option.icon, hint: option.description, disabled: option.disabled, checked: selected.includes(option.value), onBlur: field.handleBlur, onChange: (event) => {
|
|
177
179
|
toggle(option.value, event.currentTarget.checked);
|
|
178
|
-
} }
|
|
180
|
+
} }, option.value))) })) }));
|
|
179
181
|
}
|
|
180
182
|
const cards = spec.options.some((option) => option.description !== undefined || option.meta !== undefined);
|
|
181
|
-
return (_jsx(FieldSet, { legend: legend, layout: layout, ...(layout === "columns" ? { legendTitle: legend } : {}), description: spec.help, disabled: disabled, error: error ?? null, children: _jsx(RadioGroup, { name: spec.name, "aria-label": legend, value: text(field), onValueChange: (next) => {
|
|
183
|
+
return (_jsx(FieldSet, { legend: legend, icon: spec.icon, layout: layout, ...(layout === "columns" ? { legendTitle: legend } : {}), description: spec.help, disabled: disabled, error: error ?? null, children: _jsx(RadioGroup, { name: spec.name, "aria-label": legend, value: text(field), onValueChange: (next) => {
|
|
182
184
|
field.handleChange(next);
|
|
183
185
|
field.handleBlur();
|
|
184
186
|
},
|
package/dist/styles.css
CHANGED
|
@@ -12905,6 +12905,19 @@
|
|
|
12905
12905
|
.list-row > .caps-field {
|
|
12906
12906
|
grid-column: 1 / -1;
|
|
12907
12907
|
}
|
|
12908
|
+
.list.list-sm > .list-row > .caps-field {
|
|
12909
|
+
--caps-field-icon-size: 1rem;
|
|
12910
|
+
}
|
|
12911
|
+
.list.list-sm > .list-row > .caps-field .caps-field-lead {
|
|
12912
|
+
align-items: flex-start;
|
|
12913
|
+
gap: var(--spacing-xs);
|
|
12914
|
+
}
|
|
12915
|
+
.list.list-sm > .list-row > .caps-field .caps-field-icon {
|
|
12916
|
+
height: calc(var(--caps-control-label-size, var(--text-sm)) * 1.5);
|
|
12917
|
+
}
|
|
12918
|
+
.list.list-sm > .list-row > .caps-field:has(> .caps-field-row > .caps-field-lead) > .caps-field-error-reveal {
|
|
12919
|
+
padding-inline-start: calc(var(--caps-field-icon-size) + var(--spacing-xs));
|
|
12920
|
+
}
|
|
12908
12921
|
.caps-field:has(> .caps-field-row > .caps-field-lead) > .caps-field-error-reveal {
|
|
12909
12922
|
padding-inline-start: calc(var(--caps-field-icon-size) + var(--spacing-sm));
|
|
12910
12923
|
}
|
|
@@ -12939,6 +12952,15 @@
|
|
|
12939
12952
|
line-height: 1.5;
|
|
12940
12953
|
color: var(--color-base-content);
|
|
12941
12954
|
}
|
|
12955
|
+
.caps-fieldset-legend:has(> .caps-field-icon) {
|
|
12956
|
+
display: flex;
|
|
12957
|
+
align-items: center;
|
|
12958
|
+
gap: var(--spacing-xs);
|
|
12959
|
+
}
|
|
12960
|
+
.caps-fieldset-legend > .caps-field-icon {
|
|
12961
|
+
width: 1rem;
|
|
12962
|
+
height: 1rem;
|
|
12963
|
+
}
|
|
12942
12964
|
.caps-fieldset-description {
|
|
12943
12965
|
margin: 0;
|
|
12944
12966
|
font-size: var(--text-xs);
|
|
@@ -13056,7 +13078,7 @@
|
|
|
13056
13078
|
padding-bottom: var(--spacing-sm);
|
|
13057
13079
|
}
|
|
13058
13080
|
:is(.caps-form, .caps-form-section, .caps-fieldset)[data-layout="columns"] {
|
|
13059
|
-
--caps-form-label-col:
|
|
13081
|
+
--caps-form-label-col: 7rem;
|
|
13060
13082
|
}
|
|
13061
13083
|
.caps-form[data-layout="columns"] :is(.caps-form-body, .caps-form-section-fields) {
|
|
13062
13084
|
gap: var(--spacing-sm);
|
|
@@ -13135,6 +13157,13 @@
|
|
|
13135
13157
|
grid-column: 1;
|
|
13136
13158
|
grid-row: 1;
|
|
13137
13159
|
margin: 0;
|
|
13160
|
+
float: left;
|
|
13161
|
+
align-self: start;
|
|
13162
|
+
min-height: var(--caps-fieldset-legend-line, 2.25rem);
|
|
13163
|
+
align-content: center;
|
|
13164
|
+
}
|
|
13165
|
+
.caps-fieldset[data-layout="columns"]:has( .caps-icon-choice:not([data-swatch]) ) {
|
|
13166
|
+
--caps-fieldset-legend-line: 3.25rem;
|
|
13138
13167
|
}
|
|
13139
13168
|
.caps-fieldset[data-layout="columns"] > .caps-fieldset-fields {
|
|
13140
13169
|
grid-column: 2;
|
|
@@ -13142,6 +13171,7 @@
|
|
|
13142
13171
|
flex-direction: row;
|
|
13143
13172
|
flex-wrap: wrap;
|
|
13144
13173
|
margin: 0;
|
|
13174
|
+
max-width: 30rem;
|
|
13145
13175
|
}
|
|
13146
13176
|
.caps-fieldset[data-layout="columns"] > :is(.caps-fieldset-description, .caps-field-error-reveal) {
|
|
13147
13177
|
grid-column: 2;
|
|
@@ -13162,7 +13192,38 @@
|
|
|
13162
13192
|
max-width: none;
|
|
13163
13193
|
}
|
|
13164
13194
|
}
|
|
13165
|
-
|
|
13195
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"] :is( .caps-field[data-orientation="vertical"], .caps-field[data-orientation="horizontal"] > .caps-field-row ) > .caps-field-lead {
|
|
13196
|
+
display: contents;
|
|
13197
|
+
}
|
|
13198
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"] :is( .caps-field[data-orientation="vertical"], .caps-field[data-orientation="horizontal"] > .caps-field-row ) > .caps-field-lead > .caps-field-copy {
|
|
13199
|
+
display: contents;
|
|
13200
|
+
}
|
|
13201
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"] :is( .caps-field[data-orientation="vertical"], .caps-field[data-orientation="horizontal"] > .caps-field-row ) > .caps-field-lead > .caps-field-icon {
|
|
13202
|
+
grid-column: 1;
|
|
13203
|
+
grid-row: 1;
|
|
13204
|
+
justify-self: start;
|
|
13205
|
+
}
|
|
13206
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"] :is( .caps-field[data-orientation="vertical"], .caps-field[data-orientation="horizontal"] > .caps-field-row ) > .caps-field-lead > .caps-field-copy > label {
|
|
13207
|
+
grid-column: 1;
|
|
13208
|
+
grid-row: 1;
|
|
13209
|
+
min-width: 0;
|
|
13210
|
+
padding-inline-start: calc(var(--caps-field-icon-size) + var(--spacing-xs));
|
|
13211
|
+
overflow: hidden;
|
|
13212
|
+
text-overflow: ellipsis;
|
|
13213
|
+
white-space: nowrap;
|
|
13214
|
+
}
|
|
13215
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"] :is( .caps-field[data-orientation="vertical"], .caps-field[data-orientation="horizontal"] > .caps-field-row ) > .caps-field-lead > .caps-field-copy > .caps-field-description {
|
|
13216
|
+
grid-column: 2;
|
|
13217
|
+
grid-row: 2;
|
|
13218
|
+
}
|
|
13219
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"] .caps-field, .caps-fieldset[data-layout="columns"] {
|
|
13220
|
+
--caps-field-icon-size: 1rem;
|
|
13221
|
+
}
|
|
13222
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"] .caps-field :is(.caps-field-copy > label, .caps-field-icon), .caps-fieldset[data-layout="columns"] > .caps-fieldset-legend {
|
|
13223
|
+
color: var(--stipe-fg-muted);
|
|
13224
|
+
font-weight: 400;
|
|
13225
|
+
}
|
|
13226
|
+
@container (max-width: 24rem) {
|
|
13166
13227
|
:is(.caps-form, .caps-form-section)[data-layout="columns"] :is( .caps-field[data-orientation="vertical"], .caps-field[data-orientation="horizontal"] > .caps-field-row ), .caps-fieldset[data-layout="columns"] {
|
|
13167
13228
|
grid-template-columns: minmax(0, 1fr);
|
|
13168
13229
|
}
|
|
@@ -13179,11 +13240,16 @@
|
|
|
13179
13240
|
overflow: visible;
|
|
13180
13241
|
text-overflow: clip;
|
|
13181
13242
|
white-space: normal;
|
|
13243
|
+
min-height: 0;
|
|
13182
13244
|
}
|
|
13183
13245
|
:is(.caps-form, .caps-form-section)[data-layout="columns"] .caps-field[data-orientation="horizontal"] > .caps-field-error-reveal, .caps-form[data-layout="columns"] .caps-form-footer {
|
|
13184
13246
|
padding-inline-start: 0;
|
|
13185
13247
|
max-width: none;
|
|
13186
13248
|
}
|
|
13249
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"] :is( .caps-field[data-orientation="vertical"], .caps-field[data-orientation="horizontal"] > .caps-field-row ) > .caps-field-lead > .caps-field-copy > .caps-field-description {
|
|
13250
|
+
grid-column: 1;
|
|
13251
|
+
grid-row: 3;
|
|
13252
|
+
}
|
|
13187
13253
|
}
|
|
13188
13254
|
.caps-choice-card {
|
|
13189
13255
|
display: flex;
|
|
@@ -14461,6 +14527,121 @@
|
|
|
14461
14527
|
.caps-icon-choice[data-appearance="compact"] .caps-icon-choice-lamp {
|
|
14462
14528
|
display: none;
|
|
14463
14529
|
}
|
|
14530
|
+
.caps-icon-choice[data-swatch] {
|
|
14531
|
+
min-width: 2.25rem;
|
|
14532
|
+
min-height: 2.25rem;
|
|
14533
|
+
}
|
|
14534
|
+
.caps-icon-choice[data-swatch] .caps-icon-choice-mark {
|
|
14535
|
+
padding: var(--spacing-xs);
|
|
14536
|
+
}
|
|
14537
|
+
.caps-icon-choice[data-art="color"] .caps-icon-choice-art {
|
|
14538
|
+
filter: none;
|
|
14539
|
+
opacity: 1;
|
|
14540
|
+
}
|
|
14541
|
+
.caps-icon-choice[data-swatch] .caps-icon-choice-art {
|
|
14542
|
+
width: 1rem;
|
|
14543
|
+
height: 1rem;
|
|
14544
|
+
}
|
|
14545
|
+
.caps-swatch-dot {
|
|
14546
|
+
display: block;
|
|
14547
|
+
width: 100%;
|
|
14548
|
+
height: 100%;
|
|
14549
|
+
border-radius: var(--radius-full);
|
|
14550
|
+
outline: var(--border) solid var(--color-base-content);
|
|
14551
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
14552
|
+
outline: var(--border) solid color-mix(in oklab, var(--color-base-content) 20%, transparent);
|
|
14553
|
+
}
|
|
14554
|
+
outline-offset: -1px;
|
|
14555
|
+
}
|
|
14556
|
+
.caps-check-keys {
|
|
14557
|
+
flex: 1 1 100%;
|
|
14558
|
+
display: grid;
|
|
14559
|
+
grid-template-columns: repeat(auto-fill, minmax(min(100%, 9rem), 1fr));
|
|
14560
|
+
gap: var(--spacing-xs);
|
|
14561
|
+
min-width: 0;
|
|
14562
|
+
}
|
|
14563
|
+
.caps-check-keys:has(> :nth-child(7)) {
|
|
14564
|
+
grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr));
|
|
14565
|
+
}
|
|
14566
|
+
.caps-check-key {
|
|
14567
|
+
display: grid;
|
|
14568
|
+
min-width: 0;
|
|
14569
|
+
cursor: pointer;
|
|
14570
|
+
}
|
|
14571
|
+
.caps-check-key > .caps-checkbox {
|
|
14572
|
+
min-height: 2.25rem;
|
|
14573
|
+
}
|
|
14574
|
+
.caps-check-key-face {
|
|
14575
|
+
grid-area: control;
|
|
14576
|
+
position: relative;
|
|
14577
|
+
display: flex;
|
|
14578
|
+
align-items: flex-start;
|
|
14579
|
+
gap: var(--spacing-xs);
|
|
14580
|
+
min-width: 0;
|
|
14581
|
+
padding: var(--spacing-xs) var(--spacing-md) var(--spacing-xs) var(--spacing-sm);
|
|
14582
|
+
border: var(--border) solid var(--caps-control-line, var(--color-base-300));
|
|
14583
|
+
border-radius: var(--radius-field);
|
|
14584
|
+
background: var(--caps-control-face, var(--color-base-100));
|
|
14585
|
+
color: var(--caps-control-dim, var(--stipe-fg-muted));
|
|
14586
|
+
pointer-events: none;
|
|
14587
|
+
transition: border-color var(--stipe-motion-fast) var(--stipe-motion-ease), background-color var(--stipe-motion-fast) var(--stipe-motion-ease);
|
|
14588
|
+
}
|
|
14589
|
+
.caps-check-key-icon {
|
|
14590
|
+
display: inline-flex;
|
|
14591
|
+
flex: none;
|
|
14592
|
+
align-items: center;
|
|
14593
|
+
width: 1rem;
|
|
14594
|
+
height: calc(var(--text-sm) * 1.5);
|
|
14595
|
+
filter: var(--caps-control-icon-off, grayscale(1) contrast(0));
|
|
14596
|
+
opacity: 0.55;
|
|
14597
|
+
}
|
|
14598
|
+
.caps-check-key-icon > :is(svg, img) {
|
|
14599
|
+
width: 1rem;
|
|
14600
|
+
height: 1rem;
|
|
14601
|
+
}
|
|
14602
|
+
.caps-check-key-copy {
|
|
14603
|
+
display: grid;
|
|
14604
|
+
min-width: 0;
|
|
14605
|
+
}
|
|
14606
|
+
.caps-check-key-label {
|
|
14607
|
+
font-size: var(--text-sm);
|
|
14608
|
+
font-weight: 600;
|
|
14609
|
+
line-height: 1.5;
|
|
14610
|
+
overflow-wrap: break-word;
|
|
14611
|
+
}
|
|
14612
|
+
.caps-check-key-hint {
|
|
14613
|
+
font-size: var(--text-xs);
|
|
14614
|
+
line-height: 1.4;
|
|
14615
|
+
color: var(--stipe-fg-muted);
|
|
14616
|
+
}
|
|
14617
|
+
.caps-check-key-lamp {
|
|
14618
|
+
position: absolute;
|
|
14619
|
+
top: 6px;
|
|
14620
|
+
right: 6px;
|
|
14621
|
+
width: 4px;
|
|
14622
|
+
height: 4px;
|
|
14623
|
+
border-radius: var(--radius-full);
|
|
14624
|
+
background: currentcolor;
|
|
14625
|
+
opacity: 0;
|
|
14626
|
+
}
|
|
14627
|
+
.caps-check-key-input:checked + .caps-check-key-face {
|
|
14628
|
+
border-color: var(--caps-control-selected-line, var(--color-base-content));
|
|
14629
|
+
background: var(--caps-control-selected-face, var(--color-base-200));
|
|
14630
|
+
color: var(--caps-control-ink, var(--color-base-content));
|
|
14631
|
+
}
|
|
14632
|
+
.caps-check-key-input:checked + .caps-check-key-face .caps-check-key-icon {
|
|
14633
|
+
filter: none;
|
|
14634
|
+
opacity: 1;
|
|
14635
|
+
}
|
|
14636
|
+
.caps-check-key-input:checked + .caps-check-key-face .caps-check-key-lamp {
|
|
14637
|
+
opacity: 1;
|
|
14638
|
+
}
|
|
14639
|
+
.caps-check-key-input:hover:not(:checked, :disabled) + .caps-check-key-face .caps-check-key-icon {
|
|
14640
|
+
opacity: 0.8;
|
|
14641
|
+
}
|
|
14642
|
+
.caps-check-key:has(.caps-check-key-input:disabled) {
|
|
14643
|
+
cursor: not-allowed;
|
|
14644
|
+
}
|
|
14464
14645
|
.caps-icon-choice:has(.caps-icon-choice-input:disabled) {
|
|
14465
14646
|
cursor: not-allowed;
|
|
14466
14647
|
}
|
|
@@ -15371,6 +15552,35 @@
|
|
|
15371
15552
|
font-size: var(--text-xs);
|
|
15372
15553
|
font-variant-numeric: tabular-nums;
|
|
15373
15554
|
}
|
|
15555
|
+
.list-row.caps-fact {
|
|
15556
|
+
--list-grid-cols: 6.5rem minmax(0, 1fr);
|
|
15557
|
+
align-items: start;
|
|
15558
|
+
gap: var(--spacing-sm);
|
|
15559
|
+
}
|
|
15560
|
+
.list-row.caps-fact::after {
|
|
15561
|
+
inset-inline: 0;
|
|
15562
|
+
}
|
|
15563
|
+
.caps-fact-label {
|
|
15564
|
+
display: flex;
|
|
15565
|
+
align-items: center;
|
|
15566
|
+
gap: var(--spacing-xs);
|
|
15567
|
+
min-height: 1lh;
|
|
15568
|
+
color: var(--stipe-fg-muted);
|
|
15569
|
+
}
|
|
15570
|
+
.caps-fact-icon {
|
|
15571
|
+
display: inline-flex;
|
|
15572
|
+
flex: none;
|
|
15573
|
+
width: 1rem;
|
|
15574
|
+
height: 1rem;
|
|
15575
|
+
}
|
|
15576
|
+
.caps-fact-icon > :is(svg, img) {
|
|
15577
|
+
width: 100%;
|
|
15578
|
+
height: 100%;
|
|
15579
|
+
}
|
|
15580
|
+
.caps-fact-value {
|
|
15581
|
+
min-width: 0;
|
|
15582
|
+
overflow-wrap: anywhere;
|
|
15583
|
+
}
|
|
15374
15584
|
.list.list-sm > .list-row {
|
|
15375
15585
|
gap: var(--spacing-sm);
|
|
15376
15586
|
padding: var(--spacing-2xs) 0;
|
|
@@ -16150,6 +16360,18 @@
|
|
|
16150
16360
|
.caps-game-meter .progress {
|
|
16151
16361
|
height: 0.625rem;
|
|
16152
16362
|
}
|
|
16363
|
+
.caps-keycap-button[data-density="compact"] {
|
|
16364
|
+
--caps-keycap-height: 2.25rem;
|
|
16365
|
+
--caps-keycap-width: auto;
|
|
16366
|
+
--caps-keycap-justify: center;
|
|
16367
|
+
--caps-keycap-font-size: var(--text-sm);
|
|
16368
|
+
--caps-keycap-font-weight: 600;
|
|
16369
|
+
}
|
|
16370
|
+
@media (max-width: 640px), (any-pointer: coarse) {
|
|
16371
|
+
.caps-keycap-button[data-density="compact"] {
|
|
16372
|
+
--caps-keycap-height: var(--caps-touch-target);
|
|
16373
|
+
}
|
|
16374
|
+
}
|
|
16153
16375
|
.caps-keycap-button {
|
|
16154
16376
|
--caps-key-face: var(--caps-keycap-face, var(--color-base-200));
|
|
16155
16377
|
--caps-keycap-rest-shadow: inset 0 1px 0 oklch(100% 0 0 / calc(var(--depth) * 0.18)),
|
|
@@ -18149,6 +18371,19 @@
|
|
|
18149
18371
|
place-self: center stretch;
|
|
18150
18372
|
height: max(100%, var(--caps-touch-target));
|
|
18151
18373
|
}
|
|
18374
|
+
.caps-switch-input, .caps-icon-choice[data-swatch] .caps-icon-choice-input {
|
|
18375
|
+
place-self: center;
|
|
18376
|
+
width: max(100%, var(--caps-touch-target));
|
|
18377
|
+
}
|
|
18378
|
+
.caps-check-key > .caps-checkbox {
|
|
18379
|
+
min-height: var(--caps-touch-target);
|
|
18380
|
+
}
|
|
18381
|
+
.caps-check-key .caps-check-key-face {
|
|
18382
|
+
align-items: center;
|
|
18383
|
+
}
|
|
18384
|
+
.caps-icon-choice[data-swatch] .caps-icon-choice-input {
|
|
18385
|
+
height: max(100%, var(--caps-touch-target));
|
|
18386
|
+
}
|
|
18152
18387
|
.caps-icon-button::after {
|
|
18153
18388
|
content: "";
|
|
18154
18389
|
position: absolute;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/caps",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"author": "Levi <levi@technomancy.dev>",
|
|
5
5
|
"description": "Product-neutral React primitives and blocks for Fungi interfaces",
|
|
6
6
|
"type": "module",
|
|
@@ -493,7 +493,7 @@
|
|
|
493
493
|
"typescript": "5.9.3",
|
|
494
494
|
"vite": "8.2.2",
|
|
495
495
|
"vitest": "4.1.11",
|
|
496
|
-
"@fungi.computer/sigils": "0.
|
|
496
|
+
"@fungi.computer/sigils": "0.3.0",
|
|
497
497
|
"postcss-value-parser": "4.2.0",
|
|
498
498
|
"@tanstack/react-form": "1.33.2",
|
|
499
499
|
"effect": "4.0.2",
|
|
@@ -579,7 +579,7 @@
|
|
|
579
579
|
"registry:stranger": "node scripts/registry-stranger.mjs",
|
|
580
580
|
"workbench:serve": "node scripts/workbench-serve.mjs",
|
|
581
581
|
"workbench:build": "node scripts/workbench-build.mjs",
|
|
582
|
-
"workbench:contract": "node scripts/workbench-contract.mjs",
|
|
582
|
+
"workbench:contract": "bash ../../tooling/proof-runner/owned-loopback.sh -- node scripts/workbench-contract.mjs",
|
|
583
583
|
"docs": "tsx ../../tooling/repository/src/docs/cli.ts check --root ../..",
|
|
584
584
|
"docs:write": "tsx ../../tooling/repository/src/docs/cli.ts write --root ../..",
|
|
585
585
|
"pack": "mkdir -p ../../.botanical/caps-pack && pnpm pack --pack-destination ../../.botanical/caps-pack",
|
|
@@ -588,10 +588,7 @@
|
|
|
588
588
|
"format:check": "node ../../tooling/repository/scripts/format-package-check.mjs",
|
|
589
589
|
"lint:anti-slop": "node ../../tooling/repository/scripts/lint-package-anti-slop.mjs",
|
|
590
590
|
"build:workbench": "node scripts/workbench-build.mjs --prepared",
|
|
591
|
-
"test:nightly": "node --import tsx ../../tooling/repository/scripts/workbench-proofs.mjs --output ../../.botanical/caps-workbench --origin-env CAPS_WORKBENCH_URL test/screen-theme.browser.mjs test/scroll-area.browser.mjs test/display-density.browser.mjs test/resize-handle.browser.mjs test/dialog-fill.browser.mjs && node test/scroll-gutter.browser.mjs",
|
|
592
|
-
"test:code-input": "node --import tsx ../../tooling/repository/scripts/workbench-proofs.mjs --output ../../.botanical/caps-code-input --origin-env CAPS_WORKBENCH_URL test/code-input.browser.mjs",
|
|
593
|
-
"test:icon-choice": "node --import tsx ../../tooling/repository/scripts/workbench-proofs.mjs --output ../../.botanical/caps-icon-choice --origin-env CAPS_WORKBENCH_URL test/icon-choice.browser.mjs",
|
|
594
|
-
"test:choice-label-alignment": "node --import tsx ../../tooling/repository/scripts/workbench-proofs.mjs --output ../../.botanical/caps-choice-alignment --origin-env CAPS_WORKBENCH_URL test/choice-label-alignment.browser.mjs",
|
|
591
|
+
"test:nightly": "node --import tsx ../../tooling/repository/scripts/workbench-proofs.mjs --output ../../.botanical/caps-workbench --origin-env CAPS_WORKBENCH_URL test/screen-theme.browser.mjs test/scroll-area.browser.mjs test/display-density.browser.mjs test/resize-handle.browser.mjs test/dialog-fill.browser.mjs test/app-bar-edge.browser.mjs test/overlay-layers.browser.mjs test/code-input.browser.mjs test/icon-choice.browser.mjs test/choice-label-alignment.browser.mjs && node test/scroll-gutter.browser.mjs && node test/card-theme.browser.mjs && node test/game-theme.browser.mjs && node test/disabled-filled.browser.mjs && node test/window-theme.browser.mjs",
|
|
595
592
|
"lint:motion": "node --test scripts/motion-token-guard.test.mjs && node scripts/motion-token-guard.mjs",
|
|
596
593
|
"test:card-theme": "node test/card-theme.browser.mjs",
|
|
597
594
|
"test:window-theme": "node test/window-theme.browser.mjs",
|
package/src/styles.css
CHANGED
|
@@ -4490,6 +4490,25 @@
|
|
|
4490
4490
|
.list-row > .caps-field {
|
|
4491
4491
|
grid-column: 1 / -1;
|
|
4492
4492
|
}
|
|
4493
|
+
/* In a compact list a settings row's icon is a 16px Sigil beside its
|
|
4494
|
+
label, the details-panel marker, not a tile. */
|
|
4495
|
+
.list.list-sm > .list-row > .caps-field {
|
|
4496
|
+
--caps-field-icon-size: 1rem;
|
|
4497
|
+
}
|
|
4498
|
+
.list.list-sm > .list-row > .caps-field .caps-field-lead {
|
|
4499
|
+
align-items: flex-start;
|
|
4500
|
+
gap: var(--spacing-xs);
|
|
4501
|
+
}
|
|
4502
|
+
/* The marker sits on the label's line, as in a details panel. */
|
|
4503
|
+
.list.list-sm > .list-row > .caps-field .caps-field-icon {
|
|
4504
|
+
height: calc(var(--caps-control-label-size, var(--text-sm)) * 1.5);
|
|
4505
|
+
}
|
|
4506
|
+
.list.list-sm
|
|
4507
|
+
> .list-row
|
|
4508
|
+
> .caps-field:has(> .caps-field-row > .caps-field-lead)
|
|
4509
|
+
> .caps-field-error-reveal {
|
|
4510
|
+
padding-inline-start: calc(var(--caps-field-icon-size) + var(--spacing-xs));
|
|
4511
|
+
}
|
|
4493
4512
|
/* An icon row's error lines up with its label, not its icon. */
|
|
4494
4513
|
.caps-field:has(> .caps-field-row > .caps-field-lead)
|
|
4495
4514
|
> .caps-field-error-reveal {
|
|
@@ -4530,6 +4549,17 @@
|
|
|
4530
4549
|
line-height: 1.5;
|
|
4531
4550
|
color: var(--color-base-content);
|
|
4532
4551
|
}
|
|
4552
|
+
/* A legend's icon is the inline 16px marker a details panel puts before
|
|
4553
|
+
its labels. */
|
|
4554
|
+
.caps-fieldset-legend:has(> .caps-field-icon) {
|
|
4555
|
+
display: flex;
|
|
4556
|
+
align-items: center;
|
|
4557
|
+
gap: var(--spacing-xs);
|
|
4558
|
+
}
|
|
4559
|
+
.caps-fieldset-legend > .caps-field-icon {
|
|
4560
|
+
width: 1rem;
|
|
4561
|
+
height: 1rem;
|
|
4562
|
+
}
|
|
4533
4563
|
.caps-fieldset-description {
|
|
4534
4564
|
margin: 0;
|
|
4535
4565
|
font-size: var(--text-xs);
|
|
@@ -4658,7 +4688,7 @@
|
|
|
4658
4688
|
/* The label gutter belongs to an opted-in form. No default Field or Form
|
|
4659
4689
|
changes geometry, including the signed-off permissions panel. */
|
|
4660
4690
|
:is(.caps-form, .caps-form-section, .caps-fieldset)[data-layout="columns"] {
|
|
4661
|
-
--caps-form-label-col:
|
|
4691
|
+
--caps-form-label-col: 7rem;
|
|
4662
4692
|
}
|
|
4663
4693
|
.caps-form[data-layout="columns"]
|
|
4664
4694
|
:is(.caps-form-body, .caps-form-section-fields) {
|
|
@@ -4791,6 +4821,19 @@
|
|
|
4791
4821
|
grid-column: 1;
|
|
4792
4822
|
grid-row: 1;
|
|
4793
4823
|
margin: 0;
|
|
4824
|
+
/* A floated legend is an ordinary grid item rather than the fieldset's
|
|
4825
|
+
rendered legend, so it shares a row with its keys. It centres on the
|
|
4826
|
+
first line of keys, as a field's label centres on its control, and
|
|
4827
|
+
stays on that line when the keys wrap. */
|
|
4828
|
+
float: left;
|
|
4829
|
+
align-self: start;
|
|
4830
|
+
min-height: var(--caps-fieldset-legend-line, 2.25rem);
|
|
4831
|
+
align-content: center;
|
|
4832
|
+
}
|
|
4833
|
+
.caps-fieldset[data-layout="columns"]:has(
|
|
4834
|
+
.caps-icon-choice:not([data-swatch])
|
|
4835
|
+
) {
|
|
4836
|
+
--caps-fieldset-legend-line: 3.25rem;
|
|
4794
4837
|
}
|
|
4795
4838
|
.caps-fieldset[data-layout="columns"] > .caps-fieldset-fields {
|
|
4796
4839
|
grid-column: 2;
|
|
@@ -4798,6 +4841,8 @@
|
|
|
4798
4841
|
flex-direction: row;
|
|
4799
4842
|
flex-wrap: wrap;
|
|
4800
4843
|
margin: 0;
|
|
4844
|
+
/* Choices share the value column's measure with the form's controls. */
|
|
4845
|
+
max-width: 30rem;
|
|
4801
4846
|
}
|
|
4802
4847
|
.caps-fieldset[data-layout="columns"]
|
|
4803
4848
|
> :is(.caps-fieldset-description, .caps-field-error-reveal) {
|
|
@@ -4823,9 +4868,78 @@
|
|
|
4823
4868
|
max-width: none;
|
|
4824
4869
|
}
|
|
4825
4870
|
}
|
|
4826
|
-
/*
|
|
4827
|
-
|
|
4828
|
-
|
|
4871
|
+
/* The label column reads like a details panel's fact column: a 16px Sigil
|
|
4872
|
+
before a quiet label, the value beside it. The icon shares the label's
|
|
4873
|
+
cell; the description and error stay under the control. */
|
|
4874
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"]
|
|
4875
|
+
:is(
|
|
4876
|
+
.caps-field[data-orientation="vertical"],
|
|
4877
|
+
.caps-field[data-orientation="horizontal"] > .caps-field-row
|
|
4878
|
+
)
|
|
4879
|
+
> .caps-field-lead {
|
|
4880
|
+
display: contents;
|
|
4881
|
+
}
|
|
4882
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"]
|
|
4883
|
+
:is(
|
|
4884
|
+
.caps-field[data-orientation="vertical"],
|
|
4885
|
+
.caps-field[data-orientation="horizontal"] > .caps-field-row
|
|
4886
|
+
)
|
|
4887
|
+
> .caps-field-lead
|
|
4888
|
+
> .caps-field-copy {
|
|
4889
|
+
display: contents;
|
|
4890
|
+
}
|
|
4891
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"]
|
|
4892
|
+
:is(
|
|
4893
|
+
.caps-field[data-orientation="vertical"],
|
|
4894
|
+
.caps-field[data-orientation="horizontal"] > .caps-field-row
|
|
4895
|
+
)
|
|
4896
|
+
> .caps-field-lead
|
|
4897
|
+
> .caps-field-icon {
|
|
4898
|
+
grid-column: 1;
|
|
4899
|
+
grid-row: 1;
|
|
4900
|
+
justify-self: start;
|
|
4901
|
+
}
|
|
4902
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"]
|
|
4903
|
+
:is(
|
|
4904
|
+
.caps-field[data-orientation="vertical"],
|
|
4905
|
+
.caps-field[data-orientation="horizontal"] > .caps-field-row
|
|
4906
|
+
)
|
|
4907
|
+
> .caps-field-lead
|
|
4908
|
+
> .caps-field-copy
|
|
4909
|
+
> label {
|
|
4910
|
+
grid-column: 1;
|
|
4911
|
+
grid-row: 1;
|
|
4912
|
+
min-width: 0;
|
|
4913
|
+
padding-inline-start: calc(var(--caps-field-icon-size) + var(--spacing-xs));
|
|
4914
|
+
overflow: hidden;
|
|
4915
|
+
text-overflow: ellipsis;
|
|
4916
|
+
white-space: nowrap;
|
|
4917
|
+
}
|
|
4918
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"]
|
|
4919
|
+
:is(
|
|
4920
|
+
.caps-field[data-orientation="vertical"],
|
|
4921
|
+
.caps-field[data-orientation="horizontal"] > .caps-field-row
|
|
4922
|
+
)
|
|
4923
|
+
> .caps-field-lead
|
|
4924
|
+
> .caps-field-copy
|
|
4925
|
+
> .caps-field-description {
|
|
4926
|
+
grid-column: 2;
|
|
4927
|
+
grid-row: 2;
|
|
4928
|
+
}
|
|
4929
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"] .caps-field,
|
|
4930
|
+
.caps-fieldset[data-layout="columns"] {
|
|
4931
|
+
--caps-field-icon-size: 1rem;
|
|
4932
|
+
}
|
|
4933
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"]
|
|
4934
|
+
.caps-field
|
|
4935
|
+
:is(.caps-field-copy > label, .caps-field-icon),
|
|
4936
|
+
.caps-fieldset[data-layout="columns"] > .caps-fieldset-legend {
|
|
4937
|
+
color: var(--stipe-fg-muted);
|
|
4938
|
+
font-weight: 400;
|
|
4939
|
+
}
|
|
4940
|
+
/* Phone-width containers stack every field: label (with its Sigil) above
|
|
4941
|
+
the control, description and error under it. */
|
|
4942
|
+
@container (max-width: 24rem) {
|
|
4829
4943
|
:is(.caps-form, .caps-form-section)[data-layout="columns"]
|
|
4830
4944
|
:is(
|
|
4831
4945
|
.caps-field[data-orientation="vertical"],
|
|
@@ -4871,6 +4985,8 @@
|
|
|
4871
4985
|
overflow: visible;
|
|
4872
4986
|
text-overflow: clip;
|
|
4873
4987
|
white-space: normal;
|
|
4988
|
+
/* Stacked, the legend sits on its own line above the keys. */
|
|
4989
|
+
min-height: 0;
|
|
4874
4990
|
}
|
|
4875
4991
|
:is(.caps-form, .caps-form-section)[data-layout="columns"]
|
|
4876
4992
|
.caps-field[data-orientation="horizontal"]
|
|
@@ -4879,6 +4995,17 @@
|
|
|
4879
4995
|
padding-inline-start: 0;
|
|
4880
4996
|
max-width: none;
|
|
4881
4997
|
}
|
|
4998
|
+
:is(.caps-form, .caps-form-section)[data-layout="columns"]
|
|
4999
|
+
:is(
|
|
5000
|
+
.caps-field[data-orientation="vertical"],
|
|
5001
|
+
.caps-field[data-orientation="horizontal"] > .caps-field-row
|
|
5002
|
+
)
|
|
5003
|
+
> .caps-field-lead
|
|
5004
|
+
> .caps-field-copy
|
|
5005
|
+
> .caps-field-description {
|
|
5006
|
+
grid-column: 1;
|
|
5007
|
+
grid-row: 3;
|
|
5008
|
+
}
|
|
4882
5009
|
}
|
|
4883
5010
|
|
|
4884
5011
|
/* Choice cards: a RadioGroup option with room for a description and a
|
|
@@ -6432,6 +6559,131 @@
|
|
|
6432
6559
|
.caps-icon-choice[data-appearance="compact"] .caps-icon-choice-lamp {
|
|
6433
6560
|
display: none;
|
|
6434
6561
|
}
|
|
6562
|
+
/* SwatchRadio: a 2.25rem icon key whose colour dot stays lit, so each
|
|
6563
|
+
colour reads before it is chosen; the lamp marks the choice. */
|
|
6564
|
+
.caps-icon-choice[data-swatch] {
|
|
6565
|
+
min-width: 2.25rem;
|
|
6566
|
+
min-height: 2.25rem;
|
|
6567
|
+
}
|
|
6568
|
+
.caps-icon-choice[data-swatch] .caps-icon-choice-mark {
|
|
6569
|
+
padding: var(--spacing-xs);
|
|
6570
|
+
}
|
|
6571
|
+
.caps-icon-choice[data-art="color"] .caps-icon-choice-art {
|
|
6572
|
+
filter: none;
|
|
6573
|
+
opacity: 1;
|
|
6574
|
+
}
|
|
6575
|
+
.caps-icon-choice[data-swatch] .caps-icon-choice-art {
|
|
6576
|
+
width: 1rem;
|
|
6577
|
+
height: 1rem;
|
|
6578
|
+
}
|
|
6579
|
+
.caps-swatch-dot {
|
|
6580
|
+
display: block;
|
|
6581
|
+
width: 100%;
|
|
6582
|
+
height: 100%;
|
|
6583
|
+
border-radius: var(--radius-full);
|
|
6584
|
+
outline: var(--border) solid
|
|
6585
|
+
color-mix(in oklab, var(--color-base-content) 20%, transparent);
|
|
6586
|
+
outline-offset: -1px;
|
|
6587
|
+
}
|
|
6588
|
+
/* CheckKey: a short multi-select as wide icon keys. The same face, line,
|
|
6589
|
+
selected face and lamp as an icon key; Sigil, label and hint read like
|
|
6590
|
+
a details-panel row. The checkbox's own focus ring and disabled ink
|
|
6591
|
+
apply to the whole key. */
|
|
6592
|
+
.caps-check-keys {
|
|
6593
|
+
flex: 1 1 100%;
|
|
6594
|
+
display: grid;
|
|
6595
|
+
grid-template-columns: repeat(auto-fill, minmax(min(100%, 9rem), 1fr));
|
|
6596
|
+
gap: var(--spacing-xs);
|
|
6597
|
+
min-width: 0;
|
|
6598
|
+
}
|
|
6599
|
+
/* A long list reads two to a row, so longer names keep whole words. */
|
|
6600
|
+
.caps-check-keys:has(> :nth-child(7)) {
|
|
6601
|
+
grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr));
|
|
6602
|
+
}
|
|
6603
|
+
.caps-check-key {
|
|
6604
|
+
display: grid;
|
|
6605
|
+
min-width: 0;
|
|
6606
|
+
cursor: pointer;
|
|
6607
|
+
}
|
|
6608
|
+
.caps-check-key > .caps-checkbox {
|
|
6609
|
+
min-height: 2.25rem;
|
|
6610
|
+
}
|
|
6611
|
+
.caps-check-key-face {
|
|
6612
|
+
grid-area: control;
|
|
6613
|
+
position: relative;
|
|
6614
|
+
display: flex;
|
|
6615
|
+
align-items: flex-start;
|
|
6616
|
+
gap: var(--spacing-xs);
|
|
6617
|
+
min-width: 0;
|
|
6618
|
+
padding: var(--spacing-xs) var(--spacing-md) var(--spacing-xs)
|
|
6619
|
+
var(--spacing-sm);
|
|
6620
|
+
border: var(--border) solid var(--caps-control-line, var(--color-base-300));
|
|
6621
|
+
border-radius: var(--radius-field);
|
|
6622
|
+
background: var(--caps-control-face, var(--color-base-100));
|
|
6623
|
+
color: var(--caps-control-dim, var(--stipe-fg-muted));
|
|
6624
|
+
pointer-events: none;
|
|
6625
|
+
transition:
|
|
6626
|
+
border-color var(--stipe-motion-fast) var(--stipe-motion-ease),
|
|
6627
|
+
background-color var(--stipe-motion-fast) var(--stipe-motion-ease);
|
|
6628
|
+
}
|
|
6629
|
+
.caps-check-key-icon {
|
|
6630
|
+
display: inline-flex;
|
|
6631
|
+
flex: none;
|
|
6632
|
+
align-items: center;
|
|
6633
|
+
width: 1rem;
|
|
6634
|
+
height: calc(var(--text-sm) * 1.5);
|
|
6635
|
+
filter: var(--caps-control-icon-off, grayscale(1) contrast(0));
|
|
6636
|
+
opacity: 0.55;
|
|
6637
|
+
}
|
|
6638
|
+
.caps-check-key-icon > :is(svg, img) {
|
|
6639
|
+
width: 1rem;
|
|
6640
|
+
height: 1rem;
|
|
6641
|
+
}
|
|
6642
|
+
.caps-check-key-copy {
|
|
6643
|
+
display: grid;
|
|
6644
|
+
min-width: 0;
|
|
6645
|
+
}
|
|
6646
|
+
.caps-check-key-label {
|
|
6647
|
+
font-size: var(--text-sm);
|
|
6648
|
+
font-weight: 600;
|
|
6649
|
+
line-height: 1.5;
|
|
6650
|
+
overflow-wrap: break-word;
|
|
6651
|
+
}
|
|
6652
|
+
.caps-check-key-hint {
|
|
6653
|
+
font-size: var(--text-xs);
|
|
6654
|
+
line-height: 1.4;
|
|
6655
|
+
color: var(--stipe-fg-muted);
|
|
6656
|
+
}
|
|
6657
|
+
.caps-check-key-lamp {
|
|
6658
|
+
position: absolute;
|
|
6659
|
+
top: 6px;
|
|
6660
|
+
right: 6px;
|
|
6661
|
+
width: 4px;
|
|
6662
|
+
height: 4px;
|
|
6663
|
+
border-radius: var(--radius-full);
|
|
6664
|
+
background: currentcolor;
|
|
6665
|
+
opacity: 0;
|
|
6666
|
+
}
|
|
6667
|
+
.caps-check-key-input:checked + .caps-check-key-face {
|
|
6668
|
+
border-color: var(--caps-control-selected-line, var(--color-base-content));
|
|
6669
|
+
background: var(--caps-control-selected-face, var(--color-base-200));
|
|
6670
|
+
color: var(--caps-control-ink, var(--color-base-content));
|
|
6671
|
+
}
|
|
6672
|
+
.caps-check-key-input:checked + .caps-check-key-face .caps-check-key-icon {
|
|
6673
|
+
filter: none;
|
|
6674
|
+
opacity: 1;
|
|
6675
|
+
}
|
|
6676
|
+
.caps-check-key-input:checked + .caps-check-key-face .caps-check-key-lamp {
|
|
6677
|
+
opacity: 1;
|
|
6678
|
+
}
|
|
6679
|
+
.caps-check-key-input:hover:not(:checked, :disabled)
|
|
6680
|
+
+ .caps-check-key-face
|
|
6681
|
+
.caps-check-key-icon {
|
|
6682
|
+
opacity: 0.8;
|
|
6683
|
+
}
|
|
6684
|
+
.caps-check-key:has(.caps-check-key-input:disabled) {
|
|
6685
|
+
cursor: not-allowed;
|
|
6686
|
+
}
|
|
6435
6687
|
.caps-icon-choice:has(.caps-icon-choice-input:disabled) {
|
|
6436
6688
|
cursor: not-allowed;
|
|
6437
6689
|
}
|
|
@@ -7483,6 +7735,37 @@
|
|
|
7483
7735
|
font-size: var(--text-xs);
|
|
7484
7736
|
font-variant-numeric: tabular-nums;
|
|
7485
7737
|
}
|
|
7738
|
+
/* FactRow: a details-panel fact. A fixed label column (16px Sigil and a
|
|
7739
|
+
quiet name) beside a wrapping value; the row rule spans both. */
|
|
7740
|
+
.list-row.caps-fact {
|
|
7741
|
+
--list-grid-cols: 6.5rem minmax(0, 1fr);
|
|
7742
|
+
align-items: start;
|
|
7743
|
+
gap: var(--spacing-sm);
|
|
7744
|
+
}
|
|
7745
|
+
.list-row.caps-fact::after {
|
|
7746
|
+
inset-inline: 0;
|
|
7747
|
+
}
|
|
7748
|
+
.caps-fact-label {
|
|
7749
|
+
display: flex;
|
|
7750
|
+
align-items: center;
|
|
7751
|
+
gap: var(--spacing-xs);
|
|
7752
|
+
min-height: 1lh;
|
|
7753
|
+
color: var(--stipe-fg-muted);
|
|
7754
|
+
}
|
|
7755
|
+
.caps-fact-icon {
|
|
7756
|
+
display: inline-flex;
|
|
7757
|
+
flex: none;
|
|
7758
|
+
width: 1rem;
|
|
7759
|
+
height: 1rem;
|
|
7760
|
+
}
|
|
7761
|
+
.caps-fact-icon > :is(svg, img) {
|
|
7762
|
+
width: 100%;
|
|
7763
|
+
height: 100%;
|
|
7764
|
+
}
|
|
7765
|
+
.caps-fact-value {
|
|
7766
|
+
min-width: 0;
|
|
7767
|
+
overflow-wrap: anywhere;
|
|
7768
|
+
}
|
|
7486
7769
|
/* DataList density="compact": rows whose content brings its own inset
|
|
7487
7770
|
(an ActionRow) sit flush with the list's edge, one control high. */
|
|
7488
7771
|
.list.list-sm > .list-row {
|
|
@@ -8368,6 +8651,21 @@
|
|
|
8368
8651
|
.caps-game-meter .progress {
|
|
8369
8652
|
height: 0.625rem;
|
|
8370
8653
|
}
|
|
8654
|
+
/* KeycapButton density="compact": the details-panel key. A row of them
|
|
8655
|
+
sizes each key to its legend; a grid still stretches them to its cells. */
|
|
8656
|
+
.caps-keycap-button[data-density="compact"] {
|
|
8657
|
+
--caps-keycap-height: 2.25rem;
|
|
8658
|
+
--caps-keycap-width: auto;
|
|
8659
|
+
--caps-keycap-justify: center;
|
|
8660
|
+
--caps-keycap-font-size: var(--text-sm);
|
|
8661
|
+
--caps-keycap-font-weight: 600;
|
|
8662
|
+
}
|
|
8663
|
+
/* On phones and touch screens the compact key keeps a thumb-sized height. */
|
|
8664
|
+
@media (max-width: 640px), (any-pointer: coarse) {
|
|
8665
|
+
.caps-keycap-button[data-density="compact"] {
|
|
8666
|
+
--caps-keycap-height: var(--caps-touch-target);
|
|
8667
|
+
}
|
|
8668
|
+
}
|
|
8371
8669
|
/* A key: today's flat key on a 1px lip, a touch richer. The face carries a
|
|
8372
8670
|
faint ±3% grade, the top edge is 0.18, a small soft drop sits under the
|
|
8373
8671
|
lip, and the linework edge is softened 20% toward the face. It reads its
|
|
@@ -10508,6 +10806,23 @@
|
|
|
10508
10806
|
place-self: center stretch;
|
|
10509
10807
|
height: max(100%, var(--caps-touch-target));
|
|
10510
10808
|
}
|
|
10809
|
+
/* A switch or a colour key is its own target, not beside a label, so its
|
|
10810
|
+
hit area reaches the touch width too; the visual keeps its size and
|
|
10811
|
+
the extra reach spills evenly into the gap around it. */
|
|
10812
|
+
.caps-switch-input,
|
|
10813
|
+
.caps-icon-choice[data-swatch] .caps-icon-choice-input {
|
|
10814
|
+
place-self: center;
|
|
10815
|
+
width: max(100%, var(--caps-touch-target));
|
|
10816
|
+
}
|
|
10817
|
+
.caps-check-key > .caps-checkbox {
|
|
10818
|
+
min-height: var(--caps-touch-target);
|
|
10819
|
+
}
|
|
10820
|
+
.caps-check-key .caps-check-key-face {
|
|
10821
|
+
align-items: center;
|
|
10822
|
+
}
|
|
10823
|
+
.caps-icon-choice[data-swatch] .caps-icon-choice-input {
|
|
10824
|
+
height: max(100%, var(--caps-touch-target));
|
|
10825
|
+
}
|
|
10511
10826
|
/* An icon button keeps its grid size and face; an invisible hit area
|
|
10512
10827
|
centred on it reaches the touch target instead. */
|
|
10513
10828
|
.caps-icon-button::after {
|