actual-css 0.12.0 → 0.14.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/CHANGELOG.md +102 -0
- package/README.md +7 -8
- package/components.json +124 -12
- package/dist/actual.full.css +1152 -309
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/docs/design-notes/architecture.md +67 -0
- package/docs/design-notes/steps.md +45 -28
- package/docs/navigation.json +2 -0
- package/docs/pages/components/alert.md +12 -2
- package/docs/pages/components/avatar.md +17 -11
- package/docs/pages/components/breadcrumb.md +16 -2
- package/docs/pages/components/button.md +3 -2
- package/docs/pages/components/list.md +15 -16
- package/docs/pages/components/navbar.md +42 -22
- package/docs/pages/components/pagination.md +19 -0
- package/docs/pages/components/segmented.md +83 -0
- package/docs/pages/components/steps.md +28 -22
- package/docs/pages/enhancements/flyout.md +5 -0
- package/docs/pages/enhancements/scrollspy.md +6 -6
- package/docs/pages/enhancements/tabs.md +79 -3
- package/docs/pages/examples/overview.md +3 -1
- package/docs/pages/forms/color-swatches.md +131 -0
- package/docs/pages/forms/combobox.md +2 -2
- package/docs/pages/forms/controls.md +6 -3
- package/docs/pages/forms/floating-field.md +21 -7
- package/docs/pages/forms/form-actions.md +3 -6
- package/docs/pages/forms/joined-controls.md +6 -2
- package/docs/pages/forms/overview.md +1 -0
- package/docs/pages/forms/states-and-sizes.md +15 -15
- package/docs/pages/forms/switch.md +4 -2
- package/docs/pages/foundations/philosophy.md +97 -1
- package/docs/pages/foundations/theming.md +1 -1
- package/docs/pages/foundations/tokens.md +94 -27
- package/docs/pages/guides/design-tools.md +14 -8
- package/docs/pages/guides/integrating-actual-css.md +74 -8
- package/docs/pages/guides/introduction.md +3 -2
- package/docs/pages/guides/modular-import.md +1 -1
- package/docs/pages/guides/progressive-enhancement.md +25 -7
- package/docs/pages/guides/upgrading.md +86 -9
- package/docs/pages/layout/center.md +2 -1
- package/docs/pages/layout/cluster.md +11 -4
- package/docs/pages/layout/grid.md +2 -0
- package/docs/pages/layout/scroll-snap.md +4 -2
- package/docs/pages/layout/sidebar-layout.md +3 -0
- package/docs/pages/layout/switcher.md +2 -0
- package/docs/pages/layout/topbar.md +6 -4
- package/docs/pages/patterns/icon-slot.md +17 -1
- package/docs/pages/utilities/flex.md +1 -1
- package/docs/pages/utilities/sizing-wrapping.md +51 -6
- package/package.json +6 -6
- package/reserved-classes.json +3 -0
- package/src/cli/actual-css.js +4 -4
- package/src/css/components/alert.css +99 -48
- package/src/css/components/app-nav.css +17 -9
- package/src/css/components/avatar.css +2 -18
- package/src/css/components/badge.css +23 -10
- package/src/css/components/breadcrumb.css +74 -8
- package/src/css/components/button.css +17 -2
- package/src/css/components/close.css +6 -3
- package/src/css/components/drawer.css +23 -42
- package/src/css/components/flyout.css +0 -1
- package/src/css/components/index.css +1 -0
- package/src/css/components/join.css +2 -2
- package/src/css/components/list.css +28 -13
- package/src/css/components/modal.css +17 -10
- package/src/css/components/navbar.css +15 -10
- package/src/css/components/pagination.css +5 -0
- package/src/css/components/scrollspy.css +1 -1
- package/src/css/components/segmented.css +186 -0
- package/src/css/components/steps.css +72 -15
- package/src/css/components/tab.css +135 -5
- package/src/css/core/focus.css +11 -6
- package/src/css/core/theme.css +6 -1
- package/src/css/core/tokens.css +24 -6
- package/src/css/core/variants.css +16 -5
- package/src/css/forms/choice-card.css +1 -1
- package/src/css/forms/choice.css +1 -1
- package/src/css/forms/color-swatch.css +159 -0
- package/src/css/forms/control.css +1 -1
- package/src/css/forms/floating-field.css +20 -18
- package/src/css/forms/form-actions.css +14 -8
- package/src/css/forms/index.css +1 -0
- package/src/css/forms/native.css +51 -21
- package/src/css/forms/otp.css +4 -5
- package/src/css/forms/switch.css +6 -3
- package/src/css/forms/validation.css +23 -30
- package/src/css/layout/center.css +2 -0
- package/src/css/layout/cluster.css +14 -0
- package/src/css/layout/grid.css +7 -0
- package/src/css/layout/scroll-snap.css +30 -0
- package/src/css/layout/scroller.css +19 -3
- package/src/css/layout/sidebar-layout.css +3 -0
- package/src/css/layout/switcher.css +3 -0
- package/src/css/layout/topbar.css +10 -0
- package/src/css/utilities/extra.css +3 -3
- package/src/js/focus.js +16 -1
- package/src/js/full.js +4 -0
- package/src/js/inline-visible.js +54 -0
- package/src/js/parsed.js +21 -0
- package/src/js/surface.js +10 -0
- package/src/js/tab.js +33 -7
- package/src/tooling/color.js +14 -1
- package/src/tooling/css-bundle.js +0 -5
- package/src/tooling/design-components.js +9 -5
- package/src/tooling/design-export.js +9 -1
- package/src/tooling/design-tokens.js +207 -76
- package/src/tooling/theme-contrast.js +182 -45
|
@@ -5,17 +5,25 @@
|
|
|
5
5
|
* (`bun run report:theme-contrast`).
|
|
6
6
|
*
|
|
7
7
|
* Measured per island (one per color scheme the theme declares):
|
|
8
|
-
* - soft ink against the resting and the hovered soft fill (4.5:1)
|
|
8
|
+
* - soft ink against the resting and the hovered soft fill (4.5:1), on each
|
|
9
|
+
* surface a soft element sits on
|
|
10
|
+
* - the solid pairs, -fg on the intent fill (4.5:1); a fill painted by a
|
|
11
|
+
* background-image reads n/a
|
|
12
|
+
* - selected ink on those surfaces: the current nav link at rest and on
|
|
13
|
+
* hover, the selected tab and the current step number (4.5:1), and the
|
|
14
|
+
* current step's ring (3:1)
|
|
9
15
|
* - the focus line against --surface and --surface-solid (3:1)
|
|
10
16
|
* - the invalid-field focus line against the field's own background (3:1)
|
|
11
17
|
* - --surface-solid-fg on --surface-solid (4.5:1)
|
|
12
|
-
*
|
|
13
|
-
*
|
|
18
|
+
* Soft fills are translucent, so each is composited over the surface under it
|
|
19
|
+
* before it is measured: the pair is what the page displays, not the token.
|
|
20
|
+
* A surface that is itself translucent (a glass theme) has no defined
|
|
21
|
+
* backdrop, so its pairs read n/a instead of a number.
|
|
14
22
|
*
|
|
15
23
|
* Bun only: Bun.WebView drives the browser.
|
|
16
24
|
*/
|
|
17
25
|
import { fileURLToPath } from "node:url";
|
|
18
|
-
import { contrast, RASTERIZE } from "./color.js";
|
|
26
|
+
import { composite, contrast, RASTERIZE } from "./color.js";
|
|
19
27
|
import { inlineImports } from "./css-bundle.js";
|
|
20
28
|
import { themeNameOf } from "./design-tokens.js";
|
|
21
29
|
|
|
@@ -23,13 +31,35 @@ const FRAMEWORK = fileURLToPath(new URL("../css/actual.full.css", import.meta.ur
|
|
|
23
31
|
|
|
24
32
|
export const INTENTS = ["primary", "secondary", "success", "warning", "danger", "neutral"];
|
|
25
33
|
|
|
34
|
+
/* The surfaces a soft badge or button is expected to sit on. --surface-subtle
|
|
35
|
+
is usually the binding one: a translucent fill over it ends darker in light
|
|
36
|
+
and lighter in dark than over --surface. */
|
|
37
|
+
export const SURFACES = ["surface", "surface-raised", "surface-subtle"];
|
|
38
|
+
|
|
26
39
|
/* Badges state .soft explicitly: a theme may fill badges by default
|
|
27
|
-
(bootstrap-v6), which would measure a solid pair against the soft hover.
|
|
28
|
-
|
|
40
|
+
(bootstrap-v6), which would measure a solid pair against the soft hover.
|
|
41
|
+
The theme name is read from a file by a regex, so it never enters this
|
|
42
|
+
markup: measureContrast() sets it through dataset. `scheme` is only ever
|
|
43
|
+
"light" or "dark". */
|
|
44
|
+
function islandMarkup(id, scheme) {
|
|
29
45
|
const style = scheme ? ` style="color-scheme: ${scheme}"` : "";
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
const softPairs = SURFACES.map(
|
|
47
|
+
(surface) => `<div data-surface="${surface}" style="background: var(--${surface})">
|
|
48
|
+
${INTENTS.map((i) => `<span class="badge soft ${i}" data-badge="${i}">t</span>`).join("")}
|
|
49
|
+
${INTENTS.map((i) => `<button class="btn soft ${i}" data-hover="${i}" type="button">t</button>`).join("")}
|
|
50
|
+
<nav class="navbar" aria-label="t">
|
|
51
|
+
<a class="nav-link" href="#t" aria-current="page" data-selected="nav">t</a>
|
|
52
|
+
<a class="nav-link" href="#t" aria-current="page" data-selected="nav-hover">t</a>
|
|
53
|
+
</nav>
|
|
54
|
+
<div class="tabs" role="tablist">
|
|
55
|
+
<button class="tab" role="tab" aria-selected="true" type="button" data-selected="tab">t</button>
|
|
56
|
+
</div>
|
|
57
|
+
<ol class="steps"><li aria-current="step" data-selected="step"><span class="step-label">t</span></li></ol>
|
|
58
|
+
</div>`,
|
|
59
|
+
).join("");
|
|
60
|
+
return `<div${style} id="${id}">
|
|
61
|
+
${softPairs}
|
|
62
|
+
${INTENTS.map((i) => `<button class="btn solid ${i}" data-solid="${i}" type="button">t</button>`).join("")}
|
|
33
63
|
<span data-pair="focus-surface" style="color: var(--focus); background: var(--surface)"></span>
|
|
34
64
|
<span data-pair="focus-solid" style="color: var(--focus); background: var(--surface-solid)"></span>
|
|
35
65
|
<span data-pair="inverse" style="color: var(--surface-solid-fg); background: var(--surface-solid)"></span>
|
|
@@ -41,23 +71,27 @@ const ratio = (fg, bg) => (fg[3] < 255 || bg[3] < 255 ? null : contrast(fg, bg))
|
|
|
41
71
|
|
|
42
72
|
/*
|
|
43
73
|
* Measures each theme of `themes` (data-theme values) in `css`, the full
|
|
44
|
-
* stylesheet with imports inlined. `null` stands for the
|
|
45
|
-
*
|
|
46
|
-
* its `color-scheme` declares. Returns one
|
|
74
|
+
* stylesheet with imports inlined. `null` stands for the root's theme — the
|
|
75
|
+
* default one, or an application theme written on :root — labelled `label`.
|
|
76
|
+
* Each theme is read in every scheme its `color-scheme` declares. Returns one
|
|
77
|
+
* row per island.
|
|
47
78
|
*/
|
|
48
|
-
export async function measureContrast({ css, themes }) {
|
|
79
|
+
export async function measureContrast({ css, themes, label = "default" }) {
|
|
49
80
|
await using view = new Bun.WebView({ backend: "chrome" });
|
|
50
81
|
await view.navigate("about:blank");
|
|
51
82
|
|
|
52
83
|
// Transitions off: a forced :hover must compute its end state at once.
|
|
53
|
-
const islands = await view.evaluate(`((css, themes) => {
|
|
84
|
+
const islands = await view.evaluate(`((css, themes, label) => {
|
|
54
85
|
const style = document.createElement("style");
|
|
55
86
|
style.textContent = css + "\\n* { transition-duration: 0s !important; }";
|
|
56
87
|
document.head.append(style);
|
|
57
88
|
|
|
89
|
+
// A root island carries no data-theme: the core redeclares the default
|
|
90
|
+
// palette on [data-theme="light"|"dark"], which would mask a :root theme.
|
|
91
|
+
// Pinning color-scheme alone is what picks the light-dark() side.
|
|
58
92
|
const schemesOf = (attribute) => {
|
|
59
93
|
const probe = document.createElement("div");
|
|
60
|
-
probe.dataset.theme = attribute;
|
|
94
|
+
if (attribute) probe.dataset.theme = attribute;
|
|
61
95
|
document.body.append(probe);
|
|
62
96
|
const declared = getComputedStyle(probe).colorScheme.split(/\\s+/);
|
|
63
97
|
probe.remove();
|
|
@@ -65,26 +99,26 @@ export async function measureContrast({ css, themes }) {
|
|
|
65
99
|
return schemes.length > 0 ? schemes : ["light"];
|
|
66
100
|
};
|
|
67
101
|
|
|
68
|
-
return themes.flatMap((
|
|
69
|
-
theme
|
|
70
|
-
? ["light", "dark"].map((scheme) => ({ theme: "default", scheme, attribute: scheme, pin: false }))
|
|
71
|
-
: schemesOf(theme).map((scheme, _, all) => ({ theme, scheme, attribute: theme, pin: all.length > 1 })),
|
|
102
|
+
return themes.flatMap((attribute) =>
|
|
103
|
+
schemesOf(attribute).map((scheme) => ({ theme: attribute ?? label, scheme, attribute })),
|
|
72
104
|
);
|
|
73
|
-
})(${JSON.stringify(css)}, ${JSON.stringify(themes)})`);
|
|
105
|
+
})(${JSON.stringify(css)}, ${JSON.stringify(themes)}, ${JSON.stringify(label)})`);
|
|
74
106
|
|
|
75
|
-
const markup = islands
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
79
|
-
.
|
|
80
|
-
|
|
107
|
+
const markup = islands.map((island, i) => islandMarkup(`island-${i}`, island.scheme)).join("\n");
|
|
108
|
+
const attributes = islands.map((island) => island.attribute);
|
|
109
|
+
await view.evaluate(`((markup, attributes) => {
|
|
110
|
+
document.body.insertAdjacentHTML("beforeend", markup);
|
|
111
|
+
attributes.forEach((attribute, i) => {
|
|
112
|
+
if (attribute) document.getElementById("island-" + i).dataset.theme = attribute;
|
|
113
|
+
});
|
|
114
|
+
})(${JSON.stringify(markup)}, ${JSON.stringify(attributes)})`);
|
|
81
115
|
|
|
82
116
|
await view.cdp("DOM.enable");
|
|
83
117
|
await view.cdp("CSS.enable");
|
|
84
118
|
const { root } = await view.cdp("DOM.getDocument");
|
|
85
119
|
const { nodeIds } = await view.cdp("DOM.querySelectorAll", {
|
|
86
120
|
nodeId: root.nodeId,
|
|
87
|
-
selector:
|
|
121
|
+
selector: '[data-hover], [data-selected="nav-hover"]',
|
|
88
122
|
});
|
|
89
123
|
for (const nodeId of nodeIds) {
|
|
90
124
|
await view.cdp("CSS.forcePseudoState", { nodeId, forcedPseudoClasses: ["hover"] });
|
|
@@ -102,30 +136,101 @@ export async function measureContrast({ css, themes }) {
|
|
|
102
136
|
const fg = el.dataset.pair === "invalid-field" ? cs(el).borderBlockEndColor : cs(el).color;
|
|
103
137
|
pairs[el.dataset.pair] = { fg: rgba(fg), bg: rgba(cs(el).backgroundColor) };
|
|
104
138
|
}
|
|
139
|
+
// A solid fill painted by a background-image (a gradient theme) has no
|
|
140
|
+
// single color to measure: it is reported, not sampled.
|
|
141
|
+
const solid = {};
|
|
142
|
+
for (const el of island.querySelectorAll("[data-solid]")) {
|
|
143
|
+
solid[el.dataset.solid] =
|
|
144
|
+
cs(el).backgroundImage === "none"
|
|
145
|
+
? { fg: rgba(cs(el).color), bg: rgba(cs(el).backgroundColor) }
|
|
146
|
+
: null;
|
|
147
|
+
}
|
|
105
148
|
const soft = {};
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
149
|
+
// Fills from the surface down to the element, outermost first: the stack
|
|
150
|
+
// the page composites under a selected ink.
|
|
151
|
+
const layers = (surface, el) => {
|
|
152
|
+
const chain = [];
|
|
153
|
+
for (let n = el; n !== surface.parentElement; n = n.parentElement) chain.unshift(n);
|
|
154
|
+
return chain.map((n) => rgba(cs(n).backgroundColor));
|
|
155
|
+
};
|
|
156
|
+
for (const surface of island.querySelectorAll("[data-surface]")) {
|
|
157
|
+
const fills = {};
|
|
158
|
+
for (const el of surface.querySelectorAll("[data-badge]")) {
|
|
159
|
+
const hover = surface.querySelector('[data-hover="' + el.dataset.badge + '"]');
|
|
160
|
+
fills[el.dataset.badge] = {
|
|
161
|
+
fg: rgba(cs(el).color),
|
|
162
|
+
rest: rgba(cs(el).backgroundColor),
|
|
163
|
+
hover: rgba(cs(hover).backgroundColor),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const selected = {};
|
|
167
|
+
for (const el of surface.querySelectorAll("[data-selected]")) {
|
|
168
|
+
const kind = el.dataset.selected;
|
|
169
|
+
if (kind === "step") {
|
|
170
|
+
// The number and ring live on the marker pseudo; the ring reads
|
|
171
|
+
// against what is under the step, the number against the marker.
|
|
172
|
+
const marker = getComputedStyle(el, "::before");
|
|
173
|
+
const under = layers(surface, el);
|
|
174
|
+
selected[kind] = {
|
|
175
|
+
layers: [...under, rgba(marker.backgroundColor)],
|
|
176
|
+
fg: rgba(marker.color),
|
|
177
|
+
ring: { layers: under, fg: rgba(marker.borderTopColor) },
|
|
178
|
+
};
|
|
179
|
+
} else {
|
|
180
|
+
selected[kind] = { layers: layers(surface, el), fg: rgba(cs(el).color) };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
soft[surface.dataset.surface] = {
|
|
184
|
+
backdrop: rgba(cs(surface).backgroundColor),
|
|
185
|
+
fills,
|
|
186
|
+
selected,
|
|
112
187
|
};
|
|
113
188
|
}
|
|
114
|
-
return { pairs, soft };
|
|
189
|
+
return { pairs, soft, solid };
|
|
115
190
|
});
|
|
116
191
|
})()`);
|
|
117
192
|
|
|
118
193
|
return islands.map(({ theme, scheme }, i) => {
|
|
119
|
-
const { pairs, soft } = colors[i];
|
|
194
|
+
const { pairs, soft, solid } = colors[i];
|
|
120
195
|
const pair = (key) => ratio(pairs[key].fg, pairs[key].bg);
|
|
196
|
+
// Fill over the surface, then ink over the fill: the colors the page shows.
|
|
197
|
+
const shown = (fg, fill, backdrop) => {
|
|
198
|
+
const bg = composite(fill, backdrop);
|
|
199
|
+
return ratio(composite(fg, bg), bg);
|
|
200
|
+
};
|
|
201
|
+
const stacked = ({ layers, fg }) => {
|
|
202
|
+
const [base, ...rest] = layers;
|
|
203
|
+
const bg = rest.reduce((under, fill) => composite(fill, under), base);
|
|
204
|
+
return ratio(composite(fg, bg), bg);
|
|
205
|
+
};
|
|
121
206
|
return {
|
|
122
207
|
theme,
|
|
123
208
|
scheme,
|
|
124
|
-
|
|
209
|
+
solid: INTENTS.map((intent) => ({
|
|
125
210
|
intent,
|
|
126
|
-
|
|
127
|
-
hover: ratio(soft[intent].fg, soft[intent].hover),
|
|
211
|
+
value: solid[intent] ? ratio(solid[intent].fg, solid[intent].bg) : null,
|
|
128
212
|
})),
|
|
213
|
+
// Selected ink on a regular surface (--state-selected-text): text needs
|
|
214
|
+
// 4.5:1, the current step's ring is a non-text accent at 3:1.
|
|
215
|
+
selected: SURFACES.flatMap((surface) => {
|
|
216
|
+
const { selected } = soft[surface];
|
|
217
|
+
return [
|
|
218
|
+
{ kind: "nav link", surface, min: 4.5, value: stacked(selected.nav) },
|
|
219
|
+
{ kind: "nav link hover", surface, min: 4.5, value: stacked(selected["nav-hover"]) },
|
|
220
|
+
{ kind: "tab", surface, min: 4.5, value: stacked(selected.tab) },
|
|
221
|
+
{ kind: "step number", surface, min: 4.5, value: stacked(selected.step) },
|
|
222
|
+
{ kind: "step ring", surface, min: 3, value: stacked(selected.step.ring) },
|
|
223
|
+
];
|
|
224
|
+
}),
|
|
225
|
+
soft: SURFACES.flatMap((surface) => {
|
|
226
|
+
const { backdrop, fills } = soft[surface];
|
|
227
|
+
return INTENTS.map((intent) => ({
|
|
228
|
+
intent,
|
|
229
|
+
surface,
|
|
230
|
+
rest: shown(fills[intent].fg, fills[intent].rest, backdrop),
|
|
231
|
+
hover: shown(fills[intent].fg, fills[intent].hover, backdrop),
|
|
232
|
+
}));
|
|
233
|
+
}),
|
|
129
234
|
focusSurface: pair("focus-surface"),
|
|
130
235
|
focusSolid: pair("focus-solid"),
|
|
131
236
|
invalidFocus: pair("invalid-field"),
|
|
@@ -136,13 +241,14 @@ export async function measureContrast({ css, themes }) {
|
|
|
136
241
|
|
|
137
242
|
/*
|
|
138
243
|
* Measures one theme file over the framework, or the default theme without
|
|
139
|
-
* `themeFile`. `name` overrides the data-theme name read from the file
|
|
244
|
+
* `themeFile`. `name` overrides the data-theme name read from the file; a file
|
|
245
|
+
* that declares none is a :root theme.
|
|
140
246
|
*/
|
|
141
247
|
export async function measureThemeFile({ themeFile, name }) {
|
|
142
248
|
const themeCss = themeFile ? await inlineImports(themeFile) : "";
|
|
143
|
-
const theme = themeFile ?
|
|
249
|
+
const theme = themeFile ? themeNameOf(themeCss, name) : null;
|
|
144
250
|
const css = `${await inlineImports(FRAMEWORK)}\n${themeCss}`;
|
|
145
|
-
return measureContrast({ css, themes: [theme] });
|
|
251
|
+
return measureContrast({ css, themes: [theme], label: themeFile ? ":root" : "default" });
|
|
146
252
|
}
|
|
147
253
|
|
|
148
254
|
/* Text tables for `rows`, and the number of pairs under their threshold. */
|
|
@@ -159,15 +265,46 @@ export function formatContrast(rows) {
|
|
|
159
265
|
const flag = (cells, min) => (cells.some((c) => c.miss) ? ` <-- under ${min}` : "");
|
|
160
266
|
const lines = [];
|
|
161
267
|
|
|
162
|
-
lines.push(
|
|
163
|
-
|
|
268
|
+
lines.push(
|
|
269
|
+
"Soft pair contrast (ink vs resting / hovered fill, composited on each surface), needs 4.5:1.",
|
|
270
|
+
"",
|
|
271
|
+
);
|
|
272
|
+
lines.push(`${"theme".padEnd(width)} scheme surface intent rest hover`);
|
|
164
273
|
for (const row of rows) {
|
|
165
|
-
for (const { intent, rest, hover } of row.soft) {
|
|
274
|
+
for (const { intent, surface, rest, hover } of row.soft) {
|
|
166
275
|
// Both states gate: the hovered fill is usually the closer one, but a
|
|
167
276
|
// theme can invert that, and a 1:1 resting pair once passed silently.
|
|
168
277
|
const cells = [cell(rest, 4.5), cell(hover, 4.5)];
|
|
169
278
|
lines.push(
|
|
170
|
-
`${head(row)} ${intent.padEnd(9)} ${cells[0].text.padStart(8)} ${cells[1].text.padStart(8)}${flag(cells, 4.5)}`,
|
|
279
|
+
`${head(row)} ${surface.padEnd(15)} ${intent.padEnd(9)} ${cells[0].text.padStart(8)} ${cells[1].text.padStart(8)}${flag(cells, 4.5)}`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
lines.push(
|
|
285
|
+
"",
|
|
286
|
+
"Solid pair contrast (-fg on the intent fill), needs 4.5:1. n/a: a gradient fill, not sampled.",
|
|
287
|
+
"",
|
|
288
|
+
);
|
|
289
|
+
lines.push(`${"theme".padEnd(width)} scheme intent ratio`);
|
|
290
|
+
for (const row of rows) {
|
|
291
|
+
for (const { intent, value } of row.solid ?? []) {
|
|
292
|
+
const c = cell(value, 4.5);
|
|
293
|
+
lines.push(`${head(row)} ${intent.padEnd(9)} ${c.text.padStart(8)}${flag([c], 4.5)}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
lines.push(
|
|
298
|
+
"",
|
|
299
|
+
"Selected ink on a surface (--state-selected-text), text 4.5:1, step ring 3:1.",
|
|
300
|
+
"",
|
|
301
|
+
);
|
|
302
|
+
lines.push(`${"theme".padEnd(width)} scheme surface selected ratio`);
|
|
303
|
+
for (const row of rows) {
|
|
304
|
+
for (const { kind, surface, min, value } of row.selected ?? []) {
|
|
305
|
+
const c = cell(value, min);
|
|
306
|
+
lines.push(
|
|
307
|
+
`${head(row)} ${surface.padEnd(15)} ${kind.padEnd(15)} ${c.text.padStart(8)}${flag([c], min)}`,
|
|
171
308
|
);
|
|
172
309
|
}
|
|
173
310
|
}
|