@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
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * ColorPickerElement v0.0.1alpha1 (http://thednp.github.io/color-picker)
2
+ * ColorPickerElement v0.0.1 (http://thednp.github.io/color-picker)
3
3
  * Copyright 2022 © thednp
4
4
  * Licensed under MIT (https://github.com/thednp/color-picker/blob/master/LICENSE)
5
5
  */
@@ -27,26 +27,17 @@
27
27
  const parentNodes = [Document, Element, HTMLElement];
28
28
 
29
29
  /**
30
- * A global array with `Element` | `HTMLElement`.
31
- */
32
- const elementNodes = [Element, HTMLElement];
33
-
34
- /**
35
- * Utility to check if target is typeof `HTMLElement`, `Element`, `Node`
36
- * or find one that matches a selector.
30
+ * Shortcut for `HTMLElement.getElementsByTagName` method. Some `Node` elements
31
+ * like `ShadowRoot` do not support `getElementsByTagName`.
37
32
  *
38
- * @param {HTMLElement | Element | string} selector the input selector or target element
39
- * @param {(HTMLElement | Element | Document)=} parent optional node to look into
40
- * @return {(HTMLElement | Element)?} the `HTMLElement` or `querySelector` result
33
+ * @param {string} selector the tag name
34
+ * @param {(HTMLElement | Element | Document)=} parent optional Element to look into
35
+ * @return {HTMLCollectionOf<HTMLElement | Element>} the 'HTMLCollection'
41
36
  */
42
- function querySelector(selector, parent) {
43
- const lookUp = parentNodes.some((x) => parent instanceof x)
44
- ? parent : getDocument();
45
-
46
- // @ts-ignore
47
- return elementNodes.some((x) => selector instanceof x)
48
- // @ts-ignore
49
- ? selector : lookUp.querySelector(selector);
37
+ function getElementsByTagName(selector, parent) {
38
+ const lookUp = parent && parentNodes
39
+ .some((x) => parent instanceof x) ? parent : getDocument();
40
+ return lookUp.getElementsByTagName(selector);
50
41
  }
51
42
 
52
43
  /**
@@ -79,6 +70,23 @@
79
70
  return newElement;
80
71
  }
81
72
 
73
+ /**
74
+ * Shortcut for `HTMLElement.setAttribute()` method.
75
+ * @param {HTMLElement | Element} element target element
76
+ * @param {string} attribute attribute name
77
+ * @param {string} value attribute value
78
+ * @returns {void}
79
+ */
80
+ const setAttribute = (element, attribute, value) => element.setAttribute(attribute, value);
81
+
82
+ /**
83
+ * Shortcut for `HTMLElement.getAttribute()` method.
84
+ * @param {HTMLElement | Element} element target element
85
+ * @param {string} attribute attribute name
86
+ * @returns {string?} attribute value
87
+ */
88
+ const getAttribute = (element, attribute) => element.getAttribute(attribute);
89
+
82
90
  /**
83
91
  * Returns the `document.head` or the `<head>` element.
84
92
  *
@@ -108,193 +116,70 @@
108
116
  return property in computedStyle ? computedStyle[property] : '';
109
117
  }
110
118
 
119
+ /**
120
+ * Shortcut for `Object.keys()` static method.
121
+ * @param {Record<string, any>} obj a target object
122
+ * @returns {string[]}
123
+ */
124
+ const ObjectKeys = (obj) => Object.keys(obj);
125
+
111
126
  /**
112
127
  * Shortcut for multiple uses of `HTMLElement.style.propertyName` method.
113
128
  * @param {HTMLElement | Element} element target element
114
129
  * @param {Partial<CSSStyleDeclaration>} styles attribute value
115
130
  */
116
131
  // @ts-ignore
117
- const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
132
+ const setElementStyle = (element, styles) => ObjectAssign(element.style, styles);
118
133
 
119
134
  /**
120
- * A complete list of web safe colors.
121
- * @see https://github.com/bahamas10/css-color-names/blob/master/css-color-names.json
122
- * @type {string[]}
135
+ * A list of explicit default non-color values.
136
+ */
137
+ const nonColors = ['transparent', 'currentColor', 'inherit', 'revert', 'initial'];
138
+
139
+ /**
140
+ * Round colour components, for all formats except HEX.
141
+ * @param {number} v one of the colour components
142
+ * @returns {number} the rounded number
123
143
  */
124
- const colorNames = [
125
- 'aliceblue',
126
- 'antiquewhite',
127
- 'aqua',
128
- 'aquamarine',
129
- 'azure',
130
- 'beige',
131
- 'bisque',
132
- 'black',
133
- 'blanchedalmond',
134
- 'blue',
135
- 'blueviolet',
136
- 'brown',
137
- 'burlywood',
138
- 'cadetblue',
139
- 'chartreuse',
140
- 'chocolate',
141
- 'coral',
142
- 'cornflowerblue',
143
- 'cornsilk',
144
- 'crimson',
145
- 'cyan',
146
- 'darkblue',
147
- 'darkcyan',
148
- 'darkgoldenrod',
149
- 'darkgray',
150
- 'darkgreen',
151
- 'darkgrey',
152
- 'darkkhaki',
153
- 'darkmagenta',
154
- 'darkolivegreen',
155
- 'darkorange',
156
- 'darkorchid',
157
- 'darkred',
158
- 'darksalmon',
159
- 'darkseagreen',
160
- 'darkslateblue',
161
- 'darkslategray',
162
- 'darkslategrey',
163
- 'darkturquoise',
164
- 'darkviolet',
165
- 'deeppink',
166
- 'deepskyblue',
167
- 'dimgray',
168
- 'dimgrey',
169
- 'dodgerblue',
170
- 'firebrick',
171
- 'floralwhite',
172
- 'forestgreen',
173
- 'fuchsia',
174
- 'gainsboro',
175
- 'ghostwhite',
176
- 'goldenrod',
177
- 'gold',
178
- 'gray',
179
- 'green',
180
- 'greenyellow',
181
- 'grey',
182
- 'honeydew',
183
- 'hotpink',
184
- 'indianred',
185
- 'indigo',
186
- 'ivory',
187
- 'khaki',
188
- 'lavenderblush',
189
- 'lavender',
190
- 'lawngreen',
191
- 'lemonchiffon',
192
- 'lightblue',
193
- 'lightcoral',
194
- 'lightcyan',
195
- 'lightgoldenrodyellow',
196
- 'lightgray',
197
- 'lightgreen',
198
- 'lightgrey',
199
- 'lightpink',
200
- 'lightsalmon',
201
- 'lightseagreen',
202
- 'lightskyblue',
203
- 'lightslategray',
204
- 'lightslategrey',
205
- 'lightsteelblue',
206
- 'lightyellow',
207
- 'lime',
208
- 'limegreen',
209
- 'linen',
210
- 'magenta',
211
- 'maroon',
212
- 'mediumaquamarine',
213
- 'mediumblue',
214
- 'mediumorchid',
215
- 'mediumpurple',
216
- 'mediumseagreen',
217
- 'mediumslateblue',
218
- 'mediumspringgreen',
219
- 'mediumturquoise',
220
- 'mediumvioletred',
221
- 'midnightblue',
222
- 'mintcream',
223
- 'mistyrose',
224
- 'moccasin',
225
- 'navajowhite',
226
- 'navy',
227
- 'oldlace',
228
- 'olive',
229
- 'olivedrab',
230
- 'orange',
231
- 'orangered',
232
- 'orchid',
233
- 'palegoldenrod',
234
- 'palegreen',
235
- 'paleturquoise',
236
- 'palevioletred',
237
- 'papayawhip',
238
- 'peachpuff',
239
- 'peru',
240
- 'pink',
241
- 'plum',
242
- 'powderblue',
243
- 'purple',
244
- 'rebeccapurple',
245
- 'red',
246
- 'rosybrown',
247
- 'royalblue',
248
- 'saddlebrown',
249
- 'salmon',
250
- 'sandybrown',
251
- 'seagreen',
252
- 'seashell',
253
- 'sienna',
254
- 'silver',
255
- 'skyblue',
256
- 'slateblue',
257
- 'slategray',
258
- 'slategrey',
259
- 'snow',
260
- 'springgreen',
261
- 'steelblue',
262
- 'tan',
263
- 'teal',
264
- 'thistle',
265
- 'tomato',
266
- 'turquoise',
267
- 'violet',
268
- 'wheat',
269
- 'white',
270
- 'whitesmoke',
271
- 'yellow',
272
- 'yellowgreen',
273
- ];
144
+ function roundPart(v) {
145
+ const floor = Math.floor(v);
146
+ return v - floor < 0.5 ? floor : Math.round(v);
147
+ }
148
+
149
+ // Color supported formats
150
+ const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
151
+
152
+ // Hue angles
153
+ const ANGLES = 'deg|rad|grad|turn';
274
154
 
275
155
  // <http://www.w3.org/TR/css3-values/#integers>
276
156
  const CSS_INTEGER = '[-\\+]?\\d+%?';
277
157
 
158
+ // Include CSS3 Module
278
159
  // <http://www.w3.org/TR/css3-values/#number-value>
279
160
  const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
280
161
 
162
+ // Include CSS4 Module Hue degrees unit
163
+ // <https://www.w3.org/TR/css3-values/#angle-value>
164
+ const CSS_ANGLE = `[-\\+]?\\d*\\.?\\d+(?:${ANGLES})?`;
165
+
281
166
  // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
282
167
  const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
283
168
 
169
+ // Add angles to the mix
170
+ const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
171
+
284
172
  // Actual matching.
285
173
  // Parentheses and commas are optional, but not required.
286
174
  // Whitespace can take the place of commas or opening paren
287
- const PERMISSIVE_MATCH3 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?`;
288
- const PERMISSIVE_MATCH4 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?`;
175
+ const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
289
176
 
290
177
  const matchers = {
291
- CSS_UNIT: new RegExp(CSS_UNIT),
292
- rgb: new RegExp(`rgb${PERMISSIVE_MATCH3}`),
293
- rgba: new RegExp(`rgba${PERMISSIVE_MATCH4}`),
294
- hsl: new RegExp(`hsl${PERMISSIVE_MATCH3}`),
295
- hsla: new RegExp(`hsla${PERMISSIVE_MATCH4}`),
296
- hsv: new RegExp(`hsv${PERMISSIVE_MATCH3}`),
297
- hsva: new RegExp(`hsva${PERMISSIVE_MATCH4}`),
178
+ CSS_UNIT: new RegExp(CSS_UNIT2),
179
+ hwb: new RegExp(`hwb${PERMISSIVE_MATCH}`),
180
+ rgb: new RegExp(`rgb(?:a)?${PERMISSIVE_MATCH}`),
181
+ hsl: new RegExp(`hsl(?:a)?${PERMISSIVE_MATCH}`),
182
+ hsv: new RegExp(`hsv(?:a)?${PERMISSIVE_MATCH}`),
298
183
  hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
299
184
  hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
300
185
  hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
@@ -304,27 +189,46 @@
304
189
  /**
305
190
  * Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
306
191
  * <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
307
- * @param {string} n
308
- * @returns {boolean}
192
+ * @param {string} n testing number
193
+ * @returns {boolean} the query result
309
194
  */
310
195
  function isOnePointZero(n) {
311
- return typeof n === 'string' && n.includes('.') && parseFloat(n) === 1;
196
+ return `${n}`.includes('.') && parseFloat(n) === 1;
312
197
  }
313
198
 
314
199
  /**
315
200
  * Check to see if string passed in is a percentage
316
- * @param {string} n
317
- * @returns {boolean}
201
+ * @param {string} n testing number
202
+ * @returns {boolean} the query result
318
203
  */
319
204
  function isPercentage(n) {
320
- return typeof n === 'string' && n.includes('%');
205
+ return `${n}`.includes('%');
206
+ }
207
+
208
+ /**
209
+ * Check to see if string passed in is an angle
210
+ * @param {string} n testing string
211
+ * @returns {boolean} the query result
212
+ */
213
+ function isAngle(n) {
214
+ return ANGLES.split('|').some((a) => `${n}`.includes(a));
215
+ }
216
+
217
+ /**
218
+ * Check to see if string passed is a web safe colour.
219
+ * @param {string} color a colour name, EG: *red*
220
+ * @returns {boolean} the query result
221
+ */
222
+ function isColorName(color) {
223
+ return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
224
+ && !/[0-9]/.test(color);
321
225
  }
322
226
 
323
227
  /**
324
228
  * Check to see if it looks like a CSS unit
325
229
  * (see `matchers` above for definition).
326
- * @param {string | number} color
327
- * @returns {boolean}
230
+ * @param {string | number} color testing value
231
+ * @returns {boolean} the query result
328
232
  */
329
233
  function isValidCSSUnit(color) {
330
234
  return Boolean(matchers.CSS_UNIT.exec(String(color)));
@@ -332,22 +236,24 @@
332
236
 
333
237
  /**
334
238
  * Take input from [0, n] and return it as [0, 1]
335
- * @param {*} n
336
- * @param {number} max
337
- * @returns {number}
239
+ * @param {*} N the input number
240
+ * @param {number} max the number maximum value
241
+ * @returns {number} the number in [0, 1] value range
338
242
  */
339
- function bound01(n, max) {
340
- let N = n;
341
- if (isOnePointZero(n)) N = '100%';
243
+ function bound01(N, max) {
244
+ let n = N;
245
+ if (isOnePointZero(n)) n = '100%';
246
+
247
+ n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
342
248
 
343
- N = max === 360 ? N : Math.min(max, Math.max(0, parseFloat(N)));
249
+ // Handle hue angles
250
+ if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
344
251
 
345
252
  // Automatically convert percentage into number
346
- if (isPercentage(N)) {
347
- N = parseInt(String(N * max), 10) / 100;
348
- }
253
+ if (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
254
+
349
255
  // Handle floating point rounding errors
350
- if (Math.abs(N - max) < 0.000001) {
256
+ if (Math.abs(n - max) < 0.000001) {
351
257
  return 1;
352
258
  }
353
259
  // Convert into [0, 1] range if it isn't already
@@ -355,23 +261,22 @@
355
261
  // If n is a hue given in degrees,
356
262
  // wrap around out-of-range values into [0, 360] range
357
263
  // then convert into [0, 1].
358
- N = (N < 0 ? (N % max) + max : N % max) / parseFloat(String(max));
264
+ n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max));
359
265
  } else {
360
266
  // If n not a hue given in degrees
361
267
  // Convert into [0, 1] range if it isn't already.
362
- N = (N % max) / parseFloat(String(max));
268
+ n = (n % max) / parseFloat(String(max));
363
269
  }
364
- return N;
270
+ return n;
365
271
  }
366
272
 
367
273
  /**
368
274
  * Return a valid alpha value [0,1] with all invalid values being set to 1.
369
- * @param {string | number} a
370
- * @returns {number}
275
+ * @param {string | number} a transparency value
276
+ * @returns {number} a transparency value in the [0, 1] range
371
277
  */
372
278
  function boundAlpha(a) {
373
- // @ts-ignore
374
- let na = parseFloat(a);
279
+ let na = parseFloat(`${a}`);
375
280
 
376
281
  if (Number.isNaN(na) || na < 0 || na > 1) {
377
282
  na = 1;
@@ -381,12 +286,12 @@
381
286
  }
382
287
 
383
288
  /**
384
- * Force a number between 0 and 1
385
- * @param {number} val
386
- * @returns {number}
289
+ * Force a number between 0 and 1.
290
+ * @param {number} v the float number
291
+ * @returns {number} - the resulting number
387
292
  */
388
- function clamp01(val) {
389
- return Math.min(1, Math.max(0, val));
293
+ function clamp01(v) {
294
+ return Math.min(1, Math.max(0, v));
390
295
  }
391
296
 
392
297
  /**
@@ -394,7 +299,7 @@
394
299
  * @param {string} name
395
300
  * @returns {string}
396
301
  */
397
- function getHexFromColorName(name) {
302
+ function getRGBFromName(name) {
398
303
  const documentHead = getDocumentHead();
399
304
  setElementStyle(documentHead, { color: name });
400
305
  const colorName = getElementStyle(documentHead, 'color');
@@ -403,59 +308,53 @@
403
308
  }
404
309
 
405
310
  /**
406
- * Replace a decimal with it's percentage value
407
- * @param {number | string} n
408
- * @return {string | number}
311
+ * Converts a decimal value to hexadecimal.
312
+ * @param {number} d the input number
313
+ * @returns {string} - the hexadecimal value
409
314
  */
410
- function convertToPercentage(n) {
411
- if (n <= 1) {
412
- return `${Number(n) * 100}%`;
413
- }
414
- return n;
315
+ function convertDecimalToHex(d) {
316
+ return roundPart(d * 255).toString(16);
415
317
  }
416
318
 
417
319
  /**
418
- * Force a hex value to have 2 characters
419
- * @param {string} c
420
- * @returns {string}
320
+ * Converts a hexadecimal value to decimal.
321
+ * @param {string} h hexadecimal value
322
+ * @returns {number} number in decimal format
421
323
  */
422
- function pad2(c) {
423
- return c.length === 1 ? `0${c}` : String(c);
324
+ function convertHexToDecimal(h) {
325
+ return parseIntFromHex(h) / 255;
424
326
  }
425
327
 
426
- // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
427
- // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
428
328
  /**
429
- * Handle bounds / percentage checking to conform to CSS color spec
430
- * * *Assumes:* r, g, b in [0, 255] or [0, 1]
431
- * * *Returns:* { r, g, b } in [0, 255]
432
- * @see http://www.w3.org/TR/css3-color/
433
- * @param {number | string} r
434
- * @param {number | string} g
435
- * @param {number | string} b
436
- * @returns {CP.RGB}
329
+ * Converts a base-16 hexadecimal value into a base-10 integer.
330
+ * @param {string} val
331
+ * @returns {number}
437
332
  */
438
- function rgbToRgb(r, g, b) {
439
- return {
440
- r: bound01(r, 255) * 255,
441
- g: bound01(g, 255) * 255,
442
- b: bound01(b, 255) * 255,
443
- };
333
+ function parseIntFromHex(val) {
334
+ return parseInt(val, 16);
335
+ }
336
+
337
+ /**
338
+ * Force a hexadecimal value to have 2 characters.
339
+ * @param {string} c string with [0-9A-F] ranged values
340
+ * @returns {string} 0 => 00, a => 0a
341
+ */
342
+ function pad2(c) {
343
+ return c.length === 1 ? `0${c}` : String(c);
444
344
  }
445
345
 
446
346
  /**
447
- * Converts an RGB color value to HSL.
448
- * *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
449
- * *Returns:* { h, s, l } in [0,1]
450
- * @param {number} R
451
- * @param {number} G
452
- * @param {number} B
453
- * @returns {CP.HSL}
347
+ * Converts an RGB colour value to HSL.
348
+ *
349
+ * @param {number} R Red component [0, 255]
350
+ * @param {number} G Green component [0, 255]
351
+ * @param {number} B Blue component [0, 255]
352
+ * @returns {CP.HSL} {h,s,l} object with [0, 1] ranged values
454
353
  */
455
354
  function rgbToHsl(R, G, B) {
456
- const r = bound01(R, 255);
457
- const g = bound01(G, 255);
458
- const b = bound01(B, 255);
355
+ const r = R / 255;
356
+ const g = G / 255;
357
+ const b = B / 255;
459
358
  const max = Math.max(r, g, b);
460
359
  const min = Math.min(r, g, b);
461
360
  let h = 0;
@@ -485,50 +384,95 @@
485
384
 
486
385
  /**
487
386
  * Returns a normalized RGB component value.
488
- * @param {number} P
489
- * @param {number} Q
490
- * @param {number} T
387
+ * @param {number} p
388
+ * @param {number} q
389
+ * @param {number} t
491
390
  * @returns {number}
492
391
  */
493
- function hue2rgb(P, Q, T) {
494
- const p = P;
495
- const q = Q;
496
- let t = T;
497
- if (t < 0) {
498
- t += 1;
499
- }
500
- if (t > 1) {
501
- t -= 1;
502
- }
503
- if (t < 1 / 6) {
504
- return p + (q - p) * (6 * t);
505
- }
506
- if (t < 1 / 2) {
507
- return q;
508
- }
509
- if (t < 2 / 3) {
510
- return p + (q - p) * (2 / 3 - t) * 6;
511
- }
392
+ function hueToRgb(p, q, t) {
393
+ let T = t;
394
+ if (T < 0) T += 1;
395
+ if (T > 1) T -= 1;
396
+ if (T < 1 / 6) return p + (q - p) * (6 * T);
397
+ if (T < 1 / 2) return q;
398
+ if (T < 2 / 3) return p + (q - p) * (2 / 3 - T) * 6;
512
399
  return p;
513
400
  }
514
401
 
402
+ /**
403
+ * Returns an HWB colour object from an RGB colour object.
404
+ * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
405
+ * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
406
+ *
407
+ * @param {number} R Red component [0, 255]
408
+ * @param {number} G Green [0, 255]
409
+ * @param {number} B Blue [0, 255]
410
+ * @return {CP.HWB} {h,w,b} object with [0, 1] ranged values
411
+ */
412
+ function rgbToHwb(R, G, B) {
413
+ const r = R / 255;
414
+ const g = G / 255;
415
+ const b = B / 255;
416
+
417
+ let f = 0;
418
+ let i = 0;
419
+ const whiteness = Math.min(r, g, b);
420
+ const max = Math.max(r, g, b);
421
+ const black = 1 - max;
422
+
423
+ if (max === whiteness) return { h: 0, w: whiteness, b: black };
424
+ if (r === whiteness) {
425
+ f = g - b;
426
+ i = 3;
427
+ } else {
428
+ f = g === whiteness ? b - r : r - g;
429
+ i = g === whiteness ? 5 : 1;
430
+ }
431
+
432
+ const h = (i - f / (max - whiteness)) / 6;
433
+ return {
434
+ h: h === 1 ? 0 : h,
435
+ w: whiteness,
436
+ b: black,
437
+ };
438
+ }
439
+
440
+ /**
441
+ * Returns an RGB colour object from an HWB colour.
442
+ *
443
+ * @param {number} H Hue Angle [0, 1]
444
+ * @param {number} W Whiteness [0, 1]
445
+ * @param {number} B Blackness [0, 1]
446
+ * @return {CP.RGB} {r,g,b} object with [0, 255] ranged values
447
+ *
448
+ * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
449
+ * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
450
+ */
451
+ function hwbToRgb(H, W, B) {
452
+ if (W + B >= 1) {
453
+ const gray = (W / (W + B)) * 255;
454
+ return { r: gray, g: gray, b: gray };
455
+ }
456
+ let { r, g, b } = hslToRgb(H, 1, 0.5);
457
+ [r, g, b] = [r, g, b]
458
+ .map((v) => (v / 255) * (1 - W - B) + W)
459
+ .map((v) => v * 255);
460
+
461
+ return { r, g, b };
462
+ }
463
+
515
464
  /**
516
465
  * Converts an HSL colour value to RGB.
517
466
  *
518
- * * *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
519
- * * *Returns:* { r, g, b } in the set [0, 255]
520
- * @param {number | string} H
521
- * @param {number | string} S
522
- * @param {number | string} L
523
- * @returns {CP.RGB}
524
- */
525
- function hslToRgb(H, S, L) {
467
+ * @param {number} h Hue Angle [0, 1]
468
+ * @param {number} s Saturation [0, 1]
469
+ * @param {number} l Lightness Angle [0, 1]
470
+ * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
471
+ */
472
+ function hslToRgb(h, s, l) {
526
473
  let r = 0;
527
474
  let g = 0;
528
475
  let b = 0;
529
- const h = bound01(H, 360);
530
- const s = bound01(S, 100);
531
- const l = bound01(L, 100);
532
476
 
533
477
  if (s === 0) {
534
478
  // achromatic
@@ -538,27 +482,27 @@
538
482
  } else {
539
483
  const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
540
484
  const p = 2 * l - q;
541
- r = hue2rgb(p, q, h + 1 / 3);
542
- g = hue2rgb(p, q, h);
543
- b = hue2rgb(p, q, h - 1 / 3);
485
+ r = hueToRgb(p, q, h + 1 / 3);
486
+ g = hueToRgb(p, q, h);
487
+ b = hueToRgb(p, q, h - 1 / 3);
544
488
  }
545
- return { r: r * 255, g: g * 255, b: b * 255 };
489
+ [r, g, b] = [r, g, b].map((x) => x * 255);
490
+
491
+ return { r, g, b };
546
492
  }
547
493
 
548
494
  /**
549
495
  * Converts an RGB colour value to HSV.
550
496
  *
551
- * *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
552
- * *Returns:* { h, s, v } in [0,1]
553
- * @param {number | string} R
554
- * @param {number | string} G
555
- * @param {number | string} B
556
- * @returns {CP.HSV}
497
+ * @param {number} R Red component [0, 255]
498
+ * @param {number} G Green [0, 255]
499
+ * @param {number} B Blue [0, 255]
500
+ * @returns {CP.HSV} {h,s,v} object with [0, 1] ranged values
557
501
  */
558
502
  function rgbToHsv(R, G, B) {
559
- const r = bound01(R, 255);
560
- const g = bound01(G, 255);
561
- const b = bound01(B, 255);
503
+ const r = R / 255;
504
+ const g = G / 255;
505
+ const b = B / 255;
562
506
  const max = Math.max(r, g, b);
563
507
  const min = Math.min(r, g, b);
564
508
  let h = 0;
@@ -585,19 +529,17 @@
585
529
  }
586
530
 
587
531
  /**
588
- * Converts an HSV color value to RGB.
532
+ * Converts an HSV colour value to RGB.
589
533
  *
590
- * *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
591
- * *Returns:* { r, g, b } in the set [0, 255]
592
- * @param {number | string} H
593
- * @param {number | string} S
594
- * @param {number | string} V
595
- * @returns {CP.RGB}
534
+ * @param {number} H Hue Angle [0, 1]
535
+ * @param {number} S Saturation [0, 1]
536
+ * @param {number} V Brightness Angle [0, 1]
537
+ * @returns {CP.RGB} {r,g,b} object with [0, 1] ranged values
596
538
  */
597
539
  function hsvToRgb(H, S, V) {
598
- const h = bound01(H, 360) * 6;
599
- const s = bound01(S, 100);
600
- const v = bound01(V, 100);
540
+ const h = H * 6;
541
+ const s = S;
542
+ const v = V;
601
543
  const i = Math.floor(h);
602
544
  const f = h - i;
603
545
  const p = v * (1 - s);
@@ -611,47 +553,65 @@
611
553
  }
612
554
 
613
555
  /**
614
- * Converts an RGB color to hex
556
+ * Converts an RGB colour to hex
615
557
  *
616
558
  * Assumes r, g, and b are contained in the set [0, 255]
617
559
  * Returns a 3 or 6 character hex
618
- * @param {number} r
619
- * @param {number} g
620
- * @param {number} b
560
+ * @param {number} r Red component [0, 255]
561
+ * @param {number} g Green [0, 255]
562
+ * @param {number} b Blue [0, 255]
563
+ * @param {boolean=} allow3Char
621
564
  * @returns {string}
622
565
  */
623
- function rgbToHex(r, g, b) {
566
+ function rgbToHex(r, g, b, allow3Char) {
624
567
  const hex = [
625
- pad2(Math.round(r).toString(16)),
626
- pad2(Math.round(g).toString(16)),
627
- pad2(Math.round(b).toString(16)),
568
+ pad2(roundPart(r).toString(16)),
569
+ pad2(roundPart(g).toString(16)),
570
+ pad2(roundPart(b).toString(16)),
628
571
  ];
629
572
 
573
+ // Return a 3 character hex if possible
574
+ if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
575
+ && hex[1].charAt(0) === hex[1].charAt(1)
576
+ && hex[2].charAt(0) === hex[2].charAt(1)) {
577
+ return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
578
+ }
579
+
630
580
  return hex.join('');
631
581
  }
632
582
 
633
583
  /**
634
- * Converts a hex value to a decimal.
635
- * @param {string} h
636
- * @returns {number}
637
- */
638
- function convertHexToDecimal(h) {
639
- return parseIntFromHex(h) / 255;
640
- }
584
+ * Converts an RGBA color plus alpha transparency to hex8.
585
+ *
586
+ * @param {number} r Red component [0, 255]
587
+ * @param {number} g Green [0, 255]
588
+ * @param {number} b Blue [0, 255]
589
+ * @param {number} a Alpha transparency [0, 1]
590
+ * @param {boolean=} allow4Char when *true* it will also find hex shorthand
591
+ * @returns {string} a hexadecimal value with alpha transparency
592
+ */
593
+ function rgbaToHex(r, g, b, a, allow4Char) {
594
+ const hex = [
595
+ pad2(roundPart(r).toString(16)),
596
+ pad2(roundPart(g).toString(16)),
597
+ pad2(roundPart(b).toString(16)),
598
+ pad2(convertDecimalToHex(a)),
599
+ ];
641
600
 
642
- /**
643
- * Parse a base-16 hex value into a base-10 integer.
644
- * @param {string} val
645
- * @returns {number}
646
- */
647
- function parseIntFromHex(val) {
648
- return parseInt(val, 16);
601
+ // Return a 4 character hex if possible
602
+ if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
603
+ && hex[1].charAt(0) === hex[1].charAt(1)
604
+ && hex[2].charAt(0) === hex[2].charAt(1)
605
+ && hex[3].charAt(0) === hex[3].charAt(1)) {
606
+ return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
607
+ }
608
+ return hex.join('');
649
609
  }
650
610
 
651
611
  /**
652
- * Returns an `{r,g,b}` color object corresponding to a given number.
653
- * @param {number} color
654
- * @returns {CP.RGB}
612
+ * Returns a colour object corresponding to a given number.
613
+ * @param {number} color input number
614
+ * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
655
615
  */
656
616
  function numberInputToObject(color) {
657
617
  /* eslint-disable no-bitwise */
@@ -664,10 +624,10 @@
664
624
  }
665
625
 
666
626
  /**
667
- * Permissive string parsing. Take in a number of formats, and output an object
668
- * based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
669
- * @param {string} input
670
- * @returns {Record<string, (number | string)> | false}
627
+ * Permissive string parsing. Take in a number of formats, and output an object
628
+ * based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
629
+ * @param {string} input colour value in any format
630
+ * @returns {Record<string, (number | string)> | false} an object matching the RegExp
671
631
  */
672
632
  function stringInputToObject(input) {
673
633
  let color = input.trim().toLowerCase();
@@ -677,12 +637,15 @@
677
637
  };
678
638
  }
679
639
  let named = false;
680
- if (colorNames.includes(color)) {
681
- color = getHexFromColorName(color);
640
+ if (isColorName(color)) {
641
+ color = getRGBFromName(color);
682
642
  named = true;
683
- } else if (color === 'transparent') {
643
+ } else if (nonColors.includes(color)) {
644
+ const isTransparent = color === 'transparent';
645
+ const rgb = isTransparent ? 0 : 255;
646
+ const a = isTransparent ? 0 : 1;
684
647
  return {
685
- r: 0, g: 0, b: 0, a: 0, format: 'name',
648
+ r: rgb, g: rgb, b: rgb, a, format: 'rgb',
686
649
  };
687
650
  }
688
651
 
@@ -691,72 +654,68 @@
691
654
  // don't worry about [0,1] or [0,100] or [0,360]
692
655
  // Just return an object and let the conversion functions handle that.
693
656
  // This way the result will be the same whether Color is initialized with string or object.
694
- let match = matchers.rgb.exec(color);
695
- if (match) {
696
- return { r: match[1], g: match[2], b: match[3] };
697
- }
698
- match = matchers.rgba.exec(color);
699
- if (match) {
657
+ let [, m1, m2, m3, m4] = matchers.rgb.exec(color) || [];
658
+ if (m1 && m2 && m3/* && m4 */) {
700
659
  return {
701
- r: match[1], g: match[2], b: match[3], a: match[4],
660
+ r: m1, g: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'rgb',
702
661
  };
703
662
  }
704
- match = matchers.hsl.exec(color);
705
- if (match) {
706
- return { h: match[1], s: match[2], l: match[3] };
707
- }
708
- match = matchers.hsla.exec(color);
709
- if (match) {
663
+ [, m1, m2, m3, m4] = matchers.hsl.exec(color) || [];
664
+ if (m1 && m2 && m3/* && m4 */) {
710
665
  return {
711
- h: match[1], s: match[2], l: match[3], a: match[4],
666
+ h: m1, s: m2, l: m3, a: m4 !== undefined ? m4 : 1, format: 'hsl',
712
667
  };
713
668
  }
714
- match = matchers.hsv.exec(color);
715
- if (match) {
716
- return { h: match[1], s: match[2], v: match[3] };
669
+ [, m1, m2, m3, m4] = matchers.hsv.exec(color) || [];
670
+ if (m1 && m2 && m3/* && m4 */) {
671
+ return {
672
+ h: m1, s: m2, v: m3, a: m4 !== undefined ? m4 : 1, format: 'hsv',
673
+ };
717
674
  }
718
- match = matchers.hsva.exec(color);
719
- if (match) {
675
+ [, m1, m2, m3, m4] = matchers.hwb.exec(color) || [];
676
+ if (m1 && m2 && m3) {
720
677
  return {
721
- h: match[1], s: match[2], v: match[3], a: match[4],
678
+ h: m1, w: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'hwb',
722
679
  };
723
680
  }
724
- match = matchers.hex8.exec(color);
725
- if (match) {
681
+ [, m1, m2, m3, m4] = matchers.hex8.exec(color) || [];
682
+ if (m1 && m2 && m3 && m4) {
726
683
  return {
727
- r: parseIntFromHex(match[1]),
728
- g: parseIntFromHex(match[2]),
729
- b: parseIntFromHex(match[3]),
730
- a: convertHexToDecimal(match[4]),
731
- format: named ? 'name' : 'hex8',
684
+ r: parseIntFromHex(m1),
685
+ g: parseIntFromHex(m2),
686
+ b: parseIntFromHex(m3),
687
+ a: convertHexToDecimal(m4),
688
+ // format: named ? 'rgb' : 'hex8',
689
+ format: named ? 'rgb' : 'hex',
732
690
  };
733
691
  }
734
- match = matchers.hex6.exec(color);
735
- if (match) {
692
+ [, m1, m2, m3] = matchers.hex6.exec(color) || [];
693
+ if (m1 && m2 && m3) {
736
694
  return {
737
- r: parseIntFromHex(match[1]),
738
- g: parseIntFromHex(match[2]),
739
- b: parseIntFromHex(match[3]),
740
- format: named ? 'name' : 'hex',
695
+ r: parseIntFromHex(m1),
696
+ g: parseIntFromHex(m2),
697
+ b: parseIntFromHex(m3),
698
+ format: named ? 'rgb' : 'hex',
741
699
  };
742
700
  }
743
- match = matchers.hex4.exec(color);
744
- if (match) {
701
+ [, m1, m2, m3, m4] = matchers.hex4.exec(color) || [];
702
+ if (m1 && m2 && m3 && m4) {
745
703
  return {
746
- r: parseIntFromHex(match[1] + match[1]),
747
- g: parseIntFromHex(match[2] + match[2]),
748
- b: parseIntFromHex(match[3] + match[3]),
749
- a: convertHexToDecimal(match[4] + match[4]),
750
- format: named ? 'name' : 'hex8',
704
+ r: parseIntFromHex(m1 + m1),
705
+ g: parseIntFromHex(m2 + m2),
706
+ b: parseIntFromHex(m3 + m3),
707
+ a: convertHexToDecimal(m4 + m4),
708
+ // format: named ? 'rgb' : 'hex8',
709
+ format: named ? 'rgb' : 'hex',
751
710
  };
752
711
  }
753
- match = matchers.hex3.exec(color);
754
- if (match) {
712
+ [, m1, m2, m3] = matchers.hex3.exec(color) || [];
713
+ if (m1 && m2 && m3) {
755
714
  return {
756
- r: parseIntFromHex(match[1] + match[1]),
757
- g: parseIntFromHex(match[2] + match[2]),
758
- b: parseIntFromHex(match[3] + match[3]),
759
- format: named ? 'name' : 'hex',
715
+ r: parseIntFromHex(m1 + m1),
716
+ g: parseIntFromHex(m2 + m2),
717
+ b: parseIntFromHex(m3 + m3),
718
+ format: named ? 'rgb' : 'hex',
760
719
  };
761
720
  }
762
721
  return false;
@@ -770,26 +729,35 @@
770
729
  * "red"
771
730
  * "#f00" or "f00"
772
731
  * "#ff0000" or "ff0000"
773
- * "#ff000000" or "ff000000"
732
+ * "#ff000000" or "ff000000" // CSS4 Module
774
733
  * "rgb 255 0 0" or "rgb (255, 0, 0)"
775
734
  * "rgb 1.0 0 0" or "rgb (1, 0, 0)"
776
- * "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
777
- * "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
735
+ * "rgba(255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
736
+ * "rgba(1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
737
+ * "rgb(255 0 0 / 10%)" or "rgb 255 0 0 0.1" // CSS4 Module
778
738
  * "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
779
739
  * "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
740
+ * "hsl(0deg 100% 50% / 50%)" or "hsl 0 100 50 50" // CSS4 Module
780
741
  * "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
742
+ * "hsva(0, 100%, 100%, 0.1)" or "hsva 0 100% 100% 0.1"
743
+ * "hsv(0deg 100% 100% / 10%)" or "hsv 0 100 100 0.1" // CSS4 Module
744
+ * "hwb(0deg, 100%, 100%, 100%)" or "hwb 0 100% 100% 0.1" // CSS4 Module
781
745
  * ```
782
746
  * @param {string | Record<string, any>} input
783
747
  * @returns {CP.ColorObject}
784
748
  */
785
749
  function inputToRGB(input) {
786
- /** @type {CP.RGB} */
787
750
  let rgb = { r: 0, g: 0, b: 0 };
788
751
  let color = input;
789
- let a;
752
+ let a = 1;
790
753
  let s = null;
791
754
  let v = null;
792
755
  let l = null;
756
+ let w = null;
757
+ let b = null;
758
+ let h = null;
759
+ let r = null;
760
+ let g = null;
793
761
  let ok = false;
794
762
  let format = 'hex';
795
763
 
@@ -800,23 +768,41 @@
800
768
  }
801
769
  if (typeof color === 'object') {
802
770
  if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
803
- rgb = rgbToRgb(color.r, color.g, color.b);
771
+ ({ r, g, b } = color);
772
+ // RGB values now are all in [0, 255] range
773
+ [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255);
774
+ rgb = { r, g, b };
804
775
  ok = true;
805
- format = `${color.r}`.slice(-1) === '%' ? 'prgb' : 'rgb';
776
+ format = 'rgb';
806
777
  } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
807
- s = convertToPercentage(color.s);
808
- v = convertToPercentage(color.v);
809
- rgb = hsvToRgb(color.h, s, v);
778
+ ({ h, s, v } = color);
779
+ h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
780
+ s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
781
+ v = typeof v === 'number' ? v : bound01(v, 100); // brightness can be `5%` or a [0, 1] value
782
+ rgb = hsvToRgb(h, s, v);
810
783
  ok = true;
811
784
  format = 'hsv';
812
785
  } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
813
- s = convertToPercentage(color.s);
814
- l = convertToPercentage(color.l);
815
- rgb = hslToRgb(color.h, s, l);
786
+ ({ h, s, l } = color);
787
+ h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
788
+ s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
789
+ l = typeof l === 'number' ? l : bound01(l, 100); // lightness can be `5%` or a [0, 1] value
790
+ rgb = hslToRgb(h, s, l);
816
791
  ok = true;
817
792
  format = 'hsl';
793
+ } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
794
+ ({ h, w, b } = color);
795
+ h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
796
+ w = typeof w === 'number' ? w : bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
797
+ b = typeof b === 'number' ? b : bound01(b, 100); // blackness can be `5%` or a [0, 1] value
798
+ rgb = hwbToRgb(h, w, b);
799
+ ok = true;
800
+ format = 'hwb';
801
+ }
802
+ if (isValidCSSUnit(color.a)) {
803
+ a = color.a;
804
+ a = isPercentage(`${a}`) ? bound01(a, 100) : a;
818
805
  }
819
- if ('a' in color) a = color.a;
820
806
  }
821
807
 
822
808
  return {
@@ -829,27 +815,21 @@
829
815
  };
830
816
  }
831
817
 
832
- /** @type {CP.ColorOptions} */
833
- const colorPickerDefaults = {
834
- format: 'hex',
835
- };
836
-
837
818
  /**
819
+ * @class
838
820
  * Returns a new `Color` instance.
839
821
  * @see https://github.com/bgrins/TinyColor
840
- * @class
841
822
  */
842
823
  class Color {
843
824
  /**
844
825
  * @constructor
845
- * @param {CP.ColorInput} input
846
- * @param {CP.ColorOptions=} config
826
+ * @param {CP.ColorInput} input the given colour value
827
+ * @param {CP.ColorFormats=} config the given format
847
828
  */
848
829
  constructor(input, config) {
849
830
  let color = input;
850
- const opts = typeof config === 'object'
851
- ? ObjectAssign(colorPickerDefaults, config)
852
- : ObjectAssign({}, colorPickerDefaults);
831
+ const configFormat = config && COLOR_FORMAT.includes(config)
832
+ ? config : 'rgb';
853
833
 
854
834
  // If input is already a `Color`, return itself
855
835
  if (color instanceof Color) {
@@ -862,36 +842,23 @@
862
842
  r, g, b, a, ok, format,
863
843
  } = inputToRGB(color);
864
844
 
845
+ // bind
846
+ const self = this;
847
+
865
848
  /** @type {CP.ColorInput} */
866
- this.originalInput = color;
849
+ self.originalInput = color;
867
850
  /** @type {number} */
868
- this.r = r;
851
+ self.r = r;
869
852
  /** @type {number} */
870
- this.g = g;
853
+ self.g = g;
871
854
  /** @type {number} */
872
- this.b = b;
855
+ self.b = b;
873
856
  /** @type {number} */
874
- this.a = a;
857
+ self.a = a;
875
858
  /** @type {boolean} */
876
- this.ok = ok;
877
- /** @type {number} */
878
- this.roundA = Math.round(100 * this.a) / 100;
859
+ self.ok = ok;
879
860
  /** @type {CP.ColorFormats} */
880
- this.format = opts.format || format;
881
-
882
- // Don't let the range of [0,255] come back in [0,1].
883
- // Potentially lose a little bit of precision here, but will fix issues where
884
- // .5 gets interpreted as half of the total, instead of half of 1
885
- // If it was supposed to be 128, this was already taken care of by `inputToRgb`
886
- if (this.r < 1) {
887
- this.r = Math.round(this.r);
888
- }
889
- if (this.g < 1) {
890
- this.g = Math.round(this.g);
891
- }
892
- if (this.b < 1) {
893
- this.b = Math.round(this.b);
894
- }
861
+ self.format = configFormat || format;
895
862
  }
896
863
 
897
864
  /**
@@ -907,44 +874,44 @@
907
874
  * @returns {boolean} the query result
908
875
  */
909
876
  get isDark() {
910
- return this.brightness < 128;
877
+ return this.brightness < 120;
911
878
  }
912
879
 
913
880
  /**
914
- * Returns the perceived luminance of a color.
881
+ * Returns the perceived luminance of a colour.
915
882
  * @see http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
916
- * @returns {number} a number in [0-1] range
883
+ * @returns {number} a number in the [0, 1] range
917
884
  */
918
885
  get luminance() {
919
886
  const { r, g, b } = this;
920
887
  let R = 0;
921
888
  let G = 0;
922
889
  let B = 0;
923
- const RsRGB = r / 255;
924
- const GsRGB = g / 255;
925
- const BsRGB = b / 255;
890
+ const rp = r / 255;
891
+ const rg = g / 255;
892
+ const rb = b / 255;
926
893
 
927
- if (RsRGB <= 0.03928) {
928
- R = RsRGB / 12.92;
894
+ if (rp <= 0.03928) {
895
+ R = rp / 12.92;
929
896
  } else {
930
- R = ((RsRGB + 0.055) / 1.055) ** 2.4;
897
+ R = ((rp + 0.055) / 1.055) ** 2.4;
931
898
  }
932
- if (GsRGB <= 0.03928) {
933
- G = GsRGB / 12.92;
899
+ if (rg <= 0.03928) {
900
+ G = rg / 12.92;
934
901
  } else {
935
- G = ((GsRGB + 0.055) / 1.055) ** 2.4;
902
+ G = ((rg + 0.055) / 1.055) ** 2.4;
936
903
  }
937
- if (BsRGB <= 0.03928) {
938
- B = BsRGB / 12.92;
904
+ if (rb <= 0.03928) {
905
+ B = rb / 12.92;
939
906
  } else {
940
- B = ((BsRGB + 0.055) / 1.055) ** 2.4;
907
+ B = ((rb + 0.055) / 1.055) ** 2.4;
941
908
  }
942
909
  return 0.2126 * R + 0.7152 * G + 0.0722 * B;
943
910
  }
944
911
 
945
912
  /**
946
- * Returns the perceived brightness of the color.
947
- * @returns {number} a number in [0-255] range
913
+ * Returns the perceived brightness of the colour.
914
+ * @returns {number} a number in the [0, 255] range
948
915
  */
949
916
  get brightness() {
950
917
  const { r, g, b } = this;
@@ -952,123 +919,287 @@
952
919
  }
953
920
 
954
921
  /**
955
- * Returns the color as a RGBA object.
956
- * @returns {CP.RGBA}
922
+ * Returns the colour as an RGBA object.
923
+ * @returns {CP.RGBA} an {r,g,b,a} object with [0, 255] ranged values
957
924
  */
958
925
  toRgb() {
926
+ const {
927
+ r, g, b, a,
928
+ } = this;
929
+
959
930
  return {
960
- r: Math.round(this.r),
961
- g: Math.round(this.g),
962
- b: Math.round(this.b),
963
- a: this.a,
931
+ r, g, b, a: roundPart(a * 100) / 100,
964
932
  };
965
933
  }
966
934
 
967
935
  /**
968
- * Returns the RGBA values concatenated into a string.
969
- * @returns {string} the CSS valid color in RGB/RGBA format
936
+ * Returns the RGBA values concatenated into a CSS3 Module string format.
937
+ * * rgb(255,255,255)
938
+ * * rgba(255,255,255,0.5)
939
+ * @returns {string} the CSS valid colour in RGB/RGBA format
970
940
  */
971
941
  toRgbString() {
972
- const r = Math.round(this.r);
973
- const g = Math.round(this.g);
974
- const b = Math.round(this.b);
975
- return this.a === 1
976
- ? `rgb(${r},${g},${b})`
977
- : `rgba(${r},${g},${b},${this.roundA})`;
942
+ const {
943
+ r, g, b, a,
944
+ } = this.toRgb();
945
+ const [R, G, B] = [r, g, b].map(roundPart);
946
+
947
+ return a === 1
948
+ ? `rgb(${R}, ${G}, ${B})`
949
+ : `rgba(${R}, ${G}, ${B}, ${a})`;
950
+ }
951
+
952
+ /**
953
+ * Returns the RGBA values concatenated into a CSS4 Module string format.
954
+ * * rgb(255 255 255)
955
+ * * rgb(255 255 255 / 50%)
956
+ * @returns {string} the CSS valid colour in CSS4 RGB format
957
+ */
958
+ toRgbCSS4String() {
959
+ const {
960
+ r, g, b, a,
961
+ } = this.toRgb();
962
+ const [R, G, B] = [r, g, b].map(roundPart);
963
+ const A = a === 1 ? '' : ` / ${roundPart(a * 100)}%`;
964
+
965
+ return `rgb(${R} ${G} ${B}${A})`;
978
966
  }
979
967
 
980
968
  /**
981
- * Returns the HEX value of the color.
982
- * @returns {string} the hexadecimal color format
969
+ * Returns the hexadecimal value of the colour. When the parameter is *true*
970
+ * it will find a 3 characters shorthand of the decimal value.
971
+ *
972
+ * @param {boolean=} allow3Char when `true` returns shorthand HEX
973
+ * @returns {string} the hexadecimal colour format
974
+ */
975
+ toHex(allow3Char) {
976
+ const {
977
+ r, g, b, a,
978
+ } = this.toRgb();
979
+
980
+ return a === 1
981
+ ? rgbToHex(r, g, b, allow3Char)
982
+ : rgbaToHex(r, g, b, a, allow3Char);
983
+ }
984
+
985
+ /**
986
+ * Returns the CSS valid hexadecimal vaue of the colour. When the parameter is *true*
987
+ * it will find a 3 characters shorthand of the value.
988
+ *
989
+ * @param {boolean=} allow3Char when `true` returns shorthand HEX
990
+ * @returns {string} the CSS valid colour in hexadecimal format
983
991
  */
984
- toHex() {
985
- return rgbToHex(this.r, this.g, this.b);
992
+ toHexString(allow3Char) {
993
+ return `#${this.toHex(allow3Char)}`;
986
994
  }
987
995
 
988
996
  /**
989
- * Returns the HEX value of the color.
990
- * @returns {string} the CSS valid color in hexadecimal format
997
+ * Returns the HEX8 value of the colour.
998
+ * @param {boolean=} allow4Char when `true` returns shorthand HEX
999
+ * @returns {string} the CSS valid colour in hexadecimal format
991
1000
  */
992
- toHexString() {
993
- return `#${this.toHex()}`;
1001
+ toHex8(allow4Char) {
1002
+ const {
1003
+ r, g, b, a,
1004
+ } = this.toRgb();
1005
+
1006
+ return rgbaToHex(r, g, b, a, allow4Char);
1007
+ }
1008
+
1009
+ /**
1010
+ * Returns the HEX8 value of the colour.
1011
+ * @param {boolean=} allow4Char when `true` returns shorthand HEX
1012
+ * @returns {string} the CSS valid colour in hexadecimal format
1013
+ */
1014
+ toHex8String(allow4Char) {
1015
+ return `#${this.toHex8(allow4Char)}`;
994
1016
  }
995
1017
 
996
1018
  /**
997
- * Returns the color as a HSVA object.
998
- * @returns {CP.HSVA} the `{h,s,v,a}` object
1019
+ * Returns the colour as a HSVA object.
1020
+ * @returns {CP.HSVA} the `{h,s,v,a}` object with [0, 1] ranged values
999
1021
  */
1000
1022
  toHsv() {
1001
- const { h, s, v } = rgbToHsv(this.r, this.g, this.b);
1023
+ const {
1024
+ r, g, b, a,
1025
+ } = this.toRgb();
1026
+ const { h, s, v } = rgbToHsv(r, g, b);
1027
+
1002
1028
  return {
1003
- h: h * 360, s, v, a: this.a,
1029
+ h, s, v, a,
1004
1030
  };
1005
1031
  }
1006
1032
 
1007
1033
  /**
1008
- * Returns the color as a HSLA object.
1009
- * @returns {CP.HSLA}
1034
+ * Returns the colour as an HSLA object.
1035
+ * @returns {CP.HSLA} the `{h,s,l,a}` object with [0, 1] ranged values
1010
1036
  */
1011
1037
  toHsl() {
1012
- const { h, s, l } = rgbToHsl(this.r, this.g, this.b);
1038
+ const {
1039
+ r, g, b, a,
1040
+ } = this.toRgb();
1041
+ const { h, s, l } = rgbToHsl(r, g, b);
1042
+
1013
1043
  return {
1014
- h: h * 360, s, l, a: this.a,
1044
+ h, s, l, a,
1015
1045
  };
1016
1046
  }
1017
1047
 
1018
1048
  /**
1019
- * Returns the HSLA values concatenated into a string.
1020
- * @returns {string} the CSS valid color in HSL/HSLA format
1049
+ * Returns the HSLA values concatenated into a CSS3 Module format string.
1050
+ * * `hsl(150, 100%, 50%)`
1051
+ * * `hsla(150, 100%, 50%, 0.5)`
1052
+ * @returns {string} the CSS valid colour in HSL/HSLA format
1021
1053
  */
1022
1054
  toHslString() {
1023
- const hsl = this.toHsl();
1024
- const h = Math.round(hsl.h);
1025
- const s = Math.round(hsl.s * 100);
1026
- const l = Math.round(hsl.l * 100);
1027
- return this.a === 1
1028
- ? `hsl(${h},${s}%,${l}%)`
1029
- : `hsla(${h},${s}%,${l}%,${this.roundA})`;
1055
+ let {
1056
+ h, s, l, a,
1057
+ } = this.toHsl();
1058
+ h = roundPart(h * 360);
1059
+ s = roundPart(s * 100);
1060
+ l = roundPart(l * 100);
1061
+ a = roundPart(a * 100) / 100;
1062
+
1063
+ return a === 1
1064
+ ? `hsl(${h}, ${s}%, ${l}%)`
1065
+ : `hsla(${h}, ${s}%, ${l}%, ${a})`;
1066
+ }
1067
+
1068
+ /**
1069
+ * Returns the HSLA values concatenated into a CSS4 Module format string.
1070
+ * * `hsl(150deg 100% 50%)`
1071
+ * * `hsl(150deg 100% 50% / 50%)`
1072
+ * @returns {string} the CSS valid colour in CSS4 HSL format
1073
+ */
1074
+ toHslCSS4String() {
1075
+ let {
1076
+ h, s, l, a,
1077
+ } = this.toHsl();
1078
+ h = roundPart(h * 360);
1079
+ s = roundPart(s * 100);
1080
+ l = roundPart(l * 100);
1081
+ a = roundPart(a * 100);
1082
+ const A = a < 100 ? ` / ${roundPart(a)}%` : '';
1083
+
1084
+ return `hsl(${h}deg ${s}% ${l}%${A})`;
1085
+ }
1086
+
1087
+ /**
1088
+ * Returns the colour as an HWBA object.
1089
+ * @returns {CP.HWBA} the `{h,w,b,a}` object with [0, 1] ranged values
1090
+ */
1091
+ toHwb() {
1092
+ const {
1093
+ r, g, b, a,
1094
+ } = this;
1095
+ const { h, w, b: bl } = rgbToHwb(r, g, b);
1096
+ return {
1097
+ h, w, b: bl, a,
1098
+ };
1099
+ }
1100
+
1101
+ /**
1102
+ * Returns the HWBA values concatenated into a string.
1103
+ * @returns {string} the CSS valid colour in HWB format
1104
+ */
1105
+ toHwbString() {
1106
+ let {
1107
+ h, w, b, a,
1108
+ } = this.toHwb();
1109
+ h = roundPart(h * 360);
1110
+ w = roundPart(w * 100);
1111
+ b = roundPart(b * 100);
1112
+ a = roundPart(a * 100);
1113
+ const A = a < 100 ? ` / ${roundPart(a)}%` : '';
1114
+
1115
+ return `hwb(${h}deg ${w}% ${b}%${A})`;
1030
1116
  }
1031
1117
 
1032
1118
  /**
1033
- * Sets the alpha value on the current color.
1034
- * @param {number} alpha a new alpha value in [0-1] range.
1035
- * @returns {Color} a new `Color` instance
1119
+ * Sets the alpha value of the current colour.
1120
+ * @param {number} alpha a new alpha value in the [0, 1] range.
1121
+ * @returns {Color} the `Color` instance
1036
1122
  */
1037
1123
  setAlpha(alpha) {
1038
- this.a = boundAlpha(alpha);
1039
- this.roundA = Math.round(100 * this.a) / 100;
1040
- return this;
1124
+ const self = this;
1125
+ self.a = boundAlpha(alpha);
1126
+ return self;
1041
1127
  }
1042
1128
 
1043
1129
  /**
1044
- * Saturate the color with a given amount.
1045
- * @param {number=} amount a value in [0-100] range
1046
- * @returns {Color} a new `Color` instance
1130
+ * Saturate the colour with a given amount.
1131
+ * @param {number=} amount a value in the [0, 100] range
1132
+ * @returns {Color} the `Color` instance
1047
1133
  */
1048
1134
  saturate(amount) {
1049
- if (!amount) return this;
1050
- const hsl = this.toHsl();
1051
- hsl.s += amount / 100;
1052
- hsl.s = clamp01(hsl.s);
1053
- return new Color(hsl);
1135
+ const self = this;
1136
+ if (typeof amount !== 'number') return self;
1137
+ const { h, s, l } = self.toHsl();
1138
+ const { r, g, b } = hslToRgb(h, clamp01(s + amount / 100), l);
1139
+
1140
+ ObjectAssign(self, { r, g, b });
1141
+ return self;
1054
1142
  }
1055
1143
 
1056
1144
  /**
1057
- * Desaturate the color with a given amount.
1058
- * @param {number=} amount a value in [0-100] range
1059
- * @returns {Color} a new `Color` instance
1145
+ * Desaturate the colour with a given amount.
1146
+ * @param {number=} amount a value in the [0, 100] range
1147
+ * @returns {Color} the `Color` instance
1060
1148
  */
1061
1149
  desaturate(amount) {
1062
- return amount ? this.saturate(-amount) : this;
1150
+ return typeof amount === 'number' ? this.saturate(-amount) : this;
1063
1151
  }
1064
1152
 
1065
1153
  /**
1066
- * Completely desaturates a color into greyscale.
1154
+ * Completely desaturates a colour into greyscale.
1067
1155
  * Same as calling `desaturate(100)`
1068
- * @returns {Color} a new `Color` instance
1156
+ * @returns {Color} the `Color` instance
1069
1157
  */
1070
1158
  greyscale() {
1071
- return this.desaturate(100);
1159
+ return this.saturate(-100);
1160
+ }
1161
+
1162
+ /**
1163
+ * Increase the colour lightness with a given amount.
1164
+ * @param {number=} amount a value in the [0, 100] range
1165
+ * @returns {Color} the `Color` instance
1166
+ */
1167
+ lighten(amount) {
1168
+ const self = this;
1169
+ if (typeof amount !== 'number') return self;
1170
+
1171
+ const { h, s, l } = self.toHsl();
1172
+ const { r, g, b } = hslToRgb(h, s, clamp01(l + amount / 100));
1173
+
1174
+ ObjectAssign(self, { r, g, b });
1175
+ return self;
1176
+ }
1177
+
1178
+ /**
1179
+ * Decrease the colour lightness with a given amount.
1180
+ * @param {number=} amount a value in the [0, 100] range
1181
+ * @returns {Color} the `Color` instance
1182
+ */
1183
+ darken(amount) {
1184
+ return typeof amount === 'number' ? this.lighten(-amount) : this;
1185
+ }
1186
+
1187
+ /**
1188
+ * Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
1189
+ * Values outside of this range will be wrapped into this range.
1190
+ *
1191
+ * @param {number=} amount a value in the [0, 100] range
1192
+ * @returns {Color} the `Color` instance
1193
+ */
1194
+ spin(amount) {
1195
+ const self = this;
1196
+ if (typeof amount !== 'number') return self;
1197
+
1198
+ const { h, s, l } = self.toHsl();
1199
+ const { r, g, b } = hslToRgb(clamp01(((h * 360 + amount) % 360) / 360), s, l);
1200
+
1201
+ ObjectAssign(self, { r, g, b });
1202
+ return self;
1072
1203
  }
1073
1204
 
1074
1205
  /** Returns a clone of the current `Color` instance. */
@@ -1077,51 +1208,56 @@
1077
1208
  }
1078
1209
 
1079
1210
  /**
1080
- * Returns the color value in CSS valid string format.
1081
- * @returns {string}
1211
+ * Returns the colour value in CSS valid string format.
1212
+ * @param {boolean=} allowShort when *true*, HEX values can be shorthand
1213
+ * @returns {string} the CSS valid colour in the configured format
1082
1214
  */
1083
- toString() {
1084
- const { format } = this;
1215
+ toString(allowShort) {
1216
+ const self = this;
1217
+ const { format } = self;
1085
1218
 
1086
- if (format === 'rgb') {
1087
- return this.toRgbString();
1088
- }
1089
- if (format === 'hsl') {
1090
- return this.toHslString();
1091
- }
1092
- return this.toHexString();
1219
+ if (format === 'hex') return self.toHexString(allowShort);
1220
+ if (format === 'hsl') return self.toHslString();
1221
+ if (format === 'hwb') return self.toHwbString();
1222
+
1223
+ return self.toRgbString();
1093
1224
  }
1094
1225
  }
1095
1226
 
1096
1227
  ObjectAssign(Color, {
1097
- colorNames,
1228
+ ANGLES,
1229
+ CSS_ANGLE,
1098
1230
  CSS_INTEGER,
1099
1231
  CSS_NUMBER,
1100
1232
  CSS_UNIT,
1101
- PERMISSIVE_MATCH3,
1102
- PERMISSIVE_MATCH4,
1233
+ CSS_UNIT2,
1234
+ PERMISSIVE_MATCH,
1103
1235
  matchers,
1104
1236
  isOnePointZero,
1105
1237
  isPercentage,
1106
1238
  isValidCSSUnit,
1239
+ pad2,
1240
+ clamp01,
1107
1241
  bound01,
1108
1242
  boundAlpha,
1109
- clamp01,
1110
- getHexFromColorName,
1111
- convertToPercentage,
1243
+ getRGBFromName,
1112
1244
  convertHexToDecimal,
1113
- pad2,
1114
- rgbToRgb,
1245
+ convertDecimalToHex,
1115
1246
  rgbToHsl,
1116
1247
  rgbToHex,
1117
1248
  rgbToHsv,
1249
+ rgbToHwb,
1250
+ rgbaToHex,
1118
1251
  hslToRgb,
1119
1252
  hsvToRgb,
1120
- hue2rgb,
1253
+ hueToRgb,
1254
+ hwbToRgb,
1121
1255
  parseIntFromHex,
1122
1256
  numberInputToObject,
1123
1257
  stringInputToObject,
1124
1258
  inputToRGB,
1259
+ roundPart,
1260
+ ObjectAssign,
1125
1261
  });
1126
1262
 
1127
1263
  /** @type {Record<string, any>} */
@@ -1220,6 +1356,12 @@
1220
1356
  }
1221
1357
  };
1222
1358
 
1359
+ /**
1360
+ * A global namespace for aria-description.
1361
+ * @type {string}
1362
+ */
1363
+ const ariaDescription = 'aria-description';
1364
+
1223
1365
  /**
1224
1366
  * A global namespace for aria-selected.
1225
1367
  * @type {string}
@@ -1232,6 +1374,24 @@
1232
1374
  */
1233
1375
  const ariaExpanded = 'aria-expanded';
1234
1376
 
1377
+ /**
1378
+ * A global namespace for aria-valuetext.
1379
+ * @type {string}
1380
+ */
1381
+ const ariaValueText = 'aria-valuetext';
1382
+
1383
+ /**
1384
+ * A global namespace for aria-valuenow.
1385
+ * @type {string}
1386
+ */
1387
+ const ariaValueNow = 'aria-valuenow';
1388
+
1389
+ /**
1390
+ * A global namespace for aria-haspopup.
1391
+ * @type {string}
1392
+ */
1393
+ const ariaHasPopup = 'aria-haspopup';
1394
+
1235
1395
  /**
1236
1396
  * A global namespace for aria-hidden.
1237
1397
  * @type {string}
@@ -1286,22 +1446,106 @@
1286
1446
  */
1287
1447
  const keyEscape = 'Escape';
1288
1448
 
1289
- // @ts-ignore
1290
- const { userAgentData: uaDATA } = navigator;
1291
-
1292
1449
  /**
1293
- * A global namespace for `userAgentData` object.
1450
+ * A global namespace for `focusin` event.
1451
+ * @type {string}
1294
1452
  */
1295
- const userAgentData = uaDATA;
1296
-
1297
- const { userAgent: userAgentString } = navigator;
1453
+ const focusinEvent = 'focusin';
1298
1454
 
1299
1455
  /**
1300
- * A global namespace for `navigator.userAgent` string.
1456
+ * A global namespace for `click` event.
1457
+ * @type {string}
1301
1458
  */
1302
- const userAgent = userAgentString;
1459
+ const mouseclickEvent = 'click';
1303
1460
 
1304
- const mobileBrands = /iPhone|iPad|iPod|Android/i;
1461
+ /**
1462
+ * A global namespace for `keydown` event.
1463
+ * @type {string}
1464
+ */
1465
+ const keydownEvent = 'keydown';
1466
+
1467
+ /**
1468
+ * A global namespace for `change` event.
1469
+ * @type {string}
1470
+ */
1471
+ const changeEvent = 'change';
1472
+
1473
+ /**
1474
+ * A global namespace for `touchstart` event.
1475
+ * @type {string}
1476
+ */
1477
+ const touchstartEvent = 'touchstart';
1478
+
1479
+ /**
1480
+ * A global namespace for `touchmove` event.
1481
+ * @type {string}
1482
+ */
1483
+ const touchmoveEvent = 'touchmove';
1484
+
1485
+ /**
1486
+ * A global namespace for `touchend` event.
1487
+ * @type {string}
1488
+ */
1489
+ const touchendEvent = 'touchend';
1490
+
1491
+ /**
1492
+ * A global namespace for `mousedown` event.
1493
+ * @type {string}
1494
+ */
1495
+ const mousedownEvent = 'mousedown';
1496
+
1497
+ /**
1498
+ * A global namespace for `mousemove` event.
1499
+ * @type {string}
1500
+ */
1501
+ const mousemoveEvent = 'mousemove';
1502
+
1503
+ /**
1504
+ * A global namespace for `mouseup` event.
1505
+ * @type {string}
1506
+ */
1507
+ const mouseupEvent = 'mouseup';
1508
+
1509
+ /**
1510
+ * A global namespace for `scroll` event.
1511
+ * @type {string}
1512
+ */
1513
+ const scrollEvent = 'scroll';
1514
+
1515
+ /**
1516
+ * A global namespace for `keyup` event.
1517
+ * @type {string}
1518
+ */
1519
+ const keyupEvent = 'keyup';
1520
+
1521
+ /**
1522
+ * A global namespace for `resize` event.
1523
+ * @type {string}
1524
+ */
1525
+ const resizeEvent = 'resize';
1526
+
1527
+ /**
1528
+ * A global namespace for `focusout` event.
1529
+ * @type {string}
1530
+ */
1531
+ const focusoutEvent = 'focusout';
1532
+
1533
+ // @ts-ignore
1534
+ const { userAgentData: uaDATA } = navigator;
1535
+
1536
+ /**
1537
+ * A global namespace for `userAgentData` object.
1538
+ */
1539
+ const userAgentData = uaDATA;
1540
+
1541
+ const { userAgent: userAgentString } = navigator;
1542
+
1543
+ /**
1544
+ * A global namespace for `navigator.userAgent` string.
1545
+ */
1546
+ const userAgent = userAgentString;
1547
+
1548
+ const mobileBrands = /iPhone|iPad|iPod|Android/i;
1305
1549
  let isMobileCheck = false;
1306
1550
 
1307
1551
  if (userAgentData) {
@@ -1317,7 +1561,39 @@
1317
1561
  */
1318
1562
  const isMobile = isMobileCheck;
1319
1563
 
1320
- let elementUID = 1;
1564
+ /**
1565
+ * Returns the `document.documentElement` or the `<html>` element.
1566
+ *
1567
+ * @param {(Node | HTMLElement | Element | globalThis)=} node
1568
+ * @returns {HTMLElement | HTMLHtmlElement}
1569
+ */
1570
+ function getDocumentElement(node) {
1571
+ return getDocument(node).documentElement;
1572
+ }
1573
+
1574
+ /**
1575
+ * Returns the `Window` object of a target node.
1576
+ * @see https://github.com/floating-ui/floating-ui
1577
+ *
1578
+ * @param {(Node | HTMLElement | Element | Window)=} node target node
1579
+ * @returns {globalThis}
1580
+ */
1581
+ function getWindow(node) {
1582
+ if (node == null) {
1583
+ return window;
1584
+ }
1585
+
1586
+ if (!(node instanceof Window)) {
1587
+ const { ownerDocument } = node;
1588
+ return ownerDocument ? ownerDocument.defaultView || window : window;
1589
+ }
1590
+
1591
+ // @ts-ignore
1592
+ return node;
1593
+ }
1594
+
1595
+ let elementUID = 0;
1596
+ let elementMapUID = 0;
1321
1597
  const elementIDMap = new Map();
1322
1598
 
1323
1599
  /**
@@ -1328,27 +1604,25 @@
1328
1604
  * @returns {number} an existing or new unique ID
1329
1605
  */
1330
1606
  function getUID(element, key) {
1331
- elementUID += 1;
1332
- let elMap = elementIDMap.get(element);
1333
- let result = elementUID;
1334
-
1335
- if (key && key.length) {
1336
- if (elMap) {
1337
- const elMapId = elMap.get(key);
1338
- if (!Number.isNaN(elMapId)) {
1339
- result = elMapId;
1340
- } else {
1341
- elMap.set(key, result);
1342
- }
1343
- } else {
1344
- elementIDMap.set(element, new Map());
1345
- elMap = elementIDMap.get(element);
1346
- elMap.set(key, result);
1607
+ let result = key ? elementUID : elementMapUID;
1608
+
1609
+ if (key) {
1610
+ const elID = getUID(element);
1611
+ const elMap = elementIDMap.get(elID) || new Map();
1612
+ if (!elementIDMap.has(elID)) {
1613
+ elementIDMap.set(elID, elMap);
1347
1614
  }
1348
- } else if (!Number.isNaN(elMap)) {
1349
- result = elMap;
1615
+ if (!elMap.has(key)) {
1616
+ elMap.set(key, result);
1617
+ elementUID += 1;
1618
+ } else result = elMap.get(key);
1350
1619
  } else {
1351
- elementIDMap.set(element, result);
1620
+ const elkey = element.id || element;
1621
+
1622
+ if (!elementIDMap.has(elkey)) {
1623
+ elementIDMap.set(elkey, result);
1624
+ elementMapUID += 1;
1625
+ } else result = elementIDMap.get(elkey);
1352
1626
  }
1353
1627
  return result;
1354
1628
  }
@@ -1388,17 +1662,56 @@
1388
1662
  }
1389
1663
 
1390
1664
  /**
1391
- * A shortcut for `(document|Element).querySelectorAll`.
1665
+ * A global namespace for 'transitionDuration' string.
1666
+ * @type {string}
1667
+ */
1668
+ const transitionDuration = 'transitionDuration';
1669
+
1670
+ /**
1671
+ * A global namespace for `transitionProperty` string for modern browsers.
1392
1672
  *
1393
- * @param {string} selector the input selector
1394
- * @param {(HTMLElement | Element | Document | Node)=} parent optional node to look into
1395
- * @return {NodeListOf<HTMLElement | Element>} the query result
1673
+ * @type {string}
1396
1674
  */
1397
- function querySelectorAll(selector, parent) {
1398
- const lookUp = parent && parentNodes
1399
- .some((x) => parent instanceof x) ? parent : getDocument();
1400
- // @ts-ignore -- `ShadowRoot` is also a node
1401
- return lookUp.querySelectorAll(selector);
1675
+ const transitionProperty = 'transitionProperty';
1676
+
1677
+ /**
1678
+ * Utility to get the computed `transitionDuration`
1679
+ * from Element in miliseconds.
1680
+ *
1681
+ * @param {HTMLElement | Element} element target
1682
+ * @return {number} the value in miliseconds
1683
+ */
1684
+ function getElementTransitionDuration(element) {
1685
+ const propertyValue = getElementStyle(element, transitionProperty);
1686
+ const durationValue = getElementStyle(element, transitionDuration);
1687
+ const durationScale = durationValue.includes('ms') ? 1 : 1000;
1688
+ const duration = propertyValue && propertyValue !== 'none'
1689
+ ? parseFloat(durationValue) * durationScale : 0;
1690
+
1691
+ return !Number.isNaN(duration) ? duration : 0;
1692
+ }
1693
+
1694
+ /**
1695
+ * A global array with `Element` | `HTMLElement`.
1696
+ */
1697
+ const elementNodes = [Element, HTMLElement];
1698
+
1699
+ /**
1700
+ * Utility to check if target is typeof `HTMLElement`, `Element`, `Node`
1701
+ * or find one that matches a selector.
1702
+ *
1703
+ * @param {HTMLElement | Element | string} selector the input selector or target element
1704
+ * @param {(HTMLElement | Element | Document)=} parent optional node to look into
1705
+ * @return {(HTMLElement | Element)?} the `HTMLElement` or `querySelector` result
1706
+ */
1707
+ function querySelector(selector, parent) {
1708
+ const lookUp = parentNodes.some((x) => parent instanceof x)
1709
+ ? parent : getDocument();
1710
+
1711
+ // @ts-ignore
1712
+ return elementNodes.some((x) => selector instanceof x)
1713
+ // @ts-ignore
1714
+ ? selector : lookUp.querySelector(selector);
1402
1715
  }
1403
1716
 
1404
1717
  /**
@@ -1418,6 +1731,20 @@
1418
1731
  || closest(element.getRootNode().host, selector)) : null;
1419
1732
  }
1420
1733
 
1734
+ /**
1735
+ * Shortcut for `HTMLElement.getElementsByClassName` method. Some `Node` elements
1736
+ * like `ShadowRoot` do not support `getElementsByClassName`.
1737
+ *
1738
+ * @param {string} selector the class name
1739
+ * @param {(HTMLElement | Element | Document)=} parent optional Element to look into
1740
+ * @return {HTMLCollectionOf<HTMLElement | Element>} the 'HTMLCollection'
1741
+ */
1742
+ function getElementsByClassName(selector, parent) {
1743
+ const lookUp = parent && parentNodes.some((x) => parent instanceof x)
1744
+ ? parent : getDocument();
1745
+ return lookUp.getElementsByClassName(selector);
1746
+ }
1747
+
1421
1748
  /**
1422
1749
  * This is a shortie for `document.createElementNS` method
1423
1750
  * which allows you to create a new `HTMLElement` for a given `tagName`
@@ -1525,6 +1852,109 @@
1525
1852
  */
1526
1853
  const getInstance = (target, component) => Data.get(target, component);
1527
1854
 
1855
+ /**
1856
+ * The raw value or a given component option.
1857
+ *
1858
+ * @typedef {string | HTMLElement | Function | number | boolean | null} niceValue
1859
+ */
1860
+
1861
+ /**
1862
+ * Utility to normalize component options
1863
+ *
1864
+ * @param {any} value the input value
1865
+ * @return {niceValue} the normalized value
1866
+ */
1867
+ function normalizeValue(value) {
1868
+ if (value === 'true') { // boolean
1869
+ return true;
1870
+ }
1871
+
1872
+ if (value === 'false') { // boolean
1873
+ return false;
1874
+ }
1875
+
1876
+ if (!Number.isNaN(+value)) { // number
1877
+ return +value;
1878
+ }
1879
+
1880
+ if (value === '' || value === 'null') { // null
1881
+ return null;
1882
+ }
1883
+
1884
+ // string / function / HTMLElement / object
1885
+ return value;
1886
+ }
1887
+
1888
+ /**
1889
+ * Shortcut for `String.toLowerCase()`.
1890
+ *
1891
+ * @param {string} source input string
1892
+ * @returns {string} lowercase output string
1893
+ */
1894
+ const toLowerCase = (source) => source.toLowerCase();
1895
+
1896
+ /**
1897
+ * Utility to normalize component options.
1898
+ *
1899
+ * @param {HTMLElement | Element} element target
1900
+ * @param {Record<string, any>} defaultOps component default options
1901
+ * @param {Record<string, any>} inputOps component instance options
1902
+ * @param {string=} ns component namespace
1903
+ * @return {Record<string, any>} normalized component options object
1904
+ */
1905
+ function normalizeOptions(element, defaultOps, inputOps, ns) {
1906
+ // @ts-ignore -- our targets are always `HTMLElement`
1907
+ const data = { ...element.dataset };
1908
+ /** @type {Record<string, any>} */
1909
+ const normalOps = {};
1910
+ /** @type {Record<string, any>} */
1911
+ const dataOps = {};
1912
+ const title = 'title';
1913
+
1914
+ ObjectKeys(data).forEach((k) => {
1915
+ const key = ns && k.includes(ns)
1916
+ ? k.replace(ns, '').replace(/[A-Z]/, (match) => toLowerCase(match))
1917
+ : k;
1918
+
1919
+ dataOps[key] = normalizeValue(data[k]);
1920
+ });
1921
+
1922
+ ObjectKeys(inputOps).forEach((k) => {
1923
+ inputOps[k] = normalizeValue(inputOps[k]);
1924
+ });
1925
+
1926
+ ObjectKeys(defaultOps).forEach((k) => {
1927
+ if (k in inputOps) {
1928
+ normalOps[k] = inputOps[k];
1929
+ } else if (k in dataOps) {
1930
+ normalOps[k] = dataOps[k];
1931
+ } else {
1932
+ normalOps[k] = k === title
1933
+ ? getAttribute(element, title)
1934
+ : defaultOps[k];
1935
+ }
1936
+ });
1937
+
1938
+ return normalOps;
1939
+ }
1940
+
1941
+ /**
1942
+ * Utility to force re-paint of an `HTMLElement` target.
1943
+ *
1944
+ * @param {HTMLElement | Element} element is the target
1945
+ * @return {number} the `Element.offsetHeight` value
1946
+ */
1947
+ // @ts-ignore
1948
+ const reflow = (element) => element.offsetHeight;
1949
+
1950
+ /**
1951
+ * Utility to focus an `HTMLElement` target.
1952
+ *
1953
+ * @param {HTMLElement | Element} element is the target
1954
+ */
1955
+ // @ts-ignore -- `Element`s resulted from querySelector can focus too
1956
+ const focus = (element) => element.focus();
1957
+
1528
1958
  /**
1529
1959
  * Check class in `HTMLElement.classList`.
1530
1960
  *
@@ -1559,37 +1989,39 @@
1559
1989
  }
1560
1990
 
1561
1991
  /**
1562
- * Shortcut for `HTMLElement.hasAttribute()` method.
1563
- * @param {HTMLElement | Element} element target element
1564
- * @param {string} attribute attribute name
1565
- * @returns {boolean} the query result
1566
- */
1567
- const hasAttribute = (element, attribute) => element.hasAttribute(attribute);
1568
-
1569
- /**
1570
- * Shortcut for `HTMLElement.setAttribute()` method.
1992
+ * Shortcut for `HTMLElement.removeAttribute()` method.
1571
1993
  * @param {HTMLElement | Element} element target element
1572
1994
  * @param {string} attribute attribute name
1573
- * @param {string} value attribute value
1574
1995
  * @returns {void}
1575
1996
  */
1576
- const setAttribute = (element, attribute, value) => element.setAttribute(attribute, value);
1997
+ const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
1577
1998
 
1578
- /**
1579
- * Shortcut for `HTMLElement.getAttribute()` method.
1580
- * @param {HTMLElement | Element} element target element
1581
- * @param {string} attribute attribute name
1582
- * @returns {string?} attribute value
1583
- */
1584
- const getAttribute = (element, attribute) => element.getAttribute(attribute);
1999
+ /** @type {Record<string, string>} */
2000
+ const colorPickerLabels = {
2001
+ pickerLabel: 'Colour Picker',
2002
+ appearanceLabel: 'Colour Appearance',
2003
+ valueLabel: 'Colour Value',
2004
+ toggleLabel: 'Select Colour',
2005
+ presetsLabel: 'Colour Presets',
2006
+ defaultsLabel: 'Colour Defaults',
2007
+ formatLabel: 'Format',
2008
+ alphaLabel: 'Alpha',
2009
+ hexLabel: 'Hexadecimal',
2010
+ hueLabel: 'Hue',
2011
+ whitenessLabel: 'Whiteness',
2012
+ blacknessLabel: 'Blackness',
2013
+ saturationLabel: 'Saturation',
2014
+ lightnessLabel: 'Lightness',
2015
+ redLabel: 'Red',
2016
+ greenLabel: 'Green',
2017
+ blueLabel: 'Blue',
2018
+ };
1585
2019
 
1586
2020
  /**
1587
- * Shortcut for `HTMLElement.removeAttribute()` method.
1588
- * @param {HTMLElement | Element} element target element
1589
- * @param {string} attribute attribute name
1590
- * @returns {void}
2021
+ * A list of 17 color names used for WAI-ARIA compliance.
2022
+ * @type {string[]}
1591
2023
  */
1592
- const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
2024
+ const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
1593
2025
 
1594
2026
  /**
1595
2027
  * Shortcut for `String.toUpperCase()`.
@@ -1602,12 +2034,13 @@
1602
2034
  const vHidden = 'v-hidden';
1603
2035
 
1604
2036
  /**
1605
- * Returns the color form.
2037
+ * Returns the color form for `ColorPicker`.
2038
+ *
1606
2039
  * @param {CP.ColorPicker} self the `ColorPicker` instance
1607
- * @returns {HTMLElement | Element}
2040
+ * @returns {HTMLElement | Element} a new `<div>` element with color component `<input>`
1608
2041
  */
1609
2042
  function getColorForm(self) {
1610
- const { format, id } = self;
2043
+ const { format, id, componentLabels } = self;
1611
2044
  const colorForm = createElement({
1612
2045
  tagName: 'div',
1613
2046
  className: `color-form ${format}`,
@@ -1616,111 +2049,353 @@
1616
2049
  let components = ['hex'];
1617
2050
  if (format === 'rgb') components = ['red', 'green', 'blue', 'alpha'];
1618
2051
  else if (format === 'hsl') components = ['hue', 'saturation', 'lightness', 'alpha'];
2052
+ else if (format === 'hwb') components = ['hue', 'whiteness', 'blackness', 'alpha'];
1619
2053
 
1620
2054
  components.forEach((c) => {
1621
2055
  const [C] = format === 'hex' ? ['#'] : toUpperCase(c).split('');
1622
2056
  const cID = `color_${format}_${c}_${id}`;
2057
+ const formatLabel = componentLabels[`${c}Label`];
1623
2058
  const cInputLabel = createElement({ tagName: 'label' });
1624
2059
  setAttribute(cInputLabel, 'for', cID);
1625
2060
  cInputLabel.append(
1626
2061
  createElement({ tagName: 'span', ariaHidden: 'true', innerText: `${C}:` }),
1627
- createElement({ tagName: 'span', className: vHidden, innerText: `${c}` }),
2062
+ createElement({ tagName: 'span', className: vHidden, innerText: formatLabel }),
1628
2063
  );
1629
2064
  const cInput = createElement({
1630
2065
  tagName: 'input',
1631
- id: cID, // name: cID,
2066
+ id: cID,
2067
+ // name: cID, - prevent saving the value to a form
1632
2068
  type: format === 'hex' ? 'text' : 'number',
1633
- value: c === 'alpha' ? '1' : '0',
2069
+ value: c === 'alpha' ? '100' : '0',
1634
2070
  className: `color-input ${c}`,
1635
- autocomplete: 'off',
1636
- spellcheck: 'false',
1637
2071
  });
1638
- if (format !== 'hex') {
1639
- // alpha
1640
- let max = '1';
1641
- let step = '0.01';
1642
- if (c !== 'alpha') {
1643
- if (format === 'rgb') { max = '255'; step = '1'; } else if (c === 'hue') { max = '360'; step = '1'; } else { max = '100'; step = '1'; }
2072
+ setAttribute(cInput, 'autocomplete', 'off');
2073
+ setAttribute(cInput, 'spellcheck', 'false');
2074
+
2075
+ // alpha
2076
+ let max = '100';
2077
+ let step = '1';
2078
+ if (c !== 'alpha') {
2079
+ if (format === 'rgb') {
2080
+ max = '255'; step = '1';
2081
+ } else if (c === 'hue') {
2082
+ max = '360'; step = '1';
1644
2083
  }
1645
- ObjectAssign(cInput, {
1646
- min: '0',
1647
- max,
1648
- step,
1649
- });
1650
2084
  }
2085
+ ObjectAssign(cInput, {
2086
+ min: '0',
2087
+ max,
2088
+ step,
2089
+ });
1651
2090
  colorForm.append(cInputLabel, cInput);
1652
2091
  });
1653
2092
  return colorForm;
1654
2093
  }
1655
2094
 
1656
2095
  /**
1657
- * Returns a new color control `HTMLElement`.
1658
- * @param {number} iteration
1659
- * @param {number} id
1660
- * @param {number} width
1661
- * @param {number} height
1662
- * @param {string=} labelledby
1663
- * @returns {HTMLElement | Element}
2096
+ * A global namespace for aria-label.
2097
+ * @type {string}
2098
+ */
2099
+ const ariaLabel = 'aria-label';
2100
+
2101
+ /**
2102
+ * A global namespace for aria-valuemin.
2103
+ * @type {string}
2104
+ */
2105
+ const ariaValueMin = 'aria-valuemin';
2106
+
2107
+ /**
2108
+ * A global namespace for aria-valuemax.
2109
+ * @type {string}
2110
+ */
2111
+ const ariaValueMax = 'aria-valuemax';
2112
+
2113
+ const tabIndex = 'tabindex';
2114
+
2115
+ /**
2116
+ * Returns all color controls for `ColorPicker`.
2117
+ *
2118
+ * @param {CP.ColorPicker} self the `ColorPicker` instance
2119
+ * @returns {HTMLElement | Element} color controls
1664
2120
  */
1665
- function getColorControl(iteration, id, width, height, labelledby) {
1666
- const labelID = `appearance${iteration}_${id}`;
1667
- const knobClass = iteration === 1 ? 'color-pointer' : 'color-slider';
1668
- const control = createElement({
2121
+ function getColorControls(self) {
2122
+ const { format, componentLabels } = self;
2123
+ const {
2124
+ hueLabel, alphaLabel, lightnessLabel, saturationLabel,
2125
+ whitenessLabel, blacknessLabel,
2126
+ } = componentLabels;
2127
+
2128
+ const max1 = format === 'hsl' ? 360 : 100;
2129
+ const max2 = format === 'hsl' ? 100 : 360;
2130
+ const max3 = 100;
2131
+
2132
+ let ctrl1Label = format === 'hsl'
2133
+ ? `${hueLabel} & ${lightnessLabel}`
2134
+ : `${lightnessLabel} & ${saturationLabel}`;
2135
+
2136
+ ctrl1Label = format === 'hwb'
2137
+ ? `${whitenessLabel} & ${blacknessLabel}`
2138
+ : ctrl1Label;
2139
+
2140
+ const ctrl2Label = format === 'hsl'
2141
+ ? `${saturationLabel}`
2142
+ : `${hueLabel}`;
2143
+
2144
+ const colorControls = createElement({
1669
2145
  tagName: 'div',
1670
- className: 'color-control',
2146
+ className: `color-controls ${format}`,
1671
2147
  });
1672
- setAttribute(control, 'role', 'presentation');
1673
2148
 
1674
- control.append(
1675
- createElement({
1676
- id: labelID,
1677
- tagName: 'label',
1678
- className: `color-label ${vHidden}`,
2149
+ const colorPointer = 'color-pointer';
2150
+ const colorSlider = 'color-slider';
2151
+
2152
+ const controls = [
2153
+ {
2154
+ i: 1,
2155
+ c: colorPointer,
2156
+ l: ctrl1Label,
2157
+ min: 0,
2158
+ max: max1,
2159
+ },
2160
+ {
2161
+ i: 2,
2162
+ c: colorSlider,
2163
+ l: ctrl2Label,
2164
+ min: 0,
2165
+ max: max2,
2166
+ },
2167
+ {
2168
+ i: 3,
2169
+ c: colorSlider,
2170
+ l: alphaLabel,
2171
+ min: 0,
2172
+ max: max3,
2173
+ },
2174
+ ];
2175
+
2176
+ controls.forEach((template) => {
2177
+ const {
2178
+ i, c, l, min, max,
2179
+ } = template;
2180
+ const control = createElement({
2181
+ tagName: 'div',
2182
+ className: 'color-control',
2183
+ });
2184
+ setAttribute(control, 'role', 'presentation');
2185
+
2186
+ control.append(
2187
+ createElement({
2188
+ tagName: 'div',
2189
+ className: `visual-control visual-control${i}`,
2190
+ }),
2191
+ );
2192
+
2193
+ const knob = createElement({
2194
+ tagName: 'div',
2195
+ className: `${c} knob`,
1679
2196
  ariaLive: 'polite',
1680
- }),
1681
- createElement({
1682
- tagName: 'canvas',
1683
- className: `visual-control${iteration}`,
1684
- ariaHidden: 'true',
1685
- width: `${width}`,
1686
- height: `${height}`,
1687
- }),
1688
- );
1689
-
1690
- const knob = createElement({
1691
- tagName: 'div',
1692
- className: `${knobClass} knob`,
2197
+ });
2198
+
2199
+ setAttribute(knob, ariaLabel, l);
2200
+ setAttribute(knob, 'role', 'slider');
2201
+ setAttribute(knob, tabIndex, '0');
2202
+ setAttribute(knob, ariaValueMin, `${min}`);
2203
+ setAttribute(knob, ariaValueMax, `${max}`);
2204
+ control.append(knob);
2205
+ colorControls.append(control);
2206
+ });
2207
+
2208
+ return colorControls;
2209
+ }
2210
+
2211
+ /**
2212
+ * Helps setting CSS variables to the color-menu.
2213
+ * @param {HTMLElement} element
2214
+ * @param {Record<string,any>} props
2215
+ */
2216
+ function setCSSProperties(element, props) {
2217
+ ObjectKeys(props).forEach((prop) => {
2218
+ element.style.setProperty(prop, props[prop]);
2219
+ });
2220
+ }
2221
+
2222
+ /**
2223
+ * @class
2224
+ * Returns a color palette with a given set of parameters.
2225
+ * @example
2226
+ * new ColorPalette(0, 12, 10);
2227
+ * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
2228
+ */
2229
+ class ColorPalette {
2230
+ /**
2231
+ * The `hue` parameter is optional, which would be set to 0.
2232
+ * @param {number[]} args represeinting hue, hueSteps, lightSteps
2233
+ * * `args.hue` the starting Hue [0, 360]
2234
+ * * `args.hueSteps` Hue Steps Count [5, 24]
2235
+ * * `args.lightSteps` Lightness Steps Count [5, 12]
2236
+ */
2237
+ constructor(...args) {
2238
+ let hue = 0;
2239
+ let hueSteps = 12;
2240
+ let lightSteps = 10;
2241
+ let lightnessArray = [0.5];
2242
+
2243
+ if (args.length === 3) {
2244
+ [hue, hueSteps, lightSteps] = args;
2245
+ } else if (args.length === 2) {
2246
+ [hueSteps, lightSteps] = args;
2247
+ } else {
2248
+ throw TypeError('ColorPalette requires minimum 2 arguments');
2249
+ }
2250
+
2251
+ /** @type {string[]} */
2252
+ const colors = [];
2253
+
2254
+ const hueStep = 360 / hueSteps;
2255
+ const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
2256
+ const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
2257
+
2258
+ let lightStep = 0.25;
2259
+ lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
2260
+ lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
2261
+ lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
2262
+ lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
2263
+ lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
2264
+ lightStep = lightSteps > 13 ? estimatedStep : lightStep;
2265
+
2266
+ // light tints
2267
+ for (let i = 1; i < half + 1; i += 1) {
2268
+ lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
2269
+ }
2270
+
2271
+ // dark tints
2272
+ for (let i = 1; i < lightSteps - half; i += 1) {
2273
+ lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
2274
+ }
2275
+
2276
+ // feed `colors` Array
2277
+ for (let i = 0; i < hueSteps; i += 1) {
2278
+ const currentHue = ((hue + i * hueStep) % 360) / 360;
2279
+ lightnessArray.forEach((l) => {
2280
+ colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
2281
+ });
2282
+ }
2283
+
2284
+ this.hue = hue;
2285
+ this.hueSteps = hueSteps;
2286
+ this.lightSteps = lightSteps;
2287
+ this.colors = colors;
2288
+ }
2289
+ }
2290
+
2291
+ /**
2292
+ * Returns a color-defaults with given values and class.
2293
+ * @param {CP.ColorPicker} self
2294
+ * @param {CP.ColorPalette | string[]} colorsSource
2295
+ * @param {string} menuClass
2296
+ * @returns {HTMLElement | Element}
2297
+ */
2298
+ function getColorMenu(self, colorsSource, menuClass) {
2299
+ const { input, format, componentLabels } = self;
2300
+ const { defaultsLabel, presetsLabel } = componentLabels;
2301
+ const isOptionsMenu = menuClass === 'color-options';
2302
+ const isPalette = colorsSource instanceof ColorPalette;
2303
+ const menuLabel = isOptionsMenu ? presetsLabel : defaultsLabel;
2304
+ let colorsArray = isPalette ? colorsSource.colors : colorsSource;
2305
+ colorsArray = colorsArray instanceof Array ? colorsArray : [];
2306
+ const colorsCount = colorsArray.length;
2307
+ const { lightSteps } = isPalette ? colorsSource : { lightSteps: null };
2308
+ const fit = lightSteps || [9, 10].find((x) => colorsCount > x * 2 && !(colorsCount % x)) || 5;
2309
+ const isMultiLine = isOptionsMenu && colorsCount > fit;
2310
+ let rowCountHover = 2;
2311
+ rowCountHover = isMultiLine && colorsCount >= fit * 2 ? 3 : rowCountHover;
2312
+ rowCountHover = colorsCount >= fit * 3 ? 4 : rowCountHover;
2313
+ rowCountHover = colorsCount >= fit * 4 ? 5 : rowCountHover;
2314
+ const rowCount = rowCountHover - (colorsCount < fit * 3 ? 1 : 2);
2315
+ const isScrollable = isMultiLine && colorsCount > rowCount * fit;
2316
+ let finalClass = menuClass;
2317
+ finalClass += isScrollable ? ' scrollable' : '';
2318
+ finalClass += isMultiLine ? ' multiline' : '';
2319
+ const gap = isMultiLine ? '1px' : '0.25rem';
2320
+ let optionSize = isMultiLine ? 1.75 : 2;
2321
+ optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2322
+ const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2323
+ const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2324
+
2325
+ const menu = createElement({
2326
+ tagName: 'ul',
2327
+ className: finalClass,
2328
+ });
2329
+ setAttribute(menu, 'role', 'listbox');
2330
+ setAttribute(menu, ariaLabel, menuLabel);
2331
+
2332
+ if (isScrollable) { // @ts-ignore
2333
+ setCSSProperties(menu, {
2334
+ '--grid-item-size': `${optionSize}rem`,
2335
+ '--grid-fit': fit,
2336
+ '--grid-gap': gap,
2337
+ '--grid-height': menuHeight,
2338
+ '--grid-hover-height': menuHeightHover,
2339
+ });
2340
+ }
2341
+
2342
+ colorsArray.forEach((x) => {
2343
+ const [value, label] = x.trim().split(':');
2344
+ const xRealColor = new Color(value, format).toString();
2345
+ const isActive = xRealColor === getAttribute(input, 'value');
2346
+ const active = isActive ? ' active' : '';
2347
+
2348
+ const option = createElement({
2349
+ tagName: 'li',
2350
+ className: `color-option${active}`,
2351
+ innerText: `${label || x}`,
2352
+ });
2353
+
2354
+ setAttribute(option, tabIndex, '0');
2355
+ setAttribute(option, 'data-value', `${value}`);
2356
+ setAttribute(option, 'role', 'option');
2357
+ setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2358
+
2359
+ if (isOptionsMenu) {
2360
+ setElementStyle(option, { backgroundColor: x });
2361
+ }
2362
+
2363
+ menu.append(option);
1693
2364
  });
1694
- setAttribute(knob, ariaLabelledBy, labelledby || labelID);
1695
- setAttribute(knob, 'tabindex', '0');
1696
- control.append(knob);
1697
- return control;
2365
+ return menu;
2366
+ }
2367
+
2368
+ /**
2369
+ * Check if a string is valid JSON string.
2370
+ * @param {string} str the string input
2371
+ * @returns {boolean} the query result
2372
+ */
2373
+ function isValidJSON(str) {
2374
+ try {
2375
+ JSON.parse(str);
2376
+ } catch (e) {
2377
+ return false;
2378
+ }
2379
+ return true;
1698
2380
  }
1699
2381
 
2382
+ var version = "0.0.1";
2383
+
2384
+ // @ts-ignore
2385
+
2386
+ const Version = version;
2387
+
1700
2388
  // ColorPicker GC
1701
2389
  // ==============
1702
2390
  const colorPickerString = 'color-picker';
1703
- const colorPickerSelector = `[data-function="${colorPickerString}"]`;
1704
- const nonColors = ['transparent', 'currentColor', 'inherit', 'initial'];
1705
- const colorNames$1 = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
1706
- const colorPickerLabels = {
1707
- pickerLabel: 'Colour Picker',
1708
- toggleLabel: 'Select colour',
1709
- menuLabel: 'Select colour preset',
1710
- requiredLabel: 'Required',
1711
- formatLabel: 'Colour Format',
1712
- formatHEX: 'Hexadecimal Format',
1713
- formatRGB: 'RGB Format',
1714
- formatHSL: 'HSL Format',
1715
- alphaLabel: 'Alpha',
1716
- appearanceLabel: 'Colour Appearance',
1717
- hexLabel: 'Hexadecimal',
1718
- hueLabel: 'Hue',
1719
- saturationLabel: 'Saturation',
1720
- lightnessLabel: 'Lightness',
1721
- redLabel: 'Red',
1722
- greenLabel: 'Green',
1723
- blueLabel: 'Blue',
2391
+ const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2392
+ const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2393
+ const colorPickerDefaults = {
2394
+ componentLabels: colorPickerLabels,
2395
+ colorLabels: colorNames,
2396
+ format: 'rgb',
2397
+ colorPresets: false,
2398
+ colorKeywords: false,
1724
2399
  };
1725
2400
 
1726
2401
  // ColorPicker Static Methods
@@ -1735,165 +2410,94 @@
1735
2410
  // ColorPicker Private Methods
1736
2411
  // ===========================
1737
2412
 
1738
- /**
1739
- * Add / remove `ColorPicker` main event listeners.
1740
- * @param {ColorPicker} self
1741
- * @param {boolean=} action
1742
- */
1743
- function toggleEvents(self, action) {
1744
- const fn = action ? addListener : removeListener;
1745
- const { input, pickerToggle, menuToggle } = self;
1746
-
1747
- fn(input, 'focusin', self.showPicker);
1748
- fn(pickerToggle, 'click', self.togglePicker);
1749
-
1750
- fn(input, 'keydown', self.keyHandler);
1751
-
1752
- if (menuToggle) {
1753
- fn(menuToggle, 'click', self.toggleMenu);
1754
- }
1755
- }
1756
-
1757
2413
  /**
1758
2414
  * Generate HTML markup and update instance properties.
1759
2415
  * @param {ColorPicker} self
1760
2416
  */
1761
2417
  function initCallback(self) {
1762
2418
  const {
1763
- input, parent, format, id, componentLabels, keywords,
2419
+ input, parent, format, id, componentLabels, colorKeywords, colorPresets,
1764
2420
  } = self;
1765
2421
  const colorValue = getAttribute(input, 'value') || '#fff';
1766
2422
 
1767
2423
  const {
1768
- toggleLabel, menuLabel, formatLabel, pickerLabel, appearanceLabel,
2424
+ toggleLabel, pickerLabel, formatLabel, hexLabel,
1769
2425
  } = componentLabels;
1770
2426
 
1771
2427
  // update color
1772
2428
  const color = nonColors.includes(colorValue) ? '#fff' : colorValue;
1773
- self.color = new Color(color, { format });
2429
+ self.color = new Color(color, format);
1774
2430
 
1775
2431
  // set initial controls dimensions
1776
2432
  // make the controls smaller on mobile
1777
- const cv1w = isMobile ? 150 : 230;
1778
- const cvh = isMobile ? 150 : 230;
1779
- const cv2w = 21;
1780
2433
  const dropClass = isMobile ? ' mobile' : '';
1781
- const ctrl1Labelledby = format === 'hsl' ? `appearance_${id} appearance1_${id}` : `appearance1_${id}`;
1782
- const ctrl2Labelledby = format === 'hsl' ? `appearance2_${id}` : `appearance_${id} appearance2_${id}`;
2434
+ const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
1783
2435
 
1784
2436
  const pickerBtn = createElement({
2437
+ id: `picker-btn-${id}`,
1785
2438
  tagName: 'button',
1786
- className: 'picker-toggle button-appearance',
1787
- ariaExpanded: 'false',
1788
- ariaHasPopup: 'true',
1789
- ariaLive: 'polite',
2439
+ className: 'picker-toggle btn-appearance',
1790
2440
  });
1791
- setAttribute(pickerBtn, 'tabindex', '-1');
2441
+ setAttribute(pickerBtn, ariaExpanded, 'false');
2442
+ setAttribute(pickerBtn, ariaHasPopup, 'true');
1792
2443
  pickerBtn.append(createElement({
1793
2444
  tagName: 'span',
1794
2445
  className: vHidden,
1795
- innerText: 'Open Color Picker',
2446
+ innerText: `${pickerLabel}. ${formatLabel}: ${formatString}`,
1796
2447
  }));
1797
2448
 
1798
- const colorPickerDropdown = createElement({
2449
+ const pickerDropdown = createElement({
1799
2450
  tagName: 'div',
1800
2451
  className: `color-dropdown picker${dropClass}`,
1801
2452
  });
1802
- setAttribute(colorPickerDropdown, ariaLabelledBy, `picker-label-${id} format-label-${id}`);
1803
- setAttribute(colorPickerDropdown, 'role', 'group');
1804
- colorPickerDropdown.append(
1805
- createElement({
1806
- tagName: 'label',
1807
- className: vHidden,
1808
- ariaHidden: 'true',
1809
- id: `picker-label-${id}`,
1810
- innerText: `${pickerLabel}`,
1811
- }),
1812
- createElement({
1813
- tagName: 'label',
1814
- className: vHidden,
1815
- ariaHidden: 'true',
1816
- id: `format-label-${id}`,
1817
- innerText: `${formatLabel}`,
1818
- }),
1819
- createElement({
1820
- tagName: 'label',
1821
- className: `color-appearance ${vHidden}`,
1822
- ariaHidden: 'true',
1823
- ariaLive: 'polite',
1824
- id: `appearance_${id}`,
1825
- innerText: `${appearanceLabel}`,
1826
- }),
1827
- );
1828
-
1829
- const colorControls = createElement({
1830
- tagName: 'div',
1831
- className: `color-controls ${format}`,
1832
- });
1833
-
1834
- colorControls.append(
1835
- getColorControl(1, id, cv1w, cvh, ctrl1Labelledby),
1836
- getColorControl(2, id, cv2w, cvh, ctrl2Labelledby),
1837
- );
1838
-
1839
- if (format !== 'hex') {
1840
- colorControls.append(
1841
- getColorControl(3, id, cv2w, cvh),
1842
- );
1843
- }
2453
+ setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
2454
+ setAttribute(pickerDropdown, 'role', 'group');
1844
2455
 
1845
- // @ts-ignore
2456
+ const colorControls = getColorControls(self);
1846
2457
  const colorForm = getColorForm(self);
1847
- colorPickerDropdown.append(colorControls, colorForm);
1848
- parent.append(pickerBtn, colorPickerDropdown);
1849
2458
 
1850
- // set color key menu template
1851
- if (keywords) {
1852
- const colorKeys = keywords;
2459
+ pickerDropdown.append(colorControls, colorForm);
2460
+ input.before(pickerBtn);
2461
+ parent.append(pickerDropdown);
2462
+
2463
+ // set colour key menu template
2464
+ if (colorKeywords || colorPresets) {
1853
2465
  const presetsDropdown = createElement({
1854
2466
  tagName: 'div',
1855
- className: `color-dropdown menu${dropClass}`,
1856
- });
1857
- const presetsMenu = createElement({
1858
- tagName: 'ul',
1859
- ariaLabel: `${menuLabel}`,
1860
- className: 'color-menu',
1861
- });
1862
- setAttribute(presetsMenu, 'role', 'listbox');
1863
- presetsDropdown.append(presetsMenu);
1864
-
1865
- colorKeys.forEach((x) => {
1866
- const xKey = x.trim();
1867
- const xRealColor = new Color(xKey, { format }).toString();
1868
- const isActive = xRealColor === getAttribute(input, 'value');
1869
- const active = isActive ? ' active' : '';
1870
-
1871
- const keyOption = createElement({
1872
- tagName: 'li',
1873
- className: `color-option${active}`,
1874
- ariaSelected: isActive ? 'true' : 'false',
1875
- innerText: `${x}`,
1876
- });
1877
- setAttribute(keyOption, 'role', 'option');
1878
- setAttribute(keyOption, 'tabindex', '0');
1879
- setAttribute(keyOption, 'data-value', `${xKey}`);
1880
- presetsMenu.append(keyOption);
2467
+ className: `color-dropdown scrollable menu${dropClass}`,
1881
2468
  });
2469
+
2470
+ // color presets
2471
+ if ((colorPresets instanceof Array && colorPresets.length)
2472
+ || (colorPresets instanceof ColorPalette && colorPresets.colors)) {
2473
+ const presetsMenu = getColorMenu(self, colorPresets, 'color-options');
2474
+ presetsDropdown.append(presetsMenu);
2475
+ }
2476
+
2477
+ // explicit defaults [reset, initial, inherit, transparent, currentColor]
2478
+ if (colorKeywords && colorKeywords.length) {
2479
+ const keywordsMenu = getColorMenu(self, colorKeywords, 'color-defaults');
2480
+ presetsDropdown.append(keywordsMenu);
2481
+ }
2482
+
1882
2483
  const presetsBtn = createElement({
1883
2484
  tagName: 'button',
1884
- className: 'menu-toggle button-appearance',
1885
- ariaExpanded: 'false',
1886
- ariaHasPopup: 'true',
2485
+ className: 'menu-toggle btn-appearance',
1887
2486
  });
2487
+ setAttribute(presetsBtn, tabIndex, '-1');
2488
+ setAttribute(presetsBtn, ariaExpanded, 'false');
2489
+ setAttribute(presetsBtn, ariaHasPopup, 'true');
2490
+
1888
2491
  const xmlns = encodeURI('http://www.w3.org/2000/svg');
1889
2492
  const presetsIcon = createElementNS(xmlns, { tagName: 'svg' });
1890
2493
  setAttribute(presetsIcon, 'xmlns', xmlns);
1891
- setAttribute(presetsIcon, ariaHidden, 'true');
1892
2494
  setAttribute(presetsIcon, 'viewBox', '0 0 512 512');
1893
- const piPath = createElementNS(xmlns, { tagName: 'path' });
1894
- setAttribute(piPath, 'd', 'M98,158l157,156L411,158l27,27L255,368L71,185L98,158z');
1895
- setAttribute(piPath, 'fill', '#fff');
1896
- presetsIcon.append(piPath);
2495
+ setAttribute(presetsIcon, ariaHidden, 'true');
2496
+
2497
+ const path = createElementNS(xmlns, { tagName: 'path' });
2498
+ setAttribute(path, 'd', 'M98,158l157,156L411,158l27,27L255,368L71,185L98,158z');
2499
+ setAttribute(path, 'fill', '#fff');
2500
+ presetsIcon.append(path);
1897
2501
  presetsBtn.append(createElement({
1898
2502
  tagName: 'span',
1899
2503
  className: vHidden,
@@ -1904,9 +2508,29 @@
1904
2508
  }
1905
2509
 
1906
2510
  // solve non-colors after settings save
1907
- if (keywords && nonColors.includes(colorValue)) {
2511
+ if (colorKeywords && nonColors.includes(colorValue)) {
1908
2512
  self.value = colorValue;
1909
2513
  }
2514
+ setAttribute(input, tabIndex, '-1');
2515
+ }
2516
+
2517
+ /**
2518
+ * Add / remove `ColorPicker` main event listeners.
2519
+ * @param {ColorPicker} self
2520
+ * @param {boolean=} action
2521
+ */
2522
+ function toggleEvents(self, action) {
2523
+ const fn = action ? addListener : removeListener;
2524
+ const { input, pickerToggle, menuToggle } = self;
2525
+
2526
+ fn(input, focusinEvent, self.showPicker);
2527
+ fn(pickerToggle, mouseclickEvent, self.togglePicker);
2528
+
2529
+ fn(input, keydownEvent, self.keyToggle);
2530
+
2531
+ if (menuToggle) {
2532
+ fn(menuToggle, mouseclickEvent, self.toggleMenu);
2533
+ }
1910
2534
  }
1911
2535
 
1912
2536
  /**
@@ -1916,26 +2540,33 @@
1916
2540
  */
1917
2541
  function toggleEventsOnShown(self, action) {
1918
2542
  const fn = action ? addListener : removeListener;
1919
- const pointerEvents = 'ontouchstart' in document
1920
- ? { down: 'touchstart', move: 'touchmove', up: 'touchend' }
1921
- : { down: 'mousedown', move: 'mousemove', up: 'mouseup' };
2543
+ const { input, colorMenu, parent } = self;
2544
+ const doc = getDocument(input);
2545
+ const win = getWindow(input);
2546
+ const pointerEvents = `on${touchstartEvent}` in doc
2547
+ ? { down: touchstartEvent, move: touchmoveEvent, up: touchendEvent }
2548
+ : { down: mousedownEvent, move: mousemoveEvent, up: mouseupEvent };
1922
2549
 
1923
2550
  fn(self.controls, pointerEvents.down, self.pointerDown);
1924
- self.controlKnobs.forEach((x) => fn(x, 'keydown', self.handleKnobs));
2551
+ self.controlKnobs.forEach((x) => fn(x, keydownEvent, self.handleKnobs));
1925
2552
 
1926
- fn(window, 'scroll', self.handleScroll);
2553
+ // @ts-ignore -- this is `Window`
2554
+ fn(win, scrollEvent, self.handleScroll);
2555
+ // @ts-ignore -- this is `Window`
2556
+ fn(win, resizeEvent, self.update);
1927
2557
 
1928
- [self.input, ...self.inputs].forEach((x) => fn(x, 'change', self.changeHandler));
2558
+ [input, ...self.inputs].forEach((x) => fn(x, changeEvent, self.changeHandler));
1929
2559
 
1930
- if (self.colorMenu) {
1931
- fn(self.colorMenu, 'click', self.menuClickHandler);
1932
- fn(self.colorMenu, 'keydown', self.menuKeyHandler);
2560
+ if (colorMenu) {
2561
+ fn(colorMenu, mouseclickEvent, self.menuClickHandler);
2562
+ fn(colorMenu, keydownEvent, self.menuKeyHandler);
1933
2563
  }
1934
2564
 
1935
- fn(document, pointerEvents.move, self.pointerMove);
1936
- fn(document, pointerEvents.up, self.pointerUp);
1937
- fn(window, 'keyup', self.handleDismiss);
1938
- fn(self.parent, 'focusout', self.handleFocusOut);
2565
+ fn(doc, pointerEvents.move, self.pointerMove);
2566
+ fn(doc, pointerEvents.up, self.pointerUp);
2567
+ fn(parent, focusoutEvent, self.handleFocusOut);
2568
+ // @ts-ignore -- this is `Window`
2569
+ fn(win, keyupEvent, self.handleDismiss);
1939
2570
  }
1940
2571
 
1941
2572
  /**
@@ -1947,61 +2578,93 @@
1947
2578
  }
1948
2579
 
1949
2580
  /**
1950
- * Toggles the visibility of a dropdown or returns false if none is visible.
2581
+ * Hides a visible dropdown.
1951
2582
  * @param {HTMLElement} element
1952
- * @param {boolean=} check
1953
- * @returns {void | boolean}
2583
+ * @returns {void}
1954
2584
  */
1955
- function classToggle(element, check) {
1956
- const fn1 = !check ? 'forEach' : 'some';
1957
- const fn2 = !check ? removeClass : hasClass;
1958
-
2585
+ function removePosition(element) {
1959
2586
  if (element) {
1960
- return ['show', 'show-top'][fn1]((x) => fn2(element, x));
2587
+ ['bottom', 'top'].forEach((x) => removeClass(element, x));
1961
2588
  }
1962
-
1963
- return false;
1964
2589
  }
1965
2590
 
1966
2591
  /**
1967
- * Shows the `ColorPicker` presets menu.
2592
+ * Shows a `ColorPicker` dropdown and close the curent open dropdown.
1968
2593
  * @param {ColorPicker} self
2594
+ * @param {HTMLElement | Element} dropdown
1969
2595
  */
1970
- function showMenu(self) {
1971
- classToggle(self.colorPicker);
1972
- addClass(self.colorMenu, 'show');
1973
- self.show();
1974
- setAttribute(self.menuToggle, ariaExpanded, 'true');
2596
+ function showDropdown(self, dropdown) {
2597
+ const {
2598
+ colorPicker, colorMenu, menuToggle, pickerToggle, parent,
2599
+ } = self;
2600
+ const isPicker = dropdown === colorPicker;
2601
+ const openDropdown = isPicker ? colorMenu : colorPicker;
2602
+ const activeBtn = isPicker ? menuToggle : pickerToggle;
2603
+ const nextBtn = !isPicker ? menuToggle : pickerToggle;
2604
+
2605
+ if (!hasClass(parent, 'open')) {
2606
+ addClass(parent, 'open');
2607
+ }
2608
+ if (openDropdown) {
2609
+ removeClass(openDropdown, 'show');
2610
+ removePosition(openDropdown);
2611
+ }
2612
+ addClass(dropdown, 'bottom');
2613
+ reflow(dropdown);
2614
+ addClass(dropdown, 'show');
2615
+
2616
+ if (isPicker) self.update();
2617
+
2618
+ if (!self.isOpen) {
2619
+ toggleEventsOnShown(self, true);
2620
+ self.updateDropdownPosition();
2621
+ self.isOpen = true;
2622
+ setAttribute(self.input, tabIndex, '0');
2623
+ if (menuToggle) {
2624
+ setAttribute(menuToggle, tabIndex, '0');
2625
+ }
2626
+ }
2627
+
2628
+ setAttribute(nextBtn, ariaExpanded, 'true');
2629
+ if (activeBtn) {
2630
+ setAttribute(activeBtn, ariaExpanded, 'false');
2631
+ }
1975
2632
  }
1976
2633
 
1977
2634
  /**
1978
- * Color Picker
2635
+ * Color Picker Web Component
1979
2636
  * @see http://thednp.github.io/color-picker
1980
2637
  */
1981
2638
  class ColorPicker {
1982
2639
  /**
1983
- * Returns a new ColorPicker instance.
2640
+ * Returns a new `ColorPicker` instance. The target of this constructor
2641
+ * must be an `HTMLInputElement`.
2642
+ *
1984
2643
  * @param {HTMLInputElement | string} target the target `<input>` element
2644
+ * @param {CP.ColorPickerOptions=} config instance options
1985
2645
  */
1986
- constructor(target) {
2646
+ constructor(target, config) {
1987
2647
  const self = this;
1988
2648
  /** @type {HTMLInputElement} */
1989
2649
  // @ts-ignore
1990
- self.input = querySelector(target);
2650
+ const input = querySelector(target);
2651
+
1991
2652
  // invalidate
1992
- if (!self.input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
1993
- const { input } = self;
2653
+ if (!input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
2654
+ self.input = input;
2655
+
2656
+ const parent = closest(input, colorPickerParentSelector);
2657
+ if (!parent) throw new TypeError('ColorPicker requires a specific markup to work.');
1994
2658
 
1995
2659
  /** @type {HTMLElement} */
1996
2660
  // @ts-ignore
1997
- self.parent = closest(input, `.${colorPickerString},${colorPickerString}`);
1998
- if (!self.parent) throw new TypeError('ColorPicker requires a specific markup to work.');
2661
+ self.parent = parent;
1999
2662
 
2000
2663
  /** @type {number} */
2001
2664
  self.id = getUID(input, colorPickerString);
2002
2665
 
2003
2666
  // set initial state
2004
- /** @type {HTMLCanvasElement?} */
2667
+ /** @type {HTMLElement?} */
2005
2668
  self.dragElement = null;
2006
2669
  /** @type {boolean} */
2007
2670
  self.isOpen = false;
@@ -2011,26 +2674,59 @@
2011
2674
  };
2012
2675
  /** @type {Record<string, string>} */
2013
2676
  self.colorLabels = {};
2014
- /** @type {Array<string> | false} */
2015
- self.keywords = false;
2016
- /** @type {Color} */
2017
- self.color = new Color('white', { format: self.format });
2677
+ /** @type {string[]=} */
2678
+ self.colorKeywords = undefined;
2679
+ /** @type {(ColorPalette | string[])=} */
2680
+ self.colorPresets = undefined;
2681
+
2682
+ // process options
2683
+ const {
2684
+ format, componentLabels, colorLabels, colorKeywords, colorPresets,
2685
+ } = normalizeOptions(this.isCE ? parent : input, colorPickerDefaults, config || {});
2686
+
2687
+ let translatedColorLabels = colorNames;
2688
+ if (colorLabels instanceof Array && colorLabels.length === 17) {
2689
+ translatedColorLabels = colorLabels;
2690
+ } else if (colorLabels && colorLabels.split(',').length === 17) {
2691
+ translatedColorLabels = colorLabels.split(',');
2692
+ }
2693
+
2694
+ // expose colour labels to all methods
2695
+ colorNames.forEach((c, i) => {
2696
+ self.colorLabels[c] = translatedColorLabels[i].trim();
2697
+ });
2698
+
2699
+ // update and expose component labels
2700
+ const tempLabels = ObjectAssign({}, colorPickerLabels);
2701
+ const jsonLabels = componentLabels && isValidJSON(componentLabels)
2702
+ ? JSON.parse(componentLabels) : componentLabels || {};
2703
+
2018
2704
  /** @type {Record<string, string>} */
2019
- self.componentLabels = ObjectAssign({}, colorPickerLabels);
2705
+ self.componentLabels = ObjectAssign(tempLabels, jsonLabels);
2020
2706
 
2021
- const { componentLabels, colorLabels, keywords } = input.dataset;
2022
- const temp = componentLabels ? JSON.parse(componentLabels) : {};
2023
- self.componentLabels = ObjectAssign(self.componentLabels, temp);
2707
+ /** @type {Color} */
2708
+ self.color = new Color('white', format);
2024
2709
 
2025
- const translatedColorLabels = colorLabels && colorLabels.split(',').length === 17
2026
- ? colorLabels.split(',') : colorNames$1;
2710
+ /** @type {CP.ColorFormats} */
2711
+ self.format = format;
2027
2712
 
2028
- // expose color labels to all methods
2029
- colorNames$1.forEach((c, i) => { self.colorLabels[c] = translatedColorLabels[i]; });
2713
+ // set colour defaults
2714
+ if (colorKeywords instanceof Array) {
2715
+ self.colorKeywords = colorKeywords;
2716
+ } else if (typeof colorKeywords === 'string' && colorKeywords.length) {
2717
+ self.colorKeywords = colorKeywords.split(',');
2718
+ }
2030
2719
 
2031
2720
  // set colour presets
2032
- if (keywords !== 'false') {
2033
- self.keywords = keywords ? keywords.split(',') : nonColors;
2721
+ if (colorPresets instanceof Array) {
2722
+ self.colorPresets = colorPresets;
2723
+ } else if (typeof colorPresets === 'string' && colorPresets.length) {
2724
+ if (isValidJSON(colorPresets)) {
2725
+ const { hue, hueSteps, lightSteps } = JSON.parse(colorPresets);
2726
+ self.colorPresets = new ColorPalette(hue, hueSteps, lightSteps);
2727
+ } else {
2728
+ self.colorPresets = colorPresets.split(',').map((x) => x.trim());
2729
+ }
2034
2730
  }
2035
2731
 
2036
2732
  // bind events
@@ -2042,17 +2738,18 @@
2042
2738
  self.pointerDown = self.pointerDown.bind(self);
2043
2739
  self.pointerMove = self.pointerMove.bind(self);
2044
2740
  self.pointerUp = self.pointerUp.bind(self);
2741
+ self.update = self.update.bind(self);
2045
2742
  self.handleScroll = self.handleScroll.bind(self);
2046
2743
  self.handleFocusOut = self.handleFocusOut.bind(self);
2047
2744
  self.changeHandler = self.changeHandler.bind(self);
2048
2745
  self.handleDismiss = self.handleDismiss.bind(self);
2049
- self.keyHandler = self.keyHandler.bind(self);
2746
+ self.keyToggle = self.keyToggle.bind(self);
2050
2747
  self.handleKnobs = self.handleKnobs.bind(self);
2051
2748
 
2052
2749
  // generate markup
2053
2750
  initCallback(self);
2054
2751
 
2055
- const { parent } = self;
2752
+ const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
2056
2753
  // set main elements
2057
2754
  /** @type {HTMLElement} */
2058
2755
  // @ts-ignore
@@ -2062,68 +2759,24 @@
2062
2759
  self.menuToggle = querySelector('.menu-toggle', parent);
2063
2760
  /** @type {HTMLElement} */
2064
2761
  // @ts-ignore
2065
- self.colorMenu = querySelector('.color-dropdown.menu', parent);
2066
- /** @type {HTMLElement} */
2067
- // @ts-ignore
2068
- self.colorPicker = querySelector('.color-dropdown.picker', parent);
2762
+ self.colorPicker = colorPicker;
2069
2763
  /** @type {HTMLElement} */
2070
2764
  // @ts-ignore
2071
- self.controls = querySelector('.color-controls', parent);
2765
+ self.colorMenu = colorMenu;
2072
2766
  /** @type {HTMLInputElement[]} */
2073
2767
  // @ts-ignore
2074
- self.inputs = [...querySelectorAll('.color-input', parent)];
2768
+ self.inputs = [...getElementsByClassName('color-input', parent)];
2769
+ const [controls] = getElementsByClassName('color-controls', parent);
2770
+ self.controls = controls;
2771
+ /** @type {(HTMLElement | Element)[]} */
2772
+ self.controlKnobs = [...getElementsByClassName('knob', controls)];
2075
2773
  /** @type {(HTMLElement)[]} */
2076
2774
  // @ts-ignore
2077
- self.controlKnobs = [...querySelectorAll('.knob', parent)];
2078
- /** @type {HTMLCanvasElement[]} */
2079
- // @ts-ignore
2080
- self.visuals = [...querySelectorAll('canvas', self.controls)];
2081
- /** @type {HTMLLabelElement[]} */
2082
- // @ts-ignore
2083
- self.knobLabels = [...querySelectorAll('.color-label', parent)];
2084
- /** @type {HTMLLabelElement} */
2085
- // @ts-ignore
2086
- self.appearance = querySelector('.color-appearance', parent);
2775
+ self.visuals = [...getElementsByClassName('visual-control', controls)];
2087
2776
 
2088
- const [v1, v2, v3] = self.visuals;
2089
- // set dimensions
2090
- /** @type {number} */
2091
- self.width1 = v1.width;
2092
- /** @type {number} */
2093
- self.height1 = v1.height;
2094
- /** @type {number} */
2095
- self.width2 = v2.width;
2096
- /** @type {number} */
2097
- self.height2 = v2.height;
2098
- // set main controls
2099
- /** @type {*} */
2100
- self.ctx1 = v1.getContext('2d');
2101
- /** @type {*} */
2102
- self.ctx2 = v2.getContext('2d');
2103
- self.ctx1.rect(0, 0, self.width1, self.height1);
2104
- self.ctx2.rect(0, 0, self.width2, self.height2);
2105
-
2106
- /** @type {number} */
2107
- self.width3 = 0;
2108
- /** @type {number} */
2109
- self.height3 = 0;
2110
-
2111
- // set alpha control except hex
2112
- if (self.format !== 'hex') {
2113
- self.width3 = v3.width;
2114
- self.height3 = v3.height;
2115
- /** @type {*} */
2116
- this.ctx3 = v3.getContext('2d');
2117
- self.ctx3.rect(0, 0, self.width3, self.height3);
2118
- }
2777
+ // update colour picker controls, inputs and visuals
2778
+ self.update();
2119
2779
 
2120
- // update color picker controls, inputs and visuals
2121
- this.setControlPositions();
2122
- this.setColorAppearence();
2123
- // don't trigger change at initialization
2124
- this.updateInputs(true);
2125
- this.updateControls();
2126
- this.updateVisuals();
2127
2780
  // add main events listeners
2128
2781
  toggleEvents(self, true);
2129
2782
 
@@ -2131,65 +2784,52 @@
2131
2784
  Data.set(input, colorPickerString, self);
2132
2785
  }
2133
2786
 
2134
- /** Returns the current color value */
2787
+ /** Returns the current colour value */
2135
2788
  get value() { return this.input.value; }
2136
2789
 
2137
2790
  /**
2138
- * Sets a new color value.
2139
- * @param {string} v new color value
2791
+ * Sets a new colour value.
2792
+ * @param {string} v new colour value
2140
2793
  */
2141
2794
  set value(v) { this.input.value = v; }
2142
2795
 
2143
- /** Check if the input is required to have a valid value. */
2144
- get required() { return hasAttribute(this.input, 'required'); }
2145
-
2146
- /**
2147
- * Returns the colour format.
2148
- * @returns {CP.ColorFormats | string}
2149
- */
2150
- get format() { return getAttribute(this.input, 'format') || 'hex'; }
2151
-
2152
- /** Returns the input name. */
2153
- get name() { return getAttribute(this.input, 'name'); }
2154
-
2155
- /**
2156
- * Returns the label associated to the input.
2157
- * @returns {HTMLLabelElement?}
2158
- */
2159
- // @ts-ignore
2160
- get label() { return querySelector(`[for="${this.input.id}"]`); }
2161
-
2162
- /** Check if the color presets include any non-color. */
2163
- get includeNonColor() {
2164
- return this.keywords instanceof Array
2165
- && this.keywords.some((x) => nonColors.includes(x));
2796
+ /** Check if the colour presets include any non-colour. */
2797
+ get hasNonColor() {
2798
+ return this.colorKeywords instanceof Array
2799
+ && this.colorKeywords.some((x) => nonColors.includes(x));
2166
2800
  }
2167
2801
 
2168
- /** Returns hexadecimal value of the current color. */
2169
- get hex() { return this.color.toHex(); }
2802
+ /** Check if the parent of the target is a `ColorPickerElement` instance. */
2803
+ get isCE() { return this.parent.localName === colorPickerString; }
2804
+
2805
+ /** Returns hexadecimal value of the current colour. */
2806
+ get hex() { return this.color.toHex(true); }
2170
2807
 
2171
- /** Returns the current color value in {h,s,v,a} object format. */
2808
+ /** Returns the current colour value in {h,s,v,a} object format. */
2172
2809
  get hsv() { return this.color.toHsv(); }
2173
2810
 
2174
- /** Returns the current color value in {h,s,l,a} object format. */
2811
+ /** Returns the current colour value in {h,s,l,a} object format. */
2175
2812
  get hsl() { return this.color.toHsl(); }
2176
2813
 
2177
- /** Returns the current color value in {r,g,b,a} object format. */
2814
+ /** Returns the current colour value in {h,w,b,a} object format. */
2815
+ get hwb() { return this.color.toHwb(); }
2816
+
2817
+ /** Returns the current colour value in {r,g,b,a} object format. */
2178
2818
  get rgb() { return this.color.toRgb(); }
2179
2819
 
2180
- /** Returns the current color brightness. */
2820
+ /** Returns the current colour brightness. */
2181
2821
  get brightness() { return this.color.brightness; }
2182
2822
 
2183
- /** Returns the current color luminance. */
2823
+ /** Returns the current colour luminance. */
2184
2824
  get luminance() { return this.color.luminance; }
2185
2825
 
2186
- /** Checks if the current colour requires a light text color. */
2826
+ /** Checks if the current colour requires a light text colour. */
2187
2827
  get isDark() {
2188
- const { rgb, brightness } = this;
2189
- return brightness < 120 && rgb.a > 0.33;
2828
+ const { color, brightness } = this;
2829
+ return brightness < 120 && color.a > 0.33;
2190
2830
  }
2191
2831
 
2192
- /** Checks if the current input value is a valid color. */
2832
+ /** Checks if the current input value is a valid colour. */
2193
2833
  get isValid() {
2194
2834
  const inputValue = this.input.value;
2195
2835
  return inputValue !== '' && new Color(inputValue).isValid;
@@ -2199,89 +2839,79 @@
2199
2839
  updateVisuals() {
2200
2840
  const self = this;
2201
2841
  const {
2202
- color, format, controlPositions,
2203
- width1, width2, width3,
2204
- height1, height2, height3,
2205
- ctx1, ctx2, ctx3,
2842
+ format, controlPositions, visuals,
2206
2843
  } = self;
2207
- const { r, g, b } = color;
2844
+ const [v1, v2, v3] = visuals;
2845
+ const { offsetWidth, offsetHeight } = v1;
2846
+ const hue = format === 'hsl'
2847
+ ? controlPositions.c1x / offsetWidth
2848
+ : controlPositions.c2y / offsetHeight;
2849
+ // @ts-ignore - `hslToRgb` is assigned to `Color` as static method
2850
+ const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
2851
+ const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
2852
+ const alpha = 1 - controlPositions.c3y / offsetHeight;
2853
+ const roundA = roundPart((alpha * 100)) / 100;
2208
2854
 
2209
2855
  if (format !== 'hsl') {
2210
- const hue = Math.round((controlPositions.c2y / height2) * 360);
2211
- ctx1.fillStyle = new Color(`hsl(${hue},100%,50%})`).toRgbString();
2212
- ctx1.fillRect(0, 0, width1, height1);
2213
-
2214
- const whiteGrad = ctx2.createLinearGradient(0, 0, width1, 0);
2215
- whiteGrad.addColorStop(0, 'rgba(255,255,255,1)');
2216
- whiteGrad.addColorStop(1, 'rgba(255,255,255,0)');
2217
- ctx1.fillStyle = whiteGrad;
2218
- ctx1.fillRect(0, 0, width1, height1);
2219
-
2220
- const blackGrad = ctx2.createLinearGradient(0, 0, 0, height1);
2221
- blackGrad.addColorStop(0, 'rgba(0,0,0,0)');
2222
- blackGrad.addColorStop(1, 'rgba(0,0,0,1)');
2223
- ctx1.fillStyle = blackGrad;
2224
- ctx1.fillRect(0, 0, width1, height1);
2225
-
2226
- const hueGrad = ctx2.createLinearGradient(0, 0, 0, height1);
2227
- hueGrad.addColorStop(0, 'rgba(255,0,0,1)');
2228
- hueGrad.addColorStop(0.17, 'rgba(255,255,0,1)');
2229
- hueGrad.addColorStop(0.34, 'rgba(0,255,0,1)');
2230
- hueGrad.addColorStop(0.51, 'rgba(0,255,255,1)');
2231
- hueGrad.addColorStop(0.68, 'rgba(0,0,255,1)');
2232
- hueGrad.addColorStop(0.85, 'rgba(255,0,255,1)');
2233
- hueGrad.addColorStop(1, 'rgba(255,0,0,1)');
2234
- ctx2.fillStyle = hueGrad;
2235
- ctx2.fillRect(0, 0, width2, height2);
2856
+ const fill = new Color({
2857
+ h: hue, s: 1, l: 0.5, a: alpha,
2858
+ }).toRgbString();
2859
+ const hueGradient = `linear-gradient(
2860
+ rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2861
+ rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2862
+ rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2863
+ rgb(255,0,0) 100%)`;
2864
+ setElementStyle(v1, {
2865
+ background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2866
+ linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2867
+ ${whiteGrad}`,
2868
+ });
2869
+ setElementStyle(v2, { background: hueGradient });
2236
2870
  } else {
2237
- const hueGrad = ctx1.createLinearGradient(0, 0, width1, 0);
2238
- const saturation = Math.round((1 - controlPositions.c2y / height2) * 100);
2239
-
2240
- hueGrad.addColorStop(0, new Color('rgba(255,0,0,1)').desaturate(100 - saturation).toRgbString());
2241
- hueGrad.addColorStop(0.17, new Color('rgba(255,255,0,1)').desaturate(100 - saturation).toRgbString());
2242
- hueGrad.addColorStop(0.34, new Color('rgba(0,255,0,1)').desaturate(100 - saturation).toRgbString());
2243
- hueGrad.addColorStop(0.51, new Color('rgba(0,255,255,1)').desaturate(100 - saturation).toRgbString());
2244
- hueGrad.addColorStop(0.68, new Color('rgba(0,0,255,1)').desaturate(100 - saturation).toRgbString());
2245
- hueGrad.addColorStop(0.85, new Color('rgba(255,0,255,1)').desaturate(100 - saturation).toRgbString());
2246
- hueGrad.addColorStop(1, new Color('rgba(255,0,0,1)').desaturate(100 - saturation).toRgbString());
2247
-
2248
- ctx1.fillStyle = hueGrad;
2249
- ctx1.fillRect(0, 0, width1, height1);
2250
-
2251
- const whiteGrad = ctx1.createLinearGradient(0, 0, 0, height1);
2252
- whiteGrad.addColorStop(0, 'rgba(255,255,255,1)');
2253
- whiteGrad.addColorStop(0.5, 'rgba(255,255,255,0)');
2254
- ctx1.fillStyle = whiteGrad;
2255
- ctx1.fillRect(0, 0, width1, height1);
2256
-
2257
- const blackGrad = ctx1.createLinearGradient(0, 0, 0, height1);
2258
- blackGrad.addColorStop(0.5, 'rgba(0,0,0,0)');
2259
- blackGrad.addColorStop(1, 'rgba(0,0,0,1)');
2260
- ctx1.fillStyle = blackGrad;
2261
- ctx1.fillRect(0, 0, width1, height1);
2262
-
2263
- const saturationGrad = ctx2.createLinearGradient(0, 0, 0, height2);
2264
- const incolor = color.clone().greyscale().toRgb();
2265
-
2266
- saturationGrad.addColorStop(0, `rgba(${r},${g},${b},1)`);
2267
- saturationGrad.addColorStop(1, `rgba(${incolor.r},${incolor.g},${incolor.b},1)`);
2268
-
2269
- ctx2.fillStyle = saturationGrad;
2270
- ctx2.fillRect(0, 0, width3, height3);
2271
- }
2272
-
2273
- if (format !== 'hex') {
2274
- ctx3.clearRect(0, 0, width3, height3);
2275
- const alphaGrad = ctx3.createLinearGradient(0, 0, 0, height3);
2276
- alphaGrad.addColorStop(0, `rgba(${r},${g},${b},1)`);
2277
- alphaGrad.addColorStop(1, `rgba(${r},${g},${b},0)`);
2278
- ctx3.fillStyle = alphaGrad;
2279
- ctx3.fillRect(0, 0, width3, height3);
2871
+ const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
2872
+ const fill0 = new Color({
2873
+ r: 255, g: 0, b: 0, a: alpha,
2874
+ }).saturate(-saturation).toRgbString();
2875
+ const fill1 = new Color({
2876
+ r: 255, g: 255, b: 0, a: alpha,
2877
+ }).saturate(-saturation).toRgbString();
2878
+ const fill2 = new Color({
2879
+ r: 0, g: 255, b: 0, a: alpha,
2880
+ }).saturate(-saturation).toRgbString();
2881
+ const fill3 = new Color({
2882
+ r: 0, g: 255, b: 255, a: alpha,
2883
+ }).saturate(-saturation).toRgbString();
2884
+ const fill4 = new Color({
2885
+ r: 0, g: 0, b: 255, a: alpha,
2886
+ }).saturate(-saturation).toRgbString();
2887
+ const fill5 = new Color({
2888
+ r: 255, g: 0, b: 255, a: alpha,
2889
+ }).saturate(-saturation).toRgbString();
2890
+ const fill6 = new Color({
2891
+ r: 255, g: 0, b: 0, a: alpha,
2892
+ }).saturate(-saturation).toRgbString();
2893
+ const fillGradient = `linear-gradient(to right,
2894
+ ${fill0} 0%, ${fill1} 16.67%, ${fill2} 33.33%, ${fill3} 50%,
2895
+ ${fill4} 66.67%, ${fill5} 83.33%, ${fill6} 100%)`;
2896
+ const lightGrad = `linear-gradient(rgba(255,255,255,${roundA}) 0%, rgba(255,255,255,0) 50%),
2897
+ linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,${roundA}) 100%)`;
2898
+
2899
+ setElementStyle(v1, { background: `${lightGrad},${fillGradient},${whiteGrad}` });
2900
+ const {
2901
+ r: gr, g: gg, b: gb,
2902
+ } = new Color({ r, g, b }).greyscale().toRgb();
2903
+
2904
+ setElementStyle(v2, {
2905
+ background: `linear-gradient(rgb(${r},${g},${b}) 0%, rgb(${gr},${gg},${gb}) 100%)`,
2906
+ });
2280
2907
  }
2908
+ setElementStyle(v3, {
2909
+ background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
2910
+ });
2281
2911
  }
2282
2912
 
2283
2913
  /**
2284
- * Handles the `focusout` listener of the `ColorPicker`.
2914
+ * The `ColorPicker` *focusout* event listener when open.
2285
2915
  * @param {FocusEvent} e
2286
2916
  * @this {ColorPicker}
2287
2917
  */
@@ -2293,7 +2923,7 @@
2293
2923
  }
2294
2924
 
2295
2925
  /**
2296
- * Handles the `focusout` listener of the `ColorPicker`.
2926
+ * The `ColorPicker` *keyup* event listener when open.
2297
2927
  * @param {KeyboardEvent} e
2298
2928
  * @this {ColorPicker}
2299
2929
  */
@@ -2305,14 +2935,13 @@
2305
2935
  }
2306
2936
 
2307
2937
  /**
2308
- * Handles the `ColorPicker` scroll listener when open.
2938
+ * The `ColorPicker` *scroll* event listener when open.
2309
2939
  * @param {Event} e
2310
2940
  * @this {ColorPicker}
2311
2941
  */
2312
2942
  handleScroll(e) {
2313
2943
  const self = this;
2314
- /** @type {*} */
2315
- const { activeElement } = document;
2944
+ const { activeElement } = getDocument(self.input);
2316
2945
 
2317
2946
  if ((isMobile && self.dragElement)
2318
2947
  || (activeElement && self.controlKnobs.includes(activeElement))) {
@@ -2324,22 +2953,51 @@
2324
2953
  }
2325
2954
 
2326
2955
  /**
2327
- * Handles all `ColorPicker` click listeners.
2956
+ * The `ColorPicker` keyboard event listener for menu navigation.
2328
2957
  * @param {KeyboardEvent} e
2329
2958
  * @this {ColorPicker}
2330
2959
  */
2331
2960
  menuKeyHandler(e) {
2332
2961
  const { target, code } = e;
2333
-
2334
- if ([keyArrowDown, keyArrowUp].includes(code)) {
2962
+ // @ts-ignore
2963
+ const { previousElementSibling, nextElementSibling, parentElement } = target;
2964
+ const isColorOptionsMenu = parentElement && hasClass(parentElement, 'color-options');
2965
+ const allSiblings = [...parentElement.children];
2966
+ const columnsCount = isColorOptionsMenu
2967
+ && getElementStyle(parentElement, 'grid-template-columns').split(' ').length;
2968
+ const currentIndex = allSiblings.indexOf(target);
2969
+ const previousElement = currentIndex > -1
2970
+ && columnsCount && allSiblings[currentIndex - columnsCount];
2971
+ const nextElement = currentIndex > -1
2972
+ && columnsCount && allSiblings[currentIndex + columnsCount];
2973
+
2974
+ if ([keyArrowDown, keyArrowUp, keySpace].includes(code)) {
2975
+ // prevent scroll when navigating the menu via arrow keys / Space
2335
2976
  e.preventDefault();
2336
- } else if ([keyEnter, keySpace].includes(code)) {
2977
+ }
2978
+ if (isColorOptionsMenu) {
2979
+ if (previousElement && code === keyArrowUp) {
2980
+ focus(previousElement);
2981
+ } else if (nextElement && code === keyArrowDown) {
2982
+ focus(nextElement);
2983
+ } else if (previousElementSibling && code === keyArrowLeft) {
2984
+ focus(previousElementSibling);
2985
+ } else if (nextElementSibling && code === keyArrowRight) {
2986
+ focus(nextElementSibling);
2987
+ }
2988
+ } else if (previousElementSibling && [keyArrowLeft, keyArrowUp].includes(code)) {
2989
+ focus(previousElementSibling);
2990
+ } else if (nextElementSibling && [keyArrowRight, keyArrowDown].includes(code)) {
2991
+ focus(nextElementSibling);
2992
+ }
2993
+
2994
+ if ([keyEnter, keySpace].includes(code)) {
2337
2995
  this.menuClickHandler({ target });
2338
2996
  }
2339
2997
  }
2340
2998
 
2341
2999
  /**
2342
- * Handles all `ColorPicker` click listeners.
3000
+ * The `ColorPicker` click event listener for the colour menu presets / defaults.
2343
3001
  * @param {Partial<Event>} e
2344
3002
  * @this {ColorPicker}
2345
3003
  */
@@ -2347,51 +3005,57 @@
2347
3005
  const self = this;
2348
3006
  /** @type {*} */
2349
3007
  const { target } = e;
2350
- const { format } = self;
3008
+ const { colorMenu } = self;
2351
3009
  const newOption = (getAttribute(target, 'data-value') || '').trim();
2352
- const currentActive = self.colorMenu.querySelector('li.active');
2353
- const newColor = nonColors.includes(newOption) ? 'white' : newOption;
2354
- self.color = new Color(newColor, { format });
2355
- self.setControlPositions();
2356
- self.setColorAppearence();
2357
- self.updateInputs(true);
2358
- self.updateControls();
2359
- self.updateVisuals();
3010
+ // invalidate for targets other than color options
3011
+ if (!newOption.length) return;
3012
+ const currentActive = querySelector('li.active', colorMenu);
3013
+ let newColor = nonColors.includes(newOption) ? 'white' : newOption;
3014
+ newColor = newOption === 'transparent' ? 'rgba(0,0,0,0)' : newOption;
2360
3015
 
2361
- if (currentActive) {
2362
- removeClass(currentActive, 'active');
2363
- removeAttribute(currentActive, ariaSelected);
2364
- }
3016
+ const {
3017
+ r, g, b, a,
3018
+ } = new Color(newColor);
3019
+
3020
+ ObjectAssign(self.color, {
3021
+ r, g, b, a,
3022
+ });
3023
+
3024
+ self.update();
2365
3025
 
2366
3026
  if (currentActive !== target) {
3027
+ if (currentActive) {
3028
+ removeClass(currentActive, 'active');
3029
+ removeAttribute(currentActive, ariaSelected);
3030
+ }
3031
+
2367
3032
  addClass(target, 'active');
2368
3033
  setAttribute(target, ariaSelected, 'true');
2369
3034
 
2370
3035
  if (nonColors.includes(newOption)) {
2371
3036
  self.value = newOption;
2372
- firePickerChange(self);
2373
3037
  }
3038
+ firePickerChange(self);
2374
3039
  }
2375
3040
  }
2376
3041
 
2377
3042
  /**
2378
- * Handles the `ColorPicker` touchstart / mousedown events listeners.
3043
+ * The `ColorPicker` *touchstart* / *mousedown* events listener for control knobs.
2379
3044
  * @param {TouchEvent} e
2380
3045
  * @this {ColorPicker}
2381
3046
  */
2382
3047
  pointerDown(e) {
2383
3048
  const self = this;
3049
+ /** @type {*} */
2384
3050
  const {
2385
- // @ts-ignore
2386
3051
  type, target, touches, pageX, pageY,
2387
3052
  } = e;
2388
- const { visuals, controlKnobs, format } = self;
3053
+ const { colorMenu, visuals, controlKnobs } = self;
2389
3054
  const [v1, v2, v3] = visuals;
2390
3055
  const [c1, c2, c3] = controlKnobs;
2391
- /** @type {HTMLCanvasElement} */
2392
- // @ts-ignore
2393
- const visual = target.tagName === 'canvas' // @ts-ignore
2394
- ? target : querySelector('canvas', target.parentElement);
3056
+ /** @type {HTMLElement} */
3057
+ const visual = hasClass(target, 'visual-control')
3058
+ ? target : querySelector('.visual-control', target.parentElement);
2395
3059
  const visualRect = getBoundingClientRect(visual);
2396
3060
  const X = type === 'touchstart' ? touches[0].pageX : pageX;
2397
3061
  const Y = type === 'touchstart' ? touches[0].pageY : pageY;
@@ -2400,42 +3064,53 @@
2400
3064
 
2401
3065
  if (target === v1 || target === c1) {
2402
3066
  self.dragElement = visual;
2403
- self.changeControl1({ offsetX, offsetY });
3067
+ self.changeControl1(offsetX, offsetY);
2404
3068
  } else if (target === v2 || target === c2) {
2405
3069
  self.dragElement = visual;
2406
- self.changeControl2({ offsetY });
2407
- } else if (format !== 'hex' && (target === v3 || target === c3)) {
3070
+ self.changeControl2(offsetY);
3071
+ } else if (target === v3 || target === c3) {
2408
3072
  self.dragElement = visual;
2409
- self.changeAlpha({ offsetY });
3073
+ self.changeAlpha(offsetY);
3074
+ }
3075
+
3076
+ if (colorMenu) {
3077
+ const currentActive = querySelector('li.active', colorMenu);
3078
+ if (currentActive) {
3079
+ removeClass(currentActive, 'active');
3080
+ removeAttribute(currentActive, ariaSelected);
3081
+ }
2410
3082
  }
2411
3083
  e.preventDefault();
2412
3084
  }
2413
3085
 
2414
3086
  /**
2415
- * Handles the `ColorPicker` touchend / mouseup events listeners.
3087
+ * The `ColorPicker` *touchend* / *mouseup* events listener for control knobs.
2416
3088
  * @param {TouchEvent} e
2417
3089
  * @this {ColorPicker}
2418
3090
  */
2419
3091
  pointerUp({ target }) {
2420
3092
  const self = this;
2421
- const selection = document.getSelection();
3093
+ const { parent } = self;
3094
+ const doc = getDocument(parent);
3095
+ const currentOpen = querySelector(`${colorPickerParentSelector}.open`, doc) !== null;
3096
+ const selection = doc.getSelection();
2422
3097
  // @ts-ignore
2423
3098
  if (!self.dragElement && !selection.toString().length
2424
3099
  // @ts-ignore
2425
- && !self.parent.contains(target)) {
2426
- self.hide();
3100
+ && !parent.contains(target)) {
3101
+ self.hide(currentOpen);
2427
3102
  }
2428
3103
 
2429
3104
  self.dragElement = null;
2430
3105
  }
2431
3106
 
2432
3107
  /**
2433
- * Handles the `ColorPicker` touchmove / mousemove events listeners.
3108
+ * The `ColorPicker` *touchmove* / *mousemove* events listener for control knobs.
2434
3109
  * @param {TouchEvent} e
2435
3110
  */
2436
3111
  pointerMove(e) {
2437
3112
  const self = this;
2438
- const { dragElement, visuals, format } = self;
3113
+ const { dragElement, visuals } = self;
2439
3114
  const [v1, v2, v3] = visuals;
2440
3115
  const {
2441
3116
  // @ts-ignore
@@ -2451,20 +3126,20 @@
2451
3126
  const offsetY = Y - window.pageYOffset - controlRect.top;
2452
3127
 
2453
3128
  if (dragElement === v1) {
2454
- self.changeControl1({ offsetX, offsetY });
3129
+ self.changeControl1(offsetX, offsetY);
2455
3130
  }
2456
3131
 
2457
3132
  if (dragElement === v2) {
2458
- self.changeControl2({ offsetY });
3133
+ self.changeControl2(offsetY);
2459
3134
  }
2460
3135
 
2461
- if (dragElement === v3 && format !== 'hex') {
2462
- self.changeAlpha({ offsetY });
3136
+ if (dragElement === v3) {
3137
+ self.changeAlpha(offsetY);
2463
3138
  }
2464
3139
  }
2465
3140
 
2466
3141
  /**
2467
- * Handles the `ColorPicker` events listeners associated with the color knobs.
3142
+ * The `ColorPicker` *keydown* event listener for control knobs.
2468
3143
  * @param {KeyboardEvent} e
2469
3144
  */
2470
3145
  handleKnobs(e) {
@@ -2475,54 +3150,64 @@
2475
3150
  if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
2476
3151
  e.preventDefault();
2477
3152
 
2478
- const { activeElement } = document;
2479
- const { controlKnobs } = self;
2480
- const currentKnob = controlKnobs.find((x) => x === activeElement);
3153
+ const { format, controlKnobs, visuals } = self;
3154
+ const { offsetWidth, offsetHeight } = visuals[0];
2481
3155
  const [c1, c2, c3] = controlKnobs;
3156
+ const { activeElement } = getDocument(c1);
3157
+ const currentKnob = controlKnobs.find((x) => x === activeElement);
3158
+ const yRatio = offsetHeight / (format === 'hsl' ? 100 : 360);
2482
3159
 
2483
3160
  if (currentKnob) {
2484
3161
  let offsetX = 0;
2485
3162
  let offsetY = 0;
3163
+
2486
3164
  if (target === c1) {
3165
+ const xRatio = offsetWidth / (format === 'hsl' ? 360 : 100);
3166
+
2487
3167
  if ([keyArrowLeft, keyArrowRight].includes(code)) {
2488
- self.controlPositions.c1x += code === keyArrowRight ? +1 : -1;
3168
+ self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
2489
3169
  } else if ([keyArrowUp, keyArrowDown].includes(code)) {
2490
- self.controlPositions.c1y += code === keyArrowDown ? +1 : -1;
3170
+ self.controlPositions.c1y += code === keyArrowDown ? yRatio : -yRatio;
2491
3171
  }
2492
3172
 
2493
3173
  offsetX = self.controlPositions.c1x;
2494
3174
  offsetY = self.controlPositions.c1y;
2495
- self.changeControl1({ offsetX, offsetY });
3175
+ self.changeControl1(offsetX, offsetY);
2496
3176
  } else if (target === c2) {
2497
- self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
3177
+ self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code)
3178
+ ? yRatio
3179
+ : -yRatio;
3180
+
2498
3181
  offsetY = self.controlPositions.c2y;
2499
- self.changeControl2({ offsetY });
3182
+ self.changeControl2(offsetY);
2500
3183
  } else if (target === c3) {
2501
- self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
3184
+ self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code)
3185
+ ? yRatio
3186
+ : -yRatio;
3187
+
2502
3188
  offsetY = self.controlPositions.c3y;
2503
- self.changeAlpha({ offsetY });
3189
+ self.changeAlpha(offsetY);
2504
3190
  }
2505
-
2506
- self.setColorAppearence();
2507
- self.updateInputs();
2508
- self.updateControls();
2509
- self.updateVisuals();
2510
3191
  self.handleScroll(e);
2511
3192
  }
2512
3193
  }
2513
3194
 
2514
- /** Handles the event listeners of the color form. */
3195
+ /** The event listener of the colour form inputs. */
2515
3196
  changeHandler() {
2516
3197
  const self = this;
2517
3198
  let colorSource;
2518
- /** @type {HTMLInputElement} */
2519
- // @ts-ignore
2520
- const { activeElement } = document;
2521
3199
  const {
2522
- inputs, format, value: currentValue, input,
3200
+ inputs, format, value: currentValue, input, controlPositions, visuals,
2523
3201
  } = self;
2524
- const [i1, i2, i3, i4] = inputs;
2525
- const isNonColorValue = self.includeNonColor && nonColors.includes(currentValue);
3202
+ /** @type {*} */
3203
+ const { activeElement } = getDocument(input);
3204
+ const { offsetHeight } = visuals[0];
3205
+ const [i1,,, i4] = inputs;
3206
+ const [v1, v2, v3, v4] = format === 'rgb'
3207
+ ? inputs.map((i) => parseFloat(i.value) / (i === i4 ? 100 : 1))
3208
+ : inputs.map((i) => parseFloat(i.value) / (i !== i1 ? 100 : 360));
3209
+ const isNonColorValue = self.hasNonColor && nonColors.includes(currentValue);
3210
+ const alpha = i4 ? v4 : (1 - controlPositions.c3y / offsetHeight);
2526
3211
 
2527
3212
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
2528
3213
  if (activeElement === input) {
@@ -2534,14 +3219,28 @@
2534
3219
  } else if (format === 'hex') {
2535
3220
  colorSource = i1.value;
2536
3221
  } else if (format === 'hsl') {
2537
- colorSource = `hsla(${i1.value},${i2.value}%,${i3.value}%,${i4.value})`;
3222
+ colorSource = {
3223
+ h: v1, s: v2, l: v3, a: alpha,
3224
+ };
3225
+ } else if (format === 'hwb') {
3226
+ colorSource = {
3227
+ h: v1, w: v2, b: v3, a: alpha,
3228
+ };
2538
3229
  } else {
2539
- colorSource = `rgba(${inputs.map((x) => x.value).join(',')})`;
3230
+ colorSource = {
3231
+ r: v1, g: v2, b: v3, a: alpha,
3232
+ };
2540
3233
  }
2541
3234
 
2542
- self.color = new Color(colorSource, { format });
3235
+ const {
3236
+ r, g, b, a,
3237
+ } = new Color(colorSource);
3238
+
3239
+ ObjectAssign(self.color, {
3240
+ r, g, b, a,
3241
+ });
2543
3242
  self.setControlPositions();
2544
- self.setColorAppearence();
3243
+ self.updateAppearance();
2545
3244
  self.updateInputs();
2546
3245
  self.updateControls();
2547
3246
  self.updateVisuals();
@@ -2558,49 +3257,57 @@
2558
3257
  * * `lightness` and `saturation` for HEX/RGB;
2559
3258
  * * `lightness` and `hue` for HSL.
2560
3259
  *
2561
- * @param {Record<string, number>} offsets
3260
+ * @param {number} X the X component of the offset
3261
+ * @param {number} Y the Y component of the offset
2562
3262
  */
2563
- changeControl1(offsets) {
3263
+ changeControl1(X, Y) {
2564
3264
  const self = this;
2565
3265
  let [offsetX, offsetY] = [0, 0];
2566
- const { offsetX: X, offsetY: Y } = offsets;
2567
3266
  const {
2568
- format, controlPositions,
2569
- height1, height2, height3, width1,
3267
+ format, controlPositions, visuals,
2570
3268
  } = self;
3269
+ const { offsetHeight, offsetWidth } = visuals[0];
2571
3270
 
2572
- if (X > width1) {
2573
- offsetX = width1;
2574
- } else if (X >= 0) {
2575
- offsetX = X;
2576
- }
3271
+ if (X > offsetWidth) offsetX = offsetWidth;
3272
+ else if (X >= 0) offsetX = X;
2577
3273
 
2578
- if (Y > height1) {
2579
- offsetY = height1;
2580
- } else if (Y >= 0) {
2581
- offsetY = Y;
2582
- }
3274
+ if (Y > offsetHeight) offsetY = offsetHeight;
3275
+ else if (Y >= 0) offsetY = Y;
3276
+
3277
+ const hue = format === 'hsl'
3278
+ ? offsetX / offsetWidth
3279
+ : controlPositions.c2y / offsetHeight;
2583
3280
 
2584
- const hue = format !== 'hsl'
2585
- ? Math.round((controlPositions.c2y / height2) * 360)
2586
- : Math.round((offsetX / width1) * 360);
3281
+ const saturation = format === 'hsl'
3282
+ ? 1 - controlPositions.c2y / offsetHeight
3283
+ : offsetX / offsetWidth;
2587
3284
 
2588
- const saturation = format !== 'hsl'
2589
- ? Math.round((offsetX / width1) * 100)
2590
- : Math.round((1 - controlPositions.c2y / height2) * 100);
3285
+ const lightness = 1 - offsetY / offsetHeight;
3286
+ const alpha = 1 - controlPositions.c3y / offsetHeight;
2591
3287
 
2592
- const lightness = Math.round((1 - offsetY / height1) * 100);
2593
- const alpha = format !== 'hex' ? Math.round((1 - controlPositions.c3y / height3) * 100) / 100 : 1;
2594
- const tempFormat = format !== 'hsl' ? 'hsva' : 'hsla';
3288
+ const colorObject = format === 'hsl'
3289
+ ? {
3290
+ h: hue, s: saturation, l: lightness, a: alpha,
3291
+ }
3292
+ : {
3293
+ h: hue, s: saturation, v: lightness, a: alpha,
3294
+ };
2595
3295
 
2596
3296
  // new color
2597
- self.color = new Color(`${tempFormat}(${hue},${saturation}%,${lightness}%,${alpha})`, { format });
3297
+ const {
3298
+ r, g, b, a,
3299
+ } = new Color(colorObject);
3300
+
3301
+ ObjectAssign(self.color, {
3302
+ r, g, b, a,
3303
+ });
3304
+
2598
3305
  // new positions
2599
3306
  self.controlPositions.c1x = offsetX;
2600
3307
  self.controlPositions.c1y = offsetY;
2601
3308
 
2602
3309
  // update color picker
2603
- self.setColorAppearence();
3310
+ self.updateAppearance();
2604
3311
  self.updateInputs();
2605
3312
  self.updateControls();
2606
3313
  self.updateVisuals();
@@ -2608,37 +3315,52 @@
2608
3315
 
2609
3316
  /**
2610
3317
  * Updates `ColorPicker` second control:
2611
- * * `hue` for HEX/RGB;
3318
+ * * `hue` for HEX/RGB/HWB;
2612
3319
  * * `saturation` for HSL.
2613
3320
  *
2614
- * @param {Record<string, number>} offset
3321
+ * @param {number} Y the Y offset
2615
3322
  */
2616
- changeControl2(offset) {
3323
+ changeControl2(Y) {
2617
3324
  const self = this;
2618
- const { offsetY: Y } = offset;
2619
3325
  const {
2620
- format, width1, height1, height2, height3, controlPositions,
3326
+ format, controlPositions, visuals,
2621
3327
  } = self;
2622
- let offsetY = 0;
3328
+ const { offsetHeight, offsetWidth } = visuals[0];
2623
3329
 
2624
- if (Y > height2) {
2625
- offsetY = height2;
2626
- } else if (Y >= 0) {
2627
- offsetY = Y;
2628
- }
3330
+ let offsetY = 0;
2629
3331
 
2630
- const hue = format !== 'hsl' ? Math.round((offsetY / height2) * 360) : Math.round((controlPositions.c1x / width1) * 360);
2631
- const saturation = format !== 'hsl' ? Math.round((controlPositions.c1x / width1) * 100) : Math.round((1 - offsetY / height2) * 100);
2632
- const lightness = Math.round((1 - controlPositions.c1y / height1) * 100);
2633
- const alpha = format !== 'hex' ? Math.round((1 - controlPositions.c3y / height3) * 100) / 100 : 1;
2634
- const colorFormat = format !== 'hsl' ? 'hsva' : 'hsla';
3332
+ if (Y > offsetHeight) offsetY = offsetHeight;
3333
+ else if (Y >= 0) offsetY = Y;
3334
+
3335
+ const hue = format === 'hsl'
3336
+ ? controlPositions.c1x / offsetWidth
3337
+ : offsetY / offsetHeight;
3338
+ const saturation = format === 'hsl'
3339
+ ? 1 - offsetY / offsetHeight
3340
+ : controlPositions.c1x / offsetWidth;
3341
+ const lightness = 1 - controlPositions.c1y / offsetHeight;
3342
+ const alpha = 1 - controlPositions.c3y / offsetHeight;
3343
+ const colorObject = format === 'hsl'
3344
+ ? {
3345
+ h: hue, s: saturation, l: lightness, a: alpha,
3346
+ }
3347
+ : {
3348
+ h: hue, s: saturation, v: lightness, a: alpha,
3349
+ };
2635
3350
 
2636
3351
  // new color
2637
- self.color = new Color(`${colorFormat}(${hue},${saturation}%,${lightness}%,${alpha})`, { format });
3352
+ const {
3353
+ r, g, b, a,
3354
+ } = new Color(colorObject);
3355
+
3356
+ ObjectAssign(self.color, {
3357
+ r, g, b, a,
3358
+ });
3359
+
2638
3360
  // new position
2639
3361
  self.controlPositions.c2y = offsetY;
2640
3362
  // update color picker
2641
- self.setColorAppearence();
3363
+ self.updateAppearance();
2642
3364
  self.updateInputs();
2643
3365
  self.updateControls();
2644
3366
  self.updateVisuals();
@@ -2646,95 +3368,108 @@
2646
3368
 
2647
3369
  /**
2648
3370
  * Updates `ColorPicker` last control,
2649
- * the `alpha` channel for RGB/HSL.
3371
+ * the `alpha` channel.
2650
3372
  *
2651
- * @param {Record<string, number>} offset
3373
+ * @param {number} Y
2652
3374
  */
2653
- changeAlpha(offset) {
3375
+ changeAlpha(Y) {
2654
3376
  const self = this;
2655
- const { height3 } = self;
2656
- const { offsetY: Y } = offset;
3377
+ const { visuals } = self;
3378
+ const { offsetHeight } = visuals[0];
2657
3379
  let offsetY = 0;
2658
3380
 
2659
- if (Y > height3) {
2660
- offsetY = height3;
2661
- } else if (Y >= 0) {
2662
- offsetY = Y;
2663
- }
3381
+ if (Y > offsetHeight) offsetY = offsetHeight;
3382
+ else if (Y >= 0) offsetY = Y;
2664
3383
 
2665
3384
  // update color alpha
2666
- const alpha = Math.round((1 - offsetY / height3) * 100);
2667
- self.color.setAlpha(alpha / 100);
3385
+ const alpha = 1 - offsetY / offsetHeight;
3386
+ self.color.setAlpha(alpha);
2668
3387
  // update position
2669
3388
  self.controlPositions.c3y = offsetY;
2670
3389
  // update color picker
3390
+ self.updateAppearance();
2671
3391
  self.updateInputs();
2672
3392
  self.updateControls();
2673
- // alpha?
2674
3393
  self.updateVisuals();
2675
3394
  }
2676
3395
 
2677
- /** Update opened dropdown position on scroll. */
3396
+ /**
3397
+ * Updates `ColorPicker` control positions on:
3398
+ * * initialization
3399
+ * * window resize
3400
+ */
3401
+ update() {
3402
+ const self = this;
3403
+ self.updateDropdownPosition();
3404
+ self.updateAppearance();
3405
+ self.setControlPositions();
3406
+ self.updateInputs(true);
3407
+ self.updateControls();
3408
+ self.updateVisuals();
3409
+ }
3410
+
3411
+ /** Updates the open dropdown position on *scroll* event. */
2678
3412
  updateDropdownPosition() {
2679
3413
  const self = this;
2680
3414
  const { input, colorPicker, colorMenu } = self;
2681
3415
  const elRect = getBoundingClientRect(input);
3416
+ const { top, bottom } = elRect;
2682
3417
  const { offsetHeight: elHeight } = input;
2683
- const windowHeight = document.documentElement.clientHeight;
2684
- const isPicker = classToggle(colorPicker, true);
3418
+ const windowHeight = getDocumentElement(input).clientHeight;
3419
+ const isPicker = hasClass(colorPicker, 'show');
2685
3420
  const dropdown = isPicker ? colorPicker : colorMenu;
3421
+ if (!dropdown) return;
2686
3422
  const { offsetHeight: dropHeight } = dropdown;
2687
- const distanceBottom = windowHeight - elRect.bottom;
2688
- const distanceTop = elRect.top;
2689
- const bottomExceed = elRect.top + dropHeight + elHeight > windowHeight; // show
2690
- const topExceed = elRect.top - dropHeight < 0; // show-top
2691
-
2692
- if (hasClass(dropdown, 'show') && distanceBottom < distanceTop && bottomExceed) {
2693
- removeClass(dropdown, 'show');
2694
- addClass(dropdown, 'show-top');
2695
- }
2696
- if (hasClass(dropdown, 'show-top') && distanceBottom > distanceTop && topExceed) {
2697
- removeClass(dropdown, 'show-top');
2698
- addClass(dropdown, 'show');
3423
+ const distanceBottom = windowHeight - bottom;
3424
+ const distanceTop = top;
3425
+ const bottomExceed = top + dropHeight + elHeight > windowHeight; // show
3426
+ const topExceed = top - dropHeight < 0; // show-top
3427
+
3428
+ if ((hasClass(dropdown, 'bottom') || !topExceed) && distanceBottom < distanceTop && bottomExceed) {
3429
+ removeClass(dropdown, 'bottom');
3430
+ addClass(dropdown, 'top');
3431
+ } else {
3432
+ removeClass(dropdown, 'top');
3433
+ addClass(dropdown, 'bottom');
2699
3434
  }
2700
3435
  }
2701
3436
 
2702
- /** Update control knobs' positions. */
3437
+ /** Updates control knobs' positions. */
2703
3438
  setControlPositions() {
2704
3439
  const self = this;
2705
3440
  const {
2706
- hsv, hsl, format, height1, height2, height3, width1,
3441
+ format, visuals, color, hsl, hsv,
2707
3442
  } = self;
3443
+ const { offsetHeight, offsetWidth } = visuals[0];
3444
+ const alpha = color.a;
2708
3445
  const hue = hsl.h;
3446
+
2709
3447
  const saturation = format !== 'hsl' ? hsv.s : hsl.s;
2710
3448
  const lightness = format !== 'hsl' ? hsv.v : hsl.l;
2711
- const alpha = hsv.a;
2712
-
2713
- self.controlPositions.c1x = format !== 'hsl' ? saturation * width1 : (hue / 360) * width1;
2714
- self.controlPositions.c1y = (1 - lightness) * height1;
2715
- self.controlPositions.c2y = format !== 'hsl' ? (hue / 360) * height2 : (1 - saturation) * height2;
2716
3449
 
2717
- if (format !== 'hex') {
2718
- self.controlPositions.c3y = (1 - alpha) * height3;
2719
- }
3450
+ self.controlPositions.c1x = format !== 'hsl' ? saturation * offsetWidth : hue * offsetWidth;
3451
+ self.controlPositions.c1y = (1 - lightness) * offsetHeight;
3452
+ self.controlPositions.c2y = format !== 'hsl' ? hue * offsetHeight : (1 - saturation) * offsetHeight;
3453
+ self.controlPositions.c3y = (1 - alpha) * offsetHeight;
2720
3454
  }
2721
3455
 
2722
- /** Update the visual appearance label. */
2723
- setColorAppearence() {
3456
+ /** Update the visual appearance label and control knob labels. */
3457
+ updateAppearance() {
2724
3458
  const self = this;
2725
3459
  const {
2726
- componentLabels, colorLabels, hsl, hsv, hex, format, knobLabels,
3460
+ componentLabels, colorLabels, color, parent,
3461
+ hsl, hsv, hex, format, controlKnobs,
2727
3462
  } = self;
2728
3463
  const {
2729
- lightnessLabel, saturationLabel, hueLabel, alphaLabel, appearanceLabel, hexLabel,
3464
+ appearanceLabel, hexLabel, valueLabel,
2730
3465
  } = componentLabels;
2731
- let { requiredLabel } = componentLabels;
2732
- const [knob1Lbl, knob2Lbl, knob3Lbl] = knobLabels;
2733
- const hue = Math.round(hsl.h);
2734
- const alpha = hsv.a;
3466
+ const { r, g, b } = color.toRgb();
3467
+ const [knob1, knob2, knob3] = controlKnobs;
3468
+ const hue = roundPart(hsl.h * 360);
3469
+ const alpha = color.a;
2735
3470
  const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
2736
- const saturation = Math.round(saturationSource * 100);
2737
- const lightness = Math.round(hsl.l * 100);
3471
+ const saturation = roundPart(saturationSource * 100);
3472
+ const lightness = roundPart(hsl.l * 100);
2738
3473
  const hsvl = hsv.v * 100;
2739
3474
  let colorName;
2740
3475
 
@@ -2770,99 +3505,118 @@
2770
3505
  colorName = colorLabels.pink;
2771
3506
  }
2772
3507
 
3508
+ let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
3509
+
2773
3510
  if (format === 'hsl') {
2774
- knob1Lbl.innerText = `${hueLabel}: ${hue}°. ${lightnessLabel}: ${lightness}%`;
2775
- knob2Lbl.innerText = `${saturationLabel}: ${saturation}%`;
3511
+ colorLabel = `HSL: ${hue}°, ${saturation}%, ${lightness}%`;
3512
+ setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3513
+ setAttribute(knob1, ariaValueText, `${hue}° & ${lightness}%`);
3514
+ setAttribute(knob1, ariaValueNow, `${hue}`);
3515
+ setAttribute(knob2, ariaValueText, `${saturation}%`);
3516
+ setAttribute(knob2, ariaValueNow, `${saturation}`);
3517
+ } else if (format === 'hwb') {
3518
+ const { hwb } = self;
3519
+ const whiteness = roundPart(hwb.w * 100);
3520
+ const blackness = roundPart(hwb.b * 100);
3521
+ colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
3522
+ setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3523
+ setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
3524
+ setAttribute(knob1, ariaValueNow, `${whiteness}`);
3525
+ setAttribute(knob2, ariaValueText, `${hue}%`);
3526
+ setAttribute(knob2, ariaValueNow, `${hue}`);
2776
3527
  } else {
2777
- knob1Lbl.innerText = `${lightnessLabel}: ${lightness}%. ${saturationLabel}: ${saturation}%`;
2778
- knob2Lbl.innerText = `${hueLabel}: ${hue}°`;
3528
+ colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
3529
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3530
+ setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
3531
+ setAttribute(knob1, ariaValueNow, `${lightness}`);
3532
+ setAttribute(knob2, ariaValueText, `${hue}°`);
3533
+ setAttribute(knob2, ariaValueNow, `${hue}`);
2779
3534
  }
2780
3535
 
2781
- if (format !== 'hex') {
2782
- const alphaValue = Math.round(alpha * 100);
2783
- knob3Lbl.innerText = `${alphaLabel}: ${alphaValue}%`;
2784
- }
3536
+ const alphaValue = roundPart(alpha * 100);
3537
+ setAttribute(knob3, ariaValueText, `${alphaValue}%`);
3538
+ setAttribute(knob3, ariaValueNow, `${alphaValue}`);
2785
3539
 
2786
- // update color labels
2787
- self.appearance.innerText = `${appearanceLabel}: ${colorName}.`;
2788
- const colorLabel = format === 'hex'
2789
- ? `${hexLabel} ${hex.split('').join(' ')}.`
2790
- : self.value.toUpperCase();
3540
+ // update the input backgroundColor
3541
+ const newColor = color.toString();
3542
+ setElementStyle(self.input, { backgroundColor: newColor });
2791
3543
 
2792
- if (self.label) {
2793
- const fieldLabel = self.label.innerText.replace('*', '').trim();
2794
- /** @type {HTMLSpanElement} */
2795
- // @ts-ignore
2796
- const [pickerBtnSpan] = self.pickerToggle.children;
2797
- requiredLabel = self.required ? ` ${requiredLabel}` : '';
2798
- pickerBtnSpan.innerText = `${fieldLabel}: ${colorLabel}${requiredLabel}`;
3544
+ // toggle dark/light classes will also style the placeholder
3545
+ // dark sets color white, light sets color black
3546
+ // isDark ? '#000' : '#fff'
3547
+ if (!self.isDark) {
3548
+ if (hasClass(parent, 'txt-dark')) removeClass(parent, 'txt-dark');
3549
+ if (!hasClass(parent, 'txt-light')) addClass(parent, 'txt-light');
3550
+ } else {
3551
+ if (hasClass(parent, 'txt-light')) removeClass(parent, 'txt-light');
3552
+ if (!hasClass(parent, 'txt-dark')) addClass(parent, 'txt-dark');
2799
3553
  }
2800
3554
  }
2801
3555
 
2802
- /** Updates the control knobs positions. */
3556
+ /** Updates the control knobs actual positions. */
2803
3557
  updateControls() {
2804
- const { format, controlKnobs, controlPositions } = this;
3558
+ const { controlKnobs, controlPositions } = this;
3559
+ let {
3560
+ c1x, c1y, c2y, c3y,
3561
+ } = controlPositions;
2805
3562
  const [control1, control2, control3] = controlKnobs;
2806
- control1.style.transform = `translate3d(${controlPositions.c1x - 3}px,${controlPositions.c1y - 3}px,0)`;
2807
- control2.style.transform = `translate3d(0,${controlPositions.c2y - 3}px,0)`;
3563
+ // round control positions
3564
+ [c1x, c1y, c2y, c3y] = [c1x, c1y, c2y, c3y].map(roundPart);
2808
3565
 
2809
- if (format !== 'hex') {
2810
- control3.style.transform = `translate3d(0,${controlPositions.c3y - 3}px,0)`;
2811
- }
3566
+ setElementStyle(control1, { transform: `translate3d(${c1x - 4}px,${c1y - 4}px,0)` });
3567
+ setElementStyle(control2, { transform: `translate3d(0,${c2y - 4}px,0)` });
3568
+ setElementStyle(control3, { transform: `translate3d(0,${c3y - 4}px,0)` });
2812
3569
  }
2813
3570
 
2814
3571
  /**
2815
- * Update all color form inputs.
3572
+ * Updates all color form inputs.
2816
3573
  * @param {boolean=} isPrevented when `true`, the component original event is prevented
2817
3574
  */
2818
3575
  updateInputs(isPrevented) {
2819
3576
  const self = this;
2820
3577
  const {
2821
- value: oldColor, rgb, hsl, hsv, format, parent, input, inputs,
3578
+ value: oldColor, format, inputs, color, hsl,
2822
3579
  } = self;
2823
3580
  const [i1, i2, i3, i4] = inputs;
2824
-
2825
- const alpha = hsl.a;
2826
- const hue = Math.round(hsl.h);
2827
- const saturation = Math.round(hsl.s * 100);
2828
- const lightSource = format === 'hsl' ? hsl.l : hsv.v;
2829
- const lightness = Math.round(lightSource * 100);
3581
+ const alpha = roundPart(color.a * 100);
3582
+ const hue = roundPart(hsl.h * 360);
2830
3583
  let newColor;
2831
3584
 
2832
3585
  if (format === 'hex') {
2833
- newColor = self.color.toHexString();
3586
+ newColor = self.color.toHexString(true);
2834
3587
  i1.value = self.hex;
2835
3588
  } else if (format === 'hsl') {
3589
+ const lightness = roundPart(hsl.l * 100);
3590
+ const saturation = roundPart(hsl.s * 100);
2836
3591
  newColor = self.color.toHslString();
2837
3592
  i1.value = `${hue}`;
2838
3593
  i2.value = `${saturation}`;
2839
3594
  i3.value = `${lightness}`;
2840
3595
  i4.value = `${alpha}`;
3596
+ } else if (format === 'hwb') {
3597
+ const { w, b } = self.hwb;
3598
+ const whiteness = roundPart(w * 100);
3599
+ const blackness = roundPart(b * 100);
3600
+
3601
+ newColor = self.color.toHwbString();
3602
+ i1.value = `${hue}`;
3603
+ i2.value = `${whiteness}`;
3604
+ i3.value = `${blackness}`;
3605
+ i4.value = `${alpha}`;
2841
3606
  } else if (format === 'rgb') {
3607
+ let { r, g, b } = self.rgb;
3608
+ [r, g, b] = [r, g, b].map(roundPart);
3609
+
2842
3610
  newColor = self.color.toRgbString();
2843
- i1.value = `${rgb.r}`;
2844
- i2.value = `${rgb.g}`;
2845
- i3.value = `${rgb.b}`;
3611
+ i1.value = `${r}`;
3612
+ i2.value = `${g}`;
3613
+ i3.value = `${b}`;
2846
3614
  i4.value = `${alpha}`;
2847
3615
  }
2848
3616
 
2849
3617
  // update the color value
2850
3618
  self.value = `${newColor}`;
2851
3619
 
2852
- // update the input backgroundColor
2853
- ObjectAssign(input.style, { backgroundColor: newColor });
2854
-
2855
- // toggle dark/light classes will also style the placeholder
2856
- // dark sets color white, light sets color black
2857
- // isDark ? '#000' : '#fff'
2858
- if (!self.isDark) {
2859
- if (hasClass(parent, 'dark')) removeClass(parent, 'dark');
2860
- if (!hasClass(parent, 'light')) addClass(parent, 'light');
2861
- } else {
2862
- if (hasClass(parent, 'light')) removeClass(parent, 'light');
2863
- if (!hasClass(parent, 'dark')) addClass(parent, 'dark');
2864
- }
2865
-
2866
3620
  // don't trigger the custom event unless it's really changed
2867
3621
  if (!isPrevented && newColor !== oldColor) {
2868
3622
  firePickerChange(self);
@@ -2870,14 +3624,15 @@
2870
3624
  }
2871
3625
 
2872
3626
  /**
2873
- * Handles the `Space` and `Enter` keys inputs.
3627
+ * The `Space` & `Enter` keys specific event listener.
3628
+ * Toggle visibility of the `ColorPicker` / the presets menu, showing one will hide the other.
2874
3629
  * @param {KeyboardEvent} e
2875
3630
  * @this {ColorPicker}
2876
3631
  */
2877
- keyHandler(e) {
3632
+ keyToggle(e) {
2878
3633
  const self = this;
2879
3634
  const { menuToggle } = self;
2880
- const { activeElement } = document;
3635
+ const { activeElement } = getDocument(menuToggle);
2881
3636
  const { code } = e;
2882
3637
 
2883
3638
  if ([keyEnter, keySpace].includes(code)) {
@@ -2900,80 +3655,79 @@
2900
3655
  togglePicker(e) {
2901
3656
  e.preventDefault();
2902
3657
  const self = this;
2903
- const pickerIsOpen = classToggle(self.colorPicker, true);
3658
+ const { colorPicker } = self;
2904
3659
 
2905
- if (self.isOpen && pickerIsOpen) {
3660
+ if (self.isOpen && hasClass(colorPicker, 'show')) {
2906
3661
  self.hide(true);
2907
3662
  } else {
2908
- self.showPicker();
3663
+ showDropdown(self, colorPicker);
2909
3664
  }
2910
3665
  }
2911
3666
 
2912
3667
  /** Shows the `ColorPicker` dropdown. */
2913
3668
  showPicker() {
2914
3669
  const self = this;
2915
- classToggle(self.colorMenu);
2916
- addClass(self.colorPicker, 'show');
2917
- self.input.focus();
2918
- self.show();
2919
- setAttribute(self.pickerToggle, ariaExpanded, 'true');
3670
+ const { colorPicker } = self;
3671
+
3672
+ if (!['top', 'bottom'].some((c) => hasClass(colorPicker, c))) {
3673
+ showDropdown(self, colorPicker);
3674
+ }
2920
3675
  }
2921
3676
 
2922
3677
  /** Toggles the visibility of the `ColorPicker` presets menu. */
2923
3678
  toggleMenu() {
2924
3679
  const self = this;
2925
- const menuIsOpen = classToggle(self.colorMenu, true);
3680
+ const { colorMenu } = self;
2926
3681
 
2927
- if (self.isOpen && menuIsOpen) {
3682
+ if (self.isOpen && hasClass(colorMenu, 'show')) {
2928
3683
  self.hide(true);
2929
3684
  } else {
2930
- showMenu(self);
2931
- }
2932
- }
2933
-
2934
- /** Show the dropdown. */
2935
- show() {
2936
- const self = this;
2937
- if (!self.isOpen) {
2938
- addClass(self.parent, 'open');
2939
- toggleEventsOnShown(self, true);
2940
- self.updateDropdownPosition();
2941
- self.isOpen = true;
3685
+ showDropdown(self, colorMenu);
2942
3686
  }
2943
3687
  }
2944
3688
 
2945
3689
  /**
2946
- * Hides the currently opened dropdown.
3690
+ * Hides the currently open `ColorPicker` dropdown.
2947
3691
  * @param {boolean=} focusPrevented
2948
3692
  */
2949
3693
  hide(focusPrevented) {
2950
3694
  const self = this;
2951
3695
  if (self.isOpen) {
2952
- const { pickerToggle, colorMenu } = self;
2953
- toggleEventsOnShown(self);
2954
-
2955
- removeClass(self.parent, 'open');
2956
-
2957
- classToggle(self.colorPicker);
2958
- setAttribute(pickerToggle, ariaExpanded, 'false');
2959
-
2960
- if (colorMenu) {
2961
- classToggle(colorMenu);
2962
- setAttribute(self.menuToggle, ariaExpanded, 'false');
3696
+ const {
3697
+ pickerToggle, menuToggle, colorPicker, colorMenu, parent, input,
3698
+ } = self;
3699
+ const openPicker = hasClass(colorPicker, 'show');
3700
+ const openDropdown = openPicker ? colorPicker : colorMenu;
3701
+ const relatedBtn = openPicker ? pickerToggle : menuToggle;
3702
+ const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
3703
+
3704
+ if (openDropdown) {
3705
+ removeClass(openDropdown, 'show');
3706
+ setAttribute(relatedBtn, ariaExpanded, 'false');
3707
+ setTimeout(() => {
3708
+ removePosition(openDropdown);
3709
+ if (!querySelector('.show', parent)) {
3710
+ removeClass(parent, 'open');
3711
+ toggleEventsOnShown(self);
3712
+ self.isOpen = false;
3713
+ }
3714
+ }, animationDuration);
2963
3715
  }
2964
3716
 
2965
3717
  if (!self.isValid) {
2966
3718
  self.value = self.color.toString();
2967
3719
  }
2968
-
2969
- self.isOpen = false;
2970
-
2971
3720
  if (!focusPrevented) {
2972
- pickerToggle.focus();
3721
+ focus(pickerToggle);
3722
+ }
3723
+ setAttribute(input, tabIndex, '-1');
3724
+ if (menuToggle) {
3725
+ setAttribute(menuToggle, tabIndex, '-1');
2973
3726
  }
2974
3727
  }
2975
3728
  }
2976
3729
 
3730
+ /** Removes `ColorPicker` from target `<input>`. */
2977
3731
  dispose() {
2978
3732
  const self = this;
2979
3733
  const { input, parent } = self;
@@ -2982,40 +3736,52 @@
2982
3736
  [...parent.children].forEach((el) => {
2983
3737
  if (el !== input) el.remove();
2984
3738
  });
3739
+
3740
+ removeAttribute(input, tabIndex);
3741
+ setElementStyle(input, { backgroundColor: '' });
3742
+
3743
+ ['txt-light', 'txt-dark'].forEach((c) => removeClass(parent, c));
2985
3744
  Data.remove(input, colorPickerString);
2986
3745
  }
2987
3746
  }
2988
3747
 
2989
3748
  ObjectAssign(ColorPicker, {
2990
3749
  Color,
3750
+ ColorPalette,
3751
+ Version,
2991
3752
  getInstance: getColorPickerInstance,
2992
3753
  init: initColorPicker,
2993
3754
  selector: colorPickerSelector,
3755
+ // utils important for render
3756
+ roundPart,
3757
+ setElementStyle,
3758
+ setAttribute,
3759
+ getBoundingClientRect,
2994
3760
  });
2995
3761
 
3762
+ let CPID = 0;
3763
+
2996
3764
  /**
2997
3765
  * `ColorPickerElement` Web Component.
2998
3766
  * @example
2999
- * <color-picker>
3000
- * <input type="text">
3767
+ * <label for="UNIQUE_ID">Label</label>
3768
+ * <color-picker data-format="hex" data-value="#075">
3769
+ * <input id="UNIQUE_ID" type="text" class="color-preview btn-appearance">
3001
3770
  * </color-picker>
3002
3771
  */
3003
3772
  class ColorPickerElement extends HTMLElement {
3004
3773
  constructor() {
3005
3774
  super();
3006
- /** @type {ColorPicker?} */
3007
- this.colorPicker = null;
3008
- /** @type {HTMLInputElement} */
3009
- // @ts-ignore - `HTMLInputElement` is also `HTMLElement`
3010
- this.input = querySelector('input', this);
3011
3775
  /** @type {boolean} */
3012
3776
  this.isDisconnected = true;
3013
3777
  this.attachShadow({ mode: 'open' });
3014
3778
  }
3015
3779
 
3016
- get value() { return this.input.value; }
3017
-
3018
- get color() { return this.colorPicker && this.colorPicker.color; }
3780
+ /**
3781
+ * Returns the current color value.
3782
+ * @returns {string?}
3783
+ */
3784
+ get value() { return this.input ? this.input.value : null; }
3019
3785
 
3020
3786
  connectedCallback() {
3021
3787
  if (this.colorPicker) {
@@ -3025,11 +3791,50 @@
3025
3791
  return;
3026
3792
  }
3027
3793
 
3028
- this.colorPicker = new ColorPicker(this.input);
3029
- this.isDisconnected = false;
3794
+ const inputs = getElementsByTagName('input', this);
3795
+
3796
+ if (!inputs.length) {
3797
+ const label = getAttribute(this, 'data-label');
3798
+ const value = getAttribute(this, 'data-value') || '#069';
3799
+ const format = getAttribute(this, 'data-format') || 'rgb';
3800
+ const newInput = createElement({
3801
+ tagName: 'input',
3802
+ type: 'text',
3803
+ className: 'color-preview btn-appearance',
3804
+ });
3805
+ let id = getAttribute(this, 'data-id');
3806
+ if (!id) {
3807
+ id = `color-picker-${format}-${CPID}`;
3808
+ CPID += 1;
3809
+ }
3810
+
3811
+ const labelElement = createElement({ tagName: 'label', innerText: label || 'Color Picker' });
3812
+ this.before(labelElement);
3813
+ setAttribute(labelElement, 'for', id);
3814
+ setAttribute(newInput, 'id', id);
3815
+ setAttribute(newInput, 'name', id);
3816
+ setAttribute(newInput, 'autocomplete', 'off');
3817
+ setAttribute(newInput, 'spellcheck', 'false');
3818
+ setAttribute(newInput, 'value', value);
3819
+ this.append(newInput);
3820
+ }
3821
+
3822
+ const [input] = inputs;
3823
+
3824
+ if (input) {
3825
+ /** @type {HTMLInputElement} */
3826
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3827
+ this.input = input;
3030
3828
 
3031
- if (this.shadowRoot) {
3032
- this.shadowRoot.append(createElement('slot'));
3829
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3830
+ this.colorPicker = new ColorPicker(input);
3831
+ this.color = this.colorPicker.color;
3832
+
3833
+ if (this.shadowRoot) {
3834
+ this.shadowRoot.append(createElement('slot'));
3835
+ }
3836
+
3837
+ this.isDisconnected = false;
3033
3838
  }
3034
3839
  }
3035
3840
 
@@ -3042,6 +3847,9 @@
3042
3847
  ObjectAssign(ColorPickerElement, {
3043
3848
  Color,
3044
3849
  ColorPicker,
3850
+ ColorPalette,
3851
+ getInstance: getColorPickerInstance,
3852
+ Version,
3045
3853
  });
3046
3854
 
3047
3855
  customElements.define('color-picker', ColorPickerElement);