theme-vir 28.22.0 → 28.23.1

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, findColorAtContrastLevel } from '@electrovir/color';
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,14 +164,38 @@ export function buildColorTheme(colorPalette, { omittedColorValues = defaultOmit
132
164
  key: color.definition.default,
133
165
  value: color,
134
166
  }));
135
- const lightSelfFgString = assertWrap.isTruthy(findColorAtContrastLevel({
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({
136
188
  foreground: colorStrings,
137
- background: defaultBackgroundString,
138
- }, ContrastLevelName.SmallBodyText), `Failed to find light mode small body text color for ${firstColor.colorName}`);
139
- const darkSelfFgString = assertWrap.isTruthy(findColorAtContrastLevel({
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({
140
196
  foreground: colorStrings,
141
- background: defaultForegroundString,
142
- }, ContrastLevelName.SmallBodyText), `Failed to find dark mode small body text color for ${firstColor.colorName}`);
197
+ background: darkestColorString,
198
+ }, ContrastLevelName.SmallBodyText, 'darkest', lightnessProxies), `Failed to find dark mode on-self foreground color for ${firstColor.colorName}`);
143
199
  // Pre-compute base name parts that don't change per cross
144
200
  const baseNameParts = [
145
201
  prefix,
@@ -2,7 +2,7 @@ import { check } from '@augment-vir/assert';
2
2
  import { VirColorPair } from '@electrovir/color';
3
3
  import { defineBookPage } from 'element-book';
4
4
  import { css, html, unsafeCSS } from 'element-vir';
5
- import { noNativeSpacing, viraColorPalette, viraTheme } from 'vira';
5
+ import { noNativeSpacing, viraTheme } from 'vira';
6
6
  import { buildColorTheme, groupColors, } from './build-color-theme.js';
7
7
  import { createColorThemeBookPages } from './color-theme-book-pages.js';
8
8
  import { themeDefaultKey } from './color-theme.js';
@@ -86,7 +86,8 @@ export function createColorPaletteBookPages({ colors, parent, title, includeCont
86
86
  & .color-details {
87
87
  font-family: monospace;
88
88
  font-size: 12px;
89
- color: ${viraColorPalette['vira-grey-50'].value};
89
+ color: ${viraTheme.colors['vira-grey-foreground-header']
90
+ .foreground.value};
90
91
  }
91
92
 
92
93
  & .color-value {
@@ -148,7 +149,8 @@ export function createColorPaletteBookPages({ colors, parent, title, includeCont
148
149
  .darkness-level {
149
150
  text-align: center;
150
151
  font-size: 12px;
151
- color: ${viraColorPalette['vira-grey-50'].value};
152
+ color: ${viraTheme.colors['vira-grey-foreground-header']
153
+ .foreground.value};
152
154
  }
153
155
 
154
156
  td {
@@ -10,8 +10,12 @@ export var HeadingLevel;
10
10
  export function createDefaultThemeOptions() {
11
11
  const defaultFont = {
12
12
  family: 'sans-serif',
13
- lineHeight: { ratio: 1.1 },
14
- size: { pixels: 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: { ratio: 1.2 },
36
+ size: {
37
+ ratio: 1.2,
38
+ },
33
39
  },
34
40
  headings: {
35
41
  h1: {
36
42
  ...bold,
37
- size: { ratio: 2 },
43
+ size: {
44
+ ratio: 2,
45
+ },
38
46
  },
39
47
  h2: {
40
48
  ...bold,
41
- size: { ratio: 1.5 },
49
+ size: {
50
+ ratio: 1.5,
51
+ },
42
52
  },
43
53
  h3: {
44
54
  ...bold,
45
- size: { ratio: 1.17 },
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: { ratio: 0.83 },
64
+ size: {
65
+ ratio: 0.83,
66
+ },
53
67
  },
54
68
  h6: {
55
69
  ...bold,
56
- size: { ratio: 0.67 },
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.22.0",
3
+ "version": "28.23.1",
4
4
  "description": "Create an entire web theme.",
5
5
  "keywords": [
6
6
  "design",
@@ -43,29 +43,29 @@
43
43
  "test:update": "virmator test web update"
44
44
  },
45
45
  "dependencies": {
46
- "@augment-vir/assert": "^31.64.1",
47
- "@augment-vir/common": "^31.64.1",
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.64.1",
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.16.1",
62
- "element-vir": "^26.14.3",
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
66
  "typedoc": "^0.28.17",
67
67
  "typescript": "5.9.3",
68
- "vira": "^29.5.6",
68
+ "vira": "^30.6.0",
69
69
  "vite": "^7.3.1",
70
70
  "vite-tsconfig-paths": "^6.1.1"
71
71
  },