@epilot/spark-ui 0.18.1 → 0.19.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/dist/components/attachment.d.cts +1 -1
- package/dist/components/attachment.d.ts +1 -1
- package/dist/components/bar-chart.cjs +1 -1
- package/dist/components/bar-chart.js +2 -2
- package/dist/components/breadcrumbs.cjs +1 -1
- package/dist/components/breadcrumbs.cjs.map +1 -1
- package/dist/components/breadcrumbs.js +1 -1
- package/dist/components/breadcrumbs.js.map +1 -1
- package/dist/components/bubble.d.cts +1 -1
- package/dist/components/bubble.d.ts +1 -1
- package/dist/components/button-group.cjs +1 -1
- package/dist/components/button-group.cjs.map +1 -1
- package/dist/components/button-group.js +1 -1
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/button.d.cts +2 -2
- package/dist/components/button.d.ts +2 -2
- package/dist/components/calendar.cjs +4 -4
- package/dist/components/calendar.cjs.map +1 -1
- package/dist/components/calendar.js +4 -4
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/checkbox.cjs +1 -1
- package/dist/components/checkbox.cjs.map +1 -1
- package/dist/components/checkbox.js +1 -1
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/day-of-week-picker.cjs +2 -2
- package/dist/components/day-of-week-picker.cjs.map +1 -1
- package/dist/components/day-of-week-picker.js +2 -2
- package/dist/components/day-of-week-picker.js.map +1 -1
- package/dist/components/dropdown-menu.cjs +8 -8
- package/dist/components/dropdown-menu.cjs.map +1 -1
- package/dist/components/dropdown-menu.js +8 -8
- package/dist/components/dropdown-menu.js.map +1 -1
- package/dist/components/field.cjs +1 -1
- package/dist/components/field.cjs.map +1 -1
- package/dist/components/field.js +1 -1
- package/dist/components/field.js.map +1 -1
- package/dist/components/input-date.cjs +6 -6
- package/dist/components/input-date.cjs.map +1 -1
- package/dist/components/input-date.d.cts +2 -1
- package/dist/components/input-date.d.ts +2 -1
- package/dist/components/input-date.js +6 -6
- package/dist/components/input-date.js.map +1 -1
- package/dist/components/input-otp.cjs +2 -2
- package/dist/components/input-otp.cjs.map +1 -1
- package/dist/components/input-otp.js +2 -2
- package/dist/components/input-otp.js.map +1 -1
- package/dist/components/input.cjs +2 -2
- package/dist/components/input.cjs.map +1 -1
- package/dist/components/input.js +2 -2
- package/dist/components/input.js.map +1 -1
- package/dist/components/label.cjs +1 -1
- package/dist/components/label.cjs.map +1 -1
- package/dist/components/label.js +1 -1
- package/dist/components/label.js.map +1 -1
- package/dist/components/line-chart.cjs +3 -3
- package/dist/components/line-chart.js +4 -4
- package/dist/components/list.cjs +2 -2
- package/dist/components/list.cjs.map +1 -1
- package/dist/components/list.js +2 -2
- package/dist/components/list.js.map +1 -1
- package/dist/components/marker.cjs +2 -2
- package/dist/components/marker.cjs.map +1 -1
- package/dist/components/marker.d.cts +1 -1
- package/dist/components/marker.d.ts +1 -1
- package/dist/components/marker.js +2 -2
- package/dist/components/marker.js.map +1 -1
- package/dist/components/message-scroller.cjs +1 -1
- package/dist/components/message-scroller.js +2 -2
- package/dist/components/on-off-button.cjs +1 -1
- package/dist/components/on-off-button.cjs.map +1 -1
- package/dist/components/on-off-button.js +1 -1
- package/dist/components/on-off-button.js.map +1 -1
- package/dist/components/page-header.d.cts +1 -1
- package/dist/components/page-header.d.ts +1 -1
- package/dist/components/power-history-chart.cjs +3 -3
- package/dist/components/power-history-chart.js +4 -4
- package/dist/components/power-history.cjs +1 -1
- package/dist/components/power-history.js +1 -1
- package/dist/components/power-network.js +1 -1
- package/dist/components/radio.cjs +1 -1
- package/dist/components/radio.cjs.map +1 -1
- package/dist/components/radio.js +1 -1
- package/dist/components/radio.js.map +1 -1
- package/dist/components/schedule-list.cjs +1 -1
- package/dist/components/schedule-list.cjs.map +1 -1
- package/dist/components/schedule-list.js +1 -1
- package/dist/components/schedule-list.js.map +1 -1
- package/dist/components/scroll-area.cjs +2 -2
- package/dist/components/scroll-area.cjs.map +1 -1
- package/dist/components/scroll-area.js +2 -2
- package/dist/components/scroll-area.js.map +1 -1
- package/dist/components/segmented-control.cjs +1 -1
- package/dist/components/segmented-control.cjs.map +1 -1
- package/dist/components/segmented-control.js +1 -1
- package/dist/components/segmented-control.js.map +1 -1
- package/dist/components/select.cjs +6 -6
- package/dist/components/select.cjs.map +1 -1
- package/dist/components/select.js +6 -6
- package/dist/components/select.js.map +1 -1
- package/dist/components/series-legend.cjs +2 -2
- package/dist/components/series-legend.cjs.map +1 -1
- package/dist/components/series-legend.js +2 -2
- package/dist/components/series-legend.js.map +1 -1
- package/dist/components/slider.cjs +1 -1
- package/dist/components/slider.cjs.map +1 -1
- package/dist/components/slider.js +1 -1
- package/dist/components/slider.js.map +1 -1
- package/dist/components/smart-toggle-button.cjs +1 -1
- package/dist/components/smart-toggle-button.cjs.map +1 -1
- package/dist/components/smart-toggle-button.js +1 -1
- package/dist/components/smart-toggle-button.js.map +1 -1
- package/dist/components/solar-forecast-chart.js +1 -1
- package/dist/components/stepper.cjs +2 -2
- package/dist/components/stepper.cjs.map +1 -1
- package/dist/components/stepper.js +2 -2
- package/dist/components/stepper.js.map +1 -1
- package/dist/components/switch.cjs +1 -1
- package/dist/components/switch.cjs.map +1 -1
- package/dist/components/switch.js +1 -1
- package/dist/components/switch.js.map +1 -1
- package/dist/components/table.cjs +147 -0
- package/dist/components/table.cjs.map +1 -0
- package/dist/components/table.d.cts +75 -0
- package/dist/components/table.d.ts +75 -0
- package/dist/components/table.js +138 -0
- package/dist/components/table.js.map +1 -0
- package/dist/components/tabs.cjs +2 -2
- package/dist/components/tabs.cjs.map +1 -1
- package/dist/components/tabs.js +2 -2
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/textarea.cjs +2 -2
- package/dist/components/textarea.cjs.map +1 -1
- package/dist/components/textarea.js +2 -2
- package/dist/components/textarea.js.map +1 -1
- package/dist/components/theme.cjs.map +1 -1
- package/dist/components/theme.d.cts +2 -2
- package/dist/components/theme.d.ts +2 -2
- package/dist/components/theme.js.map +1 -1
- package/dist/components/time-picker.cjs +1 -1
- package/dist/components/time-picker.cjs.map +1 -1
- package/dist/components/time-picker.js +1 -1
- package/dist/components/time-picker.js.map +1 -1
- package/dist/components/tooltip.cjs +1 -1
- package/dist/components/tooltip.cjs.map +1 -1
- package/dist/components/tooltip.js +1 -1
- package/dist/components/tooltip.js.map +1 -1
- package/dist/lib/accent-theme.cjs +199 -12
- package/dist/lib/accent-theme.cjs.map +1 -1
- package/dist/lib/accent-theme.d.cts +45 -2
- package/dist/lib/accent-theme.d.ts +45 -2
- package/dist/lib/accent-theme.js +195 -13
- package/dist/lib/accent-theme.js.map +1 -1
- package/dist/lib/container-chrome.cjs +1 -1
- package/dist/lib/container-chrome.cjs.map +1 -1
- package/dist/lib/container-chrome.js +1 -1
- package/dist/lib/container-chrome.js.map +1 -1
- package/dist/styles/index.css +9 -27
- package/dist/styles/spark-default.css +373 -19
- package/dist/styles/theme.css +2 -2
- package/package.json +17 -1
|
@@ -20,6 +20,8 @@ type SparkAccentScale = GeneratedColors & {
|
|
|
20
20
|
accentOnSolid: string;
|
|
21
21
|
};
|
|
22
22
|
type Appearance = 'light' | 'dark';
|
|
23
|
+
/** The epilot brand: the no-attribute default `--spark-*` in spark-default.css. */
|
|
24
|
+
declare const DEFAULT_ACCENT = "#005EB4";
|
|
23
25
|
/** Curated name → source color. `blue` = Radix blue; the no-attribute default `--spark-*` stays the epilot brand. */
|
|
24
26
|
declare const CURATED_ACCENTS: Record<string, string>;
|
|
25
27
|
interface GenerateScaleOptions {
|
|
@@ -39,8 +41,49 @@ interface GenerateScaleOptions {
|
|
|
39
41
|
}
|
|
40
42
|
/** Which appearance a canvas belongs to, by OKLCH lightness. */
|
|
41
43
|
declare function appearanceForBackground(background: string): Appearance;
|
|
42
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Generate the Radix-style accent scale for one appearance from a single accent color.
|
|
46
|
+
*
|
|
47
|
+
* Solid-fill inks come from `readableContrast`, which clears WCAG but can only reach an
|
|
48
|
+
* unreadable APCA on a light, saturated step 9 (a near-black tint at Lc 33-44). Where it
|
|
49
|
+
* does, the fill is darkened until plain white clears 4.5:1 and the scale is re-seeded
|
|
50
|
+
* from that fill — see `darkenForWhiteInk`.
|
|
51
|
+
*/
|
|
43
52
|
declare function generateAccentScale(accent: string, appearance: Appearance, opts?: GenerateScaleOptions): SparkAccentScale;
|
|
53
|
+
/**
|
|
54
|
+
* Step 11 is the ink of every non-solid style, read on the a3 fill and the a4 hover. Both
|
|
55
|
+
* are translucent, so on a soft card (neutral-a3 ≈ neutral-3 over the canvas) they
|
|
56
|
+
* composite darker than on the canvas and Radix's step 11 drops under 4.5:1 (red 3.72:1).
|
|
57
|
+
* Move step 11 away from the fill, holding chroma and hue, until a11 on a4 clears 4.5:1
|
|
58
|
+
* over every neutral knob's step 3, and re-solve a11 from it. Unchanged when it clears.
|
|
59
|
+
*/
|
|
60
|
+
declare function withReadableTintInk<T extends GeneratedColors>(scale: T): T;
|
|
61
|
+
declare const RED_A4: Record<Appearance, string>;
|
|
62
|
+
/**
|
|
63
|
+
* The hover tints a static `--neutral-placeholder` must clear (field.tsx): gray
|
|
64
|
+
* (`neutralA4`), invalid (`--red-a4`) and brand (`--spark-a4`) for the default accent and
|
|
65
|
+
* every curated one. A runtime brand outside these is not covered.
|
|
66
|
+
*/
|
|
67
|
+
declare function staticFieldHoverTints(appearance: Appearance, neutralA4: string): string[];
|
|
68
|
+
/**
|
|
69
|
+
* The lightest color between neutral a10 and a11 that reaches `PLACEHOLDER_CONTRAST` on a
|
|
70
|
+
* `card` over `page`, bare and under each of `tints`, clamped to a11, as an sRGB alpha step
|
|
71
|
+
* over `page` (opaque where the alpha solve cannot reproduce it). The card is the deepest
|
|
72
|
+
* surface a field sits on, so clearing it clears the page too.
|
|
73
|
+
*/
|
|
74
|
+
declare function solvePlaceholder({
|
|
75
|
+
a10,
|
|
76
|
+
a11,
|
|
77
|
+
page,
|
|
78
|
+
card,
|
|
79
|
+
tints
|
|
80
|
+
}: {
|
|
81
|
+
a10: string;
|
|
82
|
+
a11: string;
|
|
83
|
+
page: string;
|
|
84
|
+
card: string;
|
|
85
|
+
tints: string[];
|
|
86
|
+
}): string;
|
|
44
87
|
/** sRGB `--spark-*` declarations for a generated accent scale. */
|
|
45
88
|
declare function accentSparkVars(scale: SparkAccentScale): string[];
|
|
46
89
|
/** Wide-gamut (P3) `--spark-*` declarations — for a `color-gamut: p3` `@supports` block. */
|
|
@@ -125,5 +168,5 @@ declare function neutralSparkVars(scale: SparkAccentScale): string[];
|
|
|
125
168
|
/** Wide-gamut (P3) twin of `neutralSparkVars`. */
|
|
126
169
|
declare function neutralSparkVarsP3(scale: SparkAccentScale): string[];
|
|
127
170
|
//#endregion
|
|
128
|
-
export { Appearance, CURATED_ACCENTS, GenerateScaleOptions, GeneratedColors, SparkAccentScale, accentSparkVars, accentSparkVarsP3, appearanceForBackground, generateAccentScale, getMatchingNeutral, invertAccentScale, isParsableColor, neutralFamilyVars, neutralSparkVars, neutralSparkVarsP3, resolveAutoNeutral };
|
|
171
|
+
export { Appearance, CURATED_ACCENTS, DEFAULT_ACCENT, GenerateScaleOptions, GeneratedColors, RED_A4, SparkAccentScale, accentSparkVars, accentSparkVarsP3, appearanceForBackground, generateAccentScale, getMatchingNeutral, invertAccentScale, isParsableColor, neutralFamilyVars, neutralSparkVars, neutralSparkVarsP3, resolveAutoNeutral, solvePlaceholder, staticFieldHoverTints, withReadableTintInk };
|
|
129
172
|
//# sourceMappingURL=accent-theme.d.cts.map
|
|
@@ -20,6 +20,8 @@ type SparkAccentScale = GeneratedColors & {
|
|
|
20
20
|
accentOnSolid: string;
|
|
21
21
|
};
|
|
22
22
|
type Appearance = 'light' | 'dark';
|
|
23
|
+
/** The epilot brand: the no-attribute default `--spark-*` in spark-default.css. */
|
|
24
|
+
declare const DEFAULT_ACCENT = "#005EB4";
|
|
23
25
|
/** Curated name → source color. `blue` = Radix blue; the no-attribute default `--spark-*` stays the epilot brand. */
|
|
24
26
|
declare const CURATED_ACCENTS: Record<string, string>;
|
|
25
27
|
interface GenerateScaleOptions {
|
|
@@ -39,8 +41,49 @@ interface GenerateScaleOptions {
|
|
|
39
41
|
}
|
|
40
42
|
/** Which appearance a canvas belongs to, by OKLCH lightness. */
|
|
41
43
|
declare function appearanceForBackground(background: string): Appearance;
|
|
42
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Generate the Radix-style accent scale for one appearance from a single accent color.
|
|
46
|
+
*
|
|
47
|
+
* Solid-fill inks come from `readableContrast`, which clears WCAG but can only reach an
|
|
48
|
+
* unreadable APCA on a light, saturated step 9 (a near-black tint at Lc 33-44). Where it
|
|
49
|
+
* does, the fill is darkened until plain white clears 4.5:1 and the scale is re-seeded
|
|
50
|
+
* from that fill — see `darkenForWhiteInk`.
|
|
51
|
+
*/
|
|
43
52
|
declare function generateAccentScale(accent: string, appearance: Appearance, opts?: GenerateScaleOptions): SparkAccentScale;
|
|
53
|
+
/**
|
|
54
|
+
* Step 11 is the ink of every non-solid style, read on the a3 fill and the a4 hover. Both
|
|
55
|
+
* are translucent, so on a soft card (neutral-a3 ≈ neutral-3 over the canvas) they
|
|
56
|
+
* composite darker than on the canvas and Radix's step 11 drops under 4.5:1 (red 3.72:1).
|
|
57
|
+
* Move step 11 away from the fill, holding chroma and hue, until a11 on a4 clears 4.5:1
|
|
58
|
+
* over every neutral knob's step 3, and re-solve a11 from it. Unchanged when it clears.
|
|
59
|
+
*/
|
|
60
|
+
declare function withReadableTintInk<T extends GeneratedColors>(scale: T): T;
|
|
61
|
+
declare const RED_A4: Record<Appearance, string>;
|
|
62
|
+
/**
|
|
63
|
+
* The hover tints a static `--neutral-placeholder` must clear (field.tsx): gray
|
|
64
|
+
* (`neutralA4`), invalid (`--red-a4`) and brand (`--spark-a4`) for the default accent and
|
|
65
|
+
* every curated one. A runtime brand outside these is not covered.
|
|
66
|
+
*/
|
|
67
|
+
declare function staticFieldHoverTints(appearance: Appearance, neutralA4: string): string[];
|
|
68
|
+
/**
|
|
69
|
+
* The lightest color between neutral a10 and a11 that reaches `PLACEHOLDER_CONTRAST` on a
|
|
70
|
+
* `card` over `page`, bare and under each of `tints`, clamped to a11, as an sRGB alpha step
|
|
71
|
+
* over `page` (opaque where the alpha solve cannot reproduce it). The card is the deepest
|
|
72
|
+
* surface a field sits on, so clearing it clears the page too.
|
|
73
|
+
*/
|
|
74
|
+
declare function solvePlaceholder({
|
|
75
|
+
a10,
|
|
76
|
+
a11,
|
|
77
|
+
page,
|
|
78
|
+
card,
|
|
79
|
+
tints
|
|
80
|
+
}: {
|
|
81
|
+
a10: string;
|
|
82
|
+
a11: string;
|
|
83
|
+
page: string;
|
|
84
|
+
card: string;
|
|
85
|
+
tints: string[];
|
|
86
|
+
}): string;
|
|
44
87
|
/** sRGB `--spark-*` declarations for a generated accent scale. */
|
|
45
88
|
declare function accentSparkVars(scale: SparkAccentScale): string[];
|
|
46
89
|
/** Wide-gamut (P3) `--spark-*` declarations — for a `color-gamut: p3` `@supports` block. */
|
|
@@ -125,5 +168,5 @@ declare function neutralSparkVars(scale: SparkAccentScale): string[];
|
|
|
125
168
|
/** Wide-gamut (P3) twin of `neutralSparkVars`. */
|
|
126
169
|
declare function neutralSparkVarsP3(scale: SparkAccentScale): string[];
|
|
127
170
|
//#endregion
|
|
128
|
-
export { Appearance, CURATED_ACCENTS, GenerateScaleOptions, GeneratedColors, SparkAccentScale, accentSparkVars, accentSparkVarsP3, appearanceForBackground, generateAccentScale, getMatchingNeutral, invertAccentScale, isParsableColor, neutralFamilyVars, neutralSparkVars, neutralSparkVarsP3, resolveAutoNeutral };
|
|
171
|
+
export { Appearance, CURATED_ACCENTS, DEFAULT_ACCENT, GenerateScaleOptions, GeneratedColors, RED_A4, SparkAccentScale, accentSparkVars, accentSparkVarsP3, appearanceForBackground, generateAccentScale, getMatchingNeutral, invertAccentScale, isParsableColor, neutralFamilyVars, neutralSparkVars, neutralSparkVarsP3, resolveAutoNeutral, solvePlaceholder, staticFieldHoverTints, withReadableTintInk };
|
|
129
172
|
//# sourceMappingURL=accent-theme.d.ts.map
|
package/dist/lib/accent-theme.js
CHANGED
|
@@ -4,6 +4,8 @@ import { isSparkNeutralFamily } from "./theme-types.js";
|
|
|
4
4
|
import * as RadixColors from "@radix-ui/colors";
|
|
5
5
|
import Color from "colorjs.io";
|
|
6
6
|
//#region src/lib/accent-theme.ts
|
|
7
|
+
/** The epilot brand: the no-attribute default `--spark-*` in spark-default.css. */
|
|
8
|
+
const DEFAULT_ACCENT = "#005EB4";
|
|
7
9
|
/** Curated name → source color. `blue` = Radix blue; the no-attribute default `--spark-*` stays the epilot brand. */
|
|
8
10
|
const CURATED_ACCENTS = {
|
|
9
11
|
blue: RadixColors.blue.blue9,
|
|
@@ -79,9 +81,9 @@ const ALPHA_CAP = .5;
|
|
|
79
81
|
* per-channel in P3, and verifying that against an sRGB composite rejects perfectly good
|
|
80
82
|
* solves (they were never trying to reproduce the sRGB composite in the first place).
|
|
81
83
|
*
|
|
82
|
-
* So verify the solve and fall back to the opaque value where either check fails.
|
|
83
|
-
*
|
|
84
|
-
*
|
|
84
|
+
* So verify the solve and fall back to the opaque value where either check fails. On
|
|
85
|
+
* white/black every step solves exactly at low alpha, so the default output — and portal
|
|
86
|
+
* parity — is untouched either way.
|
|
85
87
|
*/
|
|
86
88
|
function opaqueWhereUnsolvable(background, solid, alpha, capAlphaSteps = 0, space = "srgb") {
|
|
87
89
|
let bg;
|
|
@@ -217,8 +219,8 @@ function accentScaleForCanvas(scale, background, appearance) {
|
|
|
217
219
|
};
|
|
218
220
|
}
|
|
219
221
|
}
|
|
220
|
-
/**
|
|
221
|
-
function
|
|
222
|
+
/** Build the Radix-style scale for one appearance from a single accent color. */
|
|
223
|
+
function buildScale(accent, appearance, opts) {
|
|
222
224
|
const background = opts?.background ?? DEFAULTS[appearance].background;
|
|
223
225
|
const scale = generateRadixColors({
|
|
224
226
|
appearance,
|
|
@@ -231,7 +233,7 @@ function generateAccentScale(accent, appearance, opts) {
|
|
|
231
233
|
tinted: false
|
|
232
234
|
};
|
|
233
235
|
const capAlphaSteps = tinted ? CANVAS_TINT_WEIGHTS.length : 0;
|
|
234
|
-
|
|
236
|
+
return opts?.background ? {
|
|
235
237
|
...scale,
|
|
236
238
|
accentScale,
|
|
237
239
|
accentScaleWideGamut,
|
|
@@ -242,14 +244,126 @@ function generateAccentScale(accent, appearance, opts) {
|
|
|
242
244
|
grayScaleAlpha: opaqueWhereUnsolvable(background, scale.grayScale, scale.grayScaleAlpha),
|
|
243
245
|
grayScaleAlphaWideGamut: opaqueWhereUnsolvable(background, scale.grayScaleWideGamut, scale.grayScaleAlphaWideGamut, 0, "p3")
|
|
244
246
|
} : scale;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Generate the Radix-style accent scale for one appearance from a single accent color.
|
|
250
|
+
*
|
|
251
|
+
* Solid-fill inks come from `readableContrast`, which clears WCAG but can only reach an
|
|
252
|
+
* unreadable APCA on a light, saturated step 9 (a near-black tint at Lc 33-44). Where it
|
|
253
|
+
* does, the fill is darkened until plain white clears 4.5:1 and the scale is re-seeded
|
|
254
|
+
* from that fill — see `darkenForWhiteInk`.
|
|
255
|
+
*/
|
|
256
|
+
function generateAccentScale(accent, appearance, opts) {
|
|
257
|
+
const solved = withNativeInks(buildScale(accent, appearance, opts));
|
|
258
|
+
const fill = solved.accentScale[8];
|
|
259
|
+
if (apcaOn(fill, solved.accentContrast) >= MIN_INK_APCA) return withReadableTintInk(solved);
|
|
260
|
+
const reseeded = buildScale(darkenForWhiteInk(fill), appearance, opts);
|
|
261
|
+
const reseededFill = reseeded.accentScale[8];
|
|
262
|
+
if (contrastOn(reseededFill, WHITE) < MIN_TEXT_CONTRAST) return withReadableTintInk(solved);
|
|
263
|
+
return withReadableTintInk({
|
|
264
|
+
...reseeded,
|
|
265
|
+
accentContrast: WHITE,
|
|
266
|
+
accentOnSolid: WHITE
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Step 11 is the ink of every non-solid style, read on the a3 fill and the a4 hover. Both
|
|
271
|
+
* are translucent, so on a soft card (neutral-a3 ≈ neutral-3 over the canvas) they
|
|
272
|
+
* composite darker than on the canvas and Radix's step 11 drops under 4.5:1 (red 3.72:1).
|
|
273
|
+
* Move step 11 away from the fill, holding chroma and hue, until a11 on a4 clears 4.5:1
|
|
274
|
+
* over every neutral knob's step 3, and re-solve a11 from it. Unchanged when it clears.
|
|
275
|
+
*/
|
|
276
|
+
function withReadableTintInk(scale) {
|
|
277
|
+
const canvas = scale.background;
|
|
278
|
+
const dark = appearanceForBackground(canvas) === "dark";
|
|
279
|
+
const hoverFills = [scale.grayScale[2], ...Object.keys(NEUTRAL_BASE).map((family) => {
|
|
280
|
+
return RadixColors[dark ? `${family}Dark` : family][`${family}3`];
|
|
281
|
+
})].map((page) => composite(scale.accentScaleAlpha[3], page));
|
|
282
|
+
const inkOn = (step11) => {
|
|
283
|
+
const ink = getAlphaColorSrgb(step11, canvas);
|
|
284
|
+
return Math.min(...hoverFills.map((fill) => contrastOn(fill, composite(ink, fill))));
|
|
285
|
+
};
|
|
286
|
+
const original = scale.accentScale[10];
|
|
287
|
+
if (inkOn(original) >= MIN_TEXT_CONTRAST) return scale;
|
|
288
|
+
const [lightness, chroma, hue] = new Color(original).to("oklch").coords;
|
|
289
|
+
const [, wideChroma, wideHue] = new Color(scale.accentScaleWideGamut[10]).to("oklch").coords;
|
|
290
|
+
const toward = contrastOn(canvas, "#000") > contrastOn(canvas, "#fff") ? 0 : 1;
|
|
291
|
+
const srgbAt = (l) => new Color("oklch", [
|
|
292
|
+
l,
|
|
293
|
+
chroma ?? 0,
|
|
294
|
+
hue ?? 0
|
|
295
|
+
]).toGamut({ space: "srgb" }).to("srgb").toString({ format: "hex" });
|
|
296
|
+
let fails = lightness ?? .5;
|
|
297
|
+
let passes = toward;
|
|
298
|
+
for (let i = 0; i < 16; i++) {
|
|
299
|
+
const mid = (fails + passes) / 2;
|
|
300
|
+
if (inkOn(srgbAt(mid)) >= MIN_TEXT_CONTRAST) passes = mid;
|
|
301
|
+
else fails = mid;
|
|
302
|
+
}
|
|
303
|
+
const step11 = srgbAt(passes);
|
|
304
|
+
const wideStep11 = toOklchWideGamutString(new Color("oklch", [
|
|
305
|
+
passes,
|
|
306
|
+
wideChroma ?? 0,
|
|
307
|
+
wideHue ?? 0
|
|
308
|
+
]).toGamut({ space: "p3" }));
|
|
309
|
+
const withStep = (steps, value) => steps.map((v, i) => i === 10 ? value : v);
|
|
310
|
+
return {
|
|
311
|
+
...scale,
|
|
312
|
+
accentScale: withStep(scale.accentScale, step11),
|
|
313
|
+
accentScaleWideGamut: withStep(scale.accentScaleWideGamut, wideStep11),
|
|
314
|
+
accentScaleAlpha: withStep(scale.accentScaleAlpha, getAlphaColorSrgb(step11, canvas)),
|
|
315
|
+
accentScaleAlphaWideGamut: withStep(scale.accentScaleAlphaWideGamut, getAlphaColorP3(wideStep11, canvas))
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
const composite = (top, bottom) => {
|
|
319
|
+
const fg = new Color(top).to("srgb");
|
|
320
|
+
const bg = new Color(bottom).to("srgb").coords;
|
|
321
|
+
const a = fg.alpha;
|
|
322
|
+
const channel = (k) => (Math.round(bg[k] * 255 * (1 - a)) + Math.round(fg.coords[k] * 255 * a)) / 255;
|
|
323
|
+
return new Color("srgb", [
|
|
324
|
+
channel(0),
|
|
325
|
+
channel(1),
|
|
326
|
+
channel(2)
|
|
327
|
+
]).toString({ format: "hex" });
|
|
328
|
+
};
|
|
329
|
+
function withNativeInks(scale) {
|
|
330
|
+
const fill = scale.accentScale[8];
|
|
245
331
|
return {
|
|
246
|
-
...
|
|
247
|
-
accentContrast: readableContrast(
|
|
248
|
-
accentOnSolid: readableContrast(
|
|
332
|
+
...scale,
|
|
333
|
+
accentContrast: readableContrast(fill, scale.accentContrast, MIN_TEXT_CONTRAST),
|
|
334
|
+
accentOnSolid: readableContrast(fill, scale.accentContrast, MIN_GRAPHIC_CONTRAST)
|
|
249
335
|
};
|
|
250
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* Darkest the fill needs to be for white text to clear 4.5:1, holding chroma and hue.
|
|
339
|
+
* Returns the fill unchanged when white already clears — the generator picks its ink
|
|
340
|
+
* from `Math.abs(white.contrastAPCA(fill))`, whose arguments are transposed, so it can
|
|
341
|
+
* reach for a dark tint on a fill white already handles.
|
|
342
|
+
*/
|
|
343
|
+
function darkenForWhiteInk(fillHex) {
|
|
344
|
+
const fill = new Color(fillHex);
|
|
345
|
+
if (contrastOn(fillHex, WHITE) >= MIN_TEXT_CONTRAST) return fillHex;
|
|
346
|
+
const [lightness, chroma, hue] = fill.to("oklch").coords;
|
|
347
|
+
const darkened = (l) => new Color("oklch", [
|
|
348
|
+
l,
|
|
349
|
+
chroma ?? 0,
|
|
350
|
+
hue ?? 0
|
|
351
|
+
]).toGamut({ space: "srgb" }).to("srgb").toString({ format: "hex" });
|
|
352
|
+
let fails = lightness ?? .5;
|
|
353
|
+
let passes = 0;
|
|
354
|
+
for (let i = 0; i < 16; i++) {
|
|
355
|
+
const mid = (fails + passes) / 2;
|
|
356
|
+
if (contrastOn(darkened(mid), WHITE) >= MIN_TEXT_CONTRAST) passes = mid;
|
|
357
|
+
else fails = mid;
|
|
358
|
+
}
|
|
359
|
+
return darkened(passes);
|
|
360
|
+
}
|
|
361
|
+
const WHITE = "#fff";
|
|
251
362
|
const MIN_TEXT_CONTRAST = 4.5;
|
|
252
363
|
const MIN_GRAPHIC_CONTRAST = 3;
|
|
364
|
+
const MIN_INK_APCA = 45;
|
|
365
|
+
const contrastOn = (bgHex, fgHex) => new Color(fgHex).contrast(new Color(bgHex), "WCAG21");
|
|
366
|
+
const apcaOn = (bgHex, fgHex) => Math.abs(new Color(bgHex).contrastAPCA(new Color(fgHex)));
|
|
253
367
|
function readableContrast(step9Hex, native, min) {
|
|
254
368
|
const step9 = new Color(step9Hex);
|
|
255
369
|
const [, chroma, hue] = step9.to("oklch").coords;
|
|
@@ -281,6 +395,63 @@ function readableContrast(step9Hex, native, min) {
|
|
|
281
395
|
const allRatios = all.map(ratioOf);
|
|
282
396
|
return all[allRatios.indexOf(Math.max(...allRatios))];
|
|
283
397
|
}
|
|
398
|
+
const RED_A4 = {
|
|
399
|
+
light: "#ff25002a",
|
|
400
|
+
dark: "#ff00005a"
|
|
401
|
+
};
|
|
402
|
+
/**
|
|
403
|
+
* The hover tints a static `--neutral-placeholder` must clear (field.tsx): gray
|
|
404
|
+
* (`neutralA4`), invalid (`--red-a4`) and brand (`--spark-a4`) for the default accent and
|
|
405
|
+
* every curated one. A runtime brand outside these is not covered.
|
|
406
|
+
*/
|
|
407
|
+
function staticFieldHoverTints(appearance, neutralA4) {
|
|
408
|
+
return [
|
|
409
|
+
neutralA4,
|
|
410
|
+
RED_A4[appearance],
|
|
411
|
+
...[DEFAULT_ACCENT, ...Object.values(CURATED_ACCENTS)].map((accent) => generateAccentScale(accent, appearance).accentScaleAlpha[3])
|
|
412
|
+
];
|
|
413
|
+
}
|
|
414
|
+
const PLACEHOLDER_CONTRAST = 4.55;
|
|
415
|
+
function compositeOver(background, color) {
|
|
416
|
+
const ink = new Color(color).to("srgb");
|
|
417
|
+
const alpha = ink.alpha;
|
|
418
|
+
const bg = background.to("srgb").coords;
|
|
419
|
+
return new Color("srgb", [
|
|
420
|
+
0,
|
|
421
|
+
1,
|
|
422
|
+
2
|
|
423
|
+
].map((k) => bg[k] * (1 - alpha) + ink.coords[k] * alpha));
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* The lightest color between neutral a10 and a11 that reaches `PLACEHOLDER_CONTRAST` on a
|
|
427
|
+
* `card` over `page`, bare and under each of `tints`, clamped to a11, as an sRGB alpha step
|
|
428
|
+
* over `page` (opaque where the alpha solve cannot reproduce it). The card is the deepest
|
|
429
|
+
* surface a field sits on, so clearing it clears the page too.
|
|
430
|
+
*/
|
|
431
|
+
function solvePlaceholder({ a10, a11, page, card, tints }) {
|
|
432
|
+
const bg = new Color(page);
|
|
433
|
+
const onCard = compositeOver(bg, card);
|
|
434
|
+
const fills = [onCard, ...tints.map((tint) => compositeOver(onCard, tint))];
|
|
435
|
+
const from = compositeOver(bg, a10);
|
|
436
|
+
const to = compositeOver(bg, a11);
|
|
437
|
+
const at = (t) => from.mix(to, t, { space: "oklch" }).to("srgb");
|
|
438
|
+
const inkAt = (t) => {
|
|
439
|
+
const solid = at(t).toString({ format: "hex" });
|
|
440
|
+
return opaqueWhereUnsolvable(page, [solid], [getAlphaColorSrgb(solid, page)])[0];
|
|
441
|
+
};
|
|
442
|
+
const passes = (t) => fills.every((fill) => compositeOver(fill, inkAt(t)).contrast(fill, "WCAG21") >= PLACEHOLDER_CONTRAST);
|
|
443
|
+
let found = 1;
|
|
444
|
+
if (passes(0)) found = 0;
|
|
445
|
+
else if (passes(1)) {
|
|
446
|
+
let fails = 0;
|
|
447
|
+
for (let i = 0; i < 16; i++) {
|
|
448
|
+
const mid = (fails + found) / 2;
|
|
449
|
+
if (passes(mid)) found = mid;
|
|
450
|
+
else fails = mid;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return inkAt(found);
|
|
454
|
+
}
|
|
284
455
|
/** sRGB `--spark-*` declarations for a generated accent scale. */
|
|
285
456
|
function accentSparkVars(scale) {
|
|
286
457
|
return sparkVars(scale.accentScale, scale.accentScaleAlpha, scale.accentContrast, scale.accentOnSolid, scale.accentSurface, scale.background);
|
|
@@ -529,8 +700,19 @@ function sparkVars(steps, alpha, contrast, onSolid, surface, background) {
|
|
|
529
700
|
*/
|
|
530
701
|
function neutralFamilyVars(family) {
|
|
531
702
|
const ref = (suffix) => `var(--${family}-${suffix})`;
|
|
532
|
-
return scaleVars("neutral", Array.from({ length: 12 }, (_, i) => ref(`${i + 1}`)), Array.from({ length: 12 }, (_, i) => ref(`a${i + 1}`)),
|
|
703
|
+
return [...scaleVars("neutral", Array.from({ length: 12 }, (_, i) => ref(`${i + 1}`)), Array.from({ length: 12 }, (_, i) => ref(`a${i + 1}`)), `var(--${family}-contrast)`, "#fff", "var(--neutral-2)"), `--neutral-placeholder: ${ref("placeholder")};`];
|
|
533
704
|
}
|
|
705
|
+
const neutralPlaceholderVar = (scale) => `--neutral-placeholder: ${solvePlaceholder({
|
|
706
|
+
a10: scale.grayScaleAlpha[9],
|
|
707
|
+
a11: scale.grayScaleAlpha[10],
|
|
708
|
+
page: scale.background,
|
|
709
|
+
card: scale.grayScale[2],
|
|
710
|
+
tints: [
|
|
711
|
+
scale.grayScaleAlpha[3],
|
|
712
|
+
RED_A4[appearanceForBackground(scale.background)],
|
|
713
|
+
scale.accentScaleAlpha[3]
|
|
714
|
+
]
|
|
715
|
+
})};`;
|
|
534
716
|
/**
|
|
535
717
|
* `--neutral-*` declarations from the gray scale the generator produces alongside the
|
|
536
718
|
* accent — normally discarded, because `[data-neutral-color]` points `--neutral-*` at a
|
|
@@ -542,13 +724,13 @@ function neutralFamilyVars(family) {
|
|
|
542
724
|
* than the generator's translucent `graySurface`.
|
|
543
725
|
*/
|
|
544
726
|
function neutralSparkVars(scale) {
|
|
545
|
-
return scaleVars("neutral", scale.grayScale, scale.grayScaleAlpha, readableContrast(scale.grayScale[8], "#fff", MIN_TEXT_CONTRAST), readableContrast(scale.grayScale[8], "#fff", MIN_GRAPHIC_CONTRAST), scale.grayScale[1]);
|
|
727
|
+
return [...scaleVars("neutral", scale.grayScale, scale.grayScaleAlpha, readableContrast(scale.grayScale[8], "#fff", MIN_TEXT_CONTRAST), readableContrast(scale.grayScale[8], "#fff", MIN_GRAPHIC_CONTRAST), scale.grayScale[1]), neutralPlaceholderVar(scale)];
|
|
546
728
|
}
|
|
547
729
|
/** Wide-gamut (P3) twin of `neutralSparkVars`. */
|
|
548
730
|
function neutralSparkVarsP3(scale) {
|
|
549
|
-
return scaleVars("neutral", scale.grayScaleWideGamut, scale.grayScaleAlphaWideGamut, readableContrast(scale.grayScale[8], "#fff", MIN_TEXT_CONTRAST), readableContrast(scale.grayScale[8], "#fff", MIN_GRAPHIC_CONTRAST), scale.grayScaleWideGamut[1]);
|
|
731
|
+
return [...scaleVars("neutral", scale.grayScaleWideGamut, scale.grayScaleAlphaWideGamut, readableContrast(scale.grayScale[8], "#fff", MIN_TEXT_CONTRAST), readableContrast(scale.grayScale[8], "#fff", MIN_GRAPHIC_CONTRAST), scale.grayScaleWideGamut[1]), neutralPlaceholderVar(scale)];
|
|
550
732
|
}
|
|
551
733
|
//#endregion
|
|
552
|
-
export { CURATED_ACCENTS, accentSparkVars, accentSparkVarsP3, appearanceForBackground, generateAccentScale, getMatchingNeutral, invertAccentScale, isParsableColor, neutralFamilyVars, neutralSparkVars, neutralSparkVarsP3, resolveAutoNeutral };
|
|
734
|
+
export { CURATED_ACCENTS, DEFAULT_ACCENT, RED_A4, accentSparkVars, accentSparkVarsP3, appearanceForBackground, generateAccentScale, getMatchingNeutral, invertAccentScale, isParsableColor, neutralFamilyVars, neutralSparkVars, neutralSparkVarsP3, resolveAutoNeutral, solvePlaceholder, staticFieldHoverTints, withReadableTintInk };
|
|
553
735
|
|
|
554
736
|
//# sourceMappingURL=accent-theme.js.map
|