@thednp/color-picker 0.0.1-alpha1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,860 @@
1
+ import getDocumentHead from 'shorter-js/src/get/getDocumentHead';
2
+ import getElementStyle from 'shorter-js/src/get/getElementStyle';
3
+ import setElementStyle from 'shorter-js/src/misc/setElementStyle';
4
+ import ObjectAssign from 'shorter-js/src/misc/ObjectAssign';
5
+
6
+ import colorNames from './util/colorNames';
7
+
8
+ // <http://www.w3.org/TR/css3-values/#integers>
9
+ const CSS_INTEGER = '[-\\+]?\\d+%?';
10
+
11
+ // <http://www.w3.org/TR/css3-values/#number-value>
12
+ const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
13
+
14
+ // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
15
+ const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
16
+
17
+ // Actual matching.
18
+ // Parentheses and commas are optional, but not required.
19
+ // Whitespace can take the place of commas or opening paren
20
+ const PERMISSIVE_MATCH3 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?`;
21
+ const PERMISSIVE_MATCH4 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?`;
22
+
23
+ const matchers = {
24
+ CSS_UNIT: new RegExp(CSS_UNIT),
25
+ rgb: new RegExp(`rgb${PERMISSIVE_MATCH3}`),
26
+ rgba: new RegExp(`rgba${PERMISSIVE_MATCH4}`),
27
+ hsl: new RegExp(`hsl${PERMISSIVE_MATCH3}`),
28
+ hsla: new RegExp(`hsla${PERMISSIVE_MATCH4}`),
29
+ hsv: new RegExp(`hsv${PERMISSIVE_MATCH3}`),
30
+ hsva: new RegExp(`hsva${PERMISSIVE_MATCH4}`),
31
+ hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
32
+ hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
33
+ hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
34
+ hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
35
+ };
36
+
37
+ /**
38
+ * Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
39
+ * <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
40
+ * @param {string} n
41
+ * @returns {boolean}
42
+ */
43
+ function isOnePointZero(n) {
44
+ return typeof n === 'string' && n.includes('.') && parseFloat(n) === 1;
45
+ }
46
+
47
+ /**
48
+ * Check to see if string passed in is a percentage
49
+ * @param {string} n
50
+ * @returns {boolean}
51
+ */
52
+ function isPercentage(n) {
53
+ return typeof n === 'string' && n.includes('%');
54
+ }
55
+
56
+ /**
57
+ * Check to see if it looks like a CSS unit
58
+ * (see `matchers` above for definition).
59
+ * @param {string | number} color
60
+ * @returns {boolean}
61
+ */
62
+ function isValidCSSUnit(color) {
63
+ return Boolean(matchers.CSS_UNIT.exec(String(color)));
64
+ }
65
+
66
+ /**
67
+ * Take input from [0, n] and return it as [0, 1]
68
+ * @param {*} n
69
+ * @param {number} max
70
+ * @returns {number}
71
+ */
72
+ function bound01(n, max) {
73
+ let N = n;
74
+ if (isOnePointZero(n)) N = '100%';
75
+
76
+ N = max === 360 ? N : Math.min(max, Math.max(0, parseFloat(N)));
77
+
78
+ // Automatically convert percentage into number
79
+ if (isPercentage(N)) {
80
+ N = parseInt(String(N * max), 10) / 100;
81
+ }
82
+ // Handle floating point rounding errors
83
+ if (Math.abs(N - max) < 0.000001) {
84
+ return 1;
85
+ }
86
+ // Convert into [0, 1] range if it isn't already
87
+ if (max === 360) {
88
+ // If n is a hue given in degrees,
89
+ // wrap around out-of-range values into [0, 360] range
90
+ // then convert into [0, 1].
91
+ N = (N < 0 ? (N % max) + max : N % max) / parseFloat(String(max));
92
+ } else {
93
+ // If n not a hue given in degrees
94
+ // Convert into [0, 1] range if it isn't already.
95
+ N = (N % max) / parseFloat(String(max));
96
+ }
97
+ return N;
98
+ }
99
+
100
+ /**
101
+ * Return a valid alpha value [0,1] with all invalid values being set to 1.
102
+ * @param {string | number} a
103
+ * @returns {number}
104
+ */
105
+ function boundAlpha(a) {
106
+ // @ts-ignore
107
+ let na = parseFloat(a);
108
+
109
+ if (Number.isNaN(na) || na < 0 || na > 1) {
110
+ na = 1;
111
+ }
112
+
113
+ return na;
114
+ }
115
+
116
+ /**
117
+ * Force a number between 0 and 1
118
+ * @param {number} val
119
+ * @returns {number}
120
+ */
121
+ function clamp01(val) {
122
+ return Math.min(1, Math.max(0, val));
123
+ }
124
+
125
+ /**
126
+ * Returns the hexadecimal value of a web safe colour.
127
+ * @param {string} name
128
+ * @returns {string}
129
+ */
130
+ function getHexFromColorName(name) {
131
+ const documentHead = getDocumentHead();
132
+ setElementStyle(documentHead, { color: name });
133
+ const colorName = getElementStyle(documentHead, 'color');
134
+ setElementStyle(documentHead, { color: '' });
135
+ return colorName;
136
+ }
137
+
138
+ /**
139
+ * Replace a decimal with it's percentage value
140
+ * @param {number | string} n
141
+ * @return {string | number}
142
+ */
143
+ function convertToPercentage(n) {
144
+ if (n <= 1) {
145
+ return `${Number(n) * 100}%`;
146
+ }
147
+ return n;
148
+ }
149
+
150
+ /**
151
+ * Force a hex value to have 2 characters
152
+ * @param {string} c
153
+ * @returns {string}
154
+ */
155
+ function pad2(c) {
156
+ return c.length === 1 ? `0${c}` : String(c);
157
+ }
158
+
159
+ // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
160
+ // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
161
+ /**
162
+ * Handle bounds / percentage checking to conform to CSS color spec
163
+ * * *Assumes:* r, g, b in [0, 255] or [0, 1]
164
+ * * *Returns:* { r, g, b } in [0, 255]
165
+ * @see http://www.w3.org/TR/css3-color/
166
+ * @param {number | string} r
167
+ * @param {number | string} g
168
+ * @param {number | string} b
169
+ * @returns {CP.RGB}
170
+ */
171
+ function rgbToRgb(r, g, b) {
172
+ return {
173
+ r: bound01(r, 255) * 255,
174
+ g: bound01(g, 255) * 255,
175
+ b: bound01(b, 255) * 255,
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Converts an RGB color value to HSL.
181
+ * *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
182
+ * *Returns:* { h, s, l } in [0,1]
183
+ * @param {number} R
184
+ * @param {number} G
185
+ * @param {number} B
186
+ * @returns {CP.HSL}
187
+ */
188
+ function rgbToHsl(R, G, B) {
189
+ const r = bound01(R, 255);
190
+ const g = bound01(G, 255);
191
+ const b = bound01(B, 255);
192
+ const max = Math.max(r, g, b);
193
+ const min = Math.min(r, g, b);
194
+ let h = 0;
195
+ let s = 0;
196
+ const l = (max + min) / 2;
197
+ if (max === min) {
198
+ s = 0;
199
+ h = 0; // achromatic
200
+ } else {
201
+ const d = max - min;
202
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
203
+ switch (max) {
204
+ case r:
205
+ h = (g - b) / d + (g < b ? 6 : 0);
206
+ break;
207
+ case g:
208
+ h = (b - r) / d + 2;
209
+ break;
210
+ case b:
211
+ h = (r - g) / d + 4;
212
+ break;
213
+ default:
214
+ }
215
+ h /= 6;
216
+ }
217
+ return { h, s, l };
218
+ }
219
+
220
+ /**
221
+ * Returns a normalized RGB component value.
222
+ * @param {number} P
223
+ * @param {number} Q
224
+ * @param {number} T
225
+ * @returns {number}
226
+ */
227
+ function hue2rgb(P, Q, T) {
228
+ const p = P;
229
+ const q = Q;
230
+ let t = T;
231
+ if (t < 0) {
232
+ t += 1;
233
+ }
234
+ if (t > 1) {
235
+ t -= 1;
236
+ }
237
+ if (t < 1 / 6) {
238
+ return p + (q - p) * (6 * t);
239
+ }
240
+ if (t < 1 / 2) {
241
+ return q;
242
+ }
243
+ if (t < 2 / 3) {
244
+ return p + (q - p) * (2 / 3 - t) * 6;
245
+ }
246
+ return p;
247
+ }
248
+
249
+ /**
250
+ * Converts an HSL colour value to RGB.
251
+ *
252
+ * * *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
253
+ * * *Returns:* { r, g, b } in the set [0, 255]
254
+ * @param {number | string} H
255
+ * @param {number | string} S
256
+ * @param {number | string} L
257
+ * @returns {CP.RGB}
258
+ */
259
+ function hslToRgb(H, S, L) {
260
+ let r = 0;
261
+ let g = 0;
262
+ let b = 0;
263
+ const h = bound01(H, 360);
264
+ const s = bound01(S, 100);
265
+ const l = bound01(L, 100);
266
+
267
+ if (s === 0) {
268
+ // achromatic
269
+ g = l;
270
+ b = l;
271
+ r = l;
272
+ } else {
273
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
274
+ const p = 2 * l - q;
275
+ r = hue2rgb(p, q, h + 1 / 3);
276
+ g = hue2rgb(p, q, h);
277
+ b = hue2rgb(p, q, h - 1 / 3);
278
+ }
279
+ return { r: r * 255, g: g * 255, b: b * 255 };
280
+ }
281
+
282
+ /**
283
+ * Converts an RGB colour value to HSV.
284
+ *
285
+ * *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
286
+ * *Returns:* { h, s, v } in [0,1]
287
+ * @param {number | string} R
288
+ * @param {number | string} G
289
+ * @param {number | string} B
290
+ * @returns {CP.HSV}
291
+ */
292
+ function rgbToHsv(R, G, B) {
293
+ const r = bound01(R, 255);
294
+ const g = bound01(G, 255);
295
+ const b = bound01(B, 255);
296
+ const max = Math.max(r, g, b);
297
+ const min = Math.min(r, g, b);
298
+ let h = 0;
299
+ const v = max;
300
+ const d = max - min;
301
+ const s = max === 0 ? 0 : d / max;
302
+ if (max === min) {
303
+ h = 0; // achromatic
304
+ } else {
305
+ switch (max) {
306
+ case r:
307
+ h = (g - b) / d + (g < b ? 6 : 0);
308
+ break;
309
+ case g:
310
+ h = (b - r) / d + 2;
311
+ break;
312
+ case b:
313
+ h = (r - g) / d + 4;
314
+ break;
315
+ default:
316
+ }
317
+ h /= 6;
318
+ }
319
+ return { h, s, v };
320
+ }
321
+
322
+ /**
323
+ * Converts an HSV color value to RGB.
324
+ *
325
+ * *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
326
+ * *Returns:* { r, g, b } in the set [0, 255]
327
+ * @param {number | string} H
328
+ * @param {number | string} S
329
+ * @param {number | string} V
330
+ * @returns {CP.RGB}
331
+ */
332
+ function hsvToRgb(H, S, V) {
333
+ const h = bound01(H, 360) * 6;
334
+ const s = bound01(S, 100);
335
+ const v = bound01(V, 100);
336
+ const i = Math.floor(h);
337
+ const f = h - i;
338
+ const p = v * (1 - s);
339
+ const q = v * (1 - f * s);
340
+ const t = v * (1 - (1 - f) * s);
341
+ const mod = i % 6;
342
+ const r = [v, q, p, p, t, v][mod];
343
+ const g = [t, v, v, q, p, p][mod];
344
+ const b = [p, p, t, v, v, q][mod];
345
+ return { r: r * 255, g: g * 255, b: b * 255 };
346
+ }
347
+
348
+ /**
349
+ * Converts an RGB color to hex
350
+ *
351
+ * Assumes r, g, and b are contained in the set [0, 255]
352
+ * Returns a 3 or 6 character hex
353
+ * @param {number} r
354
+ * @param {number} g
355
+ * @param {number} b
356
+ * @returns {string}
357
+ */
358
+ function rgbToHex(r, g, b) {
359
+ const hex = [
360
+ pad2(Math.round(r).toString(16)),
361
+ pad2(Math.round(g).toString(16)),
362
+ pad2(Math.round(b).toString(16)),
363
+ ];
364
+
365
+ return hex.join('');
366
+ }
367
+
368
+ /**
369
+ * Converts a hex value to a decimal.
370
+ * @param {string} h
371
+ * @returns {number}
372
+ */
373
+ function convertHexToDecimal(h) {
374
+ return parseIntFromHex(h) / 255;
375
+ }
376
+
377
+ /**
378
+ * Parse a base-16 hex value into a base-10 integer.
379
+ * @param {string} val
380
+ * @returns {number}
381
+ */
382
+ function parseIntFromHex(val) {
383
+ return parseInt(val, 16);
384
+ }
385
+
386
+ /**
387
+ * Returns an `{r,g,b}` color object corresponding to a given number.
388
+ * @param {number} color
389
+ * @returns {CP.RGB}
390
+ */
391
+ function numberInputToObject(color) {
392
+ /* eslint-disable no-bitwise */
393
+ return {
394
+ r: color >> 16,
395
+ g: (color & 0xff00) >> 8,
396
+ b: color & 0xff,
397
+ };
398
+ /* eslint-enable no-bitwise */
399
+ }
400
+
401
+ /**
402
+ * Permissive string parsing. Take in a number of formats, and output an object
403
+ * based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
404
+ * @param {string} input
405
+ * @returns {Record<string, (number | string)> | false}
406
+ */
407
+ function stringInputToObject(input) {
408
+ let color = input.trim().toLowerCase();
409
+ if (color.length === 0) {
410
+ return {
411
+ r: 0, g: 0, b: 0, a: 0,
412
+ };
413
+ }
414
+ let named = false;
415
+ if (colorNames.includes(color)) {
416
+ color = getHexFromColorName(color);
417
+ named = true;
418
+ } else if (color === 'transparent') {
419
+ return {
420
+ r: 0, g: 0, b: 0, a: 0, format: 'name',
421
+ };
422
+ }
423
+
424
+ // Try to match string input using regular expressions.
425
+ // Keep most of the number bounding out of this function,
426
+ // don't worry about [0,1] or [0,100] or [0,360]
427
+ // Just return an object and let the conversion functions handle that.
428
+ // This way the result will be the same whether Color is initialized with string or object.
429
+ let match = matchers.rgb.exec(color);
430
+ if (match) {
431
+ return { r: match[1], g: match[2], b: match[3] };
432
+ }
433
+ match = matchers.rgba.exec(color);
434
+ if (match) {
435
+ return {
436
+ r: match[1], g: match[2], b: match[3], a: match[4],
437
+ };
438
+ }
439
+ match = matchers.hsl.exec(color);
440
+ if (match) {
441
+ return { h: match[1], s: match[2], l: match[3] };
442
+ }
443
+ match = matchers.hsla.exec(color);
444
+ if (match) {
445
+ return {
446
+ h: match[1], s: match[2], l: match[3], a: match[4],
447
+ };
448
+ }
449
+ match = matchers.hsv.exec(color);
450
+ if (match) {
451
+ return { h: match[1], s: match[2], v: match[3] };
452
+ }
453
+ match = matchers.hsva.exec(color);
454
+ if (match) {
455
+ return {
456
+ h: match[1], s: match[2], v: match[3], a: match[4],
457
+ };
458
+ }
459
+ match = matchers.hex8.exec(color);
460
+ if (match) {
461
+ return {
462
+ r: parseIntFromHex(match[1]),
463
+ g: parseIntFromHex(match[2]),
464
+ b: parseIntFromHex(match[3]),
465
+ a: convertHexToDecimal(match[4]),
466
+ format: named ? 'name' : 'hex8',
467
+ };
468
+ }
469
+ match = matchers.hex6.exec(color);
470
+ if (match) {
471
+ return {
472
+ r: parseIntFromHex(match[1]),
473
+ g: parseIntFromHex(match[2]),
474
+ b: parseIntFromHex(match[3]),
475
+ format: named ? 'name' : 'hex',
476
+ };
477
+ }
478
+ match = matchers.hex4.exec(color);
479
+ if (match) {
480
+ return {
481
+ r: parseIntFromHex(match[1] + match[1]),
482
+ g: parseIntFromHex(match[2] + match[2]),
483
+ b: parseIntFromHex(match[3] + match[3]),
484
+ a: convertHexToDecimal(match[4] + match[4]),
485
+ format: named ? 'name' : 'hex8',
486
+ };
487
+ }
488
+ match = matchers.hex3.exec(color);
489
+ if (match) {
490
+ return {
491
+ r: parseIntFromHex(match[1] + match[1]),
492
+ g: parseIntFromHex(match[2] + match[2]),
493
+ b: parseIntFromHex(match[3] + match[3]),
494
+ format: named ? 'name' : 'hex',
495
+ };
496
+ }
497
+ return false;
498
+ }
499
+
500
+ /**
501
+ * Given a string or object, convert that input to RGB
502
+ *
503
+ * Possible string inputs:
504
+ * ```
505
+ * "red"
506
+ * "#f00" or "f00"
507
+ * "#ff0000" or "ff0000"
508
+ * "#ff000000" or "ff000000"
509
+ * "rgb 255 0 0" or "rgb (255, 0, 0)"
510
+ * "rgb 1.0 0 0" or "rgb (1, 0, 0)"
511
+ * "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
512
+ * "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
513
+ * "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
514
+ * "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
515
+ * "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
516
+ * ```
517
+ * @param {string | Record<string, any>} input
518
+ * @returns {CP.ColorObject}
519
+ */
520
+ function inputToRGB(input) {
521
+ /** @type {CP.RGB} */
522
+ let rgb = { r: 0, g: 0, b: 0 };
523
+ let color = input;
524
+ let a;
525
+ let s = null;
526
+ let v = null;
527
+ let l = null;
528
+ let ok = false;
529
+ let format = 'hex';
530
+
531
+ if (typeof input === 'string') {
532
+ // @ts-ignore -- this now is converted to object
533
+ color = stringInputToObject(input);
534
+ if (color) ok = true;
535
+ }
536
+ if (typeof color === 'object') {
537
+ if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
538
+ rgb = rgbToRgb(color.r, color.g, color.b);
539
+ ok = true;
540
+ format = `${color.r}`.slice(-1) === '%' ? 'prgb' : 'rgb';
541
+ } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
542
+ s = convertToPercentage(color.s);
543
+ v = convertToPercentage(color.v);
544
+ rgb = hsvToRgb(color.h, s, v);
545
+ ok = true;
546
+ format = 'hsv';
547
+ } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
548
+ s = convertToPercentage(color.s);
549
+ l = convertToPercentage(color.l);
550
+ rgb = hslToRgb(color.h, s, l);
551
+ ok = true;
552
+ format = 'hsl';
553
+ }
554
+ if ('a' in color) a = color.a;
555
+ }
556
+
557
+ return {
558
+ ok, // @ts-ignore
559
+ format: color.format || format,
560
+ r: Math.min(255, Math.max(rgb.r, 0)),
561
+ g: Math.min(255, Math.max(rgb.g, 0)),
562
+ b: Math.min(255, Math.max(rgb.b, 0)),
563
+ a: boundAlpha(a),
564
+ };
565
+ }
566
+
567
+ /** @type {CP.ColorOptions} */
568
+ const colorPickerDefaults = {
569
+ format: 'hex',
570
+ };
571
+
572
+ /**
573
+ * Returns a new `Color` instance.
574
+ * @see https://github.com/bgrins/TinyColor
575
+ * @class
576
+ */
577
+ export default class Color {
578
+ /**
579
+ * @constructor
580
+ * @param {CP.ColorInput} input
581
+ * @param {CP.ColorOptions=} config
582
+ */
583
+ constructor(input, config) {
584
+ let color = input;
585
+ const opts = typeof config === 'object'
586
+ ? ObjectAssign(colorPickerDefaults, config)
587
+ : ObjectAssign({}, colorPickerDefaults);
588
+
589
+ // If input is already a `Color`, return itself
590
+ if (color instanceof Color) {
591
+ color = inputToRGB(color);
592
+ }
593
+ if (typeof color === 'number') {
594
+ color = numberInputToObject(color);
595
+ }
596
+ const {
597
+ r, g, b, a, ok, format,
598
+ } = inputToRGB(color);
599
+
600
+ /** @type {CP.ColorInput} */
601
+ this.originalInput = color;
602
+ /** @type {number} */
603
+ this.r = r;
604
+ /** @type {number} */
605
+ this.g = g;
606
+ /** @type {number} */
607
+ this.b = b;
608
+ /** @type {number} */
609
+ this.a = a;
610
+ /** @type {boolean} */
611
+ this.ok = ok;
612
+ /** @type {number} */
613
+ this.roundA = Math.round(100 * this.a) / 100;
614
+ /** @type {CP.ColorFormats} */
615
+ this.format = opts.format || format;
616
+
617
+ // Don't let the range of [0,255] come back in [0,1].
618
+ // Potentially lose a little bit of precision here, but will fix issues where
619
+ // .5 gets interpreted as half of the total, instead of half of 1
620
+ // If it was supposed to be 128, this was already taken care of by `inputToRgb`
621
+ if (this.r < 1) {
622
+ this.r = Math.round(this.r);
623
+ }
624
+ if (this.g < 1) {
625
+ this.g = Math.round(this.g);
626
+ }
627
+ if (this.b < 1) {
628
+ this.b = Math.round(this.b);
629
+ }
630
+ }
631
+
632
+ /**
633
+ * Checks if the current input value is a valid colour.
634
+ * @returns {boolean} the query result
635
+ */
636
+ get isValid() {
637
+ return this.ok;
638
+ }
639
+
640
+ /**
641
+ * Checks if the current colour requires a light text colour.
642
+ * @returns {boolean} the query result
643
+ */
644
+ get isDark() {
645
+ return this.brightness < 128;
646
+ }
647
+
648
+ /**
649
+ * Returns the perceived luminance of a color.
650
+ * @see http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
651
+ * @returns {number} a number in [0-1] range
652
+ */
653
+ get luminance() {
654
+ const { r, g, b } = this;
655
+ let R = 0;
656
+ let G = 0;
657
+ let B = 0;
658
+ const RsRGB = r / 255;
659
+ const GsRGB = g / 255;
660
+ const BsRGB = b / 255;
661
+
662
+ if (RsRGB <= 0.03928) {
663
+ R = RsRGB / 12.92;
664
+ } else {
665
+ R = ((RsRGB + 0.055) / 1.055) ** 2.4;
666
+ }
667
+ if (GsRGB <= 0.03928) {
668
+ G = GsRGB / 12.92;
669
+ } else {
670
+ G = ((GsRGB + 0.055) / 1.055) ** 2.4;
671
+ }
672
+ if (BsRGB <= 0.03928) {
673
+ B = BsRGB / 12.92;
674
+ } else {
675
+ B = ((BsRGB + 0.055) / 1.055) ** 2.4;
676
+ }
677
+ return 0.2126 * R + 0.7152 * G + 0.0722 * B;
678
+ }
679
+
680
+ /**
681
+ * Returns the perceived brightness of the color.
682
+ * @returns {number} a number in [0-255] range
683
+ */
684
+ get brightness() {
685
+ const { r, g, b } = this;
686
+ return (r * 299 + g * 587 + b * 114) / 1000;
687
+ }
688
+
689
+ /**
690
+ * Returns the color as a RGBA object.
691
+ * @returns {CP.RGBA}
692
+ */
693
+ toRgb() {
694
+ return {
695
+ r: Math.round(this.r),
696
+ g: Math.round(this.g),
697
+ b: Math.round(this.b),
698
+ a: this.a,
699
+ };
700
+ }
701
+
702
+ /**
703
+ * Returns the RGBA values concatenated into a string.
704
+ * @returns {string} the CSS valid color in RGB/RGBA format
705
+ */
706
+ toRgbString() {
707
+ const r = Math.round(this.r);
708
+ const g = Math.round(this.g);
709
+ const b = Math.round(this.b);
710
+ return this.a === 1
711
+ ? `rgb(${r},${g},${b})`
712
+ : `rgba(${r},${g},${b},${this.roundA})`;
713
+ }
714
+
715
+ /**
716
+ * Returns the HEX value of the color.
717
+ * @returns {string} the hexadecimal color format
718
+ */
719
+ toHex() {
720
+ return rgbToHex(this.r, this.g, this.b);
721
+ }
722
+
723
+ /**
724
+ * Returns the HEX value of the color.
725
+ * @returns {string} the CSS valid color in hexadecimal format
726
+ */
727
+ toHexString() {
728
+ return `#${this.toHex()}`;
729
+ }
730
+
731
+ /**
732
+ * Returns the color as a HSVA object.
733
+ * @returns {CP.HSVA} the `{h,s,v,a}` object
734
+ */
735
+ toHsv() {
736
+ const { h, s, v } = rgbToHsv(this.r, this.g, this.b);
737
+ return {
738
+ h: h * 360, s, v, a: this.a,
739
+ };
740
+ }
741
+
742
+ /**
743
+ * Returns the color as a HSLA object.
744
+ * @returns {CP.HSLA}
745
+ */
746
+ toHsl() {
747
+ const { h, s, l } = rgbToHsl(this.r, this.g, this.b);
748
+ return {
749
+ h: h * 360, s, l, a: this.a,
750
+ };
751
+ }
752
+
753
+ /**
754
+ * Returns the HSLA values concatenated into a string.
755
+ * @returns {string} the CSS valid color in HSL/HSLA format
756
+ */
757
+ toHslString() {
758
+ const hsl = this.toHsl();
759
+ const h = Math.round(hsl.h);
760
+ const s = Math.round(hsl.s * 100);
761
+ const l = Math.round(hsl.l * 100);
762
+ return this.a === 1
763
+ ? `hsl(${h},${s}%,${l}%)`
764
+ : `hsla(${h},${s}%,${l}%,${this.roundA})`;
765
+ }
766
+
767
+ /**
768
+ * Sets the alpha value on the current color.
769
+ * @param {number} alpha a new alpha value in [0-1] range.
770
+ * @returns {Color} a new `Color` instance
771
+ */
772
+ setAlpha(alpha) {
773
+ this.a = boundAlpha(alpha);
774
+ this.roundA = Math.round(100 * this.a) / 100;
775
+ return this;
776
+ }
777
+
778
+ /**
779
+ * Saturate the color with a given amount.
780
+ * @param {number=} amount a value in [0-100] range
781
+ * @returns {Color} a new `Color` instance
782
+ */
783
+ saturate(amount) {
784
+ if (!amount) return this;
785
+ const hsl = this.toHsl();
786
+ hsl.s += amount / 100;
787
+ hsl.s = clamp01(hsl.s);
788
+ return new Color(hsl);
789
+ }
790
+
791
+ /**
792
+ * Desaturate the color with a given amount.
793
+ * @param {number=} amount a value in [0-100] range
794
+ * @returns {Color} a new `Color` instance
795
+ */
796
+ desaturate(amount) {
797
+ return amount ? this.saturate(-amount) : this;
798
+ }
799
+
800
+ /**
801
+ * Completely desaturates a color into greyscale.
802
+ * Same as calling `desaturate(100)`
803
+ * @returns {Color} a new `Color` instance
804
+ */
805
+ greyscale() {
806
+ return this.desaturate(100);
807
+ }
808
+
809
+ /** Returns a clone of the current `Color` instance. */
810
+ clone() {
811
+ return new Color(this);
812
+ }
813
+
814
+ /**
815
+ * Returns the color value in CSS valid string format.
816
+ * @returns {string}
817
+ */
818
+ toString() {
819
+ const { format } = this;
820
+
821
+ if (format === 'rgb') {
822
+ return this.toRgbString();
823
+ }
824
+ if (format === 'hsl') {
825
+ return this.toHslString();
826
+ }
827
+ return this.toHexString();
828
+ }
829
+ }
830
+
831
+ ObjectAssign(Color, {
832
+ colorNames,
833
+ CSS_INTEGER,
834
+ CSS_NUMBER,
835
+ CSS_UNIT,
836
+ PERMISSIVE_MATCH3,
837
+ PERMISSIVE_MATCH4,
838
+ matchers,
839
+ isOnePointZero,
840
+ isPercentage,
841
+ isValidCSSUnit,
842
+ bound01,
843
+ boundAlpha,
844
+ clamp01,
845
+ getHexFromColorName,
846
+ convertToPercentage,
847
+ convertHexToDecimal,
848
+ pad2,
849
+ rgbToRgb,
850
+ rgbToHsl,
851
+ rgbToHex,
852
+ rgbToHsv,
853
+ hslToRgb,
854
+ hsvToRgb,
855
+ hue2rgb,
856
+ parseIntFromHex,
857
+ numberInputToObject,
858
+ stringInputToObject,
859
+ inputToRGB,
860
+ });