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

adobe / spectrum-web-components / 14094812696

26 Mar 2025 10:37PM UTC coverage: 71.929% (-26.1%) from 98.002%
14094812696

Pull #5095

github

web-flow
Merge 2c0bd48e0 into 3184c1e6a
Pull Request #5095: feat(accordion,base,theme): initial global system reporting

168 of 203 branches covered (82.76%)

Branch coverage included in aggregate %.

11 of 14 new or added lines in 2 files covered. (78.57%)

656 existing lines in 5 files now uncovered.

1659 of 2337 relevant lines covered (70.99%)

305.59 hits per line

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

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

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

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

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

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

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

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

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

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

27✔
123
        // RGB color formats
27✔
124
        const rgbRegExpArray = [
27✔
125
            // With commas
27✔
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)
27✔
127
            /rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i, // rgb(r, g, b)
27✔
128

27✔
129
            // With spaces
27✔
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
27✔
131
            /^rgb\s+(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s*$/i, // rgb r g b
27✔
132

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

27✔
137
            // Percentage values
27✔
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%)
27✔
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)
27✔
140
        ];
27✔
141

27✔
142
        // HSL color formats
27✔
143
        const hslRegExpArray = [
27✔
144
            // With commas
27✔
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)
27✔
146
            /hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*\)/i, // hsl(h, s, l)
27✔
147

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

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

27✔
157
        // HSV color formats
27✔
158
        const hsvRegExpArray = [
27✔
159
            // With commas
27✔
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)
27✔
161
            /hsv\(\s*(\d{1,3})\s*,\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*\)/i, // hsv(h, s, v)
27✔
162

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

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

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

27✔
178
        const rgbaMatch = rgbRegExpArray
27✔
179
            .find((regex) => regex.test(color))
11✔
180
            ?.exec(color);
11✔
181
        const hslaMatch = hslRegExpArray
27✔
182
            .find((regex) => regex.test(color))
4✔
183
            ?.exec(color);
4✔
184
        const hsvaMatch = hsvRegExpArray
27✔
185
            .find((regex) => regex.test(color))
3✔
186
            ?.exec(color);
3✔
187
        const hexMatch = hexRegExpArray
27✔
188
            .find((regex) => regex.test(color))
6✔
189
            ?.exec(color);
6✔
190

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
339
    /**
1✔
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).
1✔
341
     * This property can be used to define a specific management strategy or identifier.
1✔
342
     */
1✔
343
    private manageAs?: string;
1✔
344

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

1✔
353
    /**
1✔
354
     * Private helper method to convert RGB color to hex format with optional alpha
1✔
355
     *
1✔
356
     * @private
1✔
357
     * @param {boolean} includeHash - Whether to include the # prefix in the returned string
1✔
358
     * @param {boolean} includeAlpha - Whether to include the alpha channel in the returned string
1✔
359
     * @returns {string} The color in hex format
1✔
360
     */
1✔
361
    private _getHexString(includeHash: boolean, includeAlpha: boolean): string {
1✔
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

1✔
381
    /**
1✔
382
     * Sets the color value for the controller. The color can be provided in various formats:
1✔
383
     * - A string representing a color name, hex code, or other color format.
1✔
384
     * - An instance of the `Color` class.
1✔
385
     * - An object containing color properties such as `h`, `s`, `l`, `v`, `r`, `g`, `b`, and optionally `a`.
1✔
386
     *
1✔
387
     * The method validates and parses the input color, converting it to a `Color` instance.
1✔
388
     * If the color is invalid, it attempts to parse it as a hex code or returns without setting a new color.
1✔
389
     *
1✔
390
     * @param {ColorTypes} color - The color value to set. It can be a string, an instance of `Color`, or an object with color properties.
1✔
391
     */
1✔
392
    set color(color: ColorTypes) {
1✔
393
        this._colorOrigin = color;
26✔
394
        let newColor!: Color;
26✔
395
        if (typeof color === 'string') {
26✔
396
            const colorValidationResult = this.validateColorString(
21✔
397
                color as string
21✔
398
            );
21✔
399
            if (colorValidationResult.isValid) {
21✔
400
                const [coord1, coord2, coord3] = colorValidationResult.coords;
19✔
401
                newColor = new Color(
19✔
402
                    `${colorValidationResult.spaceId}`,
19✔
403
                    [coord1, coord2, coord3],
19✔
404
                    colorValidationResult.alpha
19✔
405
                );
19✔
406
            } else {
21✔
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) {
26✔
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!
UNCOV
431
                const lv = l ?? v;
×
UNCOV
432
                newColor = new Color(
×
UNCOV
433
                    typeof l !== 'undefined' ? 'hsl' : 'hsv',
×
UNCOV
434
                    [
×
UNCOV
435
                        parseFloat(h),
×
UNCOV
436
                        typeof s !== 'string' ? s * 100 : parseFloat(s),
×
UNCOV
437
                        typeof lv !== 'string' ? lv * 100 : parseFloat(lv),
×
UNCOV
438
                    ],
×
UNCOV
439
                    parseFloat(a || '1')
×
UNCOV
440
                );
×
UNCOV
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

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

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

1✔
470
    /**
1✔
471
     * Gets the color value in various formats based on the original color input.
1✔
472
     *
1✔
473
     * The method determines the color space of the original color input and converts
1✔
474
     * the color to the appropriate format. The supported color spaces are:
1✔
475
     * - HSV (Hue, Saturation, Value)
1✔
476
     * - HSL (Hue, Saturation, Lightness)
1✔
477
     * - Hexadecimal (with or without alpha)
1✔
478
     * - RGB (Red, Green, Blue) with optional alpha
1✔
479
     *
1✔
480
     * @returns {ColorTypes} The color value in the appropriate format.
1✔
481
     *
1✔
482
     * The method handles the following cases:
1✔
483
     * - If the original color input is a string, it checks the prefix to determine the color space.
1✔
484
     * - If the original color input is an object, it checks the properties to determine the color space.
1✔
485
     * - If the original color input is not provided, it defaults to the current color space of the color object.
1✔
486
     *
1✔
487
     * The returned color value can be in one of the following formats:
1✔
488
     * - `hsv(h, s%, v%)` or `hsva(h, s%, v%, a)`
1✔
489
     * - `hsl(h, s%, l%)` or `hsla(h, s%, l%, a)`
1✔
490
     * - `#rrggbb` or `#rrggbbaa`
1✔
491
     * - `rgb(r, g, b)` or `rgba(r, g, b, a)`
1✔
492
     * - `{ h, s, v, a }` for HSV object
1✔
493
     * - `{ h, s, l, a }` for HSL object
1✔
494
     * - `{ r, g, b, a }` for RGB object
1✔
495
     */
1✔
496
    get colorValue(): ColorTypes {
1✔
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!
UNCOV
508
                spaceId = 'hex';
×
UNCOV
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!
UNCOV
539
                    // Check if the original input included alpha
×
UNCOV
540
                    const hadAlpha =
×
UNCOV
541
                        this._colorOrigin.length === 8 || // RRGGBBAA format (no #)
×
UNCOV
542
                        this._colorOrigin.length === 4; // RGBA format (no #)
×
UNCOV
543
                    return this._getHexString(false, hadAlpha);
×
UNCOV
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!
UNCOV
579
                    typeof s !== 'undefined' &&
×
UNCOV
580
                    typeof l !== 'undefined'
×
581
                ) {
1!
UNCOV
582
                    spaceId = 'hsl';
×
UNCOV
583
                } else if (
×
584
                    typeof h !== 'undefined' &&
1!
UNCOV
585
                    typeof s !== 'undefined' &&
×
UNCOV
586
                    typeof v !== 'undefined'
×
587
                ) {
1!
UNCOV
588
                    spaceId = 'hsv';
×
UNCOV
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!
UNCOV
598
            ({ spaceId } = this.color);
×
UNCOV
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

1✔
645
    protected host: ReactiveElement;
1✔
646

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

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

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

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

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

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

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

1✔
722
    /**
1✔
723
     * Restores the color to the previously saved color value.
1✔
724
     *
1✔
725
     * This method sets the current color (`_color`) to the previously stored color (`_previousColor`).
1✔
726
     */
1✔
727
    restorePreviousColor(): void {
1✔
728
        this._color = this._previousColor;
1✔
729
    }
1✔
730
}
1✔
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