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

adobe / spectrum-web-components / 14074743608

26 Mar 2025 03:18AM UTC coverage: 97.985% (+0.003%) from 97.982%
14074743608

Pull #5246

github

web-flow
Merge 03263ecee into 72be86686
Pull Request #5246: fix(color-field): respects alpha value in hex form

5315 of 5619 branches covered (94.59%)

Branch coverage included in aggregate %.

85 of 85 new or added lines in 1 file covered. (100.0%)

5 existing lines in 1 file now uncovered.

33694 of 34192 relevant lines covered (98.54%)

646.9 hits per line

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

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

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

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

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

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

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

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

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

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

128✔
123
        const rgbRegExpArray = [
128✔
124
            /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) with commas
128✔
125
            /rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i, // rgb(r, g, b) with commas
128✔
126
            /^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 with spaces
128✔
127
            /^rgb\s+(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s*$/i, // rgb r g b with spaces
128✔
128
            /^rgba\(\s*(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s+(\d*\.?\d+)\s*\)$/i, // rgba(r g b a) with spaces inside
128✔
129
            /^rgb\(\s*(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s*\)$/i, // rgb(r g b) with spaces inside
128✔
130
            /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%) percentage values
128✔
131
            /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) percentage values
128✔
132
        ];
128✔
133
        const hslRegExpArray = [
128✔
134
            /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) with commas
128✔
135
            /hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*\)/i, // hsl(h, s, l) with commas
128✔
136
            /^hsla\s+(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s+(\d*\.?\d+)\s*$/i, // hsla h s l a with spaces
128✔
137
            /^hsl\s+(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s*$/i, // hsl h s l with spaces
128✔
138
            /^hsla\(\s*(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s+(\d*\.?\d+)\s*\)$/i, // hsla(h s l a) with spaces inside
128✔
139
            /^hsl\(\s*(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s*\)$/i, // hsl(h s l) with spaces inside
128✔
140
        ];
128✔
141
        const hsvRegExpArray = [
128✔
142
            /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) with commas
128✔
143
            /hsv\(\s*(\d{1,3})\s*,\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*\)/i, // hsv(h, s, v) with commas
128✔
144
            /^hsva\s+(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s+(\d*\.?\d+)\s*$/i, // hsva h s v a with spaces
128✔
145
            /^hsv\s+(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s*$/i, // hsv h s v with spaces
128✔
146
            /^hsva\(\s*(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s+(\d*\.?\d+)\s*\)$/i, // hsva(h s v a) with spaces inside
128✔
147
            /^hsv\(\s*(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s*\)$/i, // hsv(h s v) with spaces inside
128✔
148
        ];
128✔
149
        const hexRegExpArray = [
128✔
150
            /^#([A-Fa-f0-9]{6})([A-Fa-f0-9]{2})?$/, // 6-digit hex with optional hex alpha
128✔
151
            /^#([A-Fa-f0-9]{3})([A-Fa-f0-9]{1})?$/, // 3-digit hex with optional hex alpha
128✔
152
        ];
128✔
153

128✔
154
        const rgbaMatch = rgbRegExpArray
128✔
155
            .find((regex) => regex.test(color))
21✔
156
            ?.exec(color);
21✔
157
        const hslaMatch = hslRegExpArray
128✔
158
            .find((regex) => regex.test(color))
27✔
159
            ?.exec(color);
27✔
160
        const hsvaMatch = hsvRegExpArray
128✔
161
            .find((regex) => regex.test(color))
16✔
162
            ?.exec(color);
16✔
163
        const hexMatch = hexRegExpArray
128✔
164
            .find((regex) => regex.test(color))
36✔
165
            ?.exec(color);
36✔
166

128✔
167
        if (rgbaMatch) {
128✔
168
            const [, r, g, b, a] = rgbaMatch.filter(
21✔
169
                (element) => typeof element === 'string'
21✔
170
            );
21✔
171
            const alpha = a === undefined ? 1 : Number(a);
21✔
172
            const processValue = (value: string): number => {
21✔
173
                if (value.includes('%')) {
63✔
174
                    return Number(value.replace('%', '')) / 100;
12✔
175
                } else {
60✔
176
                    return Number(value) / 255;
51✔
177
                }
51✔
178
            };
63✔
179
            const numericR = processValue(r);
21✔
180
            const numericG = processValue(g);
21✔
181
            const numericB = processValue(b);
21✔
182

21✔
183
            result.spaceId = 'srgb';
21✔
184
            result.coords = [numericR, numericG, numericB];
21✔
185
            result.alpha = alpha;
21✔
186
            result.isValid =
21✔
187
                numericR >= 0 &&
21✔
188
                numericR <= 1 &&
21✔
189
                numericG >= 0 &&
21✔
190
                numericG <= 1 &&
21✔
191
                numericB >= 0 &&
21✔
192
                numericB <= 1 &&
21✔
193
                alpha >= 0 &&
21✔
194
                alpha <= 1;
21✔
195
        } else if (hslaMatch) {
128✔
196
            const [, h, s, l, a] = hslaMatch;
27✔
197
            const values = [h, s, l, a === undefined ? '1' : a].map((value) =>
27✔
198
                Number(value.replace(/[^\d.]/g, ''))
108✔
199
            );
27✔
200
            const [numericH, numericS, numericL, numericA] = values;
27✔
201

27✔
202
            result.spaceId = 'hsl';
27✔
203
            result.coords = [numericH, numericS, numericL];
27✔
204
            result.alpha = numericA;
27✔
205
            result.isValid =
27✔
206
                numericH >= 0 &&
27✔
207
                numericH <= 360 &&
27✔
208
                numericS >= 0 &&
27✔
209
                numericS <= 100 &&
27✔
210
                numericL >= 0 &&
27✔
211
                numericL <= 100 &&
27✔
212
                numericA >= 0 &&
27✔
213
                numericA <= 1;
27✔
214
        } else if (hsvaMatch) {
107✔
215
            const [, h, s, v, a] = hsvaMatch;
16✔
216
            const values = [h, s, v, a === undefined ? '1' : a].map((value) =>
16✔
217
                Number(value.replace(/[^\d.]/g, ''))
64✔
218
            );
16✔
219
            const [numericH, numericS, numericV, numericA] = values;
16✔
220

16✔
221
            result.spaceId = 'hsv';
16✔
222
            result.coords = [numericH, numericS, numericV];
16✔
223
            result.alpha = numericA;
16✔
224
            result.isValid =
16✔
225
                numericH >= 0 &&
16✔
226
                numericH <= 360 &&
16✔
227
                numericS >= 0 &&
16✔
228
                numericS <= 100 &&
16✔
229
                numericV >= 0 &&
16✔
230
                numericV <= 100 &&
16✔
231
                numericA >= 0 &&
16✔
232
                numericA <= 1;
16✔
233
        } else if (hexMatch) {
80✔
234
            const [, hex, alphaHex] = hexMatch;
36✔
235

36✔
236
            // Function to process 2-digit or repeated 1-digit hex
36✔
237
            const processHex = (hex: string): number => {
36✔
238
                // For 3-digit hex values, repeat each digit
122✔
239
                if (hex.length === 1) {
122✔
240
                    hex = hex + hex;
32✔
241
                }
32✔
242
                return parseInt(hex, 16) / 255;
122✔
243
            };
122✔
244

36✔
245
            // Handle both 3-digit and 6-digit hex
36✔
246
            let numericR, numericG, numericB;
36✔
247
            if (hex.length === 3) {
36✔
248
                // 3-digit hex (e.g., #3a7 -> #33aa77)
9✔
249
                numericR = processHex(hex.substring(0, 1));
9✔
250
                numericG = processHex(hex.substring(1, 2));
9✔
251
                numericB = processHex(hex.substring(2, 3));
9✔
252
            } else {
36✔
253
                // 6-digit hex (e.g., #33aa77)
27✔
254
                numericR = processHex(hex.substring(0, 2));
27✔
255
                numericG = processHex(hex.substring(2, 4));
27✔
256
                numericB = processHex(hex.substring(4, 6));
27✔
257
            }
27✔
258

36✔
259
            // Process hex alpha if provided (convert from 0-255 to 0-1)
36✔
260
            const numericA = alphaHex ? processHex(alphaHex) : 1;
36✔
261

36✔
262
            // Validate the color values
36✔
263
            result.spaceId = 'srgb';
36✔
264
            result.coords = [numericR, numericG, numericB];
36✔
265
            result.alpha = numericA;
36✔
266
            result.isValid =
36✔
267
                numericR >= 0 &&
36✔
268
                numericR <= 1 &&
36✔
269
                numericG >= 0 &&
36✔
270
                numericG <= 1 &&
36✔
271
                numericB >= 0 &&
36✔
272
                numericB <= 1 &&
36✔
273
                numericA >= 0 &&
36✔
274
                numericA <= 1;
36✔
275
        }
36✔
276

128✔
277
        return result;
128✔
278
    }
128✔
279

6✔
280
    /**
6✔
281
     * Represents the color state of the component.
6✔
282
     * Initialized with an HSV color model with hue 0, saturation 100, and value 100, and an alpha value of 1.
6✔
283
     *
6✔
284
     * @private
6✔
285
     * @type {Color}
6✔
286
     */
6✔
287
    private _color: Color = new Color('hsv', [0, 100, 100], 1);
6✔
288

6✔
289
    /**
6✔
290
     * Represents the original color value provided by the user.
6✔
291
     *
6✔
292
     * @private
6✔
293
     * @type {ColorTypes}
6✔
294
     */
6✔
295
    private _colorOrigin!: ColorTypes;
6✔
296

6✔
297
    /**
6✔
298
     * Gets the original color value provided by the user.
6✔
299
     *
6✔
300
     * @returns {ColorTypes} The original color value.
6✔
301
     */
6✔
302
    get colorOrigin(): ColorTypes {
6✔
UNCOV
303
        return this._colorOrigin;
×
UNCOV
304
    }
×
305

6✔
306
    /**
6✔
307
     * Sets the original color value provided by the user.
6✔
308
     *
6✔
309
     * @param {ColorTypes} colorOrigin - The original color value to set.
6✔
310
     */
6✔
311
    set colorOrigin(colorOrigin: ColorTypes) {
6✔
312
        this._colorOrigin = colorOrigin;
1✔
313
    }
1✔
314

6✔
315
    /**
6✔
316
     * 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).
6✔
317
     * This property can be used to define a specific management strategy or identifier.
6✔
318
     */
6✔
319
    private manageAs?: string;
6✔
320

6✔
321
    /**
6✔
322
     * Stores the previous color value.
6✔
323
     * This is used to keep track of the color before any changes are made.
6✔
324
     *
6✔
325
     * @private
6✔
326
     */
6✔
327
    private _previousColor!: Color;
6✔
328

6✔
329
    /**
6✔
330
     * Private helper method to convert RGB color to hex format with optional alpha
6✔
331
     *
6✔
332
     * @private
6✔
333
     * @param {boolean} includeHash - Whether to include the # prefix in the returned string
6✔
334
     * @param {boolean} includeAlpha - Whether to include the alpha channel in the returned string
6✔
335
     * @returns {string} The color in hex format
6✔
336
     */
6✔
337
    private _getHexString(includeHash: boolean, includeAlpha: boolean): string {
6✔
338
        const { r, g, b } = (this._color.to('srgb') as Color).srgb;
62✔
339
        const a = this._color.alpha;
62✔
340

62✔
341
        const rHex = Math.round(r * 255)
62✔
342
            .toString(16)
62✔
343
            .padStart(2, '0');
62✔
344
        const gHex = Math.round(g * 255)
62✔
345
            .toString(16)
62✔
346
            .padStart(2, '0');
62✔
347
        const bHex = Math.round(b * 255)
62✔
348
            .toString(16)
62✔
349
            .padStart(2, '0');
62✔
350
        const aHex = Math.round(a * 255)
62✔
351
            .toString(16)
62✔
352
            .padStart(2, '0');
62✔
353

62✔
354
        return `${includeHash ? '#' : ''}${rHex}${gHex}${bHex}${includeAlpha ? aHex : ''}`;
62✔
355
    }
62✔
356

6✔
357
    /**
6✔
358
     * Sets the color value for the controller. The color can be provided in various formats:
6✔
359
     * - A string representing a color name, hex code, or other color format.
6✔
360
     * - An instance of the `Color` class.
6✔
361
     * - An object containing color properties such as `h`, `s`, `l`, `v`, `r`, `g`, `b`, and optionally `a`.
6✔
362
     *
6✔
363
     * The method validates and parses the input color, converting it to a `Color` instance.
6✔
364
     * If the color is invalid, it attempts to parse it as a hex code or returns without setting a new color.
6✔
365
     *
6✔
366
     * @param {ColorTypes} color - The color value to set. It can be a string, an instance of `Color`, or an object with color properties.
6✔
367
     */
6✔
368
    set color(color: ColorTypes) {
6✔
369
        this._colorOrigin = color;
119✔
370
        let newColor!: Color;
119✔
371
        if (typeof color === 'string') {
119✔
372
            const colorValidationResult = this.validateColorString(
94✔
373
                color as string
94✔
374
            );
94✔
375
            if (colorValidationResult.isValid) {
94✔
376
                const [coord1, coord2, coord3] = colorValidationResult.coords;
81✔
377
                newColor = new Color(
81✔
378
                    `${colorValidationResult.spaceId}`,
81✔
379
                    [coord1, coord2, coord3],
81✔
380
                    colorValidationResult.alpha
81✔
381
                );
81✔
382
            } else {
94✔
383
                try {
13✔
384
                    Color.parse(color);
13✔
385
                } catch (error) {
13✔
386
                    try {
9✔
387
                        newColor = new Color(`#${color}`);
9✔
388
                    } catch (error) {
9✔
389
                        return;
1✔
390
                    }
1✔
391
                }
9✔
392
            }
13✔
393
        } else if (color instanceof Color) {
119✔
394
            newColor = color;
4✔
395
        } else if (!Array.isArray(color)) {
25✔
396
            const { h, s, l, v, r, g, b, a } = color as {
21✔
397
                h: string;
21✔
398
                s: string;
21✔
399
                l: string;
21✔
400
                v: string;
21✔
401
                r: string;
21✔
402
                g: string;
21✔
403
                b: string;
21✔
404
                a?: string;
21✔
405
            };
21✔
406
            if (typeof h !== 'undefined' && typeof s !== 'undefined') {
21✔
407
                const lv = l ?? v;
14✔
408
                newColor = new Color(
14✔
409
                    typeof l !== 'undefined' ? 'hsl' : 'hsv',
14✔
410
                    [
14✔
411
                        parseFloat(h),
14✔
412
                        typeof s !== 'string' ? s * 100 : parseFloat(s),
14✔
413
                        typeof lv !== 'string' ? lv * 100 : parseFloat(lv),
14✔
414
                    ],
14✔
415
                    parseFloat(a || '1')
14✔
416
                );
14✔
417
            } else if (
14✔
418
                typeof r !== 'undefined' &&
7✔
419
                typeof g !== 'undefined' &&
7✔
420
                typeof b !== 'undefined'
7✔
421
            ) {
7✔
422
                newColor = new Color(
7✔
423
                    'srgb',
7✔
424
                    [
7✔
425
                        parseFloat(r) / 255,
7✔
426
                        parseFloat(g) / 255,
7✔
427
                        parseFloat(b) / 255,
7✔
428
                    ],
7✔
429
                    parseFloat(a || '1')
7!
430
                );
7✔
431
            }
7✔
432
        }
21✔
433

118✔
434
        if (!newColor) {
119✔
435
            newColor = new Color(color as DefaultColorTypes);
4✔
436
        }
4✔
437

118✔
438
        if (this.manageAs) {
119✔
439
            this._color = newColor.to(this.manageAs) as Color;
79✔
440
        } else {
119✔
441
            this._color = newColor;
39✔
442
        }
39✔
443
        this.host.requestUpdate();
118✔
444
    }
119✔
445

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

6✔
621
    protected host: ReactiveElement;
6✔
622

6✔
623
    /**
6✔
624
     * Gets the hue value of the current color in HSL format.
6✔
625
     *
6✔
626
     * @returns {number} The hue value as a number.
6✔
627
     */
6✔
628
    get hue(): number {
6✔
629
        return Number((this._color.to('hsl') as Color).hsl.h);
2,109✔
630
    }
2,109✔
631

6✔
632
    /**
6✔
633
     * Sets the hue value of the color and requests an update from the host.
6✔
634
     *
6✔
635
     * @param hue - The hue value to set, represented as a number.
6✔
636
     */
6✔
637
    set hue(hue: number) {
6✔
638
        this._color.set('h', hue);
79✔
639
        this.host.requestUpdate();
79✔
640
    }
79✔
641

6✔
642
    /**
6✔
643
     * Creates an instance of ColorController.
6✔
644
     *
6✔
645
     * @param host - The ReactiveElement that this controller is associated with.
6✔
646
     * @param options - An object containing optional parameters.
6✔
647
     * @param options.manageAs - A string to manage the controller as a specific type.
6✔
648
     */
6✔
649
    constructor(
6✔
650
        host: ReactiveElement,
134✔
651
        {
134✔
652
            manageAs,
134✔
653
        }: {
134✔
654
            manageAs?: string;
134✔
655
        } = {}
134✔
656
    ) {
134✔
657
        this.host = host;
134✔
658
        this.manageAs = manageAs;
134✔
659
    }
134✔
660

6✔
661
    /**
6✔
662
     * Converts the current color to the specified format.
6✔
663
     *
6✔
664
     * @param format - The desired color format. It can be a string representing one of the valid formats
6✔
665
     * ('srgb', 'hsva', 'hsv', 'hsl', 'hsla') or a ColorSpace object.
6✔
666
     * @returns The color object in the specified format.
6✔
667
     * @throws Will throw an error if the provided format is not a valid string format.
6✔
668
     */
6✔
669
    getColor(format: string | ColorSpace): ColorObject {
6✔
670
        const validFormats = ['srgb', 'hsva', 'hsv', 'hsl', 'hsla'];
5✔
671
        if (typeof format === 'string' && !validFormats.includes(format)) {
5!
UNCOV
672
            throw new Error('not a valid format');
×
UNCOV
673
        }
×
674

5✔
675
        return this._color.to(format);
5✔
676
    }
5✔
677

6✔
678
    /**
6✔
679
     * Converts the current color to an HSL string representation.
6✔
680
     *
6✔
681
     * @returns {string} The HSL string representation of the current color.
6✔
682
     */
6✔
683
    getHslString(): string {
6✔
684
        return this._color.to('hsl').toString();
182✔
685
    }
182✔
686

6✔
687
    /**
6✔
688
     * Saves the current color state by cloning the current color and storing it
6✔
689
     * as the previous color. This allows for the ability to revert to the previous
6✔
690
     * color state if needed.
6✔
691
     *
6✔
692
     * @returns {void}
6✔
693
     */
6✔
694
    savePreviousColor(): void {
6✔
695
        this._previousColor = this._color.clone();
81✔
696
    }
81✔
697

6✔
698
    /**
6✔
699
     * Restores the color to the previously saved color value.
6✔
700
     *
6✔
701
     * This method sets the current color (`_color`) to the previously stored color (`_previousColor`).
6✔
702
     */
6✔
703
    restorePreviousColor(): void {
6✔
704
        this._color = this._previousColor;
3✔
705
    }
3✔
706
}
6✔
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