theme-vir 28.21.2 → 28.23.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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assert, assertWrap, check } from '@augment-vir/assert';
|
|
2
2
|
import { arrayToObject, crossProduct, filterMap, getEnumValues, getOrSet, log, mapObjectValues, removeDuplicates, stringify, } from '@augment-vir/common';
|
|
3
|
-
import { ContrastLevelName, contrastLevelLabel,
|
|
3
|
+
import { ContrastLevelName, calculateContrast, contrastLevelLabel, contrastLevelNameMap, findColorAtContrastLevel, } from '@electrovir/color';
|
|
4
4
|
import { defineColorThemeOverride } from './color-theme-override.js';
|
|
5
5
|
import { defineColorTheme, noRefColorInitToString, } from './color-theme.js';
|
|
6
6
|
/**
|
|
@@ -90,6 +90,38 @@ export const defaultLightThemePair = {
|
|
|
90
90
|
};
|
|
91
91
|
/** @category Internal */
|
|
92
92
|
export const defaultContrastLevels = getEnumValues(ContrastLevelName);
|
|
93
|
+
function findColorWithPreference(colors, desiredContrastLevel, preference,
|
|
94
|
+
/** Pre-computed contrast-against-white values per color string. Higher = darker. */
|
|
95
|
+
lightnessProxies) {
|
|
96
|
+
const minContrast = contrastLevelNameMap[desiredContrastLevel].min;
|
|
97
|
+
const candidateColors = check.isArray(colors.foreground)
|
|
98
|
+
? colors.foreground
|
|
99
|
+
: check.isArray(colors.background)
|
|
100
|
+
? colors.background
|
|
101
|
+
: [];
|
|
102
|
+
const qualifying = candidateColors.filter((candidate) => {
|
|
103
|
+
const foreground = check.isArray(colors.foreground) ? candidate : colors.foreground;
|
|
104
|
+
const background = check.isArray(colors.foreground) ? colors.background : candidate;
|
|
105
|
+
return (Math.abs(calculateContrast({
|
|
106
|
+
foreground,
|
|
107
|
+
background: background,
|
|
108
|
+
}).contrast) >= minContrast);
|
|
109
|
+
});
|
|
110
|
+
if (qualifying.length === 0) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
return qualifying.reduce((best, color) => {
|
|
114
|
+
const bestProxy = lightnessProxies[best] ?? 0;
|
|
115
|
+
const colorProxy = lightnessProxies[color] ?? 0;
|
|
116
|
+
return preference === 'lightest'
|
|
117
|
+
? colorProxy < bestProxy
|
|
118
|
+
? color
|
|
119
|
+
: best
|
|
120
|
+
: colorProxy > bestProxy
|
|
121
|
+
? color
|
|
122
|
+
: best;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
93
125
|
/**
|
|
94
126
|
* Creates a color theme from a color palette.
|
|
95
127
|
*
|
|
@@ -132,10 +164,38 @@ export function buildColorTheme(colorPalette, { omittedColorValues = defaultOmit
|
|
|
132
164
|
key: color.definition.default,
|
|
133
165
|
value: color,
|
|
134
166
|
}));
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
167
|
+
/** Pre-computed contrast-against-white per color. Higher value = darker shade. */
|
|
168
|
+
const lightnessProxies = arrayToObject(colorStrings, (colorString) => {
|
|
169
|
+
return {
|
|
170
|
+
key: colorString,
|
|
171
|
+
value: Math.abs(calculateContrast({
|
|
172
|
+
foreground: colorString,
|
|
173
|
+
background: '#ffffff',
|
|
174
|
+
}).contrast),
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
/** Lightest palette color (least contrast against white). */
|
|
178
|
+
const lightestColorString = colorStrings.reduce((lightest, color) => (lightnessProxies[color] ?? 0) < (lightnessProxies[lightest] ?? 0)
|
|
179
|
+
? color
|
|
180
|
+
: lightest);
|
|
181
|
+
/** Darkest palette color (most contrast against white). */
|
|
182
|
+
const darkestColorString = colorStrings.reduce((darkest, color) => (lightnessProxies[color] ?? 0) > (lightnessProxies[darkest] ?? 0) ? color : darkest);
|
|
183
|
+
/**
|
|
184
|
+
* On-self light mode: lightest fg achieving small-body contrast on the lightest palette
|
|
185
|
+
* bg. Fixed across all on-self contrast levels.
|
|
186
|
+
*/
|
|
187
|
+
const lightSelfFgString = assertWrap.isTruthy(findColorWithPreference({
|
|
188
|
+
foreground: colorStrings,
|
|
189
|
+
background: lightestColorString,
|
|
190
|
+
}, ContrastLevelName.SmallBodyText, 'lightest', lightnessProxies), `Failed to find light mode on-self foreground color for ${firstColor.colorName}`);
|
|
191
|
+
/**
|
|
192
|
+
* On-self dark mode: darkest fg achieving small-body contrast on the darkest palette
|
|
193
|
+
* bg. Fixed across all on-self contrast levels.
|
|
194
|
+
*/
|
|
195
|
+
const darkSelfFgString = assertWrap.isTruthy(findColorWithPreference({
|
|
196
|
+
foreground: colorStrings,
|
|
197
|
+
background: darkestColorString,
|
|
198
|
+
}, ContrastLevelName.SmallBodyText, 'darkest', lightnessProxies), `Failed to find dark mode on-self foreground color for ${firstColor.colorName}`);
|
|
139
199
|
// Pre-compute base name parts that don't change per cross
|
|
140
200
|
const baseNameParts = [
|
|
141
201
|
prefix,
|
|
@@ -154,13 +214,13 @@ export function buildColorTheme(colorPalette, { omittedColorValues = defaultOmit
|
|
|
154
214
|
}
|
|
155
215
|
: cross.crossWith === 'color-on-self-dark-mode'
|
|
156
216
|
? {
|
|
157
|
-
foreground:
|
|
158
|
-
background:
|
|
217
|
+
foreground: darkSelfFgString,
|
|
218
|
+
background: colorStrings,
|
|
159
219
|
}
|
|
160
220
|
: cross.crossWith === 'color-on-self-light-mode'
|
|
161
221
|
? {
|
|
162
|
-
foreground:
|
|
163
|
-
background:
|
|
222
|
+
foreground: lightSelfFgString,
|
|
223
|
+
background: colorStrings,
|
|
164
224
|
}
|
|
165
225
|
: cross.crossWith === 'color-behind-bg-light-mode'
|
|
166
226
|
? {
|
|
@@ -241,12 +301,16 @@ export function buildColorTheme(colorPalette, { omittedColorValues = defaultOmit
|
|
|
241
301
|
cross.crossWith === 'color-behind-fg-dark-mode';
|
|
242
302
|
const colorValue = mapObjectValues(comparison, (key, value) => {
|
|
243
303
|
if (check.isString(value)) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
304
|
+
/**
|
|
305
|
+
* For self-contrast modes, the foreground is the fixed string side —
|
|
306
|
+
* use its CSS var reference
|
|
307
|
+
*/
|
|
308
|
+
if (isSelfContrast && key === 'foreground') {
|
|
309
|
+
const selfFg = cross.crossWith === 'color-on-self-light-mode'
|
|
310
|
+
? lightSelfFgString
|
|
311
|
+
: darkSelfFgString;
|
|
312
|
+
const selfFgColor = selfFg ? colorByDefault[selfFg] : undefined;
|
|
313
|
+
return selfFgColor?.definition.value || value;
|
|
250
314
|
}
|
|
251
315
|
const referenceToDefault = referencesToDefault &&
|
|
252
316
|
check.isKeyOf(key, referencesToDefault) &&
|
|
@@ -10,8 +10,12 @@ export var HeadingLevel;
|
|
|
10
10
|
export function createDefaultThemeOptions() {
|
|
11
11
|
const defaultFont = {
|
|
12
12
|
family: 'sans-serif',
|
|
13
|
-
lineHeight: {
|
|
14
|
-
|
|
13
|
+
lineHeight: {
|
|
14
|
+
ratio: 1.1,
|
|
15
|
+
},
|
|
16
|
+
size: {
|
|
17
|
+
pixels: 14,
|
|
18
|
+
},
|
|
15
19
|
weight: 400,
|
|
16
20
|
};
|
|
17
21
|
const bold = {
|
|
@@ -29,31 +33,43 @@ export function createDefaultThemeOptions() {
|
|
|
29
33
|
monospace: {
|
|
30
34
|
...defaultFont,
|
|
31
35
|
family: 'monospace',
|
|
32
|
-
size: {
|
|
36
|
+
size: {
|
|
37
|
+
ratio: 1.2,
|
|
38
|
+
},
|
|
33
39
|
},
|
|
34
40
|
headings: {
|
|
35
41
|
h1: {
|
|
36
42
|
...bold,
|
|
37
|
-
size: {
|
|
43
|
+
size: {
|
|
44
|
+
ratio: 2,
|
|
45
|
+
},
|
|
38
46
|
},
|
|
39
47
|
h2: {
|
|
40
48
|
...bold,
|
|
41
|
-
size: {
|
|
49
|
+
size: {
|
|
50
|
+
ratio: 1.5,
|
|
51
|
+
},
|
|
42
52
|
},
|
|
43
53
|
h3: {
|
|
44
54
|
...bold,
|
|
45
|
-
size: {
|
|
55
|
+
size: {
|
|
56
|
+
ratio: 1.17,
|
|
57
|
+
},
|
|
46
58
|
},
|
|
47
59
|
h4: {
|
|
48
60
|
...bold,
|
|
49
61
|
},
|
|
50
62
|
h5: {
|
|
51
63
|
...bold,
|
|
52
|
-
size: {
|
|
64
|
+
size: {
|
|
65
|
+
ratio: 0.83,
|
|
66
|
+
},
|
|
53
67
|
},
|
|
54
68
|
h6: {
|
|
55
69
|
...bold,
|
|
56
|
-
size: {
|
|
70
|
+
size: {
|
|
71
|
+
ratio: 0.67,
|
|
72
|
+
},
|
|
57
73
|
},
|
|
58
74
|
},
|
|
59
75
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "theme-vir",
|
|
3
|
-
"version": "28.
|
|
3
|
+
"version": "28.23.0",
|
|
4
4
|
"description": "Create an entire web theme.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"design",
|
|
@@ -43,31 +43,31 @@
|
|
|
43
43
|
"test:update": "virmator test web update"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@augment-vir/assert": "^31.
|
|
47
|
-
"@augment-vir/common": "^31.
|
|
46
|
+
"@augment-vir/assert": "^31.67.1",
|
|
47
|
+
"@augment-vir/common": "^31.67.1",
|
|
48
48
|
"@electrovir/color": "^1.7.8",
|
|
49
49
|
"apca-w3": "^0.1.9",
|
|
50
50
|
"lit-css-vars": "^3.5.0",
|
|
51
51
|
"type-fest": "^5.4.4"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@augment-vir/test": "^31.
|
|
54
|
+
"@augment-vir/test": "^31.67.1",
|
|
55
55
|
"@types/apca-w3": "^0.1.3",
|
|
56
56
|
"@web/dev-server-esbuild": "^1.0.5",
|
|
57
57
|
"@web/test-runner": "^0.20.2",
|
|
58
58
|
"@web/test-runner-commands": "^0.9.0",
|
|
59
59
|
"@web/test-runner-playwright": "^0.11.1",
|
|
60
60
|
"@web/test-runner-visual-regression": "^0.10.0",
|
|
61
|
-
"element-book": "^26.
|
|
62
|
-
"element-vir": "^26.14.
|
|
61
|
+
"element-book": "^26.17.0",
|
|
62
|
+
"element-vir": "^26.14.5",
|
|
63
63
|
"esbuild": "^0.27.3",
|
|
64
64
|
"istanbul-smart-text-reporter": "^1.1.5",
|
|
65
65
|
"markdown-code-example-inserter": "^3.0.3",
|
|
66
|
-
"typedoc": "^0.28.
|
|
66
|
+
"typedoc": "^0.28.17",
|
|
67
67
|
"typescript": "5.9.3",
|
|
68
|
-
"vira": "^
|
|
68
|
+
"vira": "^30.6.0",
|
|
69
69
|
"vite": "^7.3.1",
|
|
70
|
-
"vite-tsconfig-paths": "^6.1.
|
|
70
|
+
"vite-tsconfig-paths": "^6.1.1"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
73
|
"element-book": ">=17",
|