@fungi.computer/caps 0.1.0 → 0.2.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 +3 -3
- package/dist/components/button.js +33 -12
- package/dist/components/window-crumbs.d.ts +5 -1
- package/dist/components/window-crumbs.js +14 -7
- package/dist/styles.css +15 -4
- package/docs/api/components/window-crumbs.md +17 -1
- package/package.json +8 -6
- package/src/fonts-sans.css +1 -0
- package/src/styles.css +21 -5
package/README.md
CHANGED
|
@@ -222,9 +222,9 @@ keep their shared seam.
|
|
|
222
222
|
### Class merging
|
|
223
223
|
|
|
224
224
|
`@fungi.computer/caps/lib/cn` merges classes with `cn@0.2.6` and Stipe's
|
|
225
|
-
[named spacing scale](https://
|
|
226
|
-
accepts conditional strings, arrays and objects and keeps caller-last
|
|
227
|
-
overrides, so `cn("text-xs px-xs py-sm", "px-md")` gives
|
|
225
|
+
[named spacing scale](https://docs.fungi.computer/stipe/concepts/#named-spacing).
|
|
226
|
+
It accepts conditional strings, arrays and objects and keeps caller-last
|
|
227
|
+
Tailwind overrides, so `cn("text-xs px-xs py-sm", "px-md")` gives
|
|
228
228
|
`"text-xs py-sm px-md"`. The helper keeps typography and spacing names apart.
|
|
229
229
|
|
|
230
230
|
### Forms
|
|
@@ -37,8 +37,7 @@ export const Button = forwardRef(function Button({ asChild = false, children, cl
|
|
|
37
37
|
const mountedResult = useRef(result).current;
|
|
38
38
|
const node = useRef(null);
|
|
39
39
|
useImperativeHandle(ref, () => node.current);
|
|
40
|
-
|
|
41
|
-
useWidthTween(node, [loading, result, children]);
|
|
40
|
+
useWidthTween(node, loading, result);
|
|
42
41
|
if (asChild) {
|
|
43
42
|
return (_jsx(Slot, { ...props, "aria-busy": loading ? true : props["aria-busy"], "aria-disabled": disabled === true || loading ? true : undefined, className: classes, ref: node, children: children }));
|
|
44
43
|
}
|
|
@@ -47,23 +46,45 @@ export const Button = forwardRef(function Button({ asChild = false, children, cl
|
|
|
47
46
|
/**
|
|
48
47
|
* When a button's label changes (loading, a result, new text) its width eases
|
|
49
48
|
* to the new size instead of jumping, so neighbours never lurch.
|
|
49
|
+
*
|
|
50
|
+
* What counts as "changed" is what the button renders (its text, loading and
|
|
51
|
+
* result), never the identity of its children: a caller that builds a fresh
|
|
52
|
+
* array or element with the same words each render has not changed the label.
|
|
53
|
+
* Widths are layout (CSS) pixels from `offsetWidth`, the unit the animated
|
|
54
|
+
* `width` is in, so a scaled or zoomed ancestor cannot distort the glide. A
|
|
55
|
+
* button that has no box (an ancestor is `display: none`) records no width, so
|
|
56
|
+
* when it is shown it settles at its size instead of growing from nothing.
|
|
50
57
|
*/
|
|
51
|
-
function useWidthTween(node,
|
|
58
|
+
function useWidthTween(node, loading, result) {
|
|
52
59
|
const width = useRef(null);
|
|
60
|
+
const look = useRef(null);
|
|
53
61
|
const tween = useRef(null);
|
|
62
|
+
// After every commit: the rendered text is only known once it is in the DOM.
|
|
54
63
|
useLayoutEffect(() => {
|
|
55
64
|
const element = node.current;
|
|
56
65
|
if (!element)
|
|
57
66
|
return;
|
|
67
|
+
const now = JSON.stringify([loading, result ?? null, element.textContent]);
|
|
68
|
+
const changed = now !== look.current;
|
|
69
|
+
look.current = now;
|
|
58
70
|
const running = tween.current;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
running
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
// Nothing new to say: let a glide in progress finish undisturbed.
|
|
72
|
+
if (!changed && running)
|
|
73
|
+
return;
|
|
74
|
+
const from = running ? element.offsetWidth : width.current;
|
|
75
|
+
if (running) {
|
|
76
|
+
tween.current = null;
|
|
77
|
+
running.cancel();
|
|
78
|
+
element.style.removeProperty("overflow");
|
|
79
|
+
element.style.removeProperty("white-space");
|
|
80
|
+
}
|
|
81
|
+
const to = element.offsetWidth;
|
|
82
|
+
width.current = to > 0 ? to : null;
|
|
83
|
+
if (!changed ||
|
|
84
|
+
from === null ||
|
|
85
|
+
to === 0 ||
|
|
86
|
+
Math.abs(from - to) < 1 ||
|
|
87
|
+
!canAnimate(element))
|
|
67
88
|
return;
|
|
68
89
|
const motion = readMotion(element);
|
|
69
90
|
if (motion.reduced)
|
|
@@ -82,7 +103,7 @@ function useWidthTween(node, label) {
|
|
|
82
103
|
};
|
|
83
104
|
animation.onfinish = done;
|
|
84
105
|
animation.oncancel = done;
|
|
85
|
-
}
|
|
106
|
+
});
|
|
86
107
|
}
|
|
87
108
|
/** How long the done beat holds before settling: long enough to read. */
|
|
88
109
|
const resultHoldMs = 1100;
|
|
@@ -13,6 +13,8 @@ export type WindowCrumb = {
|
|
|
13
13
|
export type WindowCommand = {
|
|
14
14
|
id: string;
|
|
15
15
|
label: string;
|
|
16
|
+
/** Render a supported glyph alone; label remains the accessible name. */
|
|
17
|
+
icon?: "play";
|
|
16
18
|
/** Which Caps button a primary command wears; default `outline`. */
|
|
17
19
|
variant?: "primary" | "outline" | "ghost";
|
|
18
20
|
/** A menu item that removes or discards something. */
|
|
@@ -23,6 +25,8 @@ export type WindowCommand = {
|
|
|
23
25
|
export type WindowStatus = {
|
|
24
26
|
id: string;
|
|
25
27
|
label: string;
|
|
28
|
+
/** Hide only the visible label, retaining it for assistive technology. */
|
|
29
|
+
presentation?: "bead";
|
|
26
30
|
tone?: Extract<StatusTone, "neutral" | "info" | "warning" | "success" | "error">;
|
|
27
31
|
};
|
|
28
32
|
/** The path as a trail; on a phone only `‹ parent › current` remain. */
|
|
@@ -38,7 +42,7 @@ export declare function WindowCommands({ primary, menu, popover, onAction, }: {
|
|
|
38
42
|
popover?: HTMLElement | null | undefined;
|
|
39
43
|
onAction?: ((id: string) => void) | undefined;
|
|
40
44
|
}): import("react/jsx-runtime").JSX.Element | null;
|
|
41
|
-
/**
|
|
45
|
+
/** Settled beads reveal their labels on hover/focus; other state stays visible. */
|
|
42
46
|
export declare function WindowStatuses({ status, }: {
|
|
43
47
|
status: readonly WindowStatus[];
|
|
44
48
|
}): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { ChevronDown, Ellipsis } from "lucide-react";
|
|
2
|
+
import { ChevronDown, Ellipsis, Play } from "lucide-react";
|
|
3
3
|
import { Button } from "./button.js";
|
|
4
4
|
import { Status } from "./status.js";
|
|
5
5
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "./dropdown-menu.js";
|
|
6
6
|
import { IconButton } from "./icon-button.js";
|
|
7
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.js";
|
|
7
8
|
/** The longest path shown whole; longer paths fold the middle into "…". */
|
|
8
9
|
const CRUMBS_SHOWN = 4;
|
|
9
10
|
function Separator() {
|
|
@@ -16,13 +17,13 @@ function Crumb({ crumb, current, popover, onCrumb, }) {
|
|
|
16
17
|
"aria-current": current ? "page" : undefined,
|
|
17
18
|
};
|
|
18
19
|
if (crumb.siblings?.length) {
|
|
19
|
-
return (_jsxs(DropdownMenu, { modal: false, children: [_jsxs(DropdownMenuTrigger, { ...common, className: "caps-window-crumb shrink", render: _jsx(Button, { size: "
|
|
20
|
+
return (_jsxs(DropdownMenu, { modal: false, children: [_jsxs(DropdownMenuTrigger, { ...common, className: "caps-window-crumb shrink", render: _jsx(Button, { size: "xs", variant: "ghost" }), "aria-label": `${crumb.label}, switch`, children: [label, _jsx(ChevronDown, { "aria-hidden": "true", className: "size-3" })] }), _jsx(DropdownMenuContent, { align: "start", container: popover, children: crumb.siblings.map((sibling) => (_jsx(DropdownMenuItem, { onSelect: () => {
|
|
20
21
|
onCrumb?.(crumb.id, sibling.id);
|
|
21
22
|
}, children: sibling.label }, sibling.id))) })] }));
|
|
22
23
|
}
|
|
23
24
|
if (current || onCrumb === undefined)
|
|
24
25
|
return (_jsx("span", { ...common, className: "caps-window-crumb", children: label }));
|
|
25
|
-
return (_jsx(Button, { ...common, size: "
|
|
26
|
+
return (_jsx(Button, { ...common, size: "xs", variant: "ghost", className: "caps-window-crumb shrink", onClick: () => {
|
|
26
27
|
onCrumb(crumb.id);
|
|
27
28
|
}, children: label }));
|
|
28
29
|
}
|
|
@@ -44,7 +45,7 @@ export function WindowCrumbs({ crumbs, popover, onCrumb, }) {
|
|
|
44
45
|
for (const crumb of head)
|
|
45
46
|
add(crumb);
|
|
46
47
|
if (middle.length > 0)
|
|
47
|
-
parts.push(_jsxs("span", { className: "caps-window-crumb-item", "data-caps-crumb-position": "ancestor", children: [_jsx(Separator, {}), _jsxs(DropdownMenu, { modal: false, children: [_jsx(DropdownMenuTrigger, { className: "caps-window-crumb", render: _jsx(Button, { size: "
|
|
48
|
+
parts.push(_jsxs("span", { className: "caps-window-crumb-item", "data-caps-crumb-position": "ancestor", children: [_jsx(Separator, {}), _jsxs(DropdownMenu, { modal: false, children: [_jsx(DropdownMenuTrigger, { className: "caps-window-crumb", render: _jsx(Button, { size: "xs", variant: "ghost" }), "aria-label": "More of the path", children: _jsx(Ellipsis, { "aria-hidden": "true", className: "size-3" }) }), _jsx(DropdownMenuContent, { align: "start", container: popover, children: middle.map((crumb) => (_jsx(DropdownMenuItem, { onSelect: () => {
|
|
48
49
|
onCrumb?.(crumb.id);
|
|
49
50
|
}, children: crumb.label }, crumb.id))) })] })] }, "fold"));
|
|
50
51
|
for (const crumb of tail)
|
|
@@ -55,15 +56,21 @@ export function WindowCrumbs({ crumbs, popover, onCrumb, }) {
|
|
|
55
56
|
export function WindowCommands({ primary, menu, popover, onAction, }) {
|
|
56
57
|
if (!primary?.length && !menu?.length)
|
|
57
58
|
return null;
|
|
58
|
-
return (_jsxs("div", { className: "caps-window-commands", role: "group", "aria-label": "Commands", children: [primary?.map((command) => (_jsx(
|
|
59
|
+
return (_jsxs("div", { className: "caps-window-commands", role: "group", "aria-label": "Commands", children: [primary?.map((command) => command.icon === "play" ? (_jsx(IconButton, { icon: _jsx(Play, {}), "aria-label": command.label, title: command.label, variant: command.variant ?? "ghost", disabled: command.disabled === true, onClick: () => {
|
|
60
|
+
onAction?.(command.id);
|
|
61
|
+
} }, command.id)) : (_jsx(Button, { size: "xs", variant: command.variant ?? "outline", disabled: command.disabled === true, onClick: () => {
|
|
59
62
|
onAction?.(command.id);
|
|
60
63
|
}, children: command.label }, command.id))), menu?.length ? (_jsxs(DropdownMenu, { modal: false, children: [_jsx(DropdownMenuTrigger, { render: _jsx(IconButton, { icon: _jsx(Ellipsis, {}), "aria-label": "More" }) }), _jsx(DropdownMenuContent, { align: "end", container: popover, children: menu.map((command) => (_jsx(DropdownMenuItem, { disabled: command.disabled === true, variant: command.tone === "destructive" ? "destructive" : "default", onSelect: () => {
|
|
61
64
|
onAction?.(command.id);
|
|
62
65
|
}, children: command.label }, command.id))) })] })) : null] }));
|
|
63
66
|
}
|
|
64
|
-
/**
|
|
67
|
+
/** Settled beads reveal their labels on hover/focus; other state stays visible. */
|
|
65
68
|
export function WindowStatuses({ status, }) {
|
|
66
69
|
if (status.length === 0)
|
|
67
70
|
return null;
|
|
68
|
-
return (_jsx("div", { className: "caps-window-statuses", role: "group", "aria-label": "State", children: status.map((item) =>
|
|
71
|
+
return (_jsx("div", { className: "caps-window-statuses", role: "group", "aria-label": "State", children: status.map((item) => {
|
|
72
|
+
const beadOnly = item.presentation === "bead";
|
|
73
|
+
const readout = (_jsx(Status, { glyph: "dot", size: "compact", tone: item.tone ?? "neutral", label: beadOnly ? (_jsx("span", { className: "sr-only", children: item.label })) : (item.label), tabIndex: beadOnly ? 0 : undefined, title: beadOnly ? undefined : item.label, className: "caps-window-status" }, item.id));
|
|
74
|
+
return beadOnly ? (_jsxs(Tooltip, { side: "bottom", children: [_jsx(TooltipTrigger, { asChild: true, children: readout }), _jsx(TooltipContent, { children: item.label })] }, item.id)) : (readout);
|
|
75
|
+
}) }));
|
|
69
76
|
}
|
package/dist/styles.css
CHANGED
|
@@ -13972,8 +13972,8 @@
|
|
|
13972
13972
|
min-height: 44px;
|
|
13973
13973
|
}
|
|
13974
13974
|
}
|
|
13975
|
-
.caps-window-header
|
|
13976
|
-
|
|
13975
|
+
.caps-window-header > .caps-window-icon {
|
|
13976
|
+
margin-inline: var(--spacing-md) calc(var(--spacing-sm) - var(--spacing-2xs));
|
|
13977
13977
|
}
|
|
13978
13978
|
.caps-window-crumbs {
|
|
13979
13979
|
display: flex;
|
|
@@ -13981,7 +13981,7 @@
|
|
|
13981
13981
|
min-width: 0;
|
|
13982
13982
|
flex-shrink: 1;
|
|
13983
13983
|
font-family: var(--caps-label-font, inherit);
|
|
13984
|
-
font-size:
|
|
13984
|
+
font-size: 11px;
|
|
13985
13985
|
font-weight: 500;
|
|
13986
13986
|
}
|
|
13987
13987
|
.caps-window-crumb-item[data-caps-crumb-position="current"] {
|
|
@@ -14022,7 +14022,7 @@
|
|
|
14022
14022
|
gap: var(--spacing-sm);
|
|
14023
14023
|
flex-shrink: 0;
|
|
14024
14024
|
padding-inline-end: var(--spacing-sm);
|
|
14025
|
-
font-size:
|
|
14025
|
+
font-size: 11px;
|
|
14026
14026
|
color: currentColor;
|
|
14027
14027
|
@supports (color: color-mix(in lab, red, red)) {
|
|
14028
14028
|
color: color-mix(in srgb, currentColor 65%, transparent);
|
|
@@ -14035,6 +14035,16 @@
|
|
|
14035
14035
|
flex-shrink: 0;
|
|
14036
14036
|
}
|
|
14037
14037
|
@media (max-width: 640px) {
|
|
14038
|
+
.caps-window-header:has(.caps-window-crumbs) {
|
|
14039
|
+
flex-wrap: wrap;
|
|
14040
|
+
}
|
|
14041
|
+
.caps-window-crumbs {
|
|
14042
|
+
min-width: min-content;
|
|
14043
|
+
flex-shrink: 0;
|
|
14044
|
+
}
|
|
14045
|
+
.caps-window-crumb-item {
|
|
14046
|
+
flex-shrink: 0;
|
|
14047
|
+
}
|
|
14038
14048
|
.caps-window-statuses {
|
|
14039
14049
|
gap: var(--spacing-2xs);
|
|
14040
14050
|
padding-inline-end: var(--spacing-2xs);
|
|
@@ -14387,6 +14397,7 @@
|
|
|
14387
14397
|
display: block;
|
|
14388
14398
|
flex: none;
|
|
14389
14399
|
overflow: visible;
|
|
14400
|
+
pointer-events: none;
|
|
14390
14401
|
transition: color var(--stipe-motion-normal) ease;
|
|
14391
14402
|
}
|
|
14392
14403
|
.caps-status-mark {
|
|
@@ -64,6 +64,14 @@ id: string;
|
|
|
64
64
|
label: string;
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
##### icon?
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
optional icon?: "play";
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Render a supported glyph alone; label remains the accessible name.
|
|
74
|
+
|
|
67
75
|
##### variant?
|
|
68
76
|
|
|
69
77
|
```ts
|
|
@@ -110,6 +118,14 @@ id: string;
|
|
|
110
118
|
label: string;
|
|
111
119
|
```
|
|
112
120
|
|
|
121
|
+
##### presentation?
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
optional presentation?: "bead";
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Hide only the visible label, retaining it for assistive technology.
|
|
128
|
+
|
|
113
129
|
##### tone?
|
|
114
130
|
|
|
115
131
|
```ts
|
|
@@ -171,7 +187,7 @@ Primary commands as buttons, the rest behind a ⋯ menu.
|
|
|
171
187
|
function WindowStatuses(__namedParameters): Element | null;
|
|
172
188
|
```
|
|
173
189
|
|
|
174
|
-
|
|
190
|
+
Settled beads reveal their labels on hover/focus; other state stays visible.
|
|
175
191
|
|
|
176
192
|
#### Parameters
|
|
177
193
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/caps",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"author": "Levi <levi@technomancy.dev>",
|
|
5
5
|
"description": "Product-neutral React primitives and blocks for Fungi interfaces",
|
|
6
6
|
"type": "module",
|
|
@@ -371,6 +371,7 @@
|
|
|
371
371
|
"import": "./dist/blocks/card-grid.js"
|
|
372
372
|
},
|
|
373
373
|
"./fonts.css": "./src/fonts.css",
|
|
374
|
+
"./fonts-sans.css": "./src/fonts-sans.css",
|
|
374
375
|
"./blocks/section-heading": {
|
|
375
376
|
"types": "./dist/blocks/section-heading.d.ts",
|
|
376
377
|
"import": "./dist/blocks/section-heading.js"
|
|
@@ -458,6 +459,7 @@
|
|
|
458
459
|
"docs/api",
|
|
459
460
|
"README.md",
|
|
460
461
|
"src/fonts.css",
|
|
462
|
+
"src/fonts-sans.css",
|
|
461
463
|
"src/status-tray.css",
|
|
462
464
|
"src/app-status-bar.css"
|
|
463
465
|
],
|
|
@@ -468,9 +470,9 @@
|
|
|
468
470
|
"effect": "4.0.2"
|
|
469
471
|
},
|
|
470
472
|
"devDependencies": {
|
|
471
|
-
"@fungi.computer/maosaico": "0.0
|
|
472
|
-
"@fungi.computer/sprite": "0.
|
|
473
|
-
"@fungi.computer/sprite-zdog": "0.
|
|
473
|
+
"@fungi.computer/maosaico": "0.1.0",
|
|
474
|
+
"@fungi.computer/sprite": "0.2.0",
|
|
475
|
+
"@fungi.computer/sprite-zdog": "0.2.0",
|
|
474
476
|
"@ladle/react": "5.1.1",
|
|
475
477
|
"@tailwindcss/cli": "4.2.1",
|
|
476
478
|
"@types/react": "18.3.3",
|
|
@@ -487,7 +489,7 @@
|
|
|
487
489
|
"typescript": "5.9.3",
|
|
488
490
|
"vite": "8.2.2",
|
|
489
491
|
"vitest": "4.1.11",
|
|
490
|
-
"@fungi.computer/sigils": "0.
|
|
492
|
+
"@fungi.computer/sigils": "0.2.0",
|
|
491
493
|
"postcss-value-parser": "4.2.0",
|
|
492
494
|
"@tanstack/react-form": "1.33.2",
|
|
493
495
|
"effect": "4.0.2",
|
|
@@ -496,7 +498,7 @@
|
|
|
496
498
|
},
|
|
497
499
|
"dependencies": {
|
|
498
500
|
"@base-ui/react": "1.9.0",
|
|
499
|
-
"@fungi.computer/stipe": "0.0
|
|
501
|
+
"@fungi.computer/stipe": "0.1.0",
|
|
500
502
|
"@radix-ui/react-slot": "1.3.0",
|
|
501
503
|
"@tailwindcss/typography": "0.5.19",
|
|
502
504
|
"@tanstack/highlight": "0.0.9",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "@fungi.computer/stipe/fonts-sans.css";
|
package/src/styles.css
CHANGED
|
@@ -5827,9 +5827,11 @@
|
|
|
5827
5827
|
}
|
|
5828
5828
|
}
|
|
5829
5829
|
/* The path trail: flat text crumbs on the header face, no new colours.
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5830
|
+
It keeps the title's size and the header's 32px height, and its icon
|
|
5831
|
+
sits where a plain title's icon sits, so switching views never shifts it. */
|
|
5832
|
+
.caps-window-header > .caps-window-icon {
|
|
5833
|
+
margin-inline: var(--spacing-md)
|
|
5834
|
+
calc(var(--spacing-sm) - var(--spacing-2xs));
|
|
5833
5835
|
}
|
|
5834
5836
|
.caps-window-crumbs {
|
|
5835
5837
|
display: flex;
|
|
@@ -5837,7 +5839,7 @@
|
|
|
5837
5839
|
min-width: 0;
|
|
5838
5840
|
flex-shrink: 1;
|
|
5839
5841
|
font-family: var(--caps-label-font, inherit);
|
|
5840
|
-
font-size:
|
|
5842
|
+
font-size: 11px;
|
|
5841
5843
|
font-weight: 500;
|
|
5842
5844
|
}
|
|
5843
5845
|
.caps-window-crumb-item[data-caps-crumb-position="current"] {
|
|
@@ -5878,7 +5880,7 @@
|
|
|
5878
5880
|
gap: var(--spacing-sm);
|
|
5879
5881
|
flex-shrink: 0;
|
|
5880
5882
|
padding-inline-end: var(--spacing-sm);
|
|
5881
|
-
font-size:
|
|
5883
|
+
font-size: 11px;
|
|
5882
5884
|
color: color-mix(in srgb, currentColor 65%, transparent);
|
|
5883
5885
|
}
|
|
5884
5886
|
.caps-window-commands {
|
|
@@ -5889,6 +5891,18 @@
|
|
|
5889
5891
|
}
|
|
5890
5892
|
/* A phone keeps `‹ parent › current`; the rest of the trail is a tap away on Back. */
|
|
5891
5893
|
@media (max-width: 640px) {
|
|
5894
|
+
/* Touch buttons cannot shrink below 44px. Reserve their path's width
|
|
5895
|
+
instead of shrinking its containers underneath neighbouring targets. */
|
|
5896
|
+
.caps-window-header:has(.caps-window-crumbs) {
|
|
5897
|
+
flex-wrap: wrap;
|
|
5898
|
+
}
|
|
5899
|
+
.caps-window-crumbs {
|
|
5900
|
+
min-width: min-content;
|
|
5901
|
+
flex-shrink: 0;
|
|
5902
|
+
}
|
|
5903
|
+
.caps-window-crumb-item {
|
|
5904
|
+
flex-shrink: 0;
|
|
5905
|
+
}
|
|
5892
5906
|
/* State shrinks to its beads; the words stay for screen readers and a long press. */
|
|
5893
5907
|
.caps-window-statuses {
|
|
5894
5908
|
gap: var(--spacing-2xs);
|
|
@@ -6321,6 +6335,8 @@
|
|
|
6321
6335
|
display: block;
|
|
6322
6336
|
flex: none;
|
|
6323
6337
|
overflow: visible;
|
|
6338
|
+
/* Decorative material overflows the bead; only its status wrapper is a target. */
|
|
6339
|
+
pointer-events: none;
|
|
6324
6340
|
transition: color var(--stipe-motion-normal) ease;
|
|
6325
6341
|
}
|
|
6326
6342
|
|