• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

adobe / spectrum-web-components / 14094674923

26 Mar 2025 10:27PM UTC coverage: 86.218% (-11.8%) from 98.002%
14094674923

Pull #5221

github

web-flow
Merge 2a1ea92e7 into 3184c1e6a
Pull Request #5221: RFC | leverage css module imports in components

1737 of 2032 branches covered (85.48%)

Branch coverage included in aggregate %.

14184 of 16434 relevant lines covered (86.31%)

85.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.12
/tools/reactive-controllers/src/ColorController.ts
1
/*
2✔
2
Copyright 2022 Adobe. All rights reserved.
2✔
3
This file is licensed to you under the Apache License, Version 2.0 (the "License");
2✔
4
you may not use this file except in compliance with the License. You may obtain a copy
2✔
5
of the License at http://www.apache.org/licenses/LICENSE-2.0
2✔
6

2✔
7
Unless required by applicable law or agreed to in writing, software distributed under
2✔
8
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
2✔
9
OF ANY KIND, either express or implied. See the License for the specific language
2✔
10
governing permissions and limitations under the License.
2✔
11
*/
2✔
12

2✔
13
import type { ReactiveElement } from 'lit';
2✔
14
import Color from 'colorjs.io';
2✔
15
import type {
2✔
16
    ColorObject,
2✔
17
    ColorTypes as DefaultColorTypes,
2✔
18
} from 'colorjs.io/types/src/color';
2✔
19
import type ColorSpace from 'colorjs.io/types/src/space';
2✔
20

2✔
21
/**
2✔
22
 * Represents various color types that can be used in the application.
2✔
23
 *
2✔
24
 * This type can be one of the following:
2✔
25
 * - `DefaultColorTypes`: A predefined set of color types.
2✔
26
 * - An object representing an RGBA color with properties:
2✔
27
 *   - `r`: Red component, can be a number or string.
2✔
28
 *   - `g`: Green component, can be a number or string.
2✔
29
 *   - `b`: Blue component, can be a number or string.
2✔
30
 *   - `a` (optional): Alpha component, can be a number or string.
2✔
31
 * - An object representing an HSLA color with properties:
2✔
32
 *   - `h`: Hue component, can be a number or string.
2✔
33
 *   - `s`: Saturation component, can be a number or string.
2✔
34
 *   - `l`: Lightness component, can be a number or string.
2✔
35
 *   - `a` (optional): Alpha component, can be a number or string.
2✔
36
 * - An object representing an HSVA color with properties:
2✔
37
 *   - `h`: Hue component, can be a number or string.
2✔
38
 *   - `s`: Saturation component, can be a number or string.
2✔
39
 *   - `v`: Value component, can be a number or string.
2✔
40
 *   - `a` (optional): Alpha component, can be a number or string.
2✔
41
 */
2✔
42
type ColorTypes =
2✔
43
    | DefaultColorTypes
2✔
44
    | {
2✔
45
          r: number | string;
2✔
46
          g: number | string;
2✔
47
          b: number | string;
2✔
48
          a?: number | string;
2✔
49
      }
2✔
50
    | {
2✔
51
          h: number | string;
2✔
52
          s: number | string;
2✔
53
          l: number | string;
2✔
54
          a?: number | string;
2✔
55
      }
2✔
56
    | {
2✔
57
          h: number | string;
2✔
58
          s: number | string;
2✔
59
          v: number | string;
2✔
60
          a?: number | string;
2✔
61
      };
2✔
62

2✔
63
export type { Color, ColorTypes };
2✔
64

2✔
65
type ColorValidationResult = {
2✔
66
    spaceId: string | null;
2✔
67
    coords: number[];
2✔
68
    isValid: boolean;
2✔
69
    alpha: number;
2✔
70
};
2✔
71

2✔
72
/**
2✔
73
 * The `ColorController` class is responsible for managing and validating color values
2✔
74
 * in various color spaces (RGB, HSL, HSV, Hex). It provides methods to set, get, and
2✔
75
 * validate colors, as well as convert between different color formats.
2✔
76
 *
2✔
77
 * @class
2✔
78
 * @property {Color} color - Gets or sets the current color value.
2✔
79
 * @property {ColorTypes} colorValue - Gets the color value in various formats based on the original color input.
2✔
80
 * @property {number} hue - Gets or sets the hue value of the current color.
2✔
81
 *
2✔
82
 * @method validateColorString(color: string): ColorValidationResult - Validates a color string and returns the validation result.
2✔
83
 * @method getColor(format: string | ColorSpace): ColorObject - Converts the current color to the specified format.
2✔
84
 * @method getHslString(): string - Returns the current color in HSL string format.
2✔
85
 * @method savePreviousColor(): void - Saves the current color as the previous color.
2✔
86
 * @method restorePreviousColor(): void - Restores the previous color.
2✔
87
 *
2✔
88
 * @constructor
2✔
89
 * @param {ReactiveElement} host - The host element that uses this controller.
2✔
90
 * @param {Object} [options] - Optional configuration options.
2✔
91
 * @param {string} [options.manageAs] - Specifies the color space to manage the color as.
2✔
92
 */
2✔
93

2✔
94
export class ColorController {
2✔
95
    get color(): Color {
2✔
96
        return this._color;
2✔
97
    }
2✔
98

2✔
99
    /**
2✔
100
     * Validates a color string and returns a result indicating the color space,
2✔
101
     * coordinates, alpha value, and whether the color is valid.
2✔
102
     *
2✔
103
     * @param color - The color string to validate. Supported formats include:
2✔
104
     *  - RGB: `rgb(r, g, b)`, `rgba(r, g, b, a)`, `rgb r g b`, `rgba r g b a`
2✔
105
     *  - HSL: `hsl(h, s, l)`, `hsla(h, s, l, a)`, `hsl h s l`, `hsla h s l a`
2✔
106
     *  - HSV: `hsv(h, s, v)`, `hsva(h, s, v, a)`, `hsv h s v`, `hsva h s v a`
2✔
107
     *  - HEX: `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`
2✔
108
     *
2✔
109
     * @returns An object containing the following properties:
2✔
110
     *  - `spaceId`: The color space identifier (`'srgb'`, `'hsl'`, or `'hsv'`).
2✔
111
     *  - `coords`: An array of numeric values representing the color coordinates.
2✔
112
     *  - `alpha`: The alpha value of the color (0 to 1).
2✔
113
     *  - `isValid`: A boolean indicating whether the color string is valid.
2✔
114
     */
2✔
115
    public validateColorString(color: string): ColorValidationResult {
2✔
116
        const result: ColorValidationResult = {
69✔
117
            spaceId: null,
69✔
118
            coords: [0, 0, 0],
69✔
119
            isValid: false,
69✔
120
            alpha: 1,
69✔
121
        };
69✔
122

69✔
123
        // RGB color formats
69✔
124
        const rgbRegExpArray = [
69✔
125
            // With commas
69✔
126
            /rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d*\.?\d+)\s*\)/i, // rgba(r, g, b, a)
69✔
127
            /rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i, // rgb(r, g, b)
69✔
128

69✔
129
            // With spaces
69✔
130
            /^rgba\s+(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s+(0|0?\.\d+|1)\s*$/i, // rgba r g b a
69✔
131
            /^rgb\s+(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s*$/i, // rgb r g b
69✔
132

69✔
133
            // Spaces inside parentheses
69✔
134
            /^rgba\(\s*(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s+(\d*\.?\d+)\s*\)$/i, // rgba(r g b a)
69✔
135
            /^rgb\(\s*(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s*\)$/i, // rgb(r g b)
69✔
136

69✔
137
            // Percentage values
69✔
138
            /rgb\(\s*(100|[0-9]{1,2}%)\s*,\s*(100|[0-9]{1,2}%)\s*,\s*(100|[0-9]{1,2}%)\s*\)/i, // rgb(r%, g%, b%)
69✔
139
            /rgba\(\s*(100|[0-9]{1,2})%\s*,\s*(100|[0-9]{1,2})%\s*,\s*(100|[0-9]{1,2})%\s*,\s*(\d*\.?\d+)\s*\)/i, // rgba(r%, g%, b%, a)
69✔
140
        ];
69✔
141

69✔
142
        // HSL color formats
69✔
143
        const hslRegExpArray = [
69✔
144
            // With commas
69✔
145
            /hsla\(\s*(\d{1,3})\s*,\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*,\s*(\d*\.?\d+)\s*\)/i, // hsla(h, s, l, a)
69✔
146
            /hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*\)/i, // hsl(h, s, l)
69✔
147

69✔
148
            // With spaces
69✔
149
            /^hsla\s+(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s+(\d*\.?\d+)\s*$/i, // hsla h s l a
69✔
150
            /^hsl\s+(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s*$/i, // hsl h s l
69✔
151

69✔
152
            // Spaces inside parentheses
69✔
153
            /^hsla\(\s*(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s+(\d*\.?\d+)\s*\)$/i, // hsla(h s l a)
69✔
154
            /^hsl\(\s*(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s*\)$/i, // hsl(h s l)
69✔
155
        ];
69✔
156

69✔
157
        // HSV color formats
69✔
158
        const hsvRegExpArray = [
69✔
159
            // With commas
69✔
160
            /hsva\(\s*(\d{1,3})\s*,\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*,\s*(\d*\.?\d+)\s*\)/i, // hsva(h, s, v, a)
69✔
161
            /hsv\(\s*(\d{1,3})\s*,\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*\)/i, // hsv(h, s, v)
69✔
162

69✔
163
            // With spaces
69✔
164
            /^hsva\s+(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s+(\d*\.?\d+)\s*$/i, // hsva h s v a
69✔
165
            /^hsv\s+(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s*$/i, // hsv h s v
69✔
166

69✔
167
            // Spaces inside parentheses
69✔
168
            /^hsva\(\s*(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s+(\d*\.?\d+)\s*\)$/i, // hsva(h s v a)
69✔
169
            /^hsv\(\s*(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s*\)$/i, // hsv(h s v)
69✔
170
        ];
69✔
171

69✔
172
        // HEX color formats
69✔
173
        const hexRegExpArray = [
69✔
174
            /^#([A-Fa-f0-9]{6})([A-Fa-f0-9]{2})?$/, // 6-digit hex with optional hex alpha
69✔
175
            /^#([A-Fa-f0-9]{3})([A-Fa-f0-9]{1})?$/, // 3-digit hex with optional hex alpha
69✔
176
        ];
69✔
177

69✔
178
        const rgbaMatch = rgbRegExpArray
69✔
179
            .find((regex) => regex.test(color))
15✔
180
            ?.exec(color);
15✔
181
        const hslaMatch = hslRegExpArray
69✔
182
            .find((regex) => regex.test(color))
8✔
183
            ?.exec(color);
8✔
184
        const hsvaMatch = hsvRegExpArray
69✔
185
            .find((regex) => regex.test(color))
7✔
186
            ?.exec(color);
7✔
187
        const hexMatch = hexRegExpArray
69✔
188
            .find((regex) => regex.test(color))
22✔
189
            ?.exec(color);
22✔
190

69✔
191
        if (rgbaMatch) {
69✔
192
            const [, r, g, b, a] = rgbaMatch.filter(
15✔
193
                (element) => typeof element === 'string'
15✔
194
            );
15✔
195
            const alpha = a === undefined ? 1 : Number(a);
15✔
196
            const processValue = (value: string): number => {
15✔
197
                if (value.includes('%')) {
45✔
198
                    return Number(value.replace('%', '')) / 100;
6✔
199
                } else {
45✔
200
                    return Number(value) / 255;
39✔
201
                }
39✔
202
            };
45✔
203
            const numericR = processValue(r);
15✔
204
            const numericG = processValue(g);
15✔
205
            const numericB = processValue(b);
15✔
206

15✔
207
            result.spaceId = 'srgb';
15✔
208
            result.coords = [numericR, numericG, numericB];
15✔
209
            result.alpha = alpha;
15✔
210
            result.isValid =
15✔
211
                numericR >= 0 &&
15✔
212
                numericR <= 1 &&
15✔
213
                numericG >= 0 &&
15✔
214
                numericG <= 1 &&
15✔
215
                numericB >= 0 &&
15✔
216
                numericB <= 1 &&
15✔
217
                alpha >= 0 &&
15✔
218
                alpha <= 1;
15✔
219
        } else if (hslaMatch) {
69✔
220
            const [, h, s, l, a] = hslaMatch;
8✔
221
            const values = [h, s, l, a === undefined ? '1' : a].map((value) =>
8✔
222
                Number(value.replace(/[^\d.]/g, ''))
32✔
223
            );
8✔
224
            const [numericH, numericS, numericL, numericA] = values;
8✔
225

8✔
226
            result.spaceId = 'hsl';
8✔
227
            result.coords = [numericH, numericS, numericL];
8✔
228
            result.alpha = numericA;
8✔
229
            result.isValid =
8✔
230
                numericH >= 0 &&
8✔
231
                numericH <= 360 &&
8✔
232
                numericS >= 0 &&
8✔
233
                numericS <= 100 &&
8✔
234
                numericL >= 0 &&
8✔
235
                numericL <= 100 &&
8✔
236
                numericA >= 0 &&
8✔
237
                numericA <= 1;
8✔
238
        } else if (hsvaMatch) {
54✔
239
            const [, h, s, v, a] = hsvaMatch;
7✔
240
            const values = [h, s, v, a === undefined ? '1' : a].map((value) =>
7✔
241
                Number(value.replace(/[^\d.]/g, ''))
28✔
242
            );
7✔
243
            const [numericH, numericS, numericV, numericA] = values;
7✔
244

7✔
245
            result.spaceId = 'hsv';
7✔
246
            result.coords = [numericH, numericS, numericV];
7✔
247
            result.alpha = numericA;
7✔
248
            result.isValid =
7✔
249
                numericH >= 0 &&
7✔
250
                numericH <= 360 &&
7✔
251
                numericS >= 0 &&
7✔
252
                numericS <= 100 &&
7✔
253
                numericV >= 0 &&
7✔
254
                numericV <= 100 &&
7✔
255
                numericA >= 0 &&
7✔
256
                numericA <= 1;
7✔
257
        } else if (hexMatch) {
46✔
258
            const [, hex, alphaHex] = hexMatch;
22✔
259

22✔
260
            // Function to process 2-digit or repeated 1-digit hex
22✔
261
            const processHex = (hex: string): number => {
22✔
262
                // For 3-digit hex values, repeat each digit
73✔
263
                if (hex.length === 1) {
73✔
264
                    hex = hex + hex;
14✔
265
                }
14✔
266
                return parseInt(hex, 16) / 255;
73✔
267
            };
73✔
268

22✔
269
            // Handle both 3-digit and 6-digit hex
22✔
270
            let numericR, numericG, numericB;
22✔
271
            if (hex.length === 3) {
22✔
272
                // 3-digit hex (e.g., #3a7 -> #33aa77)
4✔
273
                numericR = processHex(hex.substring(0, 1));
4✔
274
                numericG = processHex(hex.substring(1, 2));
4✔
275
                numericB = processHex(hex.substring(2, 3));
4✔
276
            } else {
22✔
277
                // 6-digit hex (e.g., #33aa77)
18✔
278
                numericR = processHex(hex.substring(0, 2));
18✔
279
                numericG = processHex(hex.substring(2, 4));
18✔
280
                numericB = processHex(hex.substring(4, 6));
18✔
281
            }
18✔
282

22✔
283
            // Process hex alpha if provided (convert from 0-255 to 0-1)
22✔
284
            const numericA = alphaHex ? processHex(alphaHex) : 1;
22✔
285

22✔
286
            // Validate the color values
22✔
287
            result.spaceId = 'srgb';
22✔
288
            result.coords = [numericR, numericG, numericB];
22✔
289
            result.alpha = numericA;
22✔
290
            result.isValid =
22✔
291
                numericR >= 0 &&
22✔
292
                numericR <= 1 &&
22✔
293
                numericG >= 0 &&
22✔
294
                numericG <= 1 &&
22✔
295
                numericB >= 0 &&
22✔
296
                numericB <= 1 &&
22✔
297
                numericA >= 0 &&
22✔
298
                numericA <= 1;
22✔
299
        }
22✔
300

69✔
301
        return result;
69✔
302
    }
69✔
303

2✔
304
    /**
2✔
305
     * Represents the color state of the component.
2✔
306
     * Initialized with an HSV color model with hue 0, saturation 100, and value 100, and an alpha value of 1.
2✔
307
     *
2✔
308
     * @private
2✔
309
     * @type {Color}
2✔
310
     */
2✔
311
    private _color: Color = new Color('hsv', [0, 100, 100], 1);
2✔
312

2✔
313
    /**
2✔
314
     * Represents the original color value provided by the user.
2✔
315
     *
2✔
316
     * @private
2✔
317
     * @type {ColorTypes}
2✔
318
     */
2✔
319
    private _colorOrigin!: ColorTypes;
2✔
320

2✔
321
    /**
2✔
322
     * Gets the original color value provided by the user.
2✔
323
     *
2✔
324
     * @returns {ColorTypes} The original color value.
2✔
325
     */
2✔
326
    get colorOrigin(): ColorTypes {
2✔
327
        return this._colorOrigin;
×
328
    }
×
329

2✔
330
    /**
2✔
331
     * Sets the original color value provided by the user.
2✔
332
     *
2✔
333
     * @param {ColorTypes} colorOrigin - The original color value to set.
2✔
334
     */
2✔
335
    set colorOrigin(colorOrigin: ColorTypes) {
2✔
336
        this._colorOrigin = colorOrigin;
1✔
337
    }
1✔
338

2✔
339
    /**
2✔
340
     * An optional string property that specifies how the color should be managed(its value is the name of color space in which color object will be managed).
2✔
341
     * This property can be used to define a specific management strategy or identifier.
2✔
342
     */
2✔
343
    private manageAs?: string;
2✔
344

2✔
345
    /**
2✔
346
     * Stores the previous color value.
2✔
347
     * This is used to keep track of the color before any changes are made.
2✔
348
     *
2✔
349
     * @private
2✔
350
     */
2✔
351
    private _previousColor!: Color;
2✔
352

2✔
353
    /**
2✔
354
     * Private helper method to convert RGB color to hex format with optional alpha
2✔
355
     *
2✔
356
     * @private
2✔
357
     * @param {boolean} includeHash - Whether to include the # prefix in the returned string
2✔
358
     * @param {boolean} includeAlpha - Whether to include the alpha channel in the returned string
2✔
359
     * @returns {string} The color in hex format
2✔
360
     */
2✔
361
    private _getHexString(includeHash: boolean, includeAlpha: boolean): string {
2✔
362
        const { r, g, b } = (this._color.to('srgb') as Color).srgb;
4✔
363
        const a = this._color.alpha;
4✔
364

4✔
365
        const rHex = Math.round(r * 255)
4✔
366
            .toString(16)
4✔
367
            .padStart(2, '0');
4✔
368
        const gHex = Math.round(g * 255)
4✔
369
            .toString(16)
4✔
370
            .padStart(2, '0');
4✔
371
        const bHex = Math.round(b * 255)
4✔
372
            .toString(16)
4✔
373
            .padStart(2, '0');
4✔
374
        const aHex = Math.round(a * 255)
4✔
375
            .toString(16)
4✔
376
            .padStart(2, '0');
4✔
377

4✔
378
        return `${includeHash ? '#' : ''}${rHex}${gHex}${bHex}${includeAlpha ? aHex : ''}`;
4!
379
    }
4✔
380

2✔
381
    /**
2✔
382
     * Sets the color value for the controller. The color can be provided in various formats:
2✔
383
     * - A string representing a color name, hex code, or other color format.
2✔
384
     * - An instance of the `Color` class.
2✔
385
     * - An object containing color properties such as `h`, `s`, `l`, `v`, `r`, `g`, `b`, and optionally `a`.
2✔
386
     *
2✔
387
     * The method validates and parses the input color, converting it to a `Color` instance.
2✔
388
     * If the color is invalid, it attempts to parse it as a hex code or returns without setting a new color.
2✔
389
     *
2✔
390
     * @param {ColorTypes} color - The color value to set. It can be a string, an instance of `Color`, or an object with color properties.
2✔
391
     */
2✔
392
    set color(color: ColorTypes) {
2✔
393
        this._colorOrigin = color;
40✔
394
        let newColor!: Color;
40✔
395
        if (typeof color === 'string') {
40✔
396
            const colorValidationResult = this.validateColorString(
35✔
397
                color as string
35✔
398
            );
35✔
399
            if (colorValidationResult.isValid) {
35✔
400
                const [coord1, coord2, coord3] = colorValidationResult.coords;
33✔
401
                newColor = new Color(
33✔
402
                    `${colorValidationResult.spaceId}`,
33✔
403
                    [coord1, coord2, coord3],
33✔
404
                    colorValidationResult.alpha
33✔
405
                );
33✔
406
            } else {
35✔
407
                try {
2✔
408
                    Color.parse(color);
2✔
409
                } catch (error) {
2✔
410
                    try {
2✔
411
                        newColor = new Color(`#${color}`);
2✔
412
                    } catch (error) {
2✔
413
                        return;
1✔
414
                    }
1✔
415
                }
2✔
416
            }
2✔
417
        } else if (color instanceof Color) {
40✔
418
            newColor = color;
4✔
419
        } else if (!Array.isArray(color)) {
5✔
420
            const { h, s, l, v, r, g, b, a } = color as {
1✔
421
                h: string;
1✔
422
                s: string;
1✔
423
                l: string;
1✔
424
                v: string;
1✔
425
                r: string;
1✔
426
                g: string;
1✔
427
                b: string;
1✔
428
                a?: string;
1✔
429
            };
1✔
430
            if (typeof h !== 'undefined' && typeof s !== 'undefined') {
1!
431
                const lv = l ?? v;
×
432
                newColor = new Color(
×
433
                    typeof l !== 'undefined' ? 'hsl' : 'hsv',
×
434
                    [
×
435
                        parseFloat(h),
×
436
                        typeof s !== 'string' ? s * 100 : parseFloat(s),
×
437
                        typeof lv !== 'string' ? lv * 100 : parseFloat(lv),
×
438
                    ],
×
439
                    parseFloat(a || '1')
×
440
                );
×
441
            } else if (
×
442
                typeof r !== 'undefined' &&
1✔
443
                typeof g !== 'undefined' &&
1✔
444
                typeof b !== 'undefined'
1✔
445
            ) {
1✔
446
                newColor = new Color(
1✔
447
                    'srgb',
1✔
448
                    [
1✔
449
                        parseFloat(r) / 255,
1✔
450
                        parseFloat(g) / 255,
1✔
451
                        parseFloat(b) / 255,
1✔
452
                    ],
1✔
453
                    parseFloat(a || '1')
1!
454
                );
1✔
455
            }
1✔
456
        }
1✔
457

39✔
458
        if (!newColor) {
40!
459
            newColor = new Color(color as DefaultColorTypes);
×
460
        }
✔
461

39✔
462
        if (this.manageAs) {
40!
463
            this._color = newColor.to(this.manageAs) as Color;
×
464
        } else {
40✔
465
            this._color = newColor;
39✔
466
        }
39✔
467
        this.host.requestUpdate();
39✔
468
    }
40✔
469

2✔
470
    /**
2✔
471
     * Gets the color value in various formats based on the original color input.
2✔
472
     *
2✔
473
     * The method determines the color space of the original color input and converts
2✔
474
     * the color to the appropriate format. The supported color spaces are:
2✔
475
     * - HSV (Hue, Saturation, Value)
2✔
476
     * - HSL (Hue, Saturation, Lightness)
2✔
477
     * - Hexadecimal (with or without alpha)
2✔
478
     * - RGB (Red, Green, Blue) with optional alpha
2✔
479
     *
2✔
480
     * @returns {ColorTypes} The color value in the appropriate format.
2✔
481
     *
2✔
482
     * The method handles the following cases:
2✔
483
     * - If the original color input is a string, it checks the prefix to determine the color space.
2✔
484
     * - If the original color input is an object, it checks the properties to determine the color space.
2✔
485
     * - If the original color input is not provided, it defaults to the current color space of the color object.
2✔
486
     *
2✔
487
     * The returned color value can be in one of the following formats:
2✔
488
     * - `hsv(h, s%, v%)` or `hsva(h, s%, v%, a)`
2✔
489
     * - `hsl(h, s%, l%)` or `hsla(h, s%, l%, a)`
2✔
490
     * - `#rrggbb` or `#rrggbbaa`
2✔
491
     * - `rgb(r, g, b)` or `rgba(r, g, b, a)`
2✔
492
     * - `{ h, s, v, a }` for HSV object
2✔
493
     * - `{ h, s, l, a }` for HSL object
2✔
494
     * - `{ r, g, b, a }` for RGB object
2✔
495
     */
2✔
496
    get colorValue(): ColorTypes {
2✔
497
        if (typeof this._colorOrigin === 'string') {
16✔
498
            let spaceId = '';
12✔
499
            if (this._colorOrigin.startsWith('#')) {
12✔
500
                spaceId = 'hex string';
4✔
501
            } else if (this._colorOrigin.startsWith('rgb')) {
12✔
502
                spaceId = 'rgb';
3✔
503
            } else if (this._colorOrigin.startsWith('hsl')) {
8✔
504
                spaceId = 'hsl';
3✔
505
            } else if (this._colorOrigin.startsWith('hsv')) {
5✔
506
                spaceId = 'hsv';
2✔
507
            } else {
2!
508
                spaceId = 'hex';
×
509
            }
×
510
            switch (spaceId) {
12✔
511
                case 'hsv': {
12✔
512
                    const hadAlpha = this._colorOrigin[3] === 'a';
2✔
513
                    const { h, s, v } = (this._color.to('hsv') as Color).hsv;
2✔
514
                    const a = this._color.alpha;
2✔
515
                    return `hsv${hadAlpha ? `a` : ''}(${Math.round(
2✔
516
                        h
2✔
517
                    )}, ${Math.round(s)}%, ${Math.round(v)}%${
2✔
518
                        hadAlpha ? `, ${a}` : ''
2✔
519
                    })`;
2✔
520
                }
2✔
521
                case 'hsl': {
12✔
522
                    const hadAlpha = this._colorOrigin[3] === 'a';
3✔
523
                    const { h, s, l } = (this._color.to('hsl') as Color).hsl;
3✔
524
                    const a = this._color.alpha;
3✔
525
                    return `hsl${hadAlpha ? `a` : ''}(${Math.round(
3✔
526
                        h
3✔
527
                    )}, ${Math.round(s)}%, ${Math.round(l)}%${
3✔
528
                        hadAlpha ? `, ${a}` : ''
3✔
529
                    })`;
3✔
530
                }
3✔
531
                case 'hex string': {
12✔
532
                    // Check if the original input included alpha
4✔
533
                    const hadAlpha =
4✔
534
                        this._colorOrigin.length === 9 || // #RRGGBBAA format
4✔
535
                        this._colorOrigin.length === 5; // #RGBA format
2✔
536
                    return this._getHexString(true, hadAlpha);
4✔
537
                }
4✔
538
                case 'hex': {
12!
539
                    // Check if the original input included alpha
×
540
                    const hadAlpha =
×
541
                        this._colorOrigin.length === 8 || // RRGGBBAA format (no #)
×
542
                        this._colorOrigin.length === 4; // RGBA format (no #)
×
543
                    return this._getHexString(false, hadAlpha);
×
544
                }
×
545
                //rgb
12✔
546
                default: {
12✔
547
                    const { r, g, b } = (this._color.to('srgb') as Color).srgb;
3✔
548
                    const hadAlpha = this._colorOrigin[3] === 'a';
3✔
549
                    const a = this._color.alpha;
3✔
550
                    if (this._colorOrigin.search('%') > -1) {
3✔
551
                        return `rgb${hadAlpha ? `a` : ''}(${Math.round(r * 100)}%, ${Math.round(
1!
552
                            g * 100
1✔
553
                        )}%, ${Math.round(b * 100)}%${hadAlpha ? `,${Math.round(a * 100)}%` : ''})`;
1!
554
                    }
1✔
555
                    return `rgb${hadAlpha ? `a` : ''}(${Math.round(r * 255)}, ${Math.round(
3!
556
                        g * 255
3✔
557
                    )}, ${Math.round(b * 255)}${hadAlpha ? `, ${a}` : ''})`;
3!
558
                }
3✔
559
            }
12✔
560
        }
12✔
561
        let spaceId;
4✔
562
        if (this._colorOrigin) {
4✔
563
            try {
4✔
564
                ({ spaceId } = new Color(
4✔
565
                    this._colorOrigin as DefaultColorTypes
4✔
566
                ));
4✔
567
            } catch (error) {
4✔
568
                const { h, s, l, v, r, g, b } = this._colorOrigin as {
1✔
569
                    h: string;
1✔
570
                    s: string;
1✔
571
                    l: string;
1✔
572
                    v: string;
1✔
573
                    r: string;
1✔
574
                    g: string;
1✔
575
                    b: string;
1✔
576
                };
1✔
577
                if (
1✔
578
                    typeof h !== 'undefined' &&
1!
579
                    typeof s !== 'undefined' &&
×
580
                    typeof l !== 'undefined'
×
581
                ) {
1!
582
                    spaceId = 'hsl';
×
583
                } else if (
×
584
                    typeof h !== 'undefined' &&
1!
585
                    typeof s !== 'undefined' &&
×
586
                    typeof v !== 'undefined'
×
587
                ) {
1!
588
                    spaceId = 'hsv';
×
589
                } else if (
×
590
                    typeof r !== 'undefined' &&
1✔
591
                    typeof g !== 'undefined' &&
1✔
592
                    typeof b !== 'undefined'
1✔
593
                ) {
1✔
594
                    spaceId = 'srgb';
1✔
595
                }
1✔
596
            }
1✔
597
        } else {
16!
598
            ({ spaceId } = this.color);
×
599
        }
✔
600
        switch (spaceId) {
4✔
601
            case 'hsv': {
16✔
602
                const { h, s, v } = (this._color.to('hsv') as Color).hsv;
1✔
603
                return {
1✔
604
                    h,
1✔
605
                    s: s / 100,
1✔
606
                    v: v / 100,
1✔
607
                    a: this._color.alpha,
1✔
608
                };
1✔
609
            }
1✔
610
            case 'hsl': {
16✔
611
                const { h, s, l } = (this._color.to('hsl') as Color).hsl;
1✔
612
                return {
1✔
613
                    h,
1✔
614
                    s: s / 100,
1✔
615
                    l: l / 100,
1✔
616
                    a: this._color.alpha,
1✔
617
                };
1✔
618
            }
1✔
619
            case 'srgb': {
16✔
620
                const { r, g, b } = (this._color.to('srgb') as Color).srgb;
2✔
621
                if (
2✔
622
                    this._colorOrigin &&
2✔
623
                    typeof (this._colorOrigin as { r: string }).r ===
2✔
624
                        'string' &&
2✔
625
                    (this._colorOrigin as { r: string }).r.search('%')
1✔
626
                ) {
2✔
627
                    return {
1✔
628
                        r: `${Math.round(r * 255)}%`,
1✔
629
                        g: `${Math.round(g * 255)}%`,
1✔
630
                        b: `${Math.round(b * 255)}%`,
1✔
631
                        a: this._color.alpha,
1✔
632
                    };
1✔
633
                }
1✔
634
                return {
1✔
635
                    r: Math.round(r * 255),
1✔
636
                    g: Math.round(g * 255),
1✔
637
                    b: Math.round(b * 255),
1✔
638
                    a: this._color.alpha,
1✔
639
                };
1✔
640
            }
1✔
641
        }
16!
642
        return this._color;
×
643
    }
16✔
644

2✔
645
    protected host: ReactiveElement;
2✔
646

2✔
647
    /**
2✔
648
     * Gets the hue value of the current color in HSL format.
2✔
649
     *
2✔
650
     * @returns {number} The hue value as a number.
2✔
651
     */
2✔
652
    get hue(): number {
2✔
653
        return Number((this._color.to('hsl') as Color).hsl.h);
1✔
654
    }
1✔
655

2✔
656
    /**
2✔
657
     * Sets the hue value of the color and requests an update from the host.
2✔
658
     *
2✔
659
     * @param hue - The hue value to set, represented as a number.
2✔
660
     */
2✔
661
    set hue(hue: number) {
2✔
662
        this._color.set('h', hue);
1✔
663
        this.host.requestUpdate();
1✔
664
    }
1✔
665

2✔
666
    /**
2✔
667
     * Creates an instance of ColorController.
2✔
668
     *
2✔
669
     * @param host - The ReactiveElement that this controller is associated with.
2✔
670
     * @param options - An object containing optional parameters.
2✔
671
     * @param options.manageAs - A string to manage the controller as a specific type.
2✔
672
     */
2✔
673
    constructor(
2✔
674
        host: ReactiveElement,
39✔
675
        {
39✔
676
            manageAs,
39✔
677
        }: {
39✔
678
            manageAs?: string;
39✔
679
        } = {}
39✔
680
    ) {
39✔
681
        this.host = host;
39✔
682
        this.manageAs = manageAs;
39✔
683
    }
39✔
684

2✔
685
    /**
2✔
686
     * Converts the current color to the specified format.
2✔
687
     *
2✔
688
     * @param format - The desired color format. It can be a string representing one of the valid formats
2✔
689
     * ('srgb', 'hsva', 'hsv', 'hsl', 'hsla') or a ColorSpace object.
2✔
690
     * @returns The color object in the specified format.
2✔
691
     * @throws Will throw an error if the provided format is not a valid string format.
2✔
692
     */
2✔
693
    getColor(format: string | ColorSpace): ColorObject {
2✔
694
        const validFormats = ['srgb', 'hsva', 'hsv', 'hsl', 'hsla'];
5✔
695
        if (typeof format === 'string' && !validFormats.includes(format)) {
5!
696
            throw new Error('not a valid format');
×
697
        }
×
698

5✔
699
        return this._color.to(format);
5✔
700
    }
5✔
701

2✔
702
    /**
2✔
703
     * Converts the current color to an HSL string representation.
2✔
704
     *
2✔
705
     * @returns {string} The HSL string representation of the current color.
2✔
706
     */
2✔
707
    getHslString(): string {
2✔
708
        return this._color.to('hsl').toString();
1✔
709
    }
1✔
710

2✔
711
    /**
2✔
712
     * Saves the current color state by cloning the current color and storing it
2✔
713
     * as the previous color. This allows for the ability to revert to the previous
2✔
714
     * color state if needed.
2✔
715
     *
2✔
716
     * @returns {void}
2✔
717
     */
2✔
718
    savePreviousColor(): void {
2✔
719
        this._previousColor = this._color.clone();
1✔
720
    }
1✔
721

2✔
722
    /**
2✔
723
     * Restores the color to the previously saved color value.
2✔
724
     *
2✔
725
     * This method sets the current color (`_color`) to the previously stored color (`_previousColor`).
2✔
726
     */
2✔
727
    restorePreviousColor(): void {
2✔
728
        this._color = this._previousColor;
1✔
729
    }
1✔
730
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc