theme-vir 28.14.0 → 28.15.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.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # theme-vir
2
2
 
3
- Easy-to-create, all-in-one theme generator with `element-vir`. WIP.
3
+ Easy-to-create, all-in-one theme generator with [`element-vir`](https://npmjs.com/package/element-vir). WIP.
4
4
 
5
- Reference docs: https://electrovir.github.io/vira/theme-vir/docs
5
+ Reference docs: https://electrovir.github.io/theme-vir
@@ -0,0 +1,85 @@
1
+ import { type PartialWithUndefined, type RequiredAndNotNull } from '@augment-vir/common';
2
+ import { type CssVarDefinitions, type SingleCssVarDefinition } from 'lit-css-vars';
3
+ import { type NoRefColorInit } from './color-theme.js';
4
+ import { ContrastLevelName } from './contrast.js';
5
+ /** @category Internal */
6
+ export type ColorPaletteVars = CssVarDefinitions<Record<`${string}-${string}-${number}`, any>>;
7
+ /** @category Internal */
8
+ export type PaletteColor = {
9
+ suffix: string | undefined;
10
+ prefix: string;
11
+ colorName: string;
12
+ definition: SingleCssVarDefinition;
13
+ cssVarName: string;
14
+ };
15
+ /** @category Internal */
16
+ export type ColorGroups = Record<string, PaletteColor[]>;
17
+ /**
18
+ * Black and white color values.
19
+ *
20
+ * @category Internal
21
+ */
22
+ export declare const defaultOmittedColorGroupColorValues: string[];
23
+ /** @category Internal */
24
+ export declare function groupColors(colors: Readonly<ColorPaletteVars>,
25
+ /**
26
+ * Color values to omit from the grouping. Defaults to
27
+ * {@link defaultOmittedColorGroupColorValues}.
28
+ *
29
+ * @default defaultOmittedColorGroupColorValues
30
+ */
31
+ omittedColorValues?: ReadonlyArray<string>): ColorGroups;
32
+ /** @category Internal */
33
+ export declare function extractPaletteColor(color: Readonly<SingleCssVarDefinition>): PaletteColor;
34
+ /** @category Internal */
35
+ export declare function extractParam<const T extends PropertyKey>(possibleParams: ReadonlyArray<PropertyKey> | Readonly<Record<PropertyKey, boolean>>, { mapFrom, mapTo, }: Readonly<PartialWithUndefined<{
36
+ mapTo: Record<string, T>;
37
+ mapFrom: Record<T, any>;
38
+ }>>): T[];
39
+ /** @category Internal */
40
+ export type ArrayOrSelectParam<T extends PropertyKey> = ReadonlyArray<T> | Readonly<Partial<Record<T, boolean>>>;
41
+ /** @category Internal */
42
+ export declare const defaultLightThemePair: RequiredAndNotNull<NoRefColorInit>;
43
+ /** @category Internal */
44
+ export declare const defaultContrastLevels: Readonly<ArrayOrSelectParam<ContrastLevelName>>;
45
+ /**
46
+ * Creates a color theme from a color palette.
47
+ *
48
+ * @category Color Theme
49
+ */
50
+ export declare function buildLowLevelColorTheme(colorPalette: Readonly<ColorPaletteVars>, { defaultTheme, omittedColorValues, crossContrastLevels, }?: Readonly<PartialWithUndefined<{
51
+ /**
52
+ * The default theme colors for {@link defineColorTheme}. Defaults to
53
+ * {@link defaultLightThemePair}.
54
+ *
55
+ * @default defaultLightThemePair
56
+ */
57
+ defaultTheme: RequiredAndNotNull<NoRefColorInit>;
58
+ /**
59
+ * All font weights to cross colors with. Defaults to {@link defaultContrastLevels}.
60
+ *
61
+ * @default defaultContrastLevels
62
+ */
63
+ crossContrastLevels: Readonly<ArrayOrSelectParam<ContrastLevelName>>;
64
+ /**
65
+ * Color values to omit from the grouping. Defaults to
66
+ * {@link defaultOmittedColorGroupColorValues}.
67
+ *
68
+ * @default defaultOmittedColorGroupColorValues
69
+ */
70
+ omittedColorValues: ReadonlyArray<string>;
71
+ }>>): import("./color-theme.js").ColorTheme<{
72
+ [k: string]: ((Required<Pick<{
73
+ foreground: import("./color-theme.js").ColorInitValue;
74
+ background: import("./color-theme.js").ColorInitValue;
75
+ }, "foreground">> & Partial<Pick<{
76
+ foreground: import("./color-theme.js").ColorInitValue;
77
+ background: import("./color-theme.js").ColorInitValue;
78
+ }, "background">>) | (Required<Pick<{
79
+ foreground: import("./color-theme.js").ColorInitValue;
80
+ background: import("./color-theme.js").ColorInitValue;
81
+ }, "background">> & Partial<Pick<{
82
+ foreground: import("./color-theme.js").ColorInitValue;
83
+ background: import("./color-theme.js").ColorInitValue;
84
+ }, "foreground">>)) & {};
85
+ }>;
@@ -0,0 +1,183 @@
1
+ import { assert, assertWrap, check } from '@augment-vir/assert';
2
+ import { crossProduct, filterMap, getOrSet, log, mapObjectValues, removeDuplicates, stringify, } from '@augment-vir/common';
3
+ import { defineColorTheme, noRefColorInitToString, } from './color-theme.js';
4
+ import { contrastLevelLabel, ContrastLevelName, findClosestColor, findColorAtContrastLevel, } from './contrast.js';
5
+ /**
6
+ * Black and white color values.
7
+ *
8
+ * @category Internal
9
+ */
10
+ export const defaultOmittedColorGroupColorValues = [
11
+ '#000000',
12
+ '#ffffff',
13
+ '#000',
14
+ '#fff',
15
+ 'white',
16
+ 'black',
17
+ ];
18
+ /** @category Internal */
19
+ export function groupColors(colors,
20
+ /**
21
+ * Color values to omit from the grouping. Defaults to
22
+ * {@link defaultOmittedColorGroupColorValues}.
23
+ *
24
+ * @default defaultOmittedColorGroupColorValues
25
+ */
26
+ omittedColorValues = defaultOmittedColorGroupColorValues) {
27
+ const colorGroups = {};
28
+ Object.values(colors).forEach((color) => {
29
+ if (omittedColorValues.includes(color.default)) {
30
+ return;
31
+ }
32
+ const paletteColor = extractPaletteColor(color);
33
+ getOrSet(colorGroups, paletteColor.colorName, () => []).push(paletteColor);
34
+ });
35
+ return colorGroups;
36
+ }
37
+ /** @category Internal */
38
+ export function extractPaletteColor(color) {
39
+ const split = String(color.name).replace(/^-+/, '').split('-');
40
+ const suffix = split.length > 2 ? split.at(-1) : undefined;
41
+ const prefix = assertWrap.isTruthy(split[0]);
42
+ // eslint-disable-next-line sonarjs/argument-type
43
+ const colorName = split.slice(1, suffix ? -1 : undefined).join('-');
44
+ return {
45
+ suffix,
46
+ prefix,
47
+ colorName,
48
+ definition: color,
49
+ cssVarName: String(color.name),
50
+ };
51
+ }
52
+ /** @category Internal */
53
+ export function extractParam(possibleParams, { mapFrom, mapTo, }) {
54
+ if (check.isArray(possibleParams)) {
55
+ return removeDuplicates(possibleParams.map((param) => {
56
+ if (mapFrom && check.isKeyOf(param, mapFrom)) {
57
+ return param;
58
+ }
59
+ else if (mapTo && check.isKeyOf(param, mapTo) && mapTo[param] != undefined) {
60
+ return mapTo[param];
61
+ }
62
+ else {
63
+ throw new Error(`Unknown font weight: ${String(param)}`);
64
+ }
65
+ }));
66
+ }
67
+ else {
68
+ return extractParam(filterMap(Object.entries(possibleParams), ([name, enabled,]) => {
69
+ if (enabled) {
70
+ /**
71
+ * This cast is okay because the recursive case (handling an array) will
72
+ * guard against bas names or weights.
73
+ */
74
+ return name;
75
+ }
76
+ else {
77
+ return undefined;
78
+ }
79
+ }, check.isTruthy), {
80
+ mapTo,
81
+ mapFrom,
82
+ });
83
+ }
84
+ }
85
+ /** @category Internal */
86
+ export const defaultLightThemePair = {
87
+ background: 'white',
88
+ foreground: 'black',
89
+ };
90
+ /** @category Internal */
91
+ export const defaultContrastLevels = {
92
+ [ContrastLevelName.BodyText]: true,
93
+ [ContrastLevelName.Header]: true,
94
+ [ContrastLevelName.Placeholder]: true,
95
+ [ContrastLevelName.Decoration]: true,
96
+ };
97
+ /**
98
+ * Creates a color theme from a color palette.
99
+ *
100
+ * @category Color Theme
101
+ */
102
+ export function buildLowLevelColorTheme(colorPalette, { defaultTheme = defaultLightThemePair, omittedColorValues = defaultOmittedColorGroupColorValues, crossContrastLevels = defaultContrastLevels, } = {}) {
103
+ const contrastLevels = extractParam(crossContrastLevels, {
104
+ mapFrom: contrastLevelLabel,
105
+ });
106
+ const colorGroups = groupColors(colorPalette, omittedColorValues);
107
+ const themeColors = Object.fromEntries(Object.entries(colorGroups).flatMap(([colorGroupName, colors,]) => {
108
+ assert.isLengthAtLeast(colors, 1);
109
+ const colorStrings = colors.map((color) => color.definition.default);
110
+ const allCrosses = crossProduct({
111
+ crossWith: [
112
+ 'ahead-background',
113
+ 'behind-background',
114
+ 'ahead-foreground',
115
+ 'behind-foreground',
116
+ 'self-light-front',
117
+ 'self-light-back',
118
+ ],
119
+ contrast: contrastLevels,
120
+ // fontWeight: fontWeights,
121
+ });
122
+ const firstColor = colors[0];
123
+ const defaultForegroundString = noRefColorInitToString(defaultTheme.foreground);
124
+ const defaultBackgroundString = noRefColorInitToString(defaultTheme.background);
125
+ const lightestSelf = findClosestColor('white', colorStrings);
126
+ return filterMap(allCrosses, (cross) => {
127
+ const crossName = [
128
+ firstColor.prefix,
129
+ firstColor.colorName,
130
+ cross.crossWith,
131
+ cross.contrast,
132
+ // cross.fontWeight,
133
+ ].join('-');
134
+ const comparison = cross.crossWith === 'ahead-background'
135
+ ? {
136
+ foreground: colorStrings,
137
+ background: defaultBackgroundString,
138
+ }
139
+ : cross.crossWith === 'behind-background'
140
+ ? {
141
+ foreground: defaultBackgroundString,
142
+ background: colorStrings,
143
+ }
144
+ : cross.crossWith === 'ahead-foreground'
145
+ ? {
146
+ foreground: colorStrings,
147
+ background: defaultForegroundString,
148
+ }
149
+ : cross.crossWith === 'behind-foreground'
150
+ ? {
151
+ foreground: defaultForegroundString,
152
+ background: colorStrings,
153
+ }
154
+ : cross.crossWith === 'self-light-back'
155
+ ? {
156
+ foreground: colorStrings,
157
+ background: lightestSelf,
158
+ }
159
+ : {
160
+ foreground: lightestSelf,
161
+ background: colorStrings,
162
+ };
163
+ const matchedColorString = findColorAtContrastLevel(comparison, cross.contrast);
164
+ const matchedColor = colors.find((color) => color.definition.default === matchedColorString);
165
+ if (!matchedColor) {
166
+ log.error(`No valid '${colorGroupName}' color cross found for: ${stringify(cross)} with ${stringify(colorStrings)}`);
167
+ return undefined;
168
+ }
169
+ return [
170
+ crossName,
171
+ mapObjectValues(comparison, (key, value) => {
172
+ if (check.isString(value)) {
173
+ return value;
174
+ }
175
+ else {
176
+ return matchedColor.definition.value;
177
+ }
178
+ }),
179
+ ];
180
+ }, check.isTruthy);
181
+ }));
182
+ return defineColorTheme(defaultTheme, themeColors);
183
+ }
@@ -1,16 +1,18 @@
1
1
  import { type PartialWithUndefined } from '@augment-vir/common';
2
2
  import { type BookPage } from 'element-book';
3
- import { type CssVarDefinitions, type CssVarsSetup } from 'lit-css-vars';
3
+ import { type ColorPaletteVars } from './build-color-theme.js';
4
4
  /**
5
5
  * Create multiple element-book pages to showcase a bunch of color CSS variables.
6
6
  *
7
7
  * @category Color Theme
8
8
  * @see `createColorThemeBookPages` for creating full color theme pages.
9
9
  */
10
- export declare function createColorPaletteBookPages({ colors, parent, title, includeContrast, }: {
10
+ export declare function createColorPaletteBookPages({ colors, parent, title, includeContrast, includeTheme, useVerticalTheme, }: {
11
11
  parent: Readonly<BookPage>;
12
12
  title: string;
13
- colors: CssVarDefinitions<CssVarsSetup>;
13
+ colors: Readonly<ColorPaletteVars>;
14
14
  } & PartialWithUndefined<{
15
15
  includeContrast: boolean;
16
- }>): (BookPage<{}, Readonly<BookPage>, {}> | BookPage<{}, BookPage<{}, Readonly<BookPage>, {}>, {}>)[];
16
+ includeTheme: boolean;
17
+ useVerticalTheme: boolean;
18
+ }>): BookPage[];
@@ -1,8 +1,9 @@
1
1
  import { check } from '@augment-vir/assert';
2
- import { getOrSet } from '@augment-vir/common';
3
2
  import { defineBookPage } from 'element-book';
4
3
  import { css, html, unsafeCSS } from 'element-vir';
5
4
  import { noNativeSpacing, viraColorPalette } from 'vira';
5
+ import { buildLowLevelColorTheme, groupColors, } from './build-color-theme.js';
6
+ import { createColorThemeBookPages } from './color-theme-book-pages.js';
6
7
  import { ThemeVirColorExample } from './elements/theme-vir-color-example.element.js';
7
8
  const blackWhiteCells = [
8
9
  {
@@ -46,44 +47,21 @@ const blackWhiteCells = [
46
47
  background: viraColorPalette['vira-white'],
47
48
  },
48
49
  ];
49
- const omittedColors = [
50
- '#000000',
51
- '#ffffff',
52
- '#000',
53
- '#fff',
54
- 'white',
55
- 'black',
56
- ];
57
50
  /**
58
51
  * Create multiple element-book pages to showcase a bunch of color CSS variables.
59
52
  *
60
53
  * @category Color Theme
61
54
  * @see `createColorThemeBookPages` for creating full color theme pages.
62
55
  */
63
- export function createColorPaletteBookPages({ colors, parent, title, includeContrast, }) {
64
- const colorGroups = {};
65
- Object.entries(colors).forEach(([key, color,]) => {
66
- if (omittedColors.includes(color.default)) {
67
- return;
68
- }
69
- // eslint-disable-next-line sonarjs/slow-regex
70
- const groupName = key.replace(/-[\d-]+$/, '');
71
- const suffix = key.replace(groupName, '').replace(/^-+/, '');
72
- getOrSet(colorGroups, groupName, () => []).push({
73
- key,
74
- suffix,
75
- value: color.default,
76
- definition: color,
77
- varName: String(color.name),
78
- });
79
- });
56
+ export function createColorPaletteBookPages({ colors, parent, title, includeContrast, includeTheme, useVerticalTheme, }) {
57
+ const colorGroups = groupColors(colors);
80
58
  const topColorsPage = defineBookPage({
81
59
  parent,
82
60
  title,
83
61
  });
84
62
  const colorPalettePage = defineBookPage({
85
63
  parent: topColorsPage,
86
- title: `${title} Palette`,
64
+ title: 'Palette',
87
65
  defineExamples({ defineExample }) {
88
66
  Object.entries(colorGroups).forEach(([groupName, colors,]) => {
89
67
  defineExample({
@@ -122,13 +100,15 @@ export function createColorPaletteBookPages({ colors, parent, title, includeCont
122
100
  <div
123
101
  class="swatch"
124
102
  style=${css `
125
- background-color: ${unsafeCSS(color.value)};
103
+ background-color: ${unsafeCSS(color.definition.default)};
126
104
  `}
127
105
  ></div>
128
106
  <p class="color-details">
129
- <span>${color.varName}</span>
107
+ <span>${color.cssVarName}</span>
130
108
  <br />
131
- <span class="color-value">${color.value}</span>
109
+ <span class="color-value">
110
+ ${color.definition.default}
111
+ </span>
132
112
  </p>
133
113
  </div>
134
114
  `;
@@ -138,92 +118,19 @@ export function createColorPaletteBookPages({ colors, parent, title, includeCont
138
118
  });
139
119
  },
140
120
  });
141
- const blackWhiteContrastPage = defineBookPage({
121
+ const contrastsPage = defineBookPage({
142
122
  parent: topColorsPage,
143
- title: `${title} Contrast Black White`,
144
- defineExamples({ defineExample }) {
145
- Object.entries(colorGroups).forEach(([groupName, colors,]) => {
146
- defineExample({
147
- title: groupName,
148
- styles: css `
149
- :host {
150
- display: flex;
151
- flex-direction: column;
152
- gap: 24px;
153
- }
154
-
155
- p {
156
- ${noNativeSpacing}
157
- }
158
-
159
- .darkness-level {
160
- text-align: center;
161
- font-size: 12px;
162
- color: ${viraColorPalette['vira-grey-50'].value};
163
- }
164
-
165
- td {
166
- padding: 4px 0;
167
- }
168
- `,
169
- render() {
170
- const colorRowTemplates = colors.map((color) => {
171
- const cellTemplates = blackWhiteCells.map((cell) => {
172
- return html `
173
- <td>
174
- <p class="darkness-level">${color.suffix}</p>
175
- <${ThemeVirColorExample.assign({
176
- color: {
177
- background: cell.background || color.definition,
178
- foreground: cell.foreground || color.definition,
179
- },
180
- showVarValues: true,
181
- showVarNames: false,
182
- showContrast: true,
183
- fontWeight: cell.fontWeight,
184
- })}></${ThemeVirColorExample}>
185
- </td>
186
- `;
187
- });
188
- return html `
189
- <tr>${cellTemplates}</tr>
190
- `;
191
- });
192
- const headerCells = blackWhiteCells.map((cell) => {
193
- const layerText = cell.background ? 'in back' : 'in front';
194
- const title = [
195
- cell.title,
196
- `(${layerText})`,
197
- `(${cell.fontWeight})`,
198
- ].join(' ');
199
- return html `
200
- <th>${title}</th>
201
- `;
202
- });
203
- return html `
204
- <table cellspacing="0" cellpadding="0">
205
- <thead><tr>${headerCells}</tr></thead>
206
- <tbody>${colorRowTemplates}</tbody>
207
- </table>
208
- `;
209
- },
210
- });
211
- });
212
- },
123
+ title: 'Palette Contrast',
213
124
  });
214
- function createSelfContrastPage(fontWeight) {
125
+ function createContrastPage(contrastPageTitle, contrastCellsInput) {
215
126
  return defineBookPage({
216
- parent: topColorsPage,
217
- title: `${title} Contrast Self ${fontWeight}`,
127
+ parent: contrastsPage,
128
+ title: `${title} ${contrastPageTitle}`,
218
129
  defineExamples({ defineExample }) {
219
130
  Object.entries(colorGroups).forEach(([groupName, colors,]) => {
220
- const selfContrastCells = colors.map((color) => {
221
- return {
222
- fontWeight,
223
- title: color.suffix,
224
- foreground: color.definition,
225
- };
226
- });
131
+ const contrastCells = check.isArray(contrastCellsInput)
132
+ ? contrastCellsInput
133
+ : contrastCellsInput(colors);
227
134
  defineExample({
228
135
  title: groupName,
229
136
  styles: css `
@@ -244,12 +151,13 @@ export function createColorPaletteBookPages({ colors, parent, title, includeCont
244
151
  }
245
152
 
246
153
  td {
247
- padding: 4px 0;
154
+ padding: 4px;
155
+ min-width: 170px;
248
156
  }
249
157
  `,
250
158
  render() {
251
159
  const colorRowTemplates = colors.map((color) => {
252
- const cellTemplates = selfContrastCells.map((cell) => {
160
+ const cellTemplates = contrastCells.map((cell) => {
253
161
  return html `
254
162
  <td>
255
163
  <p class="darkness-level">${color.suffix}</p>
@@ -270,7 +178,7 @@ export function createColorPaletteBookPages({ colors, parent, title, includeCont
270
178
  <tr>${cellTemplates}</tr>
271
179
  `;
272
180
  });
273
- const headerCells = selfContrastCells.map((cell) => {
181
+ const headerCells = contrastCells.map((cell) => {
274
182
  const layerText = cell.background ? 'in back' : 'in front';
275
183
  const title = [
276
184
  cell.title,
@@ -293,11 +201,32 @@ export function createColorPaletteBookPages({ colors, parent, title, includeCont
293
201
  },
294
202
  });
295
203
  }
204
+ const blackWhiteContrastPage = createContrastPage('Contrast Black White', blackWhiteCells);
205
+ function createSelfContrastPage(fontWeight) {
206
+ return createContrastPage(`Contrast Self ${fontWeight}`, (colors) => colors.map((color) => {
207
+ return {
208
+ fontWeight,
209
+ title: color.suffix || '',
210
+ foreground: color.definition,
211
+ };
212
+ }));
213
+ }
296
214
  return [
297
215
  topColorsPage,
298
216
  colorPalettePage,
217
+ contrastsPage,
299
218
  includeContrast ? blackWhiteContrastPage : undefined,
300
219
  includeContrast ? createSelfContrastPage(400) : undefined,
301
220
  includeContrast ? createSelfContrastPage(700) : undefined,
221
+ ...(includeTheme
222
+ ? createColorThemeBookPages({
223
+ parent: topColorsPage,
224
+ title: 'Theme (auto)',
225
+ theme: buildLowLevelColorTheme(colors),
226
+ hideInverseColors: true,
227
+ useVerticalLayout: useVerticalTheme,
228
+ prefixGroupByCount: 2,
229
+ })
230
+ : []),
302
231
  ].filter(check.isTruthy);
303
232
  }
@@ -7,17 +7,24 @@ import { type ColorTheme } from './color-theme.js';
7
7
  *
8
8
  * @category Color Theme
9
9
  */
10
- export declare function createColorThemeBookPages({ parent, title, theme, hideInverseColors, overrides, }: {
10
+ export declare function createColorThemeBookPages({ parent, title, theme, hideInverseColors, overrides, useVerticalLayout, prefixGroupByCount, }: {
11
11
  title: string;
12
12
  theme: Readonly<ColorTheme>;
13
13
  } & PartialWithUndefined<{
14
14
  parent: Readonly<BookPage>;
15
15
  hideInverseColors: boolean;
16
16
  overrides: ReadonlyArray<Readonly<ColorThemeOverride>>;
17
+ useVerticalLayout: boolean;
18
+ /**
19
+ * The number of CSS variable name prefixes to group colors by.
20
+ *
21
+ * @default 0
22
+ */
23
+ prefixGroupByCount: number;
17
24
  }>): (BookPage<{}, Readonly<BookPage> | undefined, {
18
- readonly 'Show Var Names': import("element-book").BookPageControlInit<BookPageControlType.Checkbox>;
19
- readonly 'Show Contrast Tips': import("element-book").BookPageControlInit<BookPageControlType.Checkbox>;
25
+ 'Show Var Names': import("element-book").BookPageControlInit<BookPageControlType.Checkbox>;
26
+ 'Show Contrast Tips': import("element-book").BookPageControlInit<BookPageControlType.Checkbox>;
20
27
  }> | BookPage<{}, BookPage<{}, Readonly<BookPage> | undefined, {
21
- readonly 'Show Var Names': import("element-book").BookPageControlInit<BookPageControlType.Checkbox>;
22
- readonly 'Show Contrast Tips': import("element-book").BookPageControlInit<BookPageControlType.Checkbox>;
28
+ 'Show Var Names': import("element-book").BookPageControlInit<BookPageControlType.Checkbox>;
29
+ 'Show Contrast Tips': import("element-book").BookPageControlInit<BookPageControlType.Checkbox>;
23
30
  }>, {}>)[];