@cueplusplus/ui 0.8.0 → 0.9.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 +262 -0
- package/README.md +49 -0
- package/dist/chat/message-list.js +2 -1
- package/dist/configurator/_export.d.ts +1 -1
- package/dist/configurator/_export.js +53 -13
- package/dist/configurator/_overrides.d.ts +25 -6
- package/dist/configurator/_overrides.js +30 -17
- package/dist/configurator/configurator.js +8 -3
- package/dist/configurator/panel-sections.js +43 -13
- package/dist/elements/command-palette.js +1 -1
- package/dist/elements/flow-graph.js +2 -2
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/surfaces.js +4 -3
- package/dist/index.d.ts +5 -2
- package/dist/index.js +2 -1
- package/dist/midi/piano-keyboard.js +5 -1
- package/dist/primitives/chip.d.ts +1 -1
- package/dist/styles.css +23 -1
- package/dist/system/density.d.ts +17 -8
- package/dist/system/density.js +39 -16
- package/dist/system/index.d.ts +5 -2
- package/dist/system/index.js +2 -1
- package/dist/system/overrides.d.ts +43 -0
- package/dist/system/overrides.js +238 -0
- package/dist/system/portal.d.ts +4 -2
- package/dist/system/portal.js +34 -3
- package/dist/system/prepaint.d.ts +58 -7
- package/dist/system/prepaint.js +72 -20
- package/dist/system/theme-provider.d.ts +133 -8
- package/dist/system/theme-provider.js +203 -72
- package/dist/system/theme-registry.d.ts +53 -0
- package/dist/system/theme-registry.js +66 -0
- package/dist/system/use-density.d.ts +13 -5
- package/dist/system/use-density.js +142 -13
- package/dist/system/use-theme.d.ts +5 -3
- package/dist/system/use-theme.js +5 -3
- package/dist/system/vocabulary.d.ts +15 -0
- package/dist/system/vocabulary.js +111 -0
- package/dist/theming/contrast.d.ts +2 -122
- package/dist/theming/contrast.js +2 -194
- package/dist/theming/create-theme.d.ts +37 -11
- package/dist/theming/create-theme.js +54 -17
- package/dist/theming/index.d.ts +3 -4
- package/dist/theming/index.js +3 -4
- package/dist/theming/serialize.d.ts +24 -11
- package/dist/theming/serialize.js +16 -18
- package/manifest/components/colors-section.json +2 -3
- package/manifest/components/cue-portal-frame.json +1 -1
- package/manifest/components/density.json +1 -1
- package/manifest/components/export-dialog.json +0 -3
- package/manifest/components/preset-section.json +2 -3
- package/manifest/components/shape-section.json +2 -3
- package/manifest/components/theme-configurator.json +0 -3
- package/manifest/components/theme-provider.json +24 -9
- package/manifest/components/token-editor.json +0 -3
- package/manifest/manifest.json +145 -24
- package/manifest/tokens.json +121 -11
- package/package.json +15 -6
- package/dist/theming/_presets.d.ts +0 -11
- package/dist/theming/_presets.js +0 -678
package/dist/theming/contrast.js
CHANGED
|
@@ -1,194 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* WCAG contrast, and the eleven pairs a theme is judged on.
|
|
5
|
-
*
|
|
6
|
-
* A generator that only derived colours would be a colour toy: the reason to
|
|
7
|
-
* compute a palette instead of picking one is that the computer can then *check*
|
|
8
|
-
* it, on every build, against the one part of visual design that is not a matter
|
|
9
|
-
* of taste. So `createTheme()` never hands back a theme without a report, and
|
|
10
|
-
* the report is the deliverable — a failing pair is information, not an error to
|
|
11
|
-
* swallow.
|
|
12
|
-
*
|
|
13
|
-
* The maths is WCAG 2.x relative luminance (sRGB, linearised, 0.2126/0.7152/
|
|
14
|
-
* 0.0722), deliberately not APCA. APCA is better, and it is also not what
|
|
15
|
-
* anyone's compliance checklist says; a theme that has to survive an audit needs
|
|
16
|
-
* the number the audit will compute.
|
|
17
|
-
*/
|
|
18
|
-
const toRgb = converter("rgb");
|
|
19
|
-
/** Non-text and large-text AA: UI components, graphics, status dots. */
|
|
20
|
-
const WCAG_AA_NON_TEXT = 3;
|
|
21
|
-
/** Body-text AA. The floor for anything a reader is expected to read. */
|
|
22
|
-
const WCAG_AA_TEXT = 4.5;
|
|
23
|
-
/** Body-text AAA. What this portfolio holds its primary ink to. */
|
|
24
|
-
const WCAG_AAA_TEXT = 7;
|
|
25
|
-
/** sRGB gamma decode, per WCAG's definition of relative luminance. */
|
|
26
|
-
function linearize(channel) {
|
|
27
|
-
return channel <= .04045 ? channel / 12.92 : ((channel + .055) / 1.055) ** 2.4;
|
|
28
|
-
}
|
|
29
|
-
/** Parse to sRGB, or say which value could not be read. */
|
|
30
|
-
function toSrgb(input, label = "colour") {
|
|
31
|
-
const color = toRgb(parse(input));
|
|
32
|
-
if (color === void 0) throw new TypeError(`${label} is not a colour this can measure: ${JSON.stringify(input)}`);
|
|
33
|
-
return color;
|
|
34
|
-
}
|
|
35
|
-
/** Source-over composite of `over` onto `under`, in (non-linear) sRGB. */
|
|
36
|
-
function composite(over, under) {
|
|
37
|
-
const alpha = over.alpha ?? 1;
|
|
38
|
-
if (alpha >= 1) return over;
|
|
39
|
-
return {
|
|
40
|
-
r: over.r * alpha + under.r * (1 - alpha),
|
|
41
|
-
g: over.g * alpha + under.g * (1 - alpha),
|
|
42
|
-
b: over.b * alpha + under.b * (1 - alpha)
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
function luminanceOf(color) {
|
|
46
|
-
return .2126 * linearize(color.r) + .7152 * linearize(color.g) + .0722 * linearize(color.b);
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* WCAG relative luminance: 0 for black, 1 for white.
|
|
50
|
-
*
|
|
51
|
-
* Alpha is ignored — a translucent colour has no luminance of its own, only one
|
|
52
|
-
* in front of something. {@link contrastRatio} is where that composite happens.
|
|
53
|
-
*
|
|
54
|
-
* @param color - Any CSS colour string.
|
|
55
|
-
* @returns Relative luminance in `[0, 1]`.
|
|
56
|
-
* @throws TypeError if the value is not a colour.
|
|
57
|
-
* @example
|
|
58
|
-
* relativeLuminance("#767676"); // → 0.1845…
|
|
59
|
-
*/
|
|
60
|
-
function relativeLuminance(color) {
|
|
61
|
-
return luminanceOf(toSrgb(color));
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* WCAG contrast ratio between two colours, from 1:1 to 21:1.
|
|
65
|
-
*
|
|
66
|
-
* Symmetric, because a ratio has no direction — the arguments are named for
|
|
67
|
-
* readability, not for order. A **translucent foreground is composited over the
|
|
68
|
-
* background first**, which is not decoration: half this system's `fg-muted`
|
|
69
|
-
* tokens are white-alpha ink, and measuring them raw would report the ratio of
|
|
70
|
-
* pure white and pass everything.
|
|
71
|
-
*
|
|
72
|
-
* @param foreground - The ink. Composited over `background` if it has alpha.
|
|
73
|
-
* @param background - The ground. Its own alpha is ignored — nothing is behind it.
|
|
74
|
-
* @returns The ratio, `>= 1`.
|
|
75
|
-
* @throws TypeError naming whichever value could not be read.
|
|
76
|
-
* @example
|
|
77
|
-
* contrastRatio("#767676", "#ffffff"); // → 4.5422…
|
|
78
|
-
*/
|
|
79
|
-
function contrastRatio(foreground, background) {
|
|
80
|
-
const ground = toSrgb(background, "background");
|
|
81
|
-
const [high, low] = [luminanceOf(composite(toSrgb(foreground, "foreground"), ground)), luminanceOf(ground)].toSorted((a, b) => b - a);
|
|
82
|
-
return (high + .05) / (low + .05);
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* The eleven pairs that decide whether a theme is usable.
|
|
86
|
-
*
|
|
87
|
-
* Not every pair in the palette: the ones a reader's comprehension actually
|
|
88
|
-
* depends on. Primary ink is held to AAA because it carries the prose; secondary
|
|
89
|
-
* ink and accent ink to AA because they carry labels; the five status colours
|
|
90
|
-
* and the live hue to the non-text AA floor because they are read as *signals*
|
|
91
|
-
* — a dot, a rim, a bar — never as body copy.
|
|
92
|
-
*
|
|
93
|
-
* `danger` and `warn` each appear twice, and they are the only tones that do.
|
|
94
|
-
* `ok`, `busy`, `info` and `stream` are *only* ever signals, so the non-text
|
|
95
|
-
* floor is the whole truth about them. The red and the amber are also grounds:
|
|
96
|
-
* they are the two tones this system fills a control with, and what they fill is
|
|
97
|
-
* the confirm on something irreversible and the confirm on something merely
|
|
98
|
-
* consequential. Text on either fill is text, so its ink is held to the text
|
|
99
|
-
* floor against the tone it sits on rather than against the page.
|
|
100
|
-
*
|
|
101
|
-
* `stream` is measured for the same reason `ok` is: `createTheme()` darkens it
|
|
102
|
-
* to this floor when it has to invent a light block, and a report that did not
|
|
103
|
-
* check what the derivation targets would be checking the wrong thing.
|
|
104
|
-
*/
|
|
105
|
-
const CONTRAST_REQUIREMENTS = [
|
|
106
|
-
{
|
|
107
|
-
id: "fg/bg",
|
|
108
|
-
foreground: "fg",
|
|
109
|
-
background: "bg",
|
|
110
|
-
minimum: 7,
|
|
111
|
-
reason: "Primary ink carries the prose, and this portfolio holds it to AAA."
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
id: "fg-muted/bg",
|
|
115
|
-
foreground: "fg-muted",
|
|
116
|
-
background: "bg",
|
|
117
|
-
minimum: WCAG_AA_TEXT,
|
|
118
|
-
reason: "Secondary ink is still text: labels, captions, table headers."
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
id: "accent-fg/accent",
|
|
122
|
-
foreground: "accent-fg",
|
|
123
|
-
background: "accent",
|
|
124
|
-
minimum: WCAG_AA_TEXT,
|
|
125
|
-
reason: "The label on a solid accent button has to be readable."
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
id: "danger-fg/danger",
|
|
129
|
-
foreground: "danger-fg",
|
|
130
|
-
background: "danger",
|
|
131
|
-
minimum: WCAG_AA_TEXT,
|
|
132
|
-
reason: "The label on a solid destructive fill is read before something irreversible happens."
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
id: "warn-fg/warn",
|
|
136
|
-
foreground: "warn-fg",
|
|
137
|
-
background: "warn",
|
|
138
|
-
minimum: WCAG_AA_TEXT,
|
|
139
|
-
reason: "The label on a solid warning fill is read before a consequential press lands."
|
|
140
|
-
},
|
|
141
|
-
...[
|
|
142
|
-
"ok",
|
|
143
|
-
"busy",
|
|
144
|
-
"warn",
|
|
145
|
-
"danger",
|
|
146
|
-
"info",
|
|
147
|
-
"stream"
|
|
148
|
-
].map((status) => ({
|
|
149
|
-
id: `${status}/bg`,
|
|
150
|
-
foreground: status,
|
|
151
|
-
background: "bg",
|
|
152
|
-
minimum: 3,
|
|
153
|
-
reason: `The ${status} tone is read as a signal — dot, rim, bar — not as body copy.`
|
|
154
|
-
}))
|
|
155
|
-
];
|
|
156
|
-
/**
|
|
157
|
-
* Measure every requirement against every block handed in.
|
|
158
|
-
*
|
|
159
|
-
* A pair whose tokens are missing is **skipped**, not failed: the report says
|
|
160
|
-
* what it measured, and a caller checking a partial palette should not be told
|
|
161
|
-
* that a colour it never supplied is unreadable.
|
|
162
|
-
*
|
|
163
|
-
* @param inputs - One entry per block, dark first by convention.
|
|
164
|
-
* @returns The report.
|
|
165
|
-
* @example
|
|
166
|
-
* contrastReport([{ mode: "dark", tokens: { fg: "#fff", bg: "#000" } }]).passes; // → true
|
|
167
|
-
*/
|
|
168
|
-
function contrastReport(inputs) {
|
|
169
|
-
const checks = [];
|
|
170
|
-
for (const { mode, tokens, provisional = false } of inputs) for (const requirement of CONTRAST_REQUIREMENTS) {
|
|
171
|
-
const foregroundValue = tokens[requirement.foreground];
|
|
172
|
-
const backgroundValue = tokens[requirement.background];
|
|
173
|
-
if (foregroundValue === void 0 || backgroundValue === void 0) continue;
|
|
174
|
-
const ratio = contrastRatio(foregroundValue, backgroundValue);
|
|
175
|
-
checks.push({
|
|
176
|
-
...requirement,
|
|
177
|
-
mode,
|
|
178
|
-
foregroundValue,
|
|
179
|
-
backgroundValue,
|
|
180
|
-
ratio,
|
|
181
|
-
passes: ratio >= requirement.minimum,
|
|
182
|
-
provisional
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
const failures = checks.filter((check) => !check.passes);
|
|
186
|
-
return {
|
|
187
|
-
passes: failures.length === 0,
|
|
188
|
-
provisional: checks.some((check) => check.provisional),
|
|
189
|
-
checks,
|
|
190
|
-
failures
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
//#endregion
|
|
194
|
-
export { CONTRAST_REQUIREMENTS, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport, relativeLuminance };
|
|
1
|
+
import { CONTRAST_REQUIREMENTS, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport as contrastReport$1, relativeLuminance } from "@cueplusplus/theme-base/contrast";
|
|
2
|
+
export { CONTRAST_REQUIREMENTS, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport$1 as contrastReport, relativeLuminance };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ContrastReport } from "./contrast.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { Density } from "@cueplusplus/tokens";
|
|
3
|
+
import { ColorToken, ThemeManifest } from "@cueplusplus/theme-base";
|
|
4
4
|
//#region src/theming/create-theme.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* `createTheme()` — the typed generator behind the spec's second theming tier.
|
|
@@ -25,8 +25,21 @@ import { ThemeName } from "@cueplusplus/tokens";
|
|
|
25
25
|
* the report rather than being silently repaired — with one exception, noted
|
|
26
26
|
* below, where the value was this module's invention in the first place.
|
|
27
27
|
*/
|
|
28
|
-
/**
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* The colour tokens every theme block declares, in the order the contract lists
|
|
30
|
+
* them.
|
|
31
|
+
*
|
|
32
|
+
* Not a list this module owns any more, and not a copy of one: it *is*
|
|
33
|
+
* `COLOR_CONTRACT`, re-exported under the name this package's public API has
|
|
34
|
+
* always used. The two were byte-identical — twenty-six names, same order —
|
|
35
|
+
* because the copy was written from the contract in the first place, and a
|
|
36
|
+
* second list that has to agree with the first is a drift test waiting to be
|
|
37
|
+
* written. `test/theming/create-theme.test.ts` asserts the identity, so a future
|
|
38
|
+
* edit that quietly reintroduces a local array fails rather than diverges.
|
|
39
|
+
*/
|
|
40
|
+
declare const THEME_COLOR_TOKENS: readonly ["bg", "sunken", "surface-1", "surface-2", "surface-3", "fg", "fg-muted", "fg-subtle", "border", "border-strong", "border-overlay", "accent", "accent-hover", "accent-fg", "ok", "busy", "warn", "warn-fg", "danger", "danger-fg", "info", "stream", "selection", "focus", "data-ground", "scrim"];
|
|
41
|
+
/** One of the colour tokens a theme block owns. */
|
|
42
|
+
type ThemeColorToken = ColorToken;
|
|
30
43
|
/**
|
|
31
44
|
* How far the sunken ground sits below the page, in oklch lightness.
|
|
32
45
|
*
|
|
@@ -85,10 +98,22 @@ interface ThemeAnchors {
|
|
|
85
98
|
*/
|
|
86
99
|
name: string;
|
|
87
100
|
/**
|
|
88
|
-
* The
|
|
89
|
-
*
|
|
101
|
+
* The manifest to start from. Every token you do not move comes back verbatim.
|
|
102
|
+
*
|
|
103
|
+
* A manifest, not a name: this package holds no table of palettes to look a
|
|
104
|
+
* name up in, and one is exactly what it stopped holding. Pass the default
|
|
105
|
+
* export of a theme package — `@cueplusplus/theme-terminal`'s, say — or any
|
|
106
|
+
* manifest of your own. Absent means the blank base: the neutral ramp
|
|
107
|
+
* `@cueplusplus/theme-base` ships, which every theme's deltas were diffed
|
|
108
|
+
* against.
|
|
109
|
+
*
|
|
110
|
+
* The package is named in prose rather than shown as an `import` line on
|
|
111
|
+
* purpose. Every barrel test in this package scrapes imports with a regex
|
|
112
|
+
* that cannot tell a doc comment from code, and to those tests an example
|
|
113
|
+
* import reads as `ui` depending on a theme package — which it may not, at
|
|
114
|
+
* runtime or otherwise.
|
|
90
115
|
*/
|
|
91
|
-
base?:
|
|
116
|
+
base?: ThemeManifest;
|
|
92
117
|
/**
|
|
93
118
|
* The ground. Moving it re-derives the sunken well, the three raised surfaces
|
|
94
119
|
* and the three border weights.
|
|
@@ -156,14 +181,15 @@ interface CreatedTheme {
|
|
|
156
181
|
* configurator's export dialog — never in a render path, because there is
|
|
157
182
|
* nothing here a stylesheet is not already doing faster.
|
|
158
183
|
*
|
|
159
|
-
* @param anchors - The name, the
|
|
184
|
+
* @param anchors - The name, the manifest to start from, and whatever you moved.
|
|
160
185
|
* @returns The stylesheet, the resolved tokens for both modes, and the report.
|
|
161
186
|
* @throws TypeError if the name is not a CSS identifier, an anchor is not a
|
|
162
|
-
* colour, a font stack carries CSS punctuation, or
|
|
187
|
+
* colour, a font stack carries CSS punctuation, or `base` is a theme *name*
|
|
188
|
+
* rather than a manifest.
|
|
163
189
|
* @example
|
|
164
|
-
* const { css, report } = createTheme({ name: "acme",
|
|
190
|
+
* const { css, report } = createTheme({ name: "acme", accent: "#ff8800" });
|
|
165
191
|
* if (!report.passes) console.warn(report.failures);
|
|
166
192
|
*/
|
|
167
193
|
declare function createTheme(anchors: ThemeAnchors): CreatedTheme;
|
|
168
194
|
//#endregion
|
|
169
|
-
export { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, CreatedTheme,
|
|
195
|
+
export { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, CreatedTheme, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, THEME_COLOR_TOKENS, ThemeAnchors, ThemeColorToken, createTheme };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { formatColorString } from "../color/_convert.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport } from "./contrast.js";
|
|
3
|
+
import { RESOLVED_MONO, assertCssValue, assertThemeName, derivedTokens, serializeSystemModeAliases, serializeThemeCss, themeSelector, tokenProperty } from "./serialize.js";
|
|
4
|
+
import { COLOR_CONTRACT, resolve } from "@cueplusplus/theme-base";
|
|
5
5
|
import { clampChroma, converter, formatHex, inGamut, parse } from "culori";
|
|
6
|
+
import tokensBase from "@cueplusplus/tokens/base.json" with { type: "json" };
|
|
6
7
|
//#region src/theming/create-theme.ts
|
|
7
8
|
/**
|
|
8
9
|
* `createTheme()` — the typed generator behind the spec's second theming tier.
|
|
@@ -27,8 +28,36 @@ import { clampChroma, converter, formatHex, inGamut, parse } from "culori";
|
|
|
27
28
|
* the report rather than being silently repaired — with one exception, noted
|
|
28
29
|
* below, where the value was this module's invention in the first place.
|
|
29
30
|
*/
|
|
30
|
-
/**
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The colour tokens every theme block declares, in the order the contract lists
|
|
33
|
+
* them.
|
|
34
|
+
*
|
|
35
|
+
* Not a list this module owns any more, and not a copy of one: it *is*
|
|
36
|
+
* `COLOR_CONTRACT`, re-exported under the name this package's public API has
|
|
37
|
+
* always used. The two were byte-identical — twenty-six names, same order —
|
|
38
|
+
* because the copy was written from the contract in the first place, and a
|
|
39
|
+
* second list that has to agree with the first is a drift test waiting to be
|
|
40
|
+
* written. `test/theming/create-theme.test.ts` asserts the identity, so a future
|
|
41
|
+
* edit that quietly reintroduces a local array fails rather than diverges.
|
|
42
|
+
*/
|
|
43
|
+
const THEME_COLOR_TOKENS = COLOR_CONTRACT;
|
|
44
|
+
/**
|
|
45
|
+
* The radius multiplier each density level contributes.
|
|
46
|
+
*
|
|
47
|
+
* Geometry belongs to the density axis, so a theme that wants rounder corners
|
|
48
|
+
* cannot simply declare `--cue-radius-scale`: its block would outrank the density
|
|
49
|
+
* block and freeze ultra-compact's 0.75 at 1. {@link createTheme} instead
|
|
50
|
+
* multiplies the two and emits one crossed rule per density that differs from the
|
|
51
|
+
* default — which is why this ladder has to be known here.
|
|
52
|
+
*
|
|
53
|
+
* `Number(…)` because `base.json` is DTCG, where every value is text: the scale
|
|
54
|
+
* arrives as `"0.75"` and what happens to it next is arithmetic. Keyed by the
|
|
55
|
+
* base five alone, because those are the rungs a build-time generator can know
|
|
56
|
+
* the geometry of; a rung a theme adds carries its own radius in its manifest.
|
|
57
|
+
*/
|
|
58
|
+
const DENSITY_RADIUS_SCALE = Object.fromEntries(Object.keys(tokensBase.densities).map((rung) => [rung, Number(tokensBase.densities[rung]["radius-scale"])]));
|
|
59
|
+
/** The default density, whose radius scale the uncrossed theme block carries. */
|
|
60
|
+
const DEFAULT_DENSITY_LEVEL = tokensBase.defaults.density;
|
|
32
61
|
/**
|
|
33
62
|
* How far the sunken ground sits below the page, in oklch lightness.
|
|
34
63
|
*
|
|
@@ -333,7 +362,7 @@ function resolveLight(dark, preset) {
|
|
|
333
362
|
const accent = colors["accent"];
|
|
334
363
|
define("accent-hover", () => shift(readColor(accent, "accent"), ACCENT_HOVER_LIGHTNESS_STEP));
|
|
335
364
|
define("accent-fg", () => pickInk(accent, [colors["bg"], colors["fg"]]));
|
|
336
|
-
for (const tone of TINTED) define(tone, () => darkenToContrast(dark.colors[tone], paperGround,
|
|
365
|
+
for (const tone of TINTED) define(tone, () => darkenToContrast(dark.colors[tone], paperGround, WCAG_AA_NON_TEXT, tone));
|
|
337
366
|
for (const tone of INKED) define(`${tone}-fg`, () => pickInk(colors[tone], [
|
|
338
367
|
colors["bg"],
|
|
339
368
|
colors["sunken"],
|
|
@@ -358,11 +387,11 @@ function resolveLight(dark, preset) {
|
|
|
358
387
|
* ancestor, purely by sitting nearer to the text, and a consumer's own preset
|
|
359
388
|
* would be the one preset in the system the typeface picker could not move.
|
|
360
389
|
*/
|
|
361
|
-
function resolveFonts(anchors, preset) {
|
|
390
|
+
function resolveFonts$1(anchors, preset) {
|
|
362
391
|
const fonts = anchors.fonts ?? {};
|
|
363
392
|
const declared = [];
|
|
364
393
|
if (fonts.sans !== void 0) declared.push(["font-sans", fonts.sans]);
|
|
365
|
-
declared.push(["font-theme-mono", fonts.mono ?? lookup(preset, "font-mono") ??
|
|
394
|
+
declared.push(["font-theme-mono", fonts.mono ?? lookup(preset, "font-mono") ?? tokensBase.base["font-mono"]]);
|
|
366
395
|
if (fonts.display !== void 0) declared.push(["font-display", fonts.display]);
|
|
367
396
|
return [...declared.map(([name, value]) => [tokenProperty(name), assertCssValue(name, value)]), [tokenProperty("font-mono"), RESOLVED_MONO]];
|
|
368
397
|
}
|
|
@@ -385,7 +414,10 @@ function radiusBlocks(name, radiusScale) {
|
|
|
385
414
|
for (const [level, scale] of Object.entries(DENSITY_RADIUS_SCALE)) {
|
|
386
415
|
if (scale === fallback) continue;
|
|
387
416
|
blocks.push({
|
|
388
|
-
selector: themeSelector(name, {
|
|
417
|
+
selector: themeSelector(name, {
|
|
418
|
+
density: level,
|
|
419
|
+
pair: true
|
|
420
|
+
}),
|
|
389
421
|
sections: [{ declarations: [[property, scaled(radiusScale, scale)]] }]
|
|
390
422
|
});
|
|
391
423
|
}
|
|
@@ -408,24 +440,29 @@ const SHAPE_COMMENT = "shape — crossed with the density ladder below, never fl
|
|
|
408
440
|
* configurator's export dialog — never in a render path, because there is
|
|
409
441
|
* nothing here a stylesheet is not already doing faster.
|
|
410
442
|
*
|
|
411
|
-
* @param anchors - The name, the
|
|
443
|
+
* @param anchors - The name, the manifest to start from, and whatever you moved.
|
|
412
444
|
* @returns The stylesheet, the resolved tokens for both modes, and the report.
|
|
413
445
|
* @throws TypeError if the name is not a CSS identifier, an anchor is not a
|
|
414
|
-
* colour, a font stack carries CSS punctuation, or
|
|
446
|
+
* colour, a font stack carries CSS punctuation, or `base` is a theme *name*
|
|
447
|
+
* rather than a manifest.
|
|
415
448
|
* @example
|
|
416
|
-
* const { css, report } = createTheme({ name: "acme",
|
|
449
|
+
* const { css, report } = createTheme({ name: "acme", accent: "#ff8800" });
|
|
417
450
|
* if (!report.passes) console.warn(report.failures);
|
|
418
451
|
*/
|
|
419
452
|
function createTheme(anchors) {
|
|
420
453
|
const name = assertThemeName(anchors.name);
|
|
421
|
-
|
|
422
|
-
const
|
|
423
|
-
|
|
454
|
+
if (typeof anchors.base === "string") throw new TypeError(`base is a manifest, not a name — import it from @cueplusplus/theme-${anchors.base}`);
|
|
455
|
+
const base = anchors.base ?? null;
|
|
456
|
+
const preset = {
|
|
457
|
+
dark: resolve(base, { mode: "dark" }).colors,
|
|
458
|
+
light: base === null || base.supportsLight ? resolve(base, { mode: "light" }).colors : null,
|
|
459
|
+
fonts: base?.fonts ?? {}
|
|
460
|
+
};
|
|
424
461
|
if (anchors.radiusScale !== void 0 && !(anchors.radiusScale > 0)) throw new TypeError(`radiusScale must be a positive number, got ${anchors.radiusScale}`);
|
|
425
462
|
const dark = resolveDark(anchors, preset.dark);
|
|
426
463
|
const light = anchors.supportsLight ?? preset.light !== null ? resolveLight(dark, preset.light) : null;
|
|
427
464
|
const derived = Object.entries(derivedTokens());
|
|
428
|
-
const fonts = resolveFonts(anchors, preset.fonts);
|
|
465
|
+
const fonts = resolveFonts$1(anchors, preset.fonts);
|
|
429
466
|
const radius = anchors.radiusScale === void 0 ? null : radiusBlocks(name, anchors.radiusScale);
|
|
430
467
|
const darkPaletteSections = [
|
|
431
468
|
{ declarations: colorDeclarations(dark.colors) },
|
|
@@ -484,4 +521,4 @@ function createTheme(anchors) {
|
|
|
484
521
|
};
|
|
485
522
|
}
|
|
486
523
|
//#endregion
|
|
487
|
-
export { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS,
|
|
524
|
+
export { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, DEFAULT_DENSITY_LEVEL, DENSITY_RADIUS_SCALE, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, THEME_COLOR_TOKENS, createTheme };
|
package/dist/theming/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { CONTRAST_REQUIREMENTS, ContrastCheck, ContrastInput, ContrastReport, ContrastRequirement, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport, relativeLuminance } from "./contrast.js";
|
|
2
|
-
import { THEME_COLOR_TOKENS, ThemeColorToken } from "./
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
export { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, CONTRAST_REQUIREMENTS, CUE_TOKEN_PREFIX, type ContrastCheck, type ContrastInput, type ContrastReport, type ContrastRequirement, type CreatedTheme, DEFAULT_THEME_BASE, DERIVED_TOKEN_TEMPLATES, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, type SerializeThemeCssOptions, THEME_COLOR_TOKENS, THEME_NAME_PATTERN, type ThemeAnchors, type ThemeBlock, type ThemeBlockSection, type ThemeColorToken, type ThemeSelectorOptions, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, assertCssValue, assertThemeName, contrastRatio, contrastReport, createTheme, derivedTokens, relativeLuminance, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };
|
|
2
|
+
import { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, CreatedTheme, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, THEME_COLOR_TOKENS, ThemeAnchors, ThemeColorToken, createTheme } from "./create-theme.js";
|
|
3
|
+
import { CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, RESOLVED_MONO, SerializeThemeCssOptions, THEME_NAME_PATTERN, ThemeBlock, ThemeBlockSection, ThemeSelectorOptions, assertCssValue, assertThemeName, derivedTokens, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty } from "./serialize.js";
|
|
4
|
+
export { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, CONTRAST_REQUIREMENTS, CUE_TOKEN_PREFIX, type ContrastCheck, type ContrastInput, type ContrastReport, type ContrastRequirement, type CreatedTheme, DERIVED_TOKEN_TEMPLATES, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, RESOLVED_MONO, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, type SerializeThemeCssOptions, THEME_COLOR_TOKENS, THEME_NAME_PATTERN, type ThemeAnchors, type ThemeBlock, type ThemeBlockSection, type ThemeColorToken, type ThemeSelectorOptions, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, assertCssValue, assertThemeName, contrastRatio, contrastReport, createTheme, derivedTokens, relativeLuminance, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };
|
package/dist/theming/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { THEME_COLOR_TOKENS } from "./_presets.js";
|
|
2
1
|
import { CONTRAST_REQUIREMENTS, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport, relativeLuminance } from "./contrast.js";
|
|
3
|
-
import { CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, THEME_NAME_PATTERN, assertCssValue, assertThemeName, derivedTokens, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty } from "./serialize.js";
|
|
4
|
-
import { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS,
|
|
5
|
-
export { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, CONTRAST_REQUIREMENTS, CUE_TOKEN_PREFIX,
|
|
2
|
+
import { CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, RESOLVED_MONO, THEME_NAME_PATTERN, assertCssValue, assertThemeName, derivedTokens, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty } from "./serialize.js";
|
|
3
|
+
import { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, THEME_COLOR_TOKENS, createTheme } from "./create-theme.js";
|
|
4
|
+
export { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, CONTRAST_REQUIREMENTS, CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, RESOLVED_MONO, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, THEME_COLOR_TOKENS, THEME_NAME_PATTERN, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, assertCssValue, assertThemeName, contrastRatio, contrastReport, createTheme, derivedTokens, relativeLuminance, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Density } from "@cueplusplus/tokens";
|
|
2
|
+
import { RESOLVED_MONO, THEME_NAME_PATTERN as THEME_NAME_PATTERN$1 } from "@cueplusplus/theme-base";
|
|
2
3
|
//#region src/theming/serialize.d.ts
|
|
3
4
|
/**
|
|
4
5
|
* Turning a resolved token map back into the stylesheet the tokens build would
|
|
@@ -10,7 +11,10 @@ import { Density } from "@cueplusplus/tokens";
|
|
|
10
11
|
* `[data-theme]` block, or a consumer ends up with two vocabularies and the
|
|
11
12
|
* generator becomes a toy. So the shapes below mirror `build.mjs` deliberately,
|
|
12
13
|
* comments included: same selector form, same declaration order, same section
|
|
13
|
-
* breaks. `test/theming/presets.test.ts`
|
|
14
|
+
* breaks. The generator-versus-build parity that `test/theming/presets.test.ts`
|
|
15
|
+
* used to diff is unguarded for this plan — the shipped preset copy that test
|
|
16
|
+
* compared against was deleted with it — and Plan 4 restores the comparison
|
|
17
|
+
* once `@cueplusplus/tokens` stops emitting presets of its own.
|
|
14
18
|
*
|
|
15
19
|
* It is also the only place that touches raw strings, which makes it the place
|
|
16
20
|
* to stop injection. A theme name lands inside an attribute selector and a font
|
|
@@ -18,15 +22,6 @@ import { Density } from "@cueplusplus/tokens";
|
|
|
18
22
|
*/
|
|
19
23
|
/** The one custom-property prefix this design system emits. */
|
|
20
24
|
declare const CUE_TOKEN_PREFIX = "--cue-";
|
|
21
|
-
/**
|
|
22
|
-
* What a theme may be called.
|
|
23
|
-
*
|
|
24
|
-
* The name is interpolated into `[data-theme="…"]`, so the pattern is a
|
|
25
|
-
* whitelist rather than an escape: a CSS identifier, which is also what a
|
|
26
|
-
* `data-theme` attribute is expected to hold and what a file called
|
|
27
|
-
* `<name>.css` can be named.
|
|
28
|
-
*/
|
|
29
|
-
declare const THEME_NAME_PATTERN: RegExp;
|
|
30
25
|
/**
|
|
31
26
|
* Check a theme name and hand it back.
|
|
32
27
|
*
|
|
@@ -65,6 +60,22 @@ interface ThemeSelectorOptions {
|
|
|
65
60
|
mode?: "dark" | "light";
|
|
66
61
|
/** Cross with the density axis. Used only for the radius multiplier. */
|
|
67
62
|
density?: Density;
|
|
63
|
+
/**
|
|
64
|
+
* Emit the descendant form beside the compound one, as `"A, B"`.
|
|
65
|
+
*
|
|
66
|
+
* Only meaningful together with `density`, and it exists because the two axes
|
|
67
|
+
* do **not** always land on the same element. The compound
|
|
68
|
+
* `[data-theme="x"][data-density="y"]` matches only a node carrying both
|
|
69
|
+
* attributes — the provider root, and nothing else. A `<Density>` island
|
|
70
|
+
* stamps `data-density` on a `<div>` *inside* that root, and
|
|
71
|
+
* `useControlHeight`'s measuring probe hangs off `document.body`, outside it
|
|
72
|
+
* entirely; both therefore carry a density with no theme beside it, and both
|
|
73
|
+
* were silently missing the crossed value. The descendant form
|
|
74
|
+
* `[data-theme="x"] [data-density="y"]` covers exactly those, and the pair is
|
|
75
|
+
* emitted rather than the descendant alone because the descendant does not
|
|
76
|
+
* match the root itself.
|
|
77
|
+
*/
|
|
78
|
+
pair?: boolean;
|
|
68
79
|
}
|
|
69
80
|
/**
|
|
70
81
|
* The selector a theme's declarations live under.
|
|
@@ -78,6 +89,8 @@ interface ThemeSelectorOptions {
|
|
|
78
89
|
* @returns The CSS selector.
|
|
79
90
|
* @example
|
|
80
91
|
* themeSelector("acme", { mode: "light" }); // → '[data-theme="acme"][data-mode="light"]'
|
|
92
|
+
* themeSelector("acme", { density: "compact", pair: true });
|
|
93
|
+
* // → '[data-theme="acme"] [data-density="compact"]:where([data-cue-theme="acme"]), [data-theme="acme"] [data-density="compact"]:where(:not([data-cue-theme]):not([data-theme]:not([data-theme="acme"])):not([data-theme="acme"] [data-theme]:not([data-theme="acme"]) *)), [data-theme="acme"][data-density="compact"]'
|
|
81
94
|
*/
|
|
82
95
|
declare function themeSelector(name: string, options?: ThemeSelectorOptions): string;
|
|
83
96
|
/**
|
|
@@ -151,4 +164,4 @@ interface SerializeThemeCssOptions {
|
|
|
151
164
|
*/
|
|
152
165
|
declare function serializeThemeCss(blocks: readonly ThemeBlock[], options?: SerializeThemeCssOptions): string;
|
|
153
166
|
//#endregion
|
|
154
|
-
export { CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, SerializeThemeCssOptions, THEME_NAME_PATTERN, ThemeBlock, ThemeBlockSection, ThemeSelectorOptions, assertCssValue, assertThemeName, derivedTokens, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };
|
|
167
|
+
export { CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, RESOLVED_MONO, SerializeThemeCssOptions, THEME_NAME_PATTERN$1 as THEME_NAME_PATTERN, ThemeBlock, ThemeBlockSection, ThemeSelectorOptions, assertCssValue, assertThemeName, derivedTokens, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RESOLVED_MONO, THEME_NAME_PATTERN as THEME_NAME_PATTERN$1 } from "@cueplusplus/theme-base";
|
|
1
2
|
//#region src/theming/serialize.ts
|
|
2
3
|
/**
|
|
3
4
|
* Turning a resolved token map back into the stylesheet the tokens build would
|
|
@@ -9,7 +10,10 @@
|
|
|
9
10
|
* `[data-theme]` block, or a consumer ends up with two vocabularies and the
|
|
10
11
|
* generator becomes a toy. So the shapes below mirror `build.mjs` deliberately,
|
|
11
12
|
* comments included: same selector form, same declaration order, same section
|
|
12
|
-
* breaks. `test/theming/presets.test.ts`
|
|
13
|
+
* breaks. The generator-versus-build parity that `test/theming/presets.test.ts`
|
|
14
|
+
* used to diff is unguarded for this plan — the shipped preset copy that test
|
|
15
|
+
* compared against was deleted with it — and Plan 4 restores the comparison
|
|
16
|
+
* once `@cueplusplus/tokens` stops emitting presets of its own.
|
|
13
17
|
*
|
|
14
18
|
* It is also the only place that touches raw strings, which makes it the place
|
|
15
19
|
* to stop injection. A theme name lands inside an attribute selector and a font
|
|
@@ -18,15 +22,6 @@
|
|
|
18
22
|
/** The one custom-property prefix this design system emits. */
|
|
19
23
|
const CUE_TOKEN_PREFIX = "--cue-";
|
|
20
24
|
/**
|
|
21
|
-
* What a theme may be called.
|
|
22
|
-
*
|
|
23
|
-
* The name is interpolated into `[data-theme="…"]`, so the pattern is a
|
|
24
|
-
* whitelist rather than an escape: a CSS identifier, which is also what a
|
|
25
|
-
* `data-theme` attribute is expected to hold and what a file called
|
|
26
|
-
* `<name>.css` can be named.
|
|
27
|
-
*/
|
|
28
|
-
const THEME_NAME_PATTERN = /^[a-z][a-z0-9-]*$/i;
|
|
29
|
-
/**
|
|
30
25
|
* Characters a value may never contain: the ones that end a declaration, end a
|
|
31
26
|
* block, or open a comment.
|
|
32
27
|
*
|
|
@@ -45,7 +40,7 @@ const UNSAFE_VALUE = /[;{}]|\/\*/;
|
|
|
45
40
|
* assertThemeName("acme-dark"); // → "acme-dark"
|
|
46
41
|
*/
|
|
47
42
|
function assertThemeName(name) {
|
|
48
|
-
if (!THEME_NAME_PATTERN.test(name)) throw new TypeError(`theme name must be a CSS identifier (letters, digits and dashes, starting with a letter), got ${JSON.stringify(name)}`);
|
|
43
|
+
if (!THEME_NAME_PATTERN$1.test(name)) throw new TypeError(`theme name must be a CSS identifier (letters, digits and dashes, starting with a letter), got ${JSON.stringify(name)}`);
|
|
49
44
|
return name;
|
|
50
45
|
}
|
|
51
46
|
/**
|
|
@@ -88,14 +83,17 @@ function tokenProperty(name) {
|
|
|
88
83
|
* @returns The CSS selector.
|
|
89
84
|
* @example
|
|
90
85
|
* themeSelector("acme", { mode: "light" }); // → '[data-theme="acme"][data-mode="light"]'
|
|
86
|
+
* themeSelector("acme", { density: "compact", pair: true });
|
|
87
|
+
* // → '[data-theme="acme"] [data-density="compact"]:where([data-cue-theme="acme"]), [data-theme="acme"] [data-density="compact"]:where(:not([data-cue-theme]):not([data-theme]:not([data-theme="acme"])):not([data-theme="acme"] [data-theme]:not([data-theme="acme"]) *)), [data-theme="acme"][data-density="compact"]'
|
|
91
88
|
*/
|
|
92
89
|
function themeSelector(name, options = {}) {
|
|
93
|
-
const { mode, density } = options;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
const { mode, density, pair } = options;
|
|
91
|
+
const themeAttr = `[data-theme="${name}"]`;
|
|
92
|
+
const theme = `${themeAttr}${mode === "light" ? "[data-mode=\"light\"]" : ""}`;
|
|
93
|
+
if (density === void 0) return theme;
|
|
94
|
+
const crossed = `[data-density="${density}"]`;
|
|
95
|
+
if (pair !== true) return `${theme}${crossed}`;
|
|
96
|
+
return `${`${theme} ${crossed}:where([data-cue-theme="${name}"])`}, ${`${theme} ${crossed}:where(:not([data-cue-theme]):not([data-theme]:not(${themeAttr})):not(${themeAttr} [data-theme]:not(${themeAttr}) *))`}, ${theme}${crossed}`;
|
|
99
97
|
}
|
|
100
98
|
/** The tone vocabulary that gets a soft fill and a soft border in every theme. */
|
|
101
99
|
const STATUSES = [
|
|
@@ -240,4 +238,4 @@ function serializeThemeCss(blocks, options = {}) {
|
|
|
240
238
|
return `${[...options.banner === void 0 ? [] : [`/*! ${options.banner}\n * Generated by createTheme(). Do not edit by hand.\n */`], ...rules].join("\n\n")}\n`;
|
|
241
239
|
}
|
|
242
240
|
//#endregion
|
|
243
|
-
export { CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, THEME_NAME_PATTERN, assertCssValue, assertThemeName, derivedTokens, serializeSystemModeAliases, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };
|
|
241
|
+
export { CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, RESOLVED_MONO, THEME_NAME_PATTERN$1 as THEME_NAME_PATTERN, assertCssValue, assertThemeName, derivedTokens, serializeSystemModeAliases, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };
|
|
@@ -38,17 +38,16 @@
|
|
|
38
38
|
"--cue-fg-muted",
|
|
39
39
|
"--cue-fg-subtle",
|
|
40
40
|
"--cue-font-mono",
|
|
41
|
-
"--cue-font-pairing-mono",
|
|
42
41
|
"--cue-font-sans",
|
|
43
42
|
"--cue-font-scale",
|
|
44
|
-
"--cue-font-theme-mono",
|
|
45
43
|
"--cue-radius-control",
|
|
46
44
|
"--cue-radius-scale",
|
|
47
45
|
"--cue-space-1",
|
|
48
46
|
"--cue-space-2",
|
|
49
47
|
"--cue-space-4",
|
|
50
48
|
"--cue-text-label",
|
|
51
|
-
"--cue-text-micro"
|
|
49
|
+
"--cue-text-micro",
|
|
50
|
+
"--cue-text-ui"
|
|
52
51
|
],
|
|
53
52
|
"summary": "Section 2 — every colour the active theme declares, one row each.",
|
|
54
53
|
"examples": [
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"importPath": "@cueplusplus/ui",
|
|
6
6
|
"peerDependencies": [],
|
|
7
7
|
"clientOnly": true,
|
|
8
|
-
"description": "Inline version of the portal contract, for portals whose container cannot be\nchosen (a third-party overlay, or content already portaled by something else).\n\nWraps `children` in a `display: contents` element carrying the same\n`data-theme` / `data-density` / `data-font` / `data-mode` / `data-cue-skin` /\n`data-cue-fidelity` stamp
|
|
8
|
+
"description": "Inline version of the portal contract, for portals whose container cannot be\nchosen (a third-party overlay, or content already portaled by something else).\n\nWraps `children` in a `display: contents` element carrying the same\n`data-theme` / `data-density` / `data-font` / `data-mode` / `data-cue-skin` /\n`data-cue-fidelity` stamp, `--cue-font-scale` and `overrides` properties as\n{@link useCuePortalProps}. Prefer the hook — a stamped container costs one\nelement per overlay instead of one per render tree — and reach for this only\nwhen the container prop is not available.",
|
|
9
9
|
"props": [
|
|
10
10
|
{
|
|
11
11
|
"name": "children",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"importPath": "@cueplusplus/ui",
|
|
6
6
|
"peerDependencies": [],
|
|
7
7
|
"clientOnly": true,
|
|
8
|
-
"description": "Re-scope any subtree to a different density level.\n\nRenders a plain `<div data-density=\"…\">`: the geometry
|
|
8
|
+
"description": "Re-scope any subtree to a different density level.\n\nRenders a plain `<div data-density=\"…\" data-cue-theme=\"…\">`: the geometry\ncustom properties are re-declared by the attribute selector and inherit\ndown, so nesting is free and needs no JS. The matching React context is\npublished alongside for the rare JS reads (`useDensity()`,\n`useControlHeight()`, portal stamping) — the DOM attributes, not the\ncontext, are what actually restyle the tree.\n\n`data-cue-theme` carries the nearest `<ThemeProvider>`'s name, or is\nomitted where there is none. It is read by nothing this island does —\ntheme and mode are deliberately *not* re-scoped here, density is the only\naxis an island may override (spec §6) — it exists so a theme's own\ngenerated CSS can address exactly this island regardless of how many\n*other* themed providers sit between it and the one it belongs to. A CSS\nancestor selector can only ask \"does some ancestor carry theme `x`\", never\n\"is `x` the *nearest* one\"; this stamp answers that question the way\n`useContext` already does, at render time, and hands the answer to CSS as\nan attribute rather than leaving it unanswerable there.",
|
|
9
9
|
"props": [
|
|
10
10
|
{
|
|
11
11
|
"name": "density",
|
|
@@ -55,12 +55,9 @@
|
|
|
55
55
|
"--cue-fg",
|
|
56
56
|
"--cue-fg-muted",
|
|
57
57
|
"--cue-font-mono",
|
|
58
|
-
"--cue-font-pairing-mono",
|
|
59
58
|
"--cue-font-sans",
|
|
60
59
|
"--cue-font-scale",
|
|
61
|
-
"--cue-font-theme-mono",
|
|
62
60
|
"--cue-radius-control",
|
|
63
|
-
"--cue-radius-scale",
|
|
64
61
|
"--cue-space-1",
|
|
65
62
|
"--cue-space-3",
|
|
66
63
|
"--cue-space-4",
|