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

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