@electrovir/color 1.0.0 → 1.1.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.
@@ -28,16 +28,6 @@ export type ColorCoordinateDefinition = {
28
28
  factor?: number | undefined;
29
29
  suffix?: string;
30
30
  }>;
31
- /**
32
- * A single color format definition.
33
- *
34
- * @category Internal
35
- */
36
- export type ColorFormatDefinition<ColorSpace extends string = string> = {
37
- coords: Record<string, ColorCoordinateDefinition>;
38
- /** This name for this color to be used in `Colorjs.to()`. Defaults to the color format key. */
39
- colorSpace: ColorSpace;
40
- };
41
31
  /**
42
32
  * All raw supported color formats.
43
33
  *
@@ -184,15 +174,17 @@ export declare const rawColorFormats: {
184
174
  };
185
175
  };
186
176
  /**
187
- * All supported color format names. This can be used as an enum.
177
+ * All supported color format names.
188
178
  *
189
179
  * @category Internal
180
+ * @enum
190
181
  */
191
- export declare const ColorFormatName: { [FormatName in ColorFormatName]: FormatName; };
182
+ export declare const ColorFormatName: { [Key in ColorFormatName]: Key; };
192
183
  /**
193
- * All supported color format names. This can be used as an enum.
184
+ * All supported color format names.
194
185
  *
195
186
  * @category Internal
187
+ * @enum
196
188
  */
197
189
  export type ColorFormatName = keyof typeof rawColorFormats;
198
190
  /**
@@ -210,9 +202,9 @@ export type ColorCoordsByFormat = {
210
202
  * @category Internal
211
203
  */
212
204
  export type ColorFormats = Readonly<{
213
- [FormatName in ColorFormatName]: ColorFormatDefinition<'colorSpace' extends keyof (typeof rawColorFormats)[FormatName] ? Extract<(typeof rawColorFormats)[FormatName], {
205
+ [FormatName in ColorFormatName]: ColorFormatDefinition<Extract<(typeof rawColorFormats)[FormatName], {
214
206
  colorSpace: any;
215
- }>['colorSpace'] : FormatName>;
207
+ }>['colorSpace'], FormatName>;
216
208
  }>;
217
209
  /**
218
210
  * All supported color formats.
@@ -221,17 +213,25 @@ export type ColorFormats = Readonly<{
221
213
  */
222
214
  export declare const colorFormats: ColorFormats;
223
215
  /**
224
- * All available color space names.
216
+ * All supported color space names.
225
217
  *
226
218
  * @category Internal
219
+ * @enum
227
220
  */
228
221
  export type ColorSpaceName = Values<typeof rawColorFormats>['colorSpace'];
222
+ /**
223
+ * All supported color space names.
224
+ *
225
+ * @category Internal
226
+ * @enum
227
+ */
228
+ export declare const ColorSpaceName: { [Key in ColorSpaceName]: Key; };
229
229
  /**
230
230
  * All value types for all supported color formats.
231
231
  *
232
232
  * @category Internal
233
233
  */
234
- export type ColorValues = {
234
+ export type ColorValue = {
235
235
  [FormatName in ColorFormatName]: Record<keyof (typeof rawColorFormats)[FormatName]['coords'], number>;
236
236
  };
237
237
  /**
@@ -239,16 +239,22 @@ export type ColorValues = {
239
239
  *
240
240
  * @category Color Format
241
241
  */
242
- export declare const colorSpaces: Record<"rgb" | "lab" | "oklab", Record<"rgb" | "hsl" | "hwb" | "lab" | "lch" | "oklab" | "oklch", ColorFormatDefinition<string>>>;
242
+ export declare const colorFormatsBySpace: { [ColorSpace in ColorSpaceName]: { [ColorFormat in ColorFormatName]: ColorFormatDefinition<ColorSpace, ColorFormat>; }; };
243
243
  /**
244
- * All color format names in an array.
244
+ * All possible coordinate names for all supported color formats in a union.
245
245
  *
246
246
  * @category Internal
247
247
  */
248
- export declare const colorFormatNames: ColorFormatName[];
248
+ export type ColorCoordinateName = keyof UnionToIntersection<Values<typeof rawColorFormats>['coords']>;
249
249
  /**
250
- * All possible coordinate names for all supported color formats in a union.
250
+ * A single color format definition.
251
251
  *
252
252
  * @category Internal
253
253
  */
254
- export type ColorCoordinateName = keyof UnionToIntersection<Values<typeof rawColorFormats>['coords']>;
254
+ export type ColorFormatDefinition<ColorSpace extends ColorSpaceName = any, ColorFormat extends ColorFormatName = any> = {
255
+ /** Which exact color coordinates exist in here depends on the set color space. */
256
+ coords: Record<ColorCoordsByFormat[ColorFormat], ColorCoordinateDefinition>;
257
+ /** This name for this color to be used in `Colorjs.to()`. Defaults to the color format key. */
258
+ colorSpace: ColorSpace;
259
+ colorFormat: ColorFormat;
260
+ };
@@ -1,4 +1,4 @@
1
- import { getObjectTypedEntries, getObjectTypedKeys, getOrSet, mapObjectValues, } from '@augment-vir/common';
1
+ import { arrayToObject, getObjectTypedEntries, getObjectTypedValues, getOrSet, mapObjectValues, } from '@augment-vir/common';
2
2
  /**
3
3
  * All raw supported color formats.
4
4
  *
@@ -145,9 +145,10 @@ export const rawColorFormats = {
145
145
  },
146
146
  };
147
147
  /**
148
- * All supported color format names. This can be used as an enum.
148
+ * All supported color format names.
149
149
  *
150
150
  * @category Internal
151
+ * @enum
151
152
  */
152
153
  export const ColorFormatName = mapObjectValues(rawColorFormats, (colorName) => colorName);
153
154
  /**
@@ -155,21 +156,34 @@ export const ColorFormatName = mapObjectValues(rawColorFormats, (colorName) => c
155
156
  *
156
157
  * @category Color Format
157
158
  */
158
- export const colorFormats = rawColorFormats;
159
+ export const colorFormats = mapObjectValues(rawColorFormats, (colorFormatName, colorFormatValue) => {
160
+ return {
161
+ ...colorFormatValue,
162
+ colorFormat: colorFormatName,
163
+ };
164
+ });
165
+ /**
166
+ * All supported color space names.
167
+ *
168
+ * @category Internal
169
+ * @enum
170
+ */
171
+ export const ColorSpaceName = arrayToObject(getObjectTypedValues(rawColorFormats), (format) => {
172
+ return {
173
+ key: format.colorSpace,
174
+ value: format.colorSpace,
175
+ };
176
+ }, {
177
+ useRequired: true,
178
+ });
159
179
  /**
160
180
  * All color formats grouped by their color space.
161
181
  *
162
182
  * @category Color Format
163
183
  */
164
- export const colorSpaces = getObjectTypedEntries(colorFormats).reduce((accum, [colorFormatName, colorFormatDefinition,]) => {
184
+ export const colorFormatsBySpace = getObjectTypedEntries(colorFormats).reduce((accum, [colorFormatName, colorFormatDefinition,]) => {
165
185
  getOrSet(accum, colorFormatDefinition.colorSpace, () => {
166
186
  return {};
167
187
  })[colorFormatName] = colorFormatDefinition;
168
188
  return accum;
169
189
  }, {});
170
- /**
171
- * All color format names in an array.
172
- *
173
- * @category Internal
174
- */
175
- export const colorFormatNames = getObjectTypedKeys(colorFormats);
@@ -1,6 +1,6 @@
1
1
  import { type PartialWithUndefined } from '@augment-vir/common';
2
2
  import { type RequireExactlyOne } from 'type-fest';
3
- import { type ColorCoordsByFormat, type ColorFormatName, type ColorValues, type HexColor } from './color-formats.js';
3
+ import { ColorFormatName, type ColorCoordsByFormat, type ColorValue, type HexColor } from './color-formats.js';
4
4
  /**
5
5
  * An update to an existing {@link Color} instance. Used in {@link Color.set}.
6
6
  *
@@ -18,7 +18,7 @@ export type ColorUpdate = RequireExactlyOne<{
18
18
  *
19
19
  * @category Internal
20
20
  */
21
- export type AllColorsValues = ColorValues & {
21
+ export type AllColorsValues = ColorValue & {
22
22
  hex: HexColor;
23
23
  names: string[];
24
24
  };
@@ -1,8 +1,8 @@
1
1
  import { assert, assertWrap, check } from '@augment-vir/assert';
2
- import { copyThroughJson, filterMap, getObjectTypedEntries, joinWithFinalConjunction, mapObjectValues, round, } from '@augment-vir/common';
2
+ import { copyThroughJson, filterMap, getEnumValues, getObjectTypedEntries, getObjectTypedKeys, joinWithFinalConjunction, mapObjectValues, round, } from '@augment-vir/common';
3
3
  import colorNames from 'color-name';
4
4
  import { clampGamut, converter, formatHex, parse } from 'culori';
5
- import { colorFormatNames, colorFormats, } from './color-formats.js';
5
+ import { ColorFormatName, colorFormats, } from './color-formats.js';
6
6
  import { maxColorNameLength } from './color-name-length.js';
7
7
  /**
8
8
  * A `Color` class with state and the following features:
@@ -109,7 +109,7 @@ export class Color {
109
109
  const colorFormatDefinition = colorFormats[colorFormatName];
110
110
  const orderedColorCoords = Object.values(mapObjectValues(colorFormatDefinition.coords, (coordName) => {
111
111
  const coordValue = colorValues[coordName];
112
- const coordDefinition = assertWrap.isDefined(colorFormatDefinition.coords[coordName]);
112
+ const coordDefinition = colorFormatDefinition.coords[assertWrap.isKeyOf(coordName, colorFormatDefinition.coords)];
113
113
  const rawCoordValue = coordValue != undefined &&
114
114
  coordValue >= coordDefinition.min &&
115
115
  coordValue <= coordDefinition.max
@@ -125,7 +125,7 @@ export class Color {
125
125
  * internal color object.
126
126
  */
127
127
  pullFromInternalColor() {
128
- colorFormatNames.forEach((colorFormatName) => {
128
+ getEnumValues(ColorFormatName).forEach((colorFormatName) => {
129
129
  const colorFormatDefinition = colorFormats[colorFormatName];
130
130
  const originalColorDefinition = check.isKeyOf(this.#internalColor.mode, colorFormats)
131
131
  ? colorFormats[this.#internalColor.mode]
@@ -137,11 +137,12 @@ export class Color {
137
137
  if (!converted) {
138
138
  assert.never(`Failed to convert color '${JSON.stringify(this.#internalColor)}' to '${colorFormatName}'.`);
139
139
  }
140
- Object.keys(this[colorFormatName]).forEach((coordName) => {
140
+ getObjectTypedKeys(this[colorFormatName]).forEach((coordName) => {
141
141
  const coordValue = converted[coordName];
142
+ const coordinateDefinition = colorFormatDefinition.coords[assertWrap.isKeyOf(coordName, colorFormatDefinition.coords)];
142
143
  if (coordValue != undefined) {
143
- this._allColors[colorFormatName][coordName] = round((coordValue || 0) * (colorFormatDefinition.coords[coordName]?.factor || 1), {
144
- digits: colorFormatDefinition.coords[coordName]?.digits || 0,
144
+ this._allColors[colorFormatName][coordName] = round((coordValue || 0) * (coordinateDefinition.factor || 1), {
145
+ digits: coordinateDefinition.digits || 0,
145
146
  });
146
147
  }
147
148
  });
@@ -0,0 +1,11 @@
1
+ import { type Color } from '../color-class/color.js';
2
+ /**
3
+ * Color sliders for all color spaces.
4
+ *
5
+ * @category Elements
6
+ */
7
+ export declare const VirAllColorSpaceSliders: import("element-vir").DeclarativeElementDefinition<"vir-all-color-space-sliders", {
8
+ color: Readonly<Color>;
9
+ }, {}, {
10
+ colorChange: import("element-vir").DefineEvent<string>;
11
+ }, "vir-all-color-space-sliders-", "vir-all-color-space-sliders-", readonly [], readonly []>;
@@ -0,0 +1,52 @@
1
+ /* node:coverage disable */
2
+ import { getObjectTypedKeys, getObjectTypedValues } from '@augment-vir/common';
3
+ import { css, defineElement, defineElementEvent, html, listen } from 'element-vir';
4
+ import { colorFormatsBySpace } from '../color-class/color-formats.js';
5
+ import { VirColorFormatSliders } from './vir-color-format-sliders.element.js';
6
+ /**
7
+ * Color sliders for all color spaces.
8
+ *
9
+ * @category Elements
10
+ */
11
+ export const VirAllColorSpaceSliders = defineElement()({
12
+ tagName: 'vir-all-color-space-sliders',
13
+ styles: css `
14
+ :host {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 16px;
18
+ }
19
+
20
+ .color-space {
21
+ display: flex;
22
+ flex-wrap: wrap;
23
+ column-gap: 32px;
24
+ row-gap: 8px;
25
+ }
26
+ `,
27
+ events: {
28
+ colorChange: defineElementEvent(),
29
+ },
30
+ render({ inputs, dispatch, events }) {
31
+ const colorSpaceTemplates = getObjectTypedValues(colorFormatsBySpace).map((colorSpaceFormats) => {
32
+ const formatTemplates = getObjectTypedKeys(colorSpaceFormats).map((colorFormatName) => {
33
+ return html `
34
+ <${VirColorFormatSliders.assign({
35
+ color: inputs.color,
36
+ colorFormatName,
37
+ })}
38
+ ${listen(VirColorFormatSliders.events.colorChange, (event) => {
39
+ dispatch(new events.colorChange(event.detail));
40
+ })}
41
+ ></${VirColorFormatSliders}>
42
+ `;
43
+ });
44
+ return html `
45
+ <section class="color-space">${formatTemplates}</section>
46
+ `;
47
+ });
48
+ return html `
49
+ ${colorSpaceTemplates}
50
+ `;
51
+ },
52
+ });
@@ -0,0 +1,13 @@
1
+ import { type ColorFormatName } from '../color-class/color-formats.js';
2
+ import { type Color } from '../color-class/color.js';
3
+ /**
4
+ * Color sliders for all coordinates within a specific color format.
5
+ *
6
+ * @category Elements
7
+ */
8
+ export declare const VirColorFormatSliders: import("element-vir").DeclarativeElementDefinition<"vir-color-format-sliders", {
9
+ color: Readonly<Color>;
10
+ colorFormatName: ColorFormatName;
11
+ }, {}, {
12
+ colorChange: import("element-vir").DefineEvent<string>;
13
+ }, "vir-color-format-sliders-", "vir-color-format-sliders-", readonly [], readonly []>;
@@ -0,0 +1,54 @@
1
+ /* node:coverage disable */
2
+ import { getObjectTypedKeys } from '@augment-vir/common';
3
+ import { css, defineElement, defineElementEvent, html, listen } from 'element-vir';
4
+ import { noNativeSpacing } from 'vira';
5
+ import { colorFormats, } from '../color-class/color-formats.js';
6
+ import { VirColorSlider } from './vir-color-slider.element.js';
7
+ /**
8
+ * Color sliders for all coordinates within a specific color format.
9
+ *
10
+ * @category Elements
11
+ */
12
+ export const VirColorFormatSliders = defineElement()({
13
+ tagName: 'vir-color-format-sliders',
14
+ styles: css `
15
+ :host {
16
+ display: flex;
17
+ flex-direction: column;
18
+ }
19
+
20
+ h3 {
21
+ ${noNativeSpacing};
22
+ }
23
+ `,
24
+ events: {
25
+ colorChange: defineElementEvent(),
26
+ },
27
+ render({ inputs, dispatch, events }) {
28
+ const colorFormat = colorFormats[inputs.colorFormatName];
29
+ const coordinateTemplates = getObjectTypedKeys(colorFormat.coords).map((colorCoordinate) => {
30
+ return html `
31
+ <${VirColorSlider.assign({
32
+ color: inputs.color,
33
+ colorCoordinateName: colorCoordinate,
34
+ colorFormatName: inputs.colorFormatName,
35
+ })}
36
+ ${listen(VirColorSlider.events.valueChange, (event) => {
37
+ const newColor = inputs.color.clone();
38
+ newColor.set({
39
+ [inputs.colorFormatName]: {
40
+ [colorCoordinate]: event.detail,
41
+ },
42
+ });
43
+ const newValue = newColor.toCss()[inputs.colorFormatName];
44
+ dispatch(new events.colorChange(newValue));
45
+ })}
46
+ ></${VirColorSlider}>
47
+ `;
48
+ });
49
+ return html `
50
+ <h3>${inputs.colorFormatName}</h3>
51
+ ${coordinateTemplates}
52
+ `;
53
+ },
54
+ });
@@ -0,0 +1,14 @@
1
+ import { type ColorCoordinateName, type ColorFormatName } from '../color-class/color-formats.js';
2
+ import { Color } from '../color-class/color.js';
3
+ /**
4
+ * A slider for a specific color coordinate in a specific color space in a specific color.
5
+ *
6
+ * @category Elements
7
+ */
8
+ export declare const VirColorSlider: import("element-vir").DeclarativeElementDefinition<"vir-color-slider", {
9
+ color: Readonly<Color>;
10
+ colorFormatName: ColorFormatName;
11
+ colorCoordinateName: ColorCoordinateName;
12
+ }, {}, {
13
+ valueChange: import("element-vir").DefineEvent<number>;
14
+ }, "vir-color-slider-", "vir-color-slider-gradient", readonly [], readonly []>;
@@ -0,0 +1,101 @@
1
+ /* node:coverage disable */
2
+ import { assertWrap } from '@augment-vir/assert';
3
+ import { createArray } from '@augment-vir/common';
4
+ import { extractEventTarget } from '@augment-vir/web';
5
+ import { css, defineElement, defineElementEvent, html, listen, unsafeCSS } from 'element-vir';
6
+ import { viraFontCssVars, ViraInput } from 'vira';
7
+ import { colorFormats, } from '../color-class/color-formats.js';
8
+ import { Color } from '../color-class/color.js';
9
+ /**
10
+ * A slider for a specific color coordinate in a specific color space in a specific color.
11
+ *
12
+ * @category Elements
13
+ */
14
+ export const VirColorSlider = defineElement()({
15
+ tagName: 'vir-color-slider',
16
+ cssVars: {
17
+ 'vir-color-slider-gradient': 'black',
18
+ },
19
+ styles: ({ cssVars }) => css `
20
+ :host {
21
+ display: flex;
22
+ align-items: center;
23
+ font-family: ${viraFontCssVars['vira-monospace'].value};
24
+ gap: 2px;
25
+ }
26
+
27
+ input[type='range'] {
28
+ flex-grow: 1;
29
+ appearance: none;
30
+ background: ${cssVars['vir-color-slider-gradient'].value};
31
+ height: 9px;
32
+ border-radius: 4px;
33
+ cursor: pointer;
34
+ }
35
+
36
+ ${ViraInput} {
37
+ width: 76px;
38
+ }
39
+
40
+ .coordinate {
41
+ font-size: 18px;
42
+ margin-top: -4px;
43
+ }
44
+ `,
45
+ events: {
46
+ valueChange: defineElementEvent(),
47
+ },
48
+ render({ inputs, events, dispatch, cssVars }) {
49
+ const formatDefinition = colorFormats[inputs.colorFormatName];
50
+ const coordinateDefinition = formatDefinition.coords[inputs.colorCoordinateName];
51
+ if (!coordinateDefinition) {
52
+ throw new Error(`Invalid color coordinate '${inputs.colorCoordinateName}' for color format '${inputs.colorFormatName}'`);
53
+ }
54
+ const totalStops = 10;
55
+ const colorStops = createArray(totalStops, (index) => {
56
+ const value = coordinateDefinition.min +
57
+ (coordinateDefinition.max - coordinateDefinition.min) * (index / totalStops);
58
+ const stopColor = new Color({
59
+ [inputs.colorFormatName]: {
60
+ ...inputs.color[inputs.colorFormatName],
61
+ [inputs.colorCoordinateName]: value,
62
+ },
63
+ });
64
+ return stopColor.toCss()[inputs.colorFormatName];
65
+ });
66
+ const gradient = css `linear-gradient(to right, ${unsafeCSS(colorStops.join(','))})`;
67
+ const coordinateValue = assertWrap.isNumber(inputs.color[inputs.colorFormatName][inputs.colorCoordinateName]);
68
+ return html `
69
+ <span class="coordinate">${inputs.colorCoordinateName.toUpperCase()}</span>
70
+ <input
71
+ type="range"
72
+ style=${css `
73
+ ${cssVars['vir-color-slider-gradient'].name}: ${gradient};
74
+ `}
75
+ min=${coordinateDefinition.min}
76
+ max=${coordinateDefinition.max}
77
+ .value=${String(coordinateValue)}
78
+ step=${Math.pow(10, coordinateDefinition.digits ? -coordinateDefinition.digits : 0)}
79
+ ${listen('input', (event) => {
80
+ const element = extractEventTarget(event, HTMLInputElement);
81
+ const newValue = Number(element.value);
82
+ if (isNaN(newValue)) {
83
+ return;
84
+ }
85
+ dispatch(new events.valueChange(newValue));
86
+ })}
87
+ />
88
+ <${ViraInput.assign({
89
+ value: String(coordinateValue),
90
+ })}
91
+ ${listen(ViraInput.events.valueChange, (event) => {
92
+ const newValue = Number(event.detail);
93
+ if (isNaN(newValue)) {
94
+ return;
95
+ }
96
+ dispatch(new events.valueChange(newValue));
97
+ })}
98
+ ></${ViraInput}>
99
+ `;
100
+ },
101
+ });
package/dist/index.d.ts CHANGED
@@ -2,5 +2,8 @@ export * from './color-class/color-formats.js';
2
2
  export * from './color-class/color-name-length.js';
3
3
  export * from './color-class/color.js';
4
4
  export * from './contrast/contrast.js';
5
+ export * from './elements/vir-all-color-space-sliders.element.js';
6
+ export * from './elements/vir-color-format-sliders.element.js';
5
7
  export * from './elements/vir-color-pair.element.js';
8
+ export * from './elements/vir-color-slider.element.js';
6
9
  export * from './elements/vir-contrast-indicator.element.js';
package/dist/index.js CHANGED
@@ -2,5 +2,8 @@ export * from './color-class/color-formats.js';
2
2
  export * from './color-class/color-name-length.js';
3
3
  export * from './color-class/color.js';
4
4
  export * from './contrast/contrast.js';
5
+ export * from './elements/vir-all-color-space-sliders.element.js';
6
+ export * from './elements/vir-color-format-sliders.element.js';
5
7
  export * from './elements/vir-color-pair.element.js';
8
+ export * from './elements/vir-color-slider.element.js';
6
9
  export * from './elements/vir-contrast-indicator.element.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electrovir/color",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "A wrapper for culori with an extremely simple API.",
5
5
  "keywords": [
6
6
  "color",
@@ -52,6 +52,7 @@
52
52
  "dependencies": {
53
53
  "@augment-vir/assert": "^31.57.3",
54
54
  "@augment-vir/common": "^31.57.3",
55
+ "@augment-vir/web": "^31.57.3",
55
56
  "apca-w3": "^0.1.9",
56
57
  "color-name": "^2.1.0",
57
58
  "culori": "^4.0.2",
@@ -101,7 +102,7 @@
101
102
  "typedoc": "^0.28.15",
102
103
  "typescript": "^5.9.3",
103
104
  "typescript-eslint": "^8.51.0",
104
- "vira": "^28.15.0",
105
+ "vira": "^28.16.0",
105
106
  "virmator": "^14.4.0",
106
107
  "vite": "^7.3.0"
107
108
  },