theme-vir 28.24.0 → 28.25.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.
@@ -43,6 +43,17 @@ export type ArrayOrSelectParam<T extends PropertyKey> = ReadonlyArray<T> | Reado
43
43
  export declare const defaultLightThemePair: RequiredAndNotNull<NoRefColorInit>;
44
44
  /** @category Internal */
45
45
  export declare const defaultContrastLevels: Readonly<ArrayOrSelectParam<ContrastLevelName>>;
46
+ /**
47
+ * Extra contrast levels generated by {@link buildColorTheme} beyond the standard `ContrastLevelName`
48
+ * values. `highest-contrast` always picks the palette color with the most contrast against the
49
+ * fixed color; `lowest-contrast` always picks the closest.
50
+ *
51
+ * @category Internal
52
+ */
53
+ export declare enum ExtremeContrastLevel {
54
+ HighestContrast = "highest-contrast",
55
+ LowestContrast = "lowest-contrast"
56
+ }
46
57
  /**
47
58
  * Options for {@link buildColorTheme}.
48
59
  *
@@ -90,6 +90,55 @@ export const defaultLightThemePair = {
90
90
  };
91
91
  /** @category Internal */
92
92
  export const defaultContrastLevels = getEnumValues(ContrastLevelName);
93
+ /**
94
+ * Extra contrast levels generated by {@link buildColorTheme} beyond the standard `ContrastLevelName`
95
+ * values. `highest-contrast` always picks the palette color with the most contrast against the
96
+ * fixed color; `lowest-contrast` always picks the closest.
97
+ *
98
+ * @category Internal
99
+ */
100
+ export var ExtremeContrastLevel;
101
+ (function (ExtremeContrastLevel) {
102
+ ExtremeContrastLevel["HighestContrast"] = "highest-contrast";
103
+ ExtremeContrastLevel["LowestContrast"] = "lowest-contrast";
104
+ })(ExtremeContrastLevel || (ExtremeContrastLevel = {}));
105
+ /**
106
+ * Picks the absolute lightest or darkest palette color based on which extreme produces the most (or
107
+ * least) contrast against the fixed color.
108
+ */
109
+ function resolveExtremeContrastColor({ comparison, isHighestContrast, lightestColorString, darkestColorString, }) {
110
+ const paletteIsBackground = check.isArray(comparison.background);
111
+ const fixedColor = paletteIsBackground
112
+ ? comparison.foreground
113
+ : comparison.background;
114
+ const lightContrastParams = paletteIsBackground
115
+ ? {
116
+ foreground: fixedColor,
117
+ background: lightestColorString,
118
+ }
119
+ : {
120
+ foreground: lightestColorString,
121
+ background: fixedColor,
122
+ };
123
+ const darkContrastParams = paletteIsBackground
124
+ ? {
125
+ foreground: fixedColor,
126
+ background: darkestColorString,
127
+ }
128
+ : {
129
+ foreground: darkestColorString,
130
+ background: fixedColor,
131
+ };
132
+ const lightestContrast = Math.abs(calculateContrast(lightContrastParams).contrast);
133
+ const darkestContrast = Math.abs(calculateContrast(darkContrastParams).contrast);
134
+ return isHighestContrast
135
+ ? lightestContrast > darkestContrast
136
+ ? lightestColorString
137
+ : darkestColorString
138
+ : lightestContrast < darkestContrast
139
+ ? lightestColorString
140
+ : darkestColorString;
141
+ }
93
142
  function findColorWithPreference(colors, desiredContrastLevel, preference,
94
143
  /** Pre-computed contrast-against-white values per color string. Higher = darker. */
95
144
  lightnessProxies) {
@@ -140,6 +189,11 @@ export function buildColorTheme(colorPalette, { omittedColorValues = defaultOmit
140
189
  const lightThemeColors = {};
141
190
  const darkThemeOverrides = {};
142
191
  // Compute these once outside the loop since they don't change
192
+ const allContrastLevels = [
193
+ ExtremeContrastLevel.HighestContrast,
194
+ ...contrastLevels,
195
+ ExtremeContrastLevel.LowestContrast,
196
+ ];
143
197
  const allCrosses = crossProduct({
144
198
  crossWith: [
145
199
  'color-in-foreground-light-mode',
@@ -151,7 +205,7 @@ export function buildColorTheme(colorPalette, { omittedColorValues = defaultOmit
151
205
  'color-on-self-light-mode',
152
206
  'color-on-self-dark-mode',
153
207
  ],
154
- contrast: contrastLevels,
208
+ contrast: allContrastLevels,
155
209
  });
156
210
  const defaultForegroundString = noRefColorInitToString(defaultTheme.foreground);
157
211
  const defaultBackgroundString = noRefColorInitToString(defaultTheme.background);
@@ -196,6 +250,15 @@ export function buildColorTheme(colorPalette, { omittedColorValues = defaultOmit
196
250
  foreground: colorStrings,
197
251
  background: darkestColorString,
198
252
  }, ContrastLevelName.SmallBodyText, 'darkest', lightnessProxies), `Failed to find dark mode on-self foreground color for ${firstColor.colorName}`);
253
+ /**
254
+ * Reversed palette order for dark mode on-self backgrounds. `findColorAtContrastLevel`
255
+ * picks the last qualifying color in array order. With the natural lightest-to-darkest
256
+ * order, high-contrast backgrounds end up as the darkest palette colors, which are
257
+ * indistinguishable from the dark page background. Reversing the order makes it select
258
+ * the lightest palette color that still achieves the required contrast level, producing
259
+ * backgrounds that are visible against the dark page.
260
+ */
261
+ const reversedColorStrings = colorStrings.toReversed();
199
262
  // Pre-compute base name parts that don't change per cross
200
263
  const baseNameParts = [
201
264
  prefix,
@@ -215,7 +278,7 @@ export function buildColorTheme(colorPalette, { omittedColorValues = defaultOmit
215
278
  : cross.crossWith === 'color-on-self-dark-mode'
216
279
  ? {
217
280
  foreground: darkSelfFgString,
218
- background: colorStrings,
281
+ background: reversedColorStrings,
219
282
  }
220
283
  : cross.crossWith === 'color-on-self-light-mode'
221
284
  ? {
@@ -285,7 +348,15 @@ export function buildColorTheme(colorPalette, { omittedColorValues = defaultOmit
285
348
  if (!comparison) {
286
349
  throw new Error(`Forgot to handle crossWith: '${cross.crossWith}'`);
287
350
  }
288
- const matchedColorString = findColorAtContrastLevel(comparison, cross.contrast);
351
+ const matchedColorString = cross.contrast === ExtremeContrastLevel.HighestContrast ||
352
+ cross.contrast === ExtremeContrastLevel.LowestContrast
353
+ ? resolveExtremeContrastColor({
354
+ comparison,
355
+ isHighestContrast: cross.contrast === ExtremeContrastLevel.HighestContrast,
356
+ lightestColorString,
357
+ darkestColorString,
358
+ })
359
+ : findColorAtContrastLevel(comparison, cross.contrast);
289
360
  const matchedColor = matchedColorString
290
361
  ? colorByDefault[matchedColorString]
291
362
  : undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theme-vir",
3
- "version": "28.24.0",
3
+ "version": "28.25.0",
4
4
  "description": "Create an entire web theme.",
5
5
  "keywords": [
6
6
  "design",