@thednp/color-picker 0.0.1-alpha1 → 0.0.1

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