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

adobe / spectrum-web-components / 13498253698

24 Feb 2025 12:40PM CUT coverage: 98.178% (-0.001%) from 98.179%
13498253698

Pull #4713

github

web-flow
Merge ef73ff457 into 199f989b0
Pull Request #4713: feat(reactive-controllers): Migrate to Colorjs from Tinycolor

5262 of 5541 branches covered (94.96%)

Branch coverage included in aggregate %.

818 of 824 new or added lines in 10 files covered. (99.27%)

1 existing line in 1 file now uncovered.

33328 of 33765 relevant lines covered (98.71%)

407.11 hits per line

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

98.92
/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 = {
123✔
117
            spaceId: null,
123✔
118
            coords: [0, 0, 0],
123✔
119
            isValid: false,
123✔
120
            alpha: 1,
123✔
121
        };
123✔
122

123✔
123
        const rgbRegExpArray = [
123✔
124
            /rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d*\.?\d+)\s*\)/i,
123✔
125
            /rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i,
123✔
126
            /^rgba\s+(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s+(0|0?\.\d+|1)\s*$/i,
123✔
127
            /^rgb\s+(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s*$/i,
123✔
128
            /^rgba\(\s*(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s+(\d*\.?\d+)\s*\)$/i,
123✔
129
            /^rgb\(\s*(\d{1,3})\s+(\d{1,3})\s+(\d{1,3})\s*\)$/i,
123✔
130
            /rgb\(\s*(100|[0-9]{1,2}%)\s*,\s*(100|[0-9]{1,2}%)\s*,\s*(100|[0-9]{1,2}%)\s*\)/i,
123✔
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,
123✔
132
        ];
123✔
133
        const hslRegExpArray = [
123✔
134
            /hsla\(\s*(\d{1,3})\s*,\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*,\s*(\d*\.?\d+)\s*\)/i,
123✔
135
            /hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*\)/i,
123✔
136
            /^hsla\s+(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s+(\d*\.?\d+)\s*$/i,
123✔
137
            /^hsl\s+(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s*$/i,
123✔
138
            /^hsla\(\s*(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s+(\d*\.?\d+)\s*\)$/i,
123✔
139
            /^hsl\(\s*(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s*\)$/i,
123✔
140
        ];
123✔
141
        const hsvRegExpArray = [
123✔
142
            /hsva\(\s*(\d{1,3})\s*,\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*,\s*(\d*\.?\d+)\s*\)/i,
123✔
143
            /hsv\(\s*(\d{1,3})\s*,\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*\)/i,
123✔
144
            /^hsva\s+(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s+(\d*\.?\d+)\s*$/i,
123✔
145
            /^hsv\s+(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s*$/i,
123✔
146
            /^hsva\(\s*(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s+(\d*\.?\d+)\s*\)$/i,
123✔
147
            /^hsv\(\s*(\d{1,3})\s+(\d{1,3}%?)\s+(\d{1,3}%?)\s*\)$/i,
123✔
148
        ];
123✔
149
        const hexRegExpArray = [
123✔
150
            /^#([A-Fa-f0-9]{6})(?:\s*([01](?:\.\d+)?))?$/,
123✔
151
            /^#([A-Fa-f0-9]{3})(?:\s*([01](?:\.\d+)?))?$/,
123✔
152
        ];
123✔
153

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

123✔
167
        if (rgbaMatch) {
123✔
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) {
123✔
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) {
102✔
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) {
75✔
234
            const [, hex, alpha] = hexMatch;
22✔
235

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

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

22✔
259
            // Numeric alpha: if not provided, default to 1
22✔
260
            const numericA = alpha ? Number(alpha) : 1;
22!
261

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

123✔
277
        return result;
123✔
278
    }
123✔
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✔
NEW
303
        return this._colorOrigin;
×
NEW
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
     * Sets the color value for the controller. The color can be provided in various formats:
6✔
331
     * - A string representing a color name, hex code, or other color format.
6✔
332
     * - An instance of the `Color` class.
6✔
333
     * - An object containing color properties such as `h`, `s`, `l`, `v`, `r`, `g`, `b`, and optionally `a`.
6✔
334
     *
6✔
335
     * The method validates and parses the input color, converting it to a `Color` instance.
6✔
336
     * If the color is invalid, it attempts to parse it as a hex code or returns without setting a new color.
6✔
337
     *
6✔
338
     * @param {ColorTypes} color - The color value to set. It can be a string, an instance of `Color`, or an object with color properties.
6✔
339
     */
6✔
340
    set color(color: ColorTypes) {
6✔
341
        this._colorOrigin = color;
117✔
342
        let newColor!: Color;
117✔
343
        if (typeof color === 'string') {
117✔
344
            const colorValidationResult = this.validateColorString(
92✔
345
                color as string
92✔
346
            );
92✔
347
            if (colorValidationResult.isValid) {
92✔
348
                const [coord1, coord2, coord3] = colorValidationResult.coords;
69✔
349
                newColor = new Color(
69✔
350
                    `${colorValidationResult.spaceId}`,
69✔
351
                    [coord1, coord2, coord3],
69✔
352
                    colorValidationResult.alpha
69✔
353
                );
69✔
354
            } else {
92✔
355
                try {
23✔
356
                    Color.parse(color);
23✔
357
                } catch (error) {
23✔
358
                    try {
9✔
359
                        newColor = new Color(`#${color}`);
9✔
360
                    } catch (error) {
9✔
361
                        return;
1✔
362
                    }
1✔
363
                }
9✔
364
            }
23✔
365
        } else if (color instanceof Color) {
117✔
366
            newColor = color;
4✔
367
        } else if (!Array.isArray(color)) {
25✔
368
            const { h, s, l, v, r, g, b, a } = color as {
21✔
369
                h: string;
21✔
370
                s: string;
21✔
371
                l: string;
21✔
372
                v: string;
21✔
373
                r: string;
21✔
374
                g: string;
21✔
375
                b: string;
21✔
376
                a?: string;
21✔
377
            };
21✔
378
            if (typeof h !== 'undefined' && typeof s !== 'undefined') {
21✔
379
                const lv = l ?? v;
14✔
380
                newColor = new Color(
14✔
381
                    typeof l !== 'undefined' ? 'hsl' : 'hsv',
14✔
382
                    [
14✔
383
                        parseFloat(h),
14✔
384
                        typeof s !== 'string' ? s * 100 : parseFloat(s),
14✔
385
                        typeof lv !== 'string' ? lv * 100 : parseFloat(lv),
14✔
386
                    ],
14✔
387
                    parseFloat(a || '1')
14✔
388
                );
14✔
389
            } else if (
14✔
390
                typeof r !== 'undefined' &&
7✔
391
                typeof g !== 'undefined' &&
7✔
392
                typeof b !== 'undefined'
7✔
393
            ) {
7✔
394
                newColor = new Color(
7✔
395
                    'srgb',
7✔
396
                    [
7✔
397
                        parseFloat(r) / 255,
7✔
398
                        parseFloat(g) / 255,
7✔
399
                        parseFloat(b) / 255,
7✔
400
                    ],
7✔
401
                    parseFloat(a || '1')
7!
402
                );
7✔
403
            }
7✔
404
        }
21✔
405

116✔
406
        if (!newColor) {
117✔
407
            newColor = new Color(color as DefaultColorTypes);
14✔
408
        }
14✔
409

116✔
410
        if (this.manageAs) {
117✔
411
            this._color = newColor.to(this.manageAs) as Color;
79✔
412
        } else {
117✔
413
            this._color = newColor;
37✔
414
        }
37✔
415
        this.host.requestUpdate();
116✔
416
    }
117✔
417

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

6✔
613
    protected host: ReactiveElement;
6✔
614

6✔
615
    /**
6✔
616
     * Gets the hue value of the current color in HSL format.
6✔
617
     *
6✔
618
     * @returns {number} The hue value as a number.
6✔
619
     */
6✔
620
    get hue(): number {
6✔
621
        return Number((this._color.to('hsl') as Color).hsl.h);
2,099✔
622
    }
2,099✔
623

6✔
624
    /**
6✔
625
     * Sets the hue value of the color and requests an update from the host.
6✔
626
     *
6✔
627
     * @param hue - The hue value to set, represented as a number.
6✔
628
     */
6✔
629
    set hue(hue: number) {
6✔
630
        this._color.set('h', hue);
79✔
631
        this.host.requestUpdate();
79✔
632
    }
79✔
633

6✔
634
    /**
6✔
635
     * Creates an instance of ColorController.
6✔
636
     *
6✔
637
     * @param host - The ReactiveElement that this controller is associated with.
6✔
638
     * @param options - An object containing optional parameters.
6✔
639
     * @param options.manageAs - A string to manage the controller as a specific type.
6✔
640
     */
6✔
641
    constructor(
6✔
642
        host: ReactiveElement,
134✔
643
        {
134✔
644
            manageAs,
134✔
645
        }: {
134✔
646
            manageAs?: string;
134✔
647
        } = {}
134✔
648
    ) {
134✔
649
        this.host = host;
134✔
650
        this.manageAs = manageAs;
134✔
651
    }
134✔
652

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

5✔
667
        return this._color.to(format);
5✔
668
    }
5✔
669

6✔
670
    /**
6✔
671
     * Converts the current color to an HSL string representation.
6✔
672
     *
6✔
673
     * @returns {string} The HSL string representation of the current color.
6✔
674
     */
6✔
675
    getHslString(): string {
6✔
676
        return this._color.to('hsl').toString();
182✔
677
    }
182✔
678

6✔
679
    /**
6✔
680
     * Saves the current color state by cloning the current color and storing it
6✔
681
     * as the previous color. This allows for the ability to revert to the previous
6✔
682
     * color state if needed.
6✔
683
     *
6✔
684
     * @returns {void}
6✔
685
     */
6✔
686
    savePreviousColor(): void {
6✔
687
        this._previousColor = this._color.clone();
81✔
688
    }
81✔
689

6✔
690
    /**
6✔
691
     * Restores the color to the previously saved color value.
6✔
692
     *
6✔
693
     * This method sets the current color (`_color`) to the previously stored color (`_previousColor`).
6✔
694
     */
6✔
695
    restorePreviousColor(): void {
6✔
696
        this._color = this._previousColor;
3✔
697
    }
3✔
698
}
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

© 2025 Coveralls, Inc