theme-vir 28.13.0 → 28.15.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,7 +1,39 @@
1
- import { assertWrap } from '@augment-vir/assert';
2
- import { arrayToObject, round } from '@augment-vir/common';
1
+ import { assertWrap, check } from '@augment-vir/assert';
2
+ import { arrayToObject, mapObjectValues, round, } from '@augment-vir/common';
3
3
  // @ts-expect-error: `fontLookupAPCA` is not in the types
4
4
  import { calcAPCA, fontLookupAPCA } from 'apca-w3';
5
+ /**
6
+ * All considered font weights in {@link FontWeight} mapped by their weight name.
7
+ *
8
+ * @category Internal
9
+ */
10
+ export const fontWeightByName = {
11
+ Thin: 100,
12
+ ExtraLight: 200,
13
+ Light: 300,
14
+ Normal: 400,
15
+ Medium: 500,
16
+ SemiBold: 600,
17
+ Bold: 700,
18
+ ExtraBold: 800,
19
+ Heavy: 900,
20
+ };
21
+ /**
22
+ * All font weight names from {@link fontWeightByName}.
23
+ *
24
+ * @category Internal
25
+ */
26
+ export const FontWeightName = mapObjectValues(fontWeightByName, (key) => key);
27
+ /**
28
+ * All considered font weights in {@link FontWeight} mapped to their weight name from
29
+ * {@link fontWeightByName}.
30
+ *
31
+ * @category Internal
32
+ */
33
+ export const fontWeightToName = Object.fromEntries(Object.entries(fontWeightByName).map(([key, value,]) => [
34
+ value,
35
+ key,
36
+ ]));
5
37
  /**
6
38
  * Calculate contrast for the given color combination.
7
39
  *
@@ -15,6 +47,65 @@ export function calculateContrast({ background, foreground, }) {
15
47
  contrastLevel: determineContrastLevel(contrast),
16
48
  };
17
49
  }
50
+ /** @category Internal */
51
+ export function findClosestColor(baseColor, possibleColors) {
52
+ return possibleColors.reduce((best, color) => {
53
+ const contrast = Math.abs(calculateContrast({
54
+ foreground: color,
55
+ background: baseColor,
56
+ }).contrast);
57
+ if (contrast > best.contrast) {
58
+ return best;
59
+ }
60
+ else {
61
+ return {
62
+ contrast,
63
+ color,
64
+ };
65
+ }
66
+ }, {
67
+ contrast: Infinity,
68
+ color: '',
69
+ }).color;
70
+ }
71
+ /**
72
+ * Find a color from an array that matches the desired contrast level.
73
+ *
74
+ * @category Internal
75
+ * @returns `undefined` if no color match is found.
76
+ */
77
+ export function findColorAtContrastLevel(colors, desiredContrastLevel) {
78
+ const otherColors = check.isArray(colors.foreground)
79
+ ? colors.foreground
80
+ : check.isArray(colors.background)
81
+ ? colors.background
82
+ : new Error('No color array provided.');
83
+ if (otherColors instanceof Error) {
84
+ throw otherColors;
85
+ }
86
+ const desiredIndex = orderedContrastLevelNames.indexOf(desiredContrastLevel);
87
+ const bestMatch = otherColors.reduce((best, otherColor) => {
88
+ const contrast = calculateContrast({
89
+ foreground: check.isString(colors.foreground) ? colors.foreground : otherColor,
90
+ background: check.isString(colors.background) ? colors.background : otherColor,
91
+ });
92
+ const contrastIndex = orderedContrastLevelNames.indexOf(contrast.contrastLevel.name);
93
+ const distance = contrastIndex - desiredIndex;
94
+ if (distance > 0 || best.distance > distance) {
95
+ return best;
96
+ }
97
+ else {
98
+ return {
99
+ color: otherColor,
100
+ distance,
101
+ };
102
+ }
103
+ }, {
104
+ distance: 0,
105
+ color: undefined,
106
+ });
107
+ return bestMatch.color;
108
+ }
18
109
  /**
19
110
  * Calculated needed font sizes for each font weight for the given color contrast.
20
111
  *
@@ -45,11 +136,11 @@ export function determineContrastLevel(contrast) {
45
136
  */
46
137
  export var ContrastLevelName;
47
138
  (function (ContrastLevelName) {
48
- ContrastLevelName["SmallBodyText"] = "small-body-text";
49
- ContrastLevelName["BodyText"] = "body-text";
50
- ContrastLevelName["NonBodyText"] = "non-body-text";
51
- ContrastLevelName["LargeText"] = "large-text";
52
- ContrastLevelName["SpotText"] = "spot-text";
139
+ ContrastLevelName["SmallBodyText"] = "small-body";
140
+ ContrastLevelName["BodyText"] = "body";
141
+ ContrastLevelName["NonBodyText"] = "non-body";
142
+ ContrastLevelName["Header"] = "header";
143
+ ContrastLevelName["Placeholder"] = "placeholder";
53
144
  ContrastLevelName["Decoration"] = "decoration";
54
145
  ContrastLevelName["Invisible"] = "invisible";
55
146
  })(ContrastLevelName || (ContrastLevelName = {}));
@@ -62,10 +153,10 @@ export const contrastLevelLabel = {
62
153
  [ContrastLevelName.SmallBodyText]: 'Small Text',
63
154
  [ContrastLevelName.BodyText]: 'Body Text',
64
155
  [ContrastLevelName.NonBodyText]: 'Non-body Text',
65
- [ContrastLevelName.LargeText]: 'Headers',
66
- [ContrastLevelName.SpotText]: 'Placeholders',
67
- [ContrastLevelName.Decoration]: 'Decorations',
68
- [ContrastLevelName.Invisible]: 'Invisible ',
156
+ [ContrastLevelName.Header]: 'Header',
157
+ [ContrastLevelName.Placeholder]: 'Placeholder',
158
+ [ContrastLevelName.Decoration]: 'Decoration',
159
+ [ContrastLevelName.Invisible]: 'Invisible',
69
160
  };
70
161
  /**
71
162
  * All {@link ContrastLevelName} values in order from highest contrast to lowest.
@@ -76,8 +167,8 @@ export const orderedContrastLevelNames = [
76
167
  ContrastLevelName.SmallBodyText,
77
168
  ContrastLevelName.BodyText,
78
169
  ContrastLevelName.NonBodyText,
79
- ContrastLevelName.LargeText,
80
- ContrastLevelName.SpotText,
170
+ ContrastLevelName.Header,
171
+ ContrastLevelName.Placeholder,
81
172
  ContrastLevelName.Decoration,
82
173
  ContrastLevelName.Invisible,
83
174
  ];
@@ -110,14 +201,14 @@ export const contrastLevels = [
110
201
  },
111
202
  {
112
203
  min: 45,
113
- name: ContrastLevelName.LargeText,
204
+ name: ContrastLevelName.Header,
114
205
  description: 'Okay for large or headline text.',
115
206
  apcaName: 'large & sub-fluent text',
116
207
  apcaDescription: 'The minimum for larger, heavier text (36px normal weight or 24px bold) such as headlines, and large text that should be fluently readable but is not body text. This is also the minimum for pictograms with fine details, or smaller outline icons, , no less than 4px in its smallest dimension.',
117
208
  },
118
209
  {
119
210
  min: 30,
120
- name: ContrastLevelName.SpotText,
211
+ name: ContrastLevelName.Placeholder,
121
212
  description: 'Okay for disabled or placeholder text, copyright lines, icons, or non-text elements.',
122
213
  apcaName: 'spot & non text only',
123
214
  apcaDescription: 'The absolute minimum for any text not listed above, which means non-content text considered as "spot readable". This includes placeholder text and disabled element text, and some non-content like a copyright bug. This is also the minimum for large/solid semantic & understandable non-text elements such as "mostly solid" icons or pictograms, no less than 10px in its smallest dimension.',
@@ -1,4 +1,5 @@
1
1
  import { type ColorThemeColor } from '../color-theme.js';
2
+ import { type FontWeight } from '../contrast.js';
2
3
  /**
3
4
  * Showcase a theme-vir color theme color.
4
5
  *
@@ -9,8 +10,8 @@ export declare const ThemeVirColorExample: import("element-vir").DeclarativeElem
9
10
  showVarValues: boolean;
10
11
  showVarNames: boolean;
11
12
  showContrast: boolean;
13
+ fontWeight: FontWeight;
12
14
  }, {
13
15
  previewElement: undefined | HTMLElement;
14
- }, {
15
- toggleShowVars: import("element-vir").DefineEvent<void>;
16
- }, "theme-vir-color-example-no-contrast-tips", "theme-vir-color-example-", readonly [], readonly []>;
16
+ forceShowEverything: boolean;
17
+ }, {}, "theme-vir-color-example-no-contrast-tips", "theme-vir-color-example-", readonly [], readonly []>;
@@ -1,5 +1,5 @@
1
1
  import { assertWrap, check } from '@augment-vir/assert';
2
- import { css, defineElement, defineElementEvent, html, listen, nothing, onDomCreated, unsafeCSS, } from 'element-vir';
2
+ import { css, defineElement, html, listen, nothing, onDomCreated, unsafeCSS } from 'element-vir';
3
3
  import { noNativeFormStyles, noNativeSpacing } from 'vira';
4
4
  import { calculateContrast } from '../contrast.js';
5
5
  import { ThemeVirContrastIndicator } from './theme-vir-contrast-indicator.element.js';
@@ -13,13 +13,11 @@ export const ThemeVirColorExample = defineElement()({
13
13
  state() {
14
14
  return {
15
15
  previewElement: undefined,
16
+ forceShowEverything: false,
16
17
  };
17
18
  },
18
- events: {
19
- toggleShowVars: defineElementEvent(),
20
- },
21
19
  hostClasses: {
22
- 'theme-vir-color-example-no-contrast-tips': ({ inputs }) => !inputs.showContrast,
20
+ 'theme-vir-color-example-no-contrast-tips': ({ inputs, state }) => !inputs.showContrast && !state.forceShowEverything,
23
21
  },
24
22
  styles: ({ hostClasses }) => css `
25
23
  :host {
@@ -109,21 +107,21 @@ export const ThemeVirColorExample = defineElement()({
109
107
  margin-top: 1px;
110
108
  }
111
109
  `,
112
- render({ state, updateState, inputs, dispatch, events }) {
110
+ render({ state, updateState, inputs }) {
113
111
  const colorRows = [
114
112
  'foreground',
115
113
  'background',
116
114
  ].map((layerKey) => {
117
115
  const keyString = [
118
116
  inputs.color[layerKey].name,
119
- inputs.showVarValues ? ':' : '',
117
+ inputs.showVarValues || state.forceShowEverything ? ':' : '',
120
118
  ]
121
119
  .filter(check.isTruthy)
122
120
  .join('');
123
- const valueTemplate = inputs.showVarValues
121
+ const valueTemplate = inputs.showVarValues || state.forceShowEverything
124
122
  ? html `
125
- <span>${inputs.color[layerKey].default}</span>
126
- `
123
+ <span>${inputs.color[layerKey].default}</span>
124
+ `
127
125
  : nothing;
128
126
  return html `
129
127
  <p>
@@ -132,10 +130,10 @@ export const ThemeVirColorExample = defineElement()({
132
130
  </p>
133
131
  `;
134
132
  });
135
- const cssVarNamesTemplate = inputs.showVarNames
133
+ const cssVarNamesTemplate = inputs.showVarNames || state.forceShowEverything
136
134
  ? html `
137
- <div class="css-var-names">${colorRows}</div>
138
- `
135
+ <div class="css-var-names">${colorRows}</div>
136
+ `
139
137
  : nothing;
140
138
  const contrast = state.previewElement
141
139
  ? calculateContrast({
@@ -147,17 +145,20 @@ export const ThemeVirColorExample = defineElement()({
147
145
  .getPropertyValue('background-color'),
148
146
  })
149
147
  : undefined;
150
- const contrastTemplate = contrast && inputs.showContrast
148
+ const contrastTemplate = contrast && (inputs.showContrast || state.forceShowEverything)
151
149
  ? html `
152
150
  <${ThemeVirContrastIndicator.assign({
153
151
  contrast,
152
+ fontWeight: inputs.fontWeight,
154
153
  })}></${ThemeVirContrastIndicator}>
155
154
  `
156
155
  : nothing;
157
156
  return html `
158
157
  <button
159
158
  ${listen('click', () => {
160
- dispatch(new events.toggleShowVars());
159
+ updateState({
160
+ forceShowEverything: !state.forceShowEverything,
161
+ });
161
162
  })}
162
163
  ${onDomCreated((element) => {
163
164
  updateState({
@@ -179,6 +180,7 @@ export const ThemeVirColorExample = defineElement()({
179
180
  visibility: ${unsafeCSS((contrast?.fontSizes[400] || Infinity) > 150
180
181
  ? 'hidden'
181
182
  : 'visible')};
183
+ font-weight: ${inputs.fontWeight};
182
184
  font-size: ${contrast ? contrast.fontSizes[400] : 14}px;
183
185
  `}
184
186
  >
@@ -1,4 +1,4 @@
1
- import { type CalculatedContrast } from '../contrast.js';
1
+ import { type CalculatedContrast, type FontWeight } from '../contrast.js';
2
2
  /**
3
3
  * Show contrast details for a theme-vir color.
4
4
  *
@@ -6,4 +6,5 @@ import { type CalculatedContrast } from '../contrast.js';
6
6
  */
7
7
  export declare const ThemeVirContrastIndicator: import("element-vir").DeclarativeElementDefinition<"theme-vir-contrast-indicator", {
8
8
  contrast: Readonly<CalculatedContrast>;
9
+ fontWeight: FontWeight;
9
10
  }, {}, {}, "theme-vir-contrast-indicator-", "theme-vir-contrast-indicator-", readonly [], readonly []>;
@@ -29,7 +29,7 @@ export const ThemeVirContrastIndicator = defineElement()({
29
29
  .${unsafeCSS(ContrastLevelName.Decoration)} {
30
30
  color: #ff6600;
31
31
  }
32
- .${unsafeCSS(ContrastLevelName.SpotText)} {
32
+ .${unsafeCSS(ContrastLevelName.Placeholder)} {
33
33
  color: #a5a520;
34
34
  }
35
35
 
@@ -76,7 +76,9 @@ export const ThemeVirContrastIndicator = defineElement()({
76
76
  '\nFont weights to font sizes:',
77
77
  JSON.stringify(inputs.contrast.fontSizes, null, 4),
78
78
  ].join('\n');
79
- const fontSize = inputs.contrast.fontSizes[400] > 150 ? '-' : `${inputs.contrast.fontSizes[400]}px`;
79
+ const fontSize = inputs.contrast.fontSizes[inputs.fontWeight] > 150
80
+ ? '-'
81
+ : `${inputs.contrast.fontSizes[inputs.fontWeight]}px`;
80
82
  return html `
81
83
  <div title=${title} class="wrapper ${inputs.contrast.contrastLevel.name}">
82
84
  <div class="gauge">${gaugeLevels}</div>
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
+ export * from './color/build-color-theme.js';
1
2
  export * from './color/color-css.js';
3
+ export * from './color/color-palette-book-pages.js';
2
4
  export * from './color/color-theme-book-pages.js';
3
5
  export * from './color/color-theme-override.js';
4
6
  export * from './color/color-theme.js';
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
+ export * from './color/build-color-theme.js';
1
2
  export * from './color/color-css.js';
3
+ export * from './color/color-palette-book-pages.js';
2
4
  export * from './color/color-theme-book-pages.js';
3
5
  export * from './color/color-theme-override.js';
4
6
  export * from './color/color-theme.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theme-vir",
3
- "version": "28.13.0",
3
+ "version": "28.15.0",
4
4
  "description": "Create an entire web theme.",
5
5
  "keywords": [
6
6
  "design",
@@ -14,13 +14,13 @@
14
14
  "web",
15
15
  "element-vir"
16
16
  ],
17
- "homepage": "https://electrovir.github.io/vira/theme-vir",
17
+ "homepage": "https://electrovir.github.io/theme-vir",
18
18
  "bugs": {
19
- "url": "https://github.com/electrovir/vira/issues"
19
+ "url": "https://github.com/electrovir/theme-vir/issues"
20
20
  },
21
21
  "repository": {
22
22
  "type": "git",
23
- "url": "git+https://github.com/electrovir/vira.git"
23
+ "url": "git+https://github.com/electrovir/theme-vir.git"
24
24
  },
25
25
  "license": "(MIT or CC0 1.0)",
26
26
  "author": {
@@ -42,33 +42,35 @@
42
42
  "test:docs": "virmator docs check"
43
43
  },
44
44
  "dependencies": {
45
- "@augment-vir/assert": "^31.54.4",
46
- "@augment-vir/common": "^31.54.4",
45
+ "@augment-vir/assert": "^31.57.3",
46
+ "@augment-vir/common": "^31.57.3",
47
47
  "apca-w3": "^0.1.9",
48
48
  "lit-css-vars": "^3.0.11",
49
49
  "type-fest": "^5.3.1"
50
50
  },
51
51
  "devDependencies": {
52
- "@augment-vir/test": "^31.54.4",
52
+ "@augment-vir/test": "^31.57.3",
53
53
  "@types/apca-w3": "^0.1.3",
54
54
  "@web/dev-server-esbuild": "^1.0.4",
55
55
  "@web/test-runner": "^0.20.2",
56
56
  "@web/test-runner-commands": "^0.9.0",
57
57
  "@web/test-runner-playwright": "^0.11.1",
58
58
  "@web/test-runner-visual-regression": "^0.10.0",
59
- "element-book": "^26.12.2",
60
- "element-vir": "^26.12.2",
59
+ "element-book": "^26.14.0",
60
+ "element-vir": "^26.14.0",
61
61
  "esbuild": "^0.27.2",
62
62
  "istanbul-smart-text-reporter": "^1.1.5",
63
63
  "markdown-code-example-inserter": "^3.0.3",
64
64
  "typedoc": "^0.28.15",
65
65
  "typescript": "5.9.3",
66
+ "vira": "^28.14.0",
66
67
  "vite": "^7.3.0",
67
68
  "vite-tsconfig-paths": "^6.0.3"
68
69
  },
69
70
  "peerDependencies": {
70
71
  "element-book": ">=17",
71
- "element-vir": ">=17"
72
+ "element-vir": ">=17",
73
+ "vira": ">28"
72
74
  },
73
75
  "engines": {
74
76
  "node": ">=22"