@thednp/color-picker 0.0.1-alpha1 → 0.0.1

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 (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
  });