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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  });