@thednp/color-picker 0.0.1 → 0.0.2-alpha1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1252 @@
1
+ /*!
2
+ * ColorPalette v0.0.2alpha1 (http://thednp.github.io/color-picker)
3
+ * Copyright 2022 © thednp
4
+ * Licensed under MIT (https://github.com/thednp/color-picker/blob/master/LICENSE)
5
+ */
6
+ /**
7
+ * Shortcut for `Object.assign()` static method.
8
+ * @param {Record<string, any>} obj a target object
9
+ * @param {Record<string, any>} source a source object
10
+ */
11
+ const ObjectAssign = (obj, source) => Object.assign(obj, source);
12
+
13
+ /**
14
+ * Round colour components, for all formats except HEX.
15
+ * @param {number} v one of the colour components
16
+ * @returns {number} the rounded number
17
+ */
18
+ function roundPart(v) {
19
+ const floor = Math.floor(v);
20
+ return v - floor < 0.5 ? floor : Math.round(v);
21
+ }
22
+
23
+ /**
24
+ * A global namespace for `document.head`.
25
+ */
26
+ const { head: documentHead } = document;
27
+
28
+ /**
29
+ * Shortcut for `window.getComputedStyle(element).propertyName`
30
+ * static method.
31
+ *
32
+ * * If `element` parameter is not an `HTMLElement`, `getComputedStyle`
33
+ * throws a `ReferenceError`.
34
+ *
35
+ * @param {HTMLElement | Element} element target
36
+ * @param {string} property the css property
37
+ * @return {string} the css property value
38
+ */
39
+ function getElementStyle(element, property) {
40
+ const computedStyle = getComputedStyle(element);
41
+
42
+ // @ts-ignore -- must use camelcase strings,
43
+ // or non-camelcase strings with `getPropertyValue`
44
+ return property in computedStyle ? computedStyle[property] : '';
45
+ }
46
+
47
+ /**
48
+ * Shortcut for multiple uses of `HTMLElement.style.propertyName` method.
49
+ * @param {HTMLElement | Element} element target element
50
+ * @param {Partial<CSSStyleDeclaration>} styles attribute value
51
+ */
52
+ // @ts-ignore
53
+ const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
54
+
55
+ /**
56
+ * Shortcut for `String.toLowerCase()`.
57
+ *
58
+ * @param {string} source input string
59
+ * @returns {string} lowercase output string
60
+ */
61
+ const toLowerCase = (source) => source.toLowerCase();
62
+
63
+ /**
64
+ * A list of explicit default non-color values.
65
+ */
66
+ const nonColors = ['transparent', 'currentColor', 'inherit', 'revert', 'initial'];
67
+
68
+ // Color supported formats
69
+ const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsv', 'hwb'];
70
+
71
+ // Hue angles
72
+ const ANGLES = 'deg|rad|grad|turn';
73
+
74
+ // <http://www.w3.org/TR/css3-values/#integers>
75
+ const CSS_INTEGER = '[-\\+]?\\d+%?';
76
+
77
+ // Include CSS3 Module
78
+ // <http://www.w3.org/TR/css3-values/#number-value>
79
+ const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
80
+
81
+ // Include CSS4 Module Hue degrees unit
82
+ // <https://www.w3.org/TR/css3-values/#angle-value>
83
+ const CSS_ANGLE = `[-\\+]?\\d*\\.?\\d+(?:${ANGLES})?`;
84
+
85
+ // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
86
+ const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
87
+
88
+ // Add angles to the mix
89
+ const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
90
+
91
+ // Start & end
92
+ const START_MATCH = '(?:[\\s|\\(\\s|\\s\\(\\s]+)?';
93
+ const END_MATCH = '(?:[\\s|\\)\\s]+)?';
94
+ // Components separation
95
+ const SEP = '(?:[,|\\s]+)';
96
+ const SEP2 = '(?:[,|\\/\\s]*)?';
97
+
98
+ // Actual matching.
99
+ // Parentheses and commas are optional, but not required.
100
+ // Whitespace can take the place of commas or opening paren
101
+ const PERMISSIVE_MATCH = `${START_MATCH}(${CSS_UNIT2})${SEP}(${CSS_UNIT})${SEP}(${CSS_UNIT})${SEP2}(${CSS_UNIT})?${END_MATCH}`;
102
+
103
+ const matchers = {
104
+ CSS_UNIT: new RegExp(CSS_UNIT2),
105
+ hwb: new RegExp(`hwb${PERMISSIVE_MATCH}`),
106
+ rgb: new RegExp(`rgb(?:a)?${PERMISSIVE_MATCH}`),
107
+ hsl: new RegExp(`hsl(?:a)?${PERMISSIVE_MATCH}`),
108
+ hsv: new RegExp(`hsv(?:a)?${PERMISSIVE_MATCH}`),
109
+ hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
110
+ hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
111
+ hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
112
+ hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
113
+ };
114
+
115
+ /**
116
+ * Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
117
+ * <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
118
+ * @param {string} n testing number
119
+ * @returns {boolean} the query result
120
+ */
121
+ function isOnePointZero(n) {
122
+ return `${n}`.includes('.') && parseFloat(n) === 1;
123
+ }
124
+
125
+ /**
126
+ * Check to see if string passed in is a percentage
127
+ * @param {string} n testing number
128
+ * @returns {boolean} the query result
129
+ */
130
+ function isPercentage(n) {
131
+ return `${n}`.includes('%');
132
+ }
133
+
134
+ /**
135
+ * Check to see if string passed is a web safe colour.
136
+ * @see https://stackoverflow.com/a/16994164
137
+ * @param {string} color a colour name, EG: *red*
138
+ * @returns {boolean} the query result
139
+ */
140
+ function isColorName(color) {
141
+ if (nonColors.includes(color)
142
+ || ['#', ...COLOR_FORMAT].some((f) => color.includes(f))) return false;
143
+
144
+ return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
145
+ setElementStyle(documentHead, { color });
146
+ const computedColor = getElementStyle(documentHead, 'color');
147
+ setElementStyle(documentHead, { color: '' });
148
+ return computedColor !== c;
149
+ });
150
+ }
151
+
152
+ /**
153
+ * Check to see if it looks like a CSS unit
154
+ * (see `matchers` above for definition).
155
+ * @param {string | number} color testing value
156
+ * @returns {boolean} the query result
157
+ */
158
+ function isValidCSSUnit(color) {
159
+ return Boolean(matchers.CSS_UNIT.exec(String(color)));
160
+ }
161
+
162
+ /**
163
+ * Take input from [0, n] and return it as [0, 1]
164
+ * @param {*} N the input number
165
+ * @param {number} max the number maximum value
166
+ * @returns {number} the number in [0, 1] value range
167
+ */
168
+ function bound01(N, max) {
169
+ let n = N;
170
+ if (isOnePointZero(N)) n = '100%';
171
+
172
+ const processPercent = isPercentage(n);
173
+ n = max === 360
174
+ ? parseFloat(n)
175
+ : Math.min(max, Math.max(0, parseFloat(n)));
176
+
177
+ // Automatically convert percentage into number
178
+ if (processPercent) n = (n * max) / 100;
179
+
180
+ // Handle floating point rounding errors
181
+ if (Math.abs(n - max) < 0.000001) {
182
+ return 1;
183
+ }
184
+ // Convert into [0, 1] range if it isn't already
185
+ if (max === 360) {
186
+ // If n is a hue given in degrees,
187
+ // wrap around out-of-range values into [0, 360] range
188
+ // then convert into [0, 1].
189
+ n = (n < 0 ? (n % max) + max : n % max) / max;
190
+ } else {
191
+ // If n not a hue given in degrees
192
+ // Convert into [0, 1] range if it isn't already.
193
+ n = (n % max) / max;
194
+ }
195
+ return n;
196
+ }
197
+
198
+ /**
199
+ * Return a valid alpha value [0,1] with all invalid values being set to 1.
200
+ * @param {string | number} a transparency value
201
+ * @returns {number} a transparency value in the [0, 1] range
202
+ */
203
+ function boundAlpha(a) {
204
+ let na = parseFloat(`${a}`);
205
+
206
+ if (Number.isNaN(na) || na < 0 || na > 1) {
207
+ na = 1;
208
+ }
209
+
210
+ return na;
211
+ }
212
+
213
+ /**
214
+ * Force a number between 0 and 1.
215
+ * @param {number} v the float number
216
+ * @returns {number} - the resulting number
217
+ */
218
+ function clamp01(v) {
219
+ return Math.min(1, Math.max(0, v));
220
+ }
221
+
222
+ /**
223
+ * Returns the hexadecimal value of a web safe colour.
224
+ * @param {string} name
225
+ * @returns {string}
226
+ */
227
+ function getRGBFromName(name) {
228
+ setElementStyle(documentHead, { color: name });
229
+ const colorName = getElementStyle(documentHead, 'color');
230
+ setElementStyle(documentHead, { color: '' });
231
+ return colorName;
232
+ }
233
+
234
+ /**
235
+ * Converts a decimal value to hexadecimal.
236
+ * @param {number} d the input number
237
+ * @returns {string} - the hexadecimal value
238
+ */
239
+ function convertDecimalToHex(d) {
240
+ return roundPart(d * 255).toString(16);
241
+ }
242
+
243
+ /**
244
+ * Converts a hexadecimal value to decimal.
245
+ * @param {string} h hexadecimal value
246
+ * @returns {number} number in decimal format
247
+ */
248
+ function convertHexToDecimal(h) {
249
+ return parseIntFromHex(h) / 255;
250
+ }
251
+
252
+ /**
253
+ * Converts a base-16 hexadecimal value into a base-10 integer.
254
+ * @param {string} val
255
+ * @returns {number}
256
+ */
257
+ function parseIntFromHex(val) {
258
+ return parseInt(val, 16);
259
+ }
260
+
261
+ /**
262
+ * Force a hexadecimal value to have 2 characters.
263
+ * @param {string} c string with [0-9A-F] ranged values
264
+ * @returns {string} 0 => 00, a => 0a
265
+ */
266
+ function pad2(c) {
267
+ return c.length === 1 ? `0${c}` : String(c);
268
+ }
269
+
270
+ /**
271
+ * Converts an RGB colour value to HSL.
272
+ *
273
+ * @param {number} R Red component [0, 255]
274
+ * @param {number} G Green component [0, 255]
275
+ * @param {number} B Blue component [0, 255]
276
+ * @returns {CP.HSL} {h,s,l} object with [0, 1] ranged values
277
+ */
278
+ function rgbToHsl(R, G, B) {
279
+ const r = R / 255;
280
+ const g = G / 255;
281
+ const b = B / 255;
282
+ const max = Math.max(r, g, b);
283
+ const min = Math.min(r, g, b);
284
+ let h = 0;
285
+ let s = 0;
286
+ const l = (max + min) / 2;
287
+ if (max === min) {
288
+ s = 0;
289
+ h = 0; // achromatic
290
+ } else {
291
+ const d = max - min;
292
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
293
+ switch (max) {
294
+ case r:
295
+ h = (g - b) / d + (g < b ? 6 : 0);
296
+ break;
297
+ case g:
298
+ h = (b - r) / d + 2;
299
+ break;
300
+ case b:
301
+ h = (r - g) / d + 4;
302
+ break;
303
+ }
304
+ h /= 6;
305
+ }
306
+ return { h, s, l };
307
+ }
308
+
309
+ /**
310
+ * Returns a normalized RGB component value.
311
+ * @param {number} p
312
+ * @param {number} q
313
+ * @param {number} t
314
+ * @returns {number}
315
+ */
316
+ function hueToRgb(p, q, t) {
317
+ let T = t;
318
+ if (T < 0) T += 1;
319
+ if (T > 1) T -= 1;
320
+ if (T < 1 / 6) return p + (q - p) * (6 * T);
321
+ if (T < 1 / 2) return q;
322
+ if (T < 2 / 3) return p + (q - p) * (2 / 3 - T) * 6;
323
+ return p;
324
+ }
325
+
326
+ /**
327
+ * Converts an HSL colour value to RGB.
328
+ *
329
+ * @param {number} h Hue Angle [0, 1]
330
+ * @param {number} s Saturation [0, 1]
331
+ * @param {number} l Lightness Angle [0, 1]
332
+ * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
333
+ */
334
+ function hslToRgb(h, s, l) {
335
+ let r = 0;
336
+ let g = 0;
337
+ let b = 0;
338
+
339
+ if (s === 0) {
340
+ // achromatic
341
+ g = l;
342
+ b = l;
343
+ r = l;
344
+ } else {
345
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
346
+ const p = 2 * l - q;
347
+ r = hueToRgb(p, q, h + 1 / 3);
348
+ g = hueToRgb(p, q, h);
349
+ b = hueToRgb(p, q, h - 1 / 3);
350
+ }
351
+ [r, g, b] = [r, g, b].map((x) => x * 255);
352
+
353
+ return { r, g, b };
354
+ }
355
+
356
+ /**
357
+ * Returns an HWB colour object from an RGB colour object.
358
+ * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
359
+ * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
360
+ *
361
+ * @param {number} R Red component [0, 255]
362
+ * @param {number} G Green [0, 255]
363
+ * @param {number} B Blue [0, 255]
364
+ * @return {CP.HWB} {h,w,b} object with [0, 1] ranged values
365
+ */
366
+ function rgbToHwb(R, G, B) {
367
+ const r = R / 255;
368
+ const g = G / 255;
369
+ const b = B / 255;
370
+
371
+ let f = 0;
372
+ let i = 0;
373
+ const whiteness = Math.min(r, g, b);
374
+ const max = Math.max(r, g, b);
375
+ const black = 1 - max;
376
+
377
+ if (max === whiteness) return { h: 0, w: whiteness, b: black };
378
+ if (r === whiteness) {
379
+ f = g - b;
380
+ i = 3;
381
+ } else {
382
+ f = g === whiteness ? b - r : r - g;
383
+ i = g === whiteness ? 5 : 1;
384
+ }
385
+
386
+ const h = (i - f / (max - whiteness)) / 6;
387
+ return {
388
+ h: h === 1 ? 0 : h,
389
+ w: whiteness,
390
+ b: black,
391
+ };
392
+ }
393
+
394
+ /**
395
+ * Returns an RGB colour object from an HWB colour.
396
+ *
397
+ * @param {number} H Hue Angle [0, 1]
398
+ * @param {number} W Whiteness [0, 1]
399
+ * @param {number} B Blackness [0, 1]
400
+ * @return {CP.RGB} {r,g,b} object with [0, 255] ranged values
401
+ *
402
+ * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
403
+ * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
404
+ */
405
+ function hwbToRgb(H, W, B) {
406
+ if (W + B >= 1) {
407
+ const gray = (W / (W + B)) * 255;
408
+ return { r: gray, g: gray, b: gray };
409
+ }
410
+ let { r, g, b } = hslToRgb(H, 1, 0.5);
411
+ [r, g, b] = [r, g, b]
412
+ .map((v) => (v / 255) * (1 - W - B) + W)
413
+ .map((v) => v * 255);
414
+
415
+ return { r, g, b };
416
+ }
417
+
418
+ /**
419
+ * Converts an RGB colour value to HSV.
420
+ *
421
+ * @param {number} R Red component [0, 255]
422
+ * @param {number} G Green [0, 255]
423
+ * @param {number} B Blue [0, 255]
424
+ * @returns {CP.HSV} {h,s,v} object with [0, 1] ranged values
425
+ */
426
+ function rgbToHsv(R, G, B) {
427
+ const r = R / 255;
428
+ const g = G / 255;
429
+ const b = B / 255;
430
+ const max = Math.max(r, g, b);
431
+ const min = Math.min(r, g, b);
432
+ let h = 0;
433
+ const v = max;
434
+ const d = max - min;
435
+ const s = max === 0 ? 0 : d / max;
436
+ if (max === min) {
437
+ h = 0; // achromatic
438
+ } else {
439
+ switch (max) {
440
+ case r:
441
+ h = (g - b) / d + (g < b ? 6 : 0);
442
+ break;
443
+ case g:
444
+ h = (b - r) / d + 2;
445
+ break;
446
+ case b:
447
+ h = (r - g) / d + 4;
448
+ break;
449
+ }
450
+ h /= 6;
451
+ }
452
+ return { h, s, v };
453
+ }
454
+
455
+ /**
456
+ * Converts an HSV colour value to RGB.
457
+ *
458
+ * @param {number} H Hue Angle [0, 1]
459
+ * @param {number} S Saturation [0, 1]
460
+ * @param {number} V Brightness Angle [0, 1]
461
+ * @returns {CP.RGB} {r,g,b} object with [0, 1] ranged values
462
+ */
463
+ function hsvToRgb(H, S, V) {
464
+ const h = H * 6;
465
+ const s = S;
466
+ const v = V;
467
+ const i = Math.floor(h);
468
+ const f = h - i;
469
+ const p = v * (1 - s);
470
+ const q = v * (1 - f * s);
471
+ const t = v * (1 - (1 - f) * s);
472
+ const mod = i % 6;
473
+ let r = [v, q, p, p, t, v][mod];
474
+ let g = [t, v, v, q, p, p][mod];
475
+ let b = [p, p, t, v, v, q][mod];
476
+ [r, g, b] = [r, g, b].map((n) => n * 255);
477
+ return { r, g, b };
478
+ }
479
+
480
+ /**
481
+ * Converts an RGB colour to hex
482
+ *
483
+ * Assumes r, g, and b are contained in the set [0, 255]
484
+ * Returns a 3 or 6 character hex
485
+ * @param {number} r Red component [0, 255]
486
+ * @param {number} g Green [0, 255]
487
+ * @param {number} b Blue [0, 255]
488
+ * @param {boolean=} allow3Char
489
+ * @returns {string}
490
+ */
491
+ function rgbToHex(r, g, b, allow3Char) {
492
+ const hex = [
493
+ pad2(roundPart(r).toString(16)),
494
+ pad2(roundPart(g).toString(16)),
495
+ pad2(roundPart(b).toString(16)),
496
+ ];
497
+
498
+ // Return a 3 character hex if possible
499
+ if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
500
+ && hex[1].charAt(0) === hex[1].charAt(1)
501
+ && hex[2].charAt(0) === hex[2].charAt(1)) {
502
+ return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
503
+ }
504
+
505
+ return hex.join('');
506
+ }
507
+
508
+ /**
509
+ * Converts an RGBA color plus alpha transparency to hex8.
510
+ *
511
+ * @param {number} r Red component [0, 255]
512
+ * @param {number} g Green [0, 255]
513
+ * @param {number} b Blue [0, 255]
514
+ * @param {number} a Alpha transparency [0, 1]
515
+ * @param {boolean=} allow4Char when *true* it will also find hex shorthand
516
+ * @returns {string} a hexadecimal value with alpha transparency
517
+ */
518
+ function rgbaToHex(r, g, b, a, allow4Char) {
519
+ const hex = [
520
+ pad2(roundPart(r).toString(16)),
521
+ pad2(roundPart(g).toString(16)),
522
+ pad2(roundPart(b).toString(16)),
523
+ pad2(convertDecimalToHex(a)),
524
+ ];
525
+
526
+ // Return a 4 character hex if possible
527
+ if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
528
+ && hex[1].charAt(0) === hex[1].charAt(1)
529
+ && hex[2].charAt(0) === hex[2].charAt(1)
530
+ && hex[3].charAt(0) === hex[3].charAt(1)) {
531
+ return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
532
+ }
533
+ return hex.join('');
534
+ }
535
+
536
+ /**
537
+ * Permissive string parsing. Take in a number of formats, and output an object
538
+ * based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
539
+ * @param {string} input colour value in any format
540
+ * @returns {Record<string, (number | string | boolean)> | false} an object matching the RegExp
541
+ */
542
+ function stringInputToObject(input) {
543
+ let color = toLowerCase(input.trim());
544
+ if (color.length === 0) {
545
+ return {
546
+ r: 0, g: 0, b: 0, a: 1,
547
+ };
548
+ }
549
+ let named = false;
550
+ if (isColorName(color)) {
551
+ color = getRGBFromName(color);
552
+ named = true;
553
+ } else if (nonColors.includes(color)) {
554
+ const a = color === 'transparent' ? 0 : 1;
555
+ return {
556
+ r: 0, g: 0, b: 0, a, format: 'rgb', ok: true,
557
+ };
558
+ }
559
+
560
+ // Try to match string input using regular expressions.
561
+ // Keep most of the number bounding out of this function,
562
+ // don't worry about [0,1] or [0,100] or [0,360]
563
+ // Just return an object and let the conversion functions handle that.
564
+ // This way the result will be the same whether Color is initialized with string or object.
565
+ let [, m1, m2, m3, m4] = matchers.rgb.exec(color) || [];
566
+ if (m1 && m2 && m3/* && m4 */) {
567
+ return {
568
+ r: m1, g: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'rgb',
569
+ };
570
+ }
571
+ [, m1, m2, m3, m4] = matchers.hsl.exec(color) || [];
572
+ if (m1 && m2 && m3/* && m4 */) {
573
+ return {
574
+ h: m1, s: m2, l: m3, a: m4 !== undefined ? m4 : 1, format: 'hsl',
575
+ };
576
+ }
577
+ [, m1, m2, m3, m4] = matchers.hsv.exec(color) || [];
578
+ if (m1 && m2 && m3/* && m4 */) {
579
+ return {
580
+ h: m1, s: m2, v: m3, a: m4 !== undefined ? m4 : 1, format: 'hsv',
581
+ };
582
+ }
583
+ [, m1, m2, m3, m4] = matchers.hwb.exec(color) || [];
584
+ if (m1 && m2 && m3) {
585
+ return {
586
+ h: m1, w: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'hwb',
587
+ };
588
+ }
589
+ [, m1, m2, m3, m4] = matchers.hex8.exec(color) || [];
590
+ if (m1 && m2 && m3 && m4) {
591
+ return {
592
+ r: parseIntFromHex(m1),
593
+ g: parseIntFromHex(m2),
594
+ b: parseIntFromHex(m3),
595
+ a: convertHexToDecimal(m4),
596
+ format: named ? 'rgb' : 'hex',
597
+ };
598
+ }
599
+ [, m1, m2, m3] = matchers.hex6.exec(color) || [];
600
+ if (m1 && m2 && m3) {
601
+ return {
602
+ r: parseIntFromHex(m1),
603
+ g: parseIntFromHex(m2),
604
+ b: parseIntFromHex(m3),
605
+ format: named ? 'rgb' : 'hex',
606
+ };
607
+ }
608
+ [, m1, m2, m3, m4] = matchers.hex4.exec(color) || [];
609
+ if (m1 && m2 && m3 && m4) {
610
+ return {
611
+ r: parseIntFromHex(m1 + m1),
612
+ g: parseIntFromHex(m2 + m2),
613
+ b: parseIntFromHex(m3 + m3),
614
+ a: convertHexToDecimal(m4 + m4),
615
+ // format: named ? 'rgb' : 'hex8',
616
+ format: named ? 'rgb' : 'hex',
617
+ };
618
+ }
619
+ [, m1, m2, m3] = matchers.hex3.exec(color) || [];
620
+ if (m1 && m2 && m3) {
621
+ return {
622
+ r: parseIntFromHex(m1 + m1),
623
+ g: parseIntFromHex(m2 + m2),
624
+ b: parseIntFromHex(m3 + m3),
625
+ format: named ? 'rgb' : 'hex',
626
+ };
627
+ }
628
+ return false;
629
+ }
630
+
631
+ /**
632
+ * Given a string or object, convert that input to RGB
633
+ *
634
+ * Possible string inputs:
635
+ * ```
636
+ * "red"
637
+ * "#f00" or "f00"
638
+ * "#ff0000" or "ff0000"
639
+ * "#ff000000" or "ff000000" // CSS4 Module
640
+ * "rgb 255 0 0" or "rgb (255, 0, 0)"
641
+ * "rgb 1.0 0 0" or "rgb (1, 0, 0)"
642
+ * "rgba(255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
643
+ * "rgba(1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
644
+ * "rgb(255 0 0 / 10%)" or "rgb 255 0 0 0.1" // CSS4 Module
645
+ * "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
646
+ * "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
647
+ * "hsl(0deg 100% 50% / 50%)" or "hsl 0 100 50 50" // CSS4 Module
648
+ * "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
649
+ * "hsva(0, 100%, 100%, 0.1)" or "hsva 0 100% 100% 0.1"
650
+ * "hsv(0deg 100% 100% / 10%)" or "hsv 0 100 100 0.1" // CSS4 Module
651
+ * "hwb(0deg, 100%, 100%, 100%)" or "hwb 0 100% 100% 0.1" // CSS4 Module
652
+ * ```
653
+ * @param {string | Record<string, any>} input
654
+ * @returns {CP.ColorObject}
655
+ */
656
+ function inputToRGB(input) {
657
+ let rgb = { r: 0, g: 0, b: 0 };
658
+ let color = input;
659
+ /** @type {string | number} */
660
+ let a = 1;
661
+ let s = null;
662
+ let v = null;
663
+ let l = null;
664
+ let w = null;
665
+ let b = null;
666
+ let h = null;
667
+ let r = null;
668
+ let g = null;
669
+ let ok = false;
670
+ const inputFormat = typeof color === 'object' && color.format;
671
+ let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
672
+
673
+ if (typeof input === 'string') {
674
+ // @ts-ignore -- this now is converted to object
675
+ color = stringInputToObject(input);
676
+ if (color) ok = true;
677
+ }
678
+ if (typeof color === 'object') {
679
+ if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
680
+ ({ r, g, b } = color);
681
+ // RGB values now are all in [0, 255] range
682
+ [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255);
683
+ rgb = { r, g, b };
684
+ ok = true;
685
+ format = 'rgb';
686
+ } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
687
+ ({ h, s, v } = color);
688
+ h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
689
+ s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
690
+ v = typeof v === 'number' ? v : bound01(v, 100); // brightness can be `5%` or a [0, 1] value
691
+ rgb = hsvToRgb(h, s, v);
692
+ ok = true;
693
+ format = 'hsv';
694
+ } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
695
+ ({ h, s, l } = color);
696
+ h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
697
+ s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
698
+ l = typeof l === 'number' ? l : bound01(l, 100); // lightness can be `5%` or a [0, 1] value
699
+ rgb = hslToRgb(h, s, l);
700
+ ok = true;
701
+ format = 'hsl';
702
+ } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
703
+ ({ h, w, b } = color);
704
+ h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
705
+ w = typeof w === 'number' ? w : bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
706
+ b = typeof b === 'number' ? b : bound01(b, 100); // blackness can be `5%` or a [0, 1] value
707
+ rgb = hwbToRgb(h, w, b);
708
+ ok = true;
709
+ format = 'hwb';
710
+ }
711
+ if (isValidCSSUnit(color.a)) {
712
+ a = color.a; // @ts-ignore -- `parseFloat` works with numbers too
713
+ a = isPercentage(`${a}`) || parseFloat(a) > 1 ? bound01(a, 100) : a;
714
+ }
715
+ }
716
+ if (typeof color === 'undefined') {
717
+ ok = true;
718
+ }
719
+
720
+ return {
721
+ ok,
722
+ format,
723
+ r: Math.min(255, Math.max(rgb.r, 0)),
724
+ g: Math.min(255, Math.max(rgb.g, 0)),
725
+ b: Math.min(255, Math.max(rgb.b, 0)),
726
+ a: boundAlpha(a),
727
+ };
728
+ }
729
+
730
+ /**
731
+ * @class
732
+ * Returns a new `Color` instance.
733
+ * @see https://github.com/bgrins/TinyColor
734
+ */
735
+ class Color {
736
+ /**
737
+ * @constructor
738
+ * @param {CP.ColorInput} input the given colour value
739
+ * @param {CP.ColorFormats=} config the given format
740
+ */
741
+ constructor(input, config) {
742
+ let color = input;
743
+ const configFormat = config && COLOR_FORMAT.includes(config)
744
+ ? config : 'rgb';
745
+
746
+ // If input is already a `Color`, return itself
747
+ if (color instanceof Color) {
748
+ color = inputToRGB(color);
749
+ }
750
+ if (typeof color === 'number') {
751
+ const len = `${color}`.length;
752
+ color = `#${(len === 2 ? '0' : '00')}${color}`;
753
+ }
754
+ const {
755
+ r, g, b, a, ok, format,
756
+ } = inputToRGB(color);
757
+
758
+ // bind
759
+ const self = this;
760
+
761
+ /** @type {CP.ColorInput} */
762
+ self.originalInput = input;
763
+ /** @type {number} */
764
+ self.r = r;
765
+ /** @type {number} */
766
+ self.g = g;
767
+ /** @type {number} */
768
+ self.b = b;
769
+ /** @type {number} */
770
+ self.a = a;
771
+ /** @type {boolean} */
772
+ self.ok = ok;
773
+ /** @type {CP.ColorFormats} */
774
+ self.format = configFormat || format;
775
+ }
776
+
777
+ /**
778
+ * Checks if the current input value is a valid colour.
779
+ * @returns {boolean} the query result
780
+ */
781
+ get isValid() {
782
+ return this.ok;
783
+ }
784
+
785
+ /**
786
+ * Checks if the current colour requires a light text colour.
787
+ * @returns {boolean} the query result
788
+ */
789
+ get isDark() {
790
+ return this.brightness < 120;
791
+ }
792
+
793
+ /**
794
+ * Returns the perceived luminance of a colour.
795
+ * @see http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
796
+ * @returns {number} a number in the [0, 1] range
797
+ */
798
+ get luminance() {
799
+ const { r, g, b } = this;
800
+ let R = 0;
801
+ let G = 0;
802
+ let B = 0;
803
+ const rp = r / 255;
804
+ const rg = g / 255;
805
+ const rb = b / 255;
806
+
807
+ if (rp <= 0.03928) {
808
+ R = rp / 12.92;
809
+ } else {
810
+ R = ((rp + 0.055) / 1.055) ** 2.4;
811
+ }
812
+ if (rg <= 0.03928) {
813
+ G = rg / 12.92;
814
+ } else {
815
+ G = ((rg + 0.055) / 1.055) ** 2.4;
816
+ }
817
+ if (rb <= 0.03928) {
818
+ B = rb / 12.92;
819
+ } else {
820
+ B = ((rb + 0.055) / 1.055) ** 2.4;
821
+ }
822
+ return 0.2126 * R + 0.7152 * G + 0.0722 * B;
823
+ }
824
+
825
+ /**
826
+ * Returns the perceived brightness of the colour.
827
+ * @returns {number} a number in the [0, 255] range
828
+ */
829
+ get brightness() {
830
+ const { r, g, b } = this;
831
+ return (r * 299 + g * 587 + b * 114) / 1000;
832
+ }
833
+
834
+ /**
835
+ * Returns the colour as an RGBA object.
836
+ * @returns {CP.RGBA} an {r,g,b,a} object with [0, 255] ranged values
837
+ */
838
+ toRgb() {
839
+ const {
840
+ r, g, b, a,
841
+ } = this;
842
+
843
+ return {
844
+ r, g, b, a: roundPart(a * 100) / 100,
845
+ };
846
+ }
847
+
848
+ /**
849
+ * Returns the RGBA values concatenated into a CSS3 Module string format.
850
+ * * rgb(255,255,255)
851
+ * * rgba(255,255,255,0.5)
852
+ * @returns {string} the CSS valid colour in RGB/RGBA format
853
+ */
854
+ toRgbString() {
855
+ const {
856
+ r, g, b, a,
857
+ } = this.toRgb();
858
+ const [R, G, B] = [r, g, b].map(roundPart);
859
+
860
+ return a === 1
861
+ ? `rgb(${R}, ${G}, ${B})`
862
+ : `rgba(${R}, ${G}, ${B}, ${a})`;
863
+ }
864
+
865
+ /**
866
+ * Returns the RGBA values concatenated into a CSS4 Module string format.
867
+ * * rgb(255 255 255)
868
+ * * rgb(255 255 255 / 50%)
869
+ * @returns {string} the CSS valid colour in CSS4 RGB format
870
+ */
871
+ toRgbCSS4String() {
872
+ const {
873
+ r, g, b, a,
874
+ } = this.toRgb();
875
+ const [R, G, B] = [r, g, b].map(roundPart);
876
+ const A = a === 1 ? '' : ` / ${roundPart(a * 100)}%`;
877
+
878
+ return `rgb(${R} ${G} ${B}${A})`;
879
+ }
880
+
881
+ /**
882
+ * Returns the hexadecimal value of the colour. When the parameter is *true*
883
+ * it will find a 3 characters shorthand of the decimal value.
884
+ *
885
+ * @param {boolean=} allow3Char when `true` returns shorthand HEX
886
+ * @returns {string} the hexadecimal colour format
887
+ */
888
+ toHex(allow3Char) {
889
+ const {
890
+ r, g, b, a,
891
+ } = this.toRgb();
892
+
893
+ return a === 1
894
+ ? rgbToHex(r, g, b, allow3Char)
895
+ : rgbaToHex(r, g, b, a, allow3Char);
896
+ }
897
+
898
+ /**
899
+ * Returns the CSS valid hexadecimal vaue of the colour. When the parameter is *true*
900
+ * it will find a 3 characters shorthand of the value.
901
+ *
902
+ * @param {boolean=} allow3Char when `true` returns shorthand HEX
903
+ * @returns {string} the CSS valid colour in hexadecimal format
904
+ */
905
+ toHexString(allow3Char) {
906
+ return `#${this.toHex(allow3Char)}`;
907
+ }
908
+
909
+ /**
910
+ * Returns the HEX8 value of the colour.
911
+ * @param {boolean=} allow4Char when `true` returns shorthand HEX
912
+ * @returns {string} the CSS valid colour in hexadecimal format
913
+ */
914
+ toHex8(allow4Char) {
915
+ const {
916
+ r, g, b, a,
917
+ } = this.toRgb();
918
+
919
+ return rgbaToHex(r, g, b, a, allow4Char);
920
+ }
921
+
922
+ /**
923
+ * Returns the HEX8 value of the colour.
924
+ * @param {boolean=} allow4Char when `true` returns shorthand HEX
925
+ * @returns {string} the CSS valid colour in hexadecimal format
926
+ */
927
+ toHex8String(allow4Char) {
928
+ return `#${this.toHex8(allow4Char)}`;
929
+ }
930
+
931
+ /**
932
+ * Returns the colour as a HSVA object.
933
+ * @returns {CP.HSVA} the `{h,s,v,a}` object with [0, 1] ranged values
934
+ */
935
+ toHsv() {
936
+ const {
937
+ r, g, b, a,
938
+ } = this.toRgb();
939
+ const { h, s, v } = rgbToHsv(r, g, b);
940
+
941
+ return {
942
+ h, s, v, a,
943
+ };
944
+ }
945
+
946
+ /**
947
+ * Returns the colour as an HSLA object.
948
+ * @returns {CP.HSLA} the `{h,s,l,a}` object with [0, 1] ranged values
949
+ */
950
+ toHsl() {
951
+ const {
952
+ r, g, b, a,
953
+ } = this.toRgb();
954
+ const { h, s, l } = rgbToHsl(r, g, b);
955
+
956
+ return {
957
+ h, s, l, a,
958
+ };
959
+ }
960
+
961
+ /**
962
+ * Returns the HSLA values concatenated into a CSS3 Module format string.
963
+ * * `hsl(150, 100%, 50%)`
964
+ * * `hsla(150, 100%, 50%, 0.5)`
965
+ * @returns {string} the CSS valid colour in HSL/HSLA format
966
+ */
967
+ toHslString() {
968
+ let {
969
+ h, s, l, a,
970
+ } = this.toHsl();
971
+ h = roundPart(h * 360);
972
+ s = roundPart(s * 100);
973
+ l = roundPart(l * 100);
974
+ a = roundPart(a * 100) / 100;
975
+
976
+ return a === 1
977
+ ? `hsl(${h}, ${s}%, ${l}%)`
978
+ : `hsla(${h}, ${s}%, ${l}%, ${a})`;
979
+ }
980
+
981
+ /**
982
+ * Returns the HSLA values concatenated into a CSS4 Module format string.
983
+ * * `hsl(150deg 100% 50%)`
984
+ * * `hsl(150deg 100% 50% / 50%)`
985
+ * @returns {string} the CSS valid colour in CSS4 HSL format
986
+ */
987
+ toHslCSS4String() {
988
+ let {
989
+ h, s, l, a,
990
+ } = this.toHsl();
991
+ h = roundPart(h * 360);
992
+ s = roundPart(s * 100);
993
+ l = roundPart(l * 100);
994
+ a = roundPart(a * 100);
995
+ const A = a < 100 ? ` / ${roundPart(a)}%` : '';
996
+
997
+ return `hsl(${h}deg ${s}% ${l}%${A})`;
998
+ }
999
+
1000
+ /**
1001
+ * Returns the colour as an HWBA object.
1002
+ * @returns {CP.HWBA} the `{h,w,b,a}` object with [0, 1] ranged values
1003
+ */
1004
+ toHwb() {
1005
+ const {
1006
+ r, g, b, a,
1007
+ } = this;
1008
+ const { h, w, b: bl } = rgbToHwb(r, g, b);
1009
+ return {
1010
+ h, w, b: bl, a,
1011
+ };
1012
+ }
1013
+
1014
+ /**
1015
+ * Returns the HWBA values concatenated into a string.
1016
+ * @returns {string} the CSS valid colour in HWB format
1017
+ */
1018
+ toHwbString() {
1019
+ let {
1020
+ h, w, b, a,
1021
+ } = this.toHwb();
1022
+ h = roundPart(h * 360);
1023
+ w = roundPart(w * 100);
1024
+ b = roundPart(b * 100);
1025
+ a = roundPart(a * 100);
1026
+ const A = a < 100 ? ` / ${roundPart(a)}%` : '';
1027
+
1028
+ return `hwb(${h}deg ${w}% ${b}%${A})`;
1029
+ }
1030
+
1031
+ /**
1032
+ * Sets the alpha value of the current colour.
1033
+ * @param {number} alpha a new alpha value in the [0, 1] range.
1034
+ * @returns {Color} the `Color` instance
1035
+ */
1036
+ setAlpha(alpha) {
1037
+ const self = this;
1038
+ self.a = boundAlpha(alpha);
1039
+ return self;
1040
+ }
1041
+
1042
+ /**
1043
+ * Saturate the colour with a given amount.
1044
+ * @param {number=} amount a value in the [0, 100] range
1045
+ * @returns {Color} the `Color` instance
1046
+ */
1047
+ saturate(amount) {
1048
+ const self = this;
1049
+ if (typeof amount !== 'number') return self;
1050
+ const { h, s, l } = self.toHsl();
1051
+ const { r, g, b } = hslToRgb(h, clamp01(s + amount / 100), l);
1052
+
1053
+ ObjectAssign(self, { r, g, b });
1054
+ return self;
1055
+ }
1056
+
1057
+ /**
1058
+ * Desaturate the colour with a given amount.
1059
+ * @param {number=} amount a value in the [0, 100] range
1060
+ * @returns {Color} the `Color` instance
1061
+ */
1062
+ desaturate(amount) {
1063
+ return typeof amount === 'number' ? this.saturate(-amount) : this;
1064
+ }
1065
+
1066
+ /**
1067
+ * Completely desaturates a colour into greyscale.
1068
+ * Same as calling `desaturate(100)`
1069
+ * @returns {Color} the `Color` instance
1070
+ */
1071
+ greyscale() {
1072
+ return this.saturate(-100);
1073
+ }
1074
+
1075
+ /**
1076
+ * Increase the colour lightness with a given amount.
1077
+ * @param {number=} amount a value in the [0, 100] range
1078
+ * @returns {Color} the `Color` instance
1079
+ */
1080
+ lighten(amount) {
1081
+ const self = this;
1082
+ if (typeof amount !== 'number') return self;
1083
+
1084
+ const { h, s, l } = self.toHsl();
1085
+ const { r, g, b } = hslToRgb(h, s, clamp01(l + amount / 100));
1086
+
1087
+ ObjectAssign(self, { r, g, b });
1088
+ return self;
1089
+ }
1090
+
1091
+ /**
1092
+ * Decrease the colour lightness with a given amount.
1093
+ * @param {number=} amount a value in the [0, 100] range
1094
+ * @returns {Color} the `Color` instance
1095
+ */
1096
+ darken(amount) {
1097
+ return typeof amount === 'number' ? this.lighten(-amount) : this;
1098
+ }
1099
+
1100
+ /**
1101
+ * Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
1102
+ * Values outside of this range will be wrapped into this range.
1103
+ *
1104
+ * @param {number=} amount a value in the [0, 100] range
1105
+ * @returns {Color} the `Color` instance
1106
+ */
1107
+ spin(amount) {
1108
+ const self = this;
1109
+ if (typeof amount !== 'number') return self;
1110
+
1111
+ const { h, s, l } = self.toHsl();
1112
+ const { r, g, b } = hslToRgb(clamp01(((h * 360 + amount) % 360) / 360), s, l);
1113
+
1114
+ ObjectAssign(self, { r, g, b });
1115
+ return self;
1116
+ }
1117
+
1118
+ /** Returns a clone of the current `Color` instance. */
1119
+ clone() {
1120
+ return new Color(this);
1121
+ }
1122
+
1123
+ /**
1124
+ * Returns the colour value in CSS valid string format.
1125
+ * @param {boolean=} allowShort when *true*, HEX values can be shorthand
1126
+ * @returns {string} the CSS valid colour in the configured format
1127
+ */
1128
+ toString(allowShort) {
1129
+ const self = this;
1130
+ const { format } = self;
1131
+
1132
+ if (format === 'hex') return self.toHexString(allowShort);
1133
+ if (format === 'hsl') return self.toHslString();
1134
+ if (format === 'hwb') return self.toHwbString();
1135
+
1136
+ return self.toRgbString();
1137
+ }
1138
+ }
1139
+
1140
+ ObjectAssign(Color, {
1141
+ ANGLES,
1142
+ CSS_ANGLE,
1143
+ CSS_INTEGER,
1144
+ CSS_NUMBER,
1145
+ CSS_UNIT,
1146
+ CSS_UNIT2,
1147
+ PERMISSIVE_MATCH,
1148
+ matchers,
1149
+ isOnePointZero,
1150
+ isPercentage,
1151
+ isValidCSSUnit,
1152
+ isColorName,
1153
+ pad2,
1154
+ clamp01,
1155
+ bound01,
1156
+ boundAlpha,
1157
+ getRGBFromName,
1158
+ convertHexToDecimal,
1159
+ convertDecimalToHex,
1160
+ rgbToHsl,
1161
+ rgbToHex,
1162
+ rgbToHsv,
1163
+ rgbToHwb,
1164
+ rgbaToHex,
1165
+ hslToRgb,
1166
+ hsvToRgb,
1167
+ hueToRgb,
1168
+ hwbToRgb,
1169
+ parseIntFromHex,
1170
+ stringInputToObject,
1171
+ inputToRGB,
1172
+ roundPart,
1173
+ getElementStyle,
1174
+ setElementStyle,
1175
+ ObjectAssign,
1176
+ });
1177
+
1178
+ /**
1179
+ * @class
1180
+ * Returns a color palette with a given set of parameters.
1181
+ * @example
1182
+ * new ColorPalette(0, 12, 10);
1183
+ * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: Array<Color> }
1184
+ */
1185
+ class ColorPalette {
1186
+ /**
1187
+ * The `hue` parameter is optional, which would be set to 0.
1188
+ * @param {number[]} args represeinting hue, hueSteps, lightSteps
1189
+ * * `args.hue` the starting Hue [0, 360]
1190
+ * * `args.hueSteps` Hue Steps Count [5, 24]
1191
+ * * `args.lightSteps` Lightness Steps Count [5, 12]
1192
+ */
1193
+ constructor(...args) {
1194
+ let hue = 0;
1195
+ let hueSteps = 12;
1196
+ let lightSteps = 10;
1197
+ let lightnessArray = [0.5];
1198
+
1199
+ if (args.length === 3) {
1200
+ [hue, hueSteps, lightSteps] = args;
1201
+ } else if (args.length === 2) {
1202
+ [hueSteps, lightSteps] = args;
1203
+ if ([hueSteps, lightSteps].some((n) => n < 1)) {
1204
+ throw TypeError('ColorPalette: when 2 arguments used, both must be larger than 0.');
1205
+ }
1206
+ } else {
1207
+ throw TypeError('ColorPalette requires minimum 2 arguments');
1208
+ }
1209
+
1210
+ /** @type {Color[]} */
1211
+ const colors = [];
1212
+
1213
+ const hueStep = 360 / hueSteps;
1214
+ const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
1215
+ const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
1216
+
1217
+ let lightStep = 0.25;
1218
+ lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
1219
+ lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
1220
+ lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
1221
+ lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
1222
+ lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
1223
+ lightStep = lightSteps > 13 ? estimatedStep : lightStep;
1224
+
1225
+ // light tints
1226
+ for (let i = 1; i < half + 1; i += 1) {
1227
+ lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
1228
+ }
1229
+
1230
+ // dark tints
1231
+ for (let i = 1; i < lightSteps - half; i += 1) {
1232
+ lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
1233
+ }
1234
+
1235
+ // feed `colors` Array
1236
+ for (let i = 0; i < hueSteps; i += 1) {
1237
+ const currentHue = ((hue + i * hueStep) % 360) / 360;
1238
+ lightnessArray.forEach((l) => {
1239
+ colors.push(new Color({ h: currentHue, s: 1, l }));
1240
+ });
1241
+ }
1242
+
1243
+ this.hue = hue;
1244
+ this.hueSteps = hueSteps;
1245
+ this.lightSteps = lightSteps;
1246
+ this.colors = colors;
1247
+ }
1248
+ }
1249
+
1250
+ ObjectAssign(ColorPalette, { Color });
1251
+
1252
+ export { ColorPalette as default };