@thednp/color-picker 0.0.1-alpha2 → 0.0.2-alpha1

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