@electrovir/color 1.3.1 → 1.5.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.
@@ -23,6 +23,14 @@ export type AllColorsValues = ColorValue & {
23
23
  [ColorSyntaxName.name]: string;
24
24
  names: string[];
25
25
  };
26
+ /**
27
+ * The output of `Color.serialize()`.
28
+ *
29
+ * @category Internal
30
+ */
31
+ export type SerializedColor = AllColorsValues & {
32
+ originalColorSyntax: ColorSyntaxName;
33
+ };
26
34
  /**
27
35
  * A `Color` class with state and the following features:
28
36
  *
@@ -39,11 +47,20 @@ export declare class Color {
39
47
  constructor(
40
48
  /** Any valid CSS color string or an object of color coordinate values. */
41
49
  initValue: string | Readonly<ColorUpdate>);
50
+ /** Checks, with a type guard, that a given input is a Color class instance. */
51
+ static isColor<T>(value: T): value is Extract<T, Readonly<Color>>;
42
52
  /**
43
53
  * Create a new {@link Color} instance by parsing the output of another instance's
44
54
  * {@link Color.serialize} method.
45
55
  */
46
56
  static deserialize(input: string): Color;
57
+ /**
58
+ * Converts the color class to a CSS string format in the color space and format that it was
59
+ * originally set with.
60
+ */
61
+ toString(): string;
62
+ /** The color syntax the this color was set with. */
63
+ protected originalColorSyntax: ColorSyntaxName;
47
64
  /** All current color values. These are updated whenever {@link Color.set} is called. */
48
65
  protected readonly _allColors: {
49
66
  names: string[];
@@ -2,7 +2,7 @@ import { assert, assertWrap, check } from '@augment-vir/assert';
2
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 { ColorFormatName, colorFormats, ColorSyntaxName, } from './color-formats.js';
5
+ import { ColorFormatName, colorFormats, ColorSyntaxName, getColorSyntaxFromCssString, } 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:
@@ -21,6 +21,10 @@ export class Color {
21
21
  initValue) {
22
22
  this.set(initValue);
23
23
  }
24
+ /** Checks, with a type guard, that a given input is a Color class instance. */
25
+ static isColor(value) {
26
+ return value instanceof Color;
27
+ }
24
28
  /**
25
29
  * Create a new {@link Color} instance by parsing the output of another instance's
26
30
  * {@link Color.serialize} method.
@@ -29,10 +33,24 @@ export class Color {
29
33
  const parsed = JSON.parse(input);
30
34
  const newColor = new Color('black');
31
35
  getObjectTypedEntries(parsed).forEach(([key, value,]) => {
32
- newColor._allColors[key] = value;
36
+ if (key === 'originalColorSyntax') {
37
+ newColor.originalColorSyntax = assertWrap.isEnumValue(value, ColorSyntaxName, 'Cannot deserialize: invalid color syntax.');
38
+ }
39
+ else {
40
+ newColor._allColors[key] = value;
41
+ }
33
42
  });
34
43
  return newColor;
35
44
  }
45
+ /**
46
+ * Converts the color class to a CSS string format in the color space and format that it was
47
+ * originally set with.
48
+ */
49
+ toString() {
50
+ return this.toCss()[this.originalColorSyntax];
51
+ }
52
+ /** The color syntax the this color was set with. */
53
+ originalColorSyntax = ColorSyntaxName.hex;
36
54
  #internalColor = assertWrap.isDefined(parse('black'));
37
55
  /** All current color values. These are updated whenever {@link Color.set} is called. */
38
56
  _allColors = {
@@ -89,6 +107,7 @@ export class Color {
89
107
  if (!newColor) {
90
108
  throw new Error(`Unable to parse invalid color string: '${cssColorString}'`);
91
109
  }
110
+ this.originalColorSyntax = getColorSyntaxFromCssString(cssColorString);
92
111
  this.#internalColor = newColor;
93
112
  this.pullFromInternalColor();
94
113
  }
@@ -157,7 +176,10 @@ export class Color {
157
176
  * match the current {@link Color} instance.
158
177
  */
159
178
  serialize() {
160
- return JSON.stringify(this.allColors);
179
+ return JSON.stringify({
180
+ ...this.allColors,
181
+ originalColorSyntax: this.originalColorSyntax,
182
+ });
161
183
  }
162
184
  /** This individual color expressed in all the supported color formats. */
163
185
  get allColors() {
@@ -0,0 +1,2 @@
1
+ import { type BookPage } from 'element-book';
2
+ export declare const allBookPages: BookPage[];
@@ -0,0 +1,9 @@
1
+ import { colorExamplesPage } from './book-pages/color-examples.book.js';
2
+ import { virColorPickerBookPage } from './book-pages/vir-color-picker.element.book.js';
3
+ import { elementsBookPage, examplesBookPage } from './top-level-pages.js';
4
+ export const allBookPages = [
5
+ elementsBookPage,
6
+ examplesBookPage,
7
+ colorExamplesPage,
8
+ virColorPickerBookPage,
9
+ ];
@@ -0,0 +1,2 @@
1
+ export declare const elementsBookPage: import("element-book").BookPage<{}, undefined, {}>;
2
+ export declare const examplesBookPage: import("element-book").BookPage<{}, undefined, {}>;
@@ -0,0 +1,9 @@
1
+ import { defineBookPage } from 'element-book';
2
+ export const elementsBookPage = defineBookPage({
3
+ parent: undefined,
4
+ title: 'Elements',
5
+ });
6
+ export const examplesBookPage = defineBookPage({
7
+ parent: undefined,
8
+ title: 'Examples',
9
+ });
@@ -0,0 +1 @@
1
+ export declare const VirColorDemo: import("element-vir").DeclarativeElementDefinition<"vir-color-demo", {}, {}, {}, "vir-color-demo-", "vir-color-demo-", readonly [], readonly []>;
@@ -0,0 +1,29 @@
1
+ /* node:coverage disable */
2
+ import { ElementBookApp } from 'element-book';
3
+ import { css, defineElement, html } from 'element-vir';
4
+ import { allBookPages } from './all-book-pages.js';
5
+ export const VirColorDemo = defineElement()({
6
+ tagName: 'vir-color-demo',
7
+ styles: css `
8
+ :host {
9
+ display: flex;
10
+ flex-direction: column;
11
+ height: 100%;
12
+ width: 100%;
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ ${ElementBookApp} {
17
+ flex-grow: 1;
18
+ max-width: 100%;
19
+ box-sizing: border-box;
20
+ }
21
+ `,
22
+ render() {
23
+ return html `
24
+ <${ElementBookApp.assign({
25
+ pages: allBookPages,
26
+ })}></${ElementBookApp}>
27
+ `;
28
+ },
29
+ });
@@ -0,0 +1,15 @@
1
+ import { type ColorSyntaxName } from '../color-class/color-formats.js';
2
+ /**
3
+ * A color picker element that shows sliders for all supported color spaces at once, as well as all
4
+ * the values for all supported color spaces in a table.
5
+ *
6
+ * @category Elements
7
+ */
8
+ export declare const VirAllSpacesColorPicker: import("element-vir").DeclarativeElementDefinition<"vir-all-spaces-color-picker", {
9
+ color: string;
10
+ }, {
11
+ inputColorString: undefined | string;
12
+ overrideInputColor: undefined | ColorSyntaxName;
13
+ }, {
14
+ colorChange: import("element-vir").DefineEvent<string>;
15
+ }, "vir-all-spaces-color-picker-", "vir-all-spaces-color-picker-", readonly [], readonly []>;
@@ -0,0 +1,67 @@
1
+ /* node:coverage disable */
2
+ import { css, defineElement, defineElementEvent, html, listen } from 'element-vir';
3
+ import { getColorSyntaxFromCssString } from '../color-class/color-formats.js';
4
+ import { Color } from '../color-class/color.js';
5
+ import { VirAllColorSpaceSliders } from './vir-all-color-space-sliders.element.js';
6
+ import { VirColorDetails } from './vir-color-details.element.js';
7
+ /**
8
+ * A color picker element that shows sliders for all supported color spaces at once, as well as all
9
+ * the values for all supported color spaces in a table.
10
+ *
11
+ * @category Elements
12
+ */
13
+ export const VirAllSpacesColorPicker = defineElement()({
14
+ tagName: 'vir-all-spaces-color-picker',
15
+ styles: css `
16
+ :host {
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: 16px;
20
+ }
21
+ `,
22
+ events: {
23
+ colorChange: defineElementEvent(),
24
+ },
25
+ state() {
26
+ return {
27
+ inputColorString: undefined,
28
+ overrideInputColor: undefined,
29
+ };
30
+ },
31
+ render({ inputs, dispatch, events, state, updateState }) {
32
+ const color = new Color(inputs.color);
33
+ if (state.overrideInputColor) {
34
+ updateState({
35
+ inputColorString: color.toCss()[state.overrideInputColor],
36
+ });
37
+ }
38
+ if (state.inputColorString == undefined) {
39
+ updateState({
40
+ inputColorString: inputs.color,
41
+ });
42
+ }
43
+ return html `
44
+ <${VirColorDetails.assign({
45
+ color: inputs.color,
46
+ showInput: true,
47
+ })}
48
+ ${listen(VirColorDetails.events.colorChange, (event) => {
49
+ updateState({
50
+ inputColorString: event.detail,
51
+ overrideInputColor: undefined,
52
+ });
53
+ dispatch(new events.colorChange(event.detail));
54
+ })}
55
+ ></${VirColorDetails}>
56
+ <${VirAllColorSpaceSliders.assign({ color })}
57
+ ${listen(VirAllColorSpaceSliders.events.colorChange, (event) => {
58
+ const colorSyntax = getColorSyntaxFromCssString(state.inputColorString || '#');
59
+ updateState({
60
+ overrideInputColor: colorSyntax,
61
+ });
62
+ dispatch(new events.colorChange(event.detail));
63
+ })}
64
+ ></${VirAllColorSpaceSliders}>
65
+ `;
66
+ },
67
+ });
@@ -0,0 +1,14 @@
1
+ import { type ColorFormatName as ColorFormatNameType } from '../color-class/color-formats.js';
2
+ import { Color } from '../color-class/color.js';
3
+ /**
4
+ * A color picker element with a swatch that opens a popup with color format sliders.
5
+ *
6
+ * @category Elements
7
+ */
8
+ export declare const VirColorPicker: import("element-vir").DeclarativeElementDefinition<"vir-color-picker", {
9
+ color: string | Readonly<Color> | undefined;
10
+ }, {
11
+ selectedFormatName: ColorFormatNameType;
12
+ }, {
13
+ colorChange: import("element-vir").DefineEvent<string>;
14
+ }, "vir-color-picker-", "vir-color-picker-width" | "vir-color-picker-height", readonly [], readonly []>;
@@ -0,0 +1,115 @@
1
+ /* node:coverage disable */
2
+ import { checkWrap } from '@augment-vir/assert';
3
+ import { getObjectTypedValues } from '@augment-vir/common';
4
+ import { css, defineElement, defineElementEvent, html, listen, onResize } from 'element-vir';
5
+ import { setCssVarValue } from 'lit-css-vars';
6
+ import { ViraPopUpTrigger, ViraSelect, viraShadows } from 'vira';
7
+ import { ColorFormatName, } from '../color-class/color-formats.js';
8
+ import { Color } from '../color-class/color.js';
9
+ import { VirColorFormatSliders } from './vir-color-format-sliders.element.js';
10
+ import { VirColorSwatch } from './vir-color-swatch.element.js';
11
+ const colorFormatOptions = getObjectTypedValues(ColorFormatName).map((formatName) => ({
12
+ value: formatName,
13
+ label: formatName.toUpperCase(),
14
+ }));
15
+ /**
16
+ * A color picker element with a swatch that opens a popup with color format sliders.
17
+ *
18
+ * @category Elements
19
+ */
20
+ export const VirColorPicker = defineElement()({
21
+ tagName: 'vir-color-picker',
22
+ cssVars: {
23
+ 'vir-color-picker-width': '100px',
24
+ 'vir-color-picker-height': '100px',
25
+ },
26
+ styles: ({ cssVars }) => css `
27
+ :host {
28
+ display: inline-block;
29
+ }
30
+
31
+ ${ViraPopUpTrigger} {
32
+ width: 100%;
33
+ height: 100%;
34
+ box-sizing: border-box;
35
+ }
36
+
37
+ ${VirColorSwatch} {
38
+ width: ${cssVars['vir-color-picker-width'].value};
39
+ height: ${cssVars['vir-color-picker-height'].value};
40
+ cursor: pointer;
41
+ box-sizing: border-box;
42
+ }
43
+
44
+ .popup-content {
45
+ display: flex;
46
+ flex-direction: column;
47
+ gap: 16px;
48
+ padding: 16px;
49
+ background: white;
50
+ border: 1px solid #ccc;
51
+ border-radius: 8px;
52
+ ${viraShadows.menuShadow}
53
+ }
54
+ `,
55
+ events: {
56
+ colorChange: defineElementEvent(),
57
+ },
58
+ state() {
59
+ return {
60
+ selectedFormatName: ColorFormatName.rgb,
61
+ };
62
+ },
63
+ render({ inputs, dispatch, events, state, updateState, host, cssVars }) {
64
+ const color = Color.isColor(inputs.color)
65
+ ? inputs.color
66
+ : new Color(inputs.color || 'black');
67
+ return html `
68
+ <${ViraPopUpTrigger.assign({
69
+ keepOpenAfterInteraction: true,
70
+ })}
71
+ ${onResize((size) => {
72
+ setCssVarValue({
73
+ onElement: host,
74
+ forCssVar: cssVars['vir-color-picker-width'],
75
+ toValue: `${size.contentRect.width}px`,
76
+ });
77
+ setCssVarValue({
78
+ onElement: host,
79
+ forCssVar: cssVars['vir-color-picker-height'],
80
+ toValue: `${size.contentRect.height}px`,
81
+ });
82
+ })}
83
+ >
84
+ <${VirColorSwatch.assign({
85
+ backgroundColor: color,
86
+ })}
87
+ slot=${ViraPopUpTrigger.slotNames.trigger}
88
+ ></${VirColorSwatch}>
89
+ <div class="popup-content" slot=${ViraPopUpTrigger.slotNames.popUp}>
90
+ <${ViraSelect.assign({
91
+ options: colorFormatOptions,
92
+ value: state.selectedFormatName,
93
+ })}
94
+ ${listen(ViraSelect.events.valueChange, (event) => {
95
+ const selectedFormat = checkWrap.isEnumValue(event.detail, ColorFormatName);
96
+ if (selectedFormat) {
97
+ updateState({
98
+ selectedFormatName: selectedFormat,
99
+ });
100
+ }
101
+ })}
102
+ ></${ViraSelect}>
103
+ <${VirColorFormatSliders.assign({
104
+ color,
105
+ colorFormatName: state.selectedFormatName,
106
+ })}
107
+ ${listen(VirColorFormatSliders.events.colorChange, (event) => {
108
+ dispatch(new events.colorChange(event.detail));
109
+ })}
110
+ ></${VirColorFormatSliders}>
111
+ </div>
112
+ </${ViraPopUpTrigger}>
113
+ `;
114
+ },
115
+ });
@@ -1,3 +1,4 @@
1
+ import { type Color } from '../color-class/color.js';
1
2
  /**
2
3
  * Show a background and/or foreground color. Note that if a foreground color is provided that it is
3
4
  * not actually applied to anything, you must provide a child element for it to be applied to.
@@ -5,15 +6,15 @@
5
6
  * @category Elements
6
7
  */
7
8
  export declare const VirColorSwatch: import("element-vir").DeclarativeElementDefinition<"vir-color-swatch", ((Required<Pick<{
8
- foregroundColor: string;
9
- backgroundColor: string;
9
+ foregroundColor: string | Readonly<Color>;
10
+ backgroundColor: string | Readonly<Color>;
10
11
  }, "foregroundColor">> & Partial<Pick<{
11
- foregroundColor: string;
12
- backgroundColor: string;
12
+ foregroundColor: string | Readonly<Color>;
13
+ backgroundColor: string | Readonly<Color>;
13
14
  }, "backgroundColor">>) | (Required<Pick<{
14
- foregroundColor: string;
15
- backgroundColor: string;
15
+ foregroundColor: string | Readonly<Color>;
16
+ backgroundColor: string | Readonly<Color>;
16
17
  }, "backgroundColor">> & Partial<Pick<{
17
- foregroundColor: string;
18
- backgroundColor: string;
18
+ foregroundColor: string | Readonly<Color>;
19
+ backgroundColor: string | Readonly<Color>;
19
20
  }, "foregroundColor">>)) & {}, {}, {}, "vir-color-swatch-", "vir-color-swatch-", readonly [], readonly []>;
package/dist/index.d.ts CHANGED
@@ -3,10 +3,12 @@ export * from './color-class/color-name-length.js';
3
3
  export * from './color-class/color.js';
4
4
  export * from './contrast/contrast.js';
5
5
  export * from './elements/vir-all-color-space-sliders.element.js';
6
+ export * from './elements/vir-all-spaces-color-picker.element.js';
6
7
  export * from './elements/vir-color-details.element.js';
7
8
  export * from './elements/vir-color-format-sliders.element.js';
8
9
  export * from './elements/vir-color-pair-contrast-summary.element.js';
9
10
  export * from './elements/vir-color-pair.element.js';
11
+ export * from './elements/vir-color-picker.element.js';
10
12
  export * from './elements/vir-color-slider.element.js';
11
13
  export * from './elements/vir-color-swatch.element.js';
12
14
  export * from './elements/vir-contrast-indicator.element.js';
package/dist/index.js CHANGED
@@ -3,10 +3,12 @@ export * from './color-class/color-name-length.js';
3
3
  export * from './color-class/color.js';
4
4
  export * from './contrast/contrast.js';
5
5
  export * from './elements/vir-all-color-space-sliders.element.js';
6
+ export * from './elements/vir-all-spaces-color-picker.element.js';
6
7
  export * from './elements/vir-color-details.element.js';
7
8
  export * from './elements/vir-color-format-sliders.element.js';
8
9
  export * from './elements/vir-color-pair-contrast-summary.element.js';
9
10
  export * from './elements/vir-color-pair.element.js';
11
+ export * from './elements/vir-color-picker.element.js';
10
12
  export * from './elements/vir-color-slider.element.js';
11
13
  export * from './elements/vir-color-swatch.element.js';
12
14
  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.3.1",
3
+ "version": "1.5.0",
4
4
  "description": "A wrapper for culori with an extremely simple API.",
5
5
  "keywords": [
6
6
  "color",
@@ -74,6 +74,7 @@
74
74
  "@web/test-runner-visual-regression": "^0.10.0",
75
75
  "cspell": "^9.4.0",
76
76
  "dependency-cruiser": "^17.3.5",
77
+ "element-book": "^26.14.1",
77
78
  "element-vir": "^26.14.0",
78
79
  "esbuild": "^0.27.2",
79
80
  "eslint": "^9.39.2",