@thednp/color-picker 0.0.1 → 0.0.2-alpha3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * ColorPickerElement v0.0.1 (http://thednp.github.io/color-picker)
2
+ * ColorPickerElement v0.0.2alpha3 (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
  */
@@ -82,14 +82,9 @@ const setAttribute = (element, attribute, value) => element.setAttribute(attribu
82
82
  const getAttribute = (element, attribute) => element.getAttribute(attribute);
83
83
 
84
84
  /**
85
- * Returns the `document.head` or the `<head>` element.
86
- *
87
- * @param {(Node | HTMLElement | Element | globalThis)=} node
88
- * @returns {HTMLElement | HTMLHeadElement}
85
+ * A global namespace for `document.head`.
89
86
  */
90
- function getDocumentHead(node) {
91
- return getDocument(node).head;
92
- }
87
+ const { head: documentHead } = document;
93
88
 
94
89
  /**
95
90
  * Shortcut for `window.getComputedStyle(element).propertyName`
@@ -110,20 +105,21 @@ function getElementStyle(element, property) {
110
105
  return property in computedStyle ? computedStyle[property] : '';
111
106
  }
112
107
 
113
- /**
114
- * Shortcut for `Object.keys()` static method.
115
- * @param {Record<string, any>} obj a target object
116
- * @returns {string[]}
117
- */
118
- const ObjectKeys = (obj) => Object.keys(obj);
119
-
120
108
  /**
121
109
  * Shortcut for multiple uses of `HTMLElement.style.propertyName` method.
122
110
  * @param {HTMLElement | Element} element target element
123
111
  * @param {Partial<CSSStyleDeclaration>} styles attribute value
124
112
  */
125
113
  // @ts-ignore
126
- const setElementStyle = (element, styles) => ObjectAssign(element.style, styles);
114
+ const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
115
+
116
+ /**
117
+ * Shortcut for `String.toLowerCase()`.
118
+ *
119
+ * @param {string} source input string
120
+ * @returns {string} lowercase output string
121
+ */
122
+ const toLowerCase = (source) => source.toLowerCase();
127
123
 
128
124
  /**
129
125
  * A list of explicit default non-color values.
@@ -141,7 +137,7 @@ function roundPart(v) {
141
137
  }
142
138
 
143
139
  // Color supported formats
144
- const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
140
+ const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsv', 'hwb'];
145
141
 
146
142
  // Hue angles
147
143
  const ANGLES = 'deg|rad|grad|turn';
@@ -163,10 +159,17 @@ const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
163
159
  // Add angles to the mix
164
160
  const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
165
161
 
162
+ // Start & end
163
+ const START_MATCH = '(?:[\\s|\\(\\s|\\s\\(\\s]+)?';
164
+ const END_MATCH = '(?:[\\s|\\)\\s]+)?';
165
+ // Components separation
166
+ const SEP = '(?:[,|\\s]+)';
167
+ const SEP2 = '(?:[,|\\/\\s]*)?';
168
+
166
169
  // Actual matching.
167
170
  // Parentheses and commas are optional, but not required.
168
171
  // Whitespace can take the place of commas or opening paren
169
- const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
172
+ const PERMISSIVE_MATCH = `${START_MATCH}(${CSS_UNIT2})${SEP}(${CSS_UNIT})${SEP}(${CSS_UNIT})${SEP2}(${CSS_UNIT})?${END_MATCH}`;
170
173
 
171
174
  const matchers = {
172
175
  CSS_UNIT: new RegExp(CSS_UNIT2),
@@ -199,23 +202,24 @@ function isPercentage(n) {
199
202
  return `${n}`.includes('%');
200
203
  }
201
204
 
202
- /**
203
- * Check to see if string passed in is an angle
204
- * @param {string} n testing string
205
- * @returns {boolean} the query result
206
- */
207
- function isAngle(n) {
208
- return ANGLES.split('|').some((a) => `${n}`.includes(a));
209
- }
210
-
211
205
  /**
212
206
  * Check to see if string passed is a web safe colour.
207
+ * @see https://stackoverflow.com/a/16994164
213
208
  * @param {string} color a colour name, EG: *red*
214
209
  * @returns {boolean} the query result
215
210
  */
216
211
  function isColorName(color) {
217
- return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
218
- && !/[0-9]/.test(color);
212
+ if (nonColors.includes(color)
213
+ || ['#', ...COLOR_FORMAT].some((f) => color.includes(f))) return false;
214
+
215
+ if (['black', 'white'].includes(color)) return true;
216
+
217
+ return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
218
+ setElementStyle(documentHead, { color });
219
+ const computedColor = getElementStyle(documentHead, 'color');
220
+ setElementStyle(documentHead, { color: '' });
221
+ return computedColor !== c;
222
+ });
219
223
  }
220
224
 
221
225
  /**
@@ -236,15 +240,20 @@ function isValidCSSUnit(color) {
236
240
  */
237
241
  function bound01(N, max) {
238
242
  let n = N;
239
- if (isOnePointZero(n)) n = '100%';
240
243
 
241
- n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
244
+ if (typeof N === 'number'
245
+ && Math.min(N, 0) === 0 // round values to 6 decimals Math.round(N * (10 ** 6)) / 10 ** 6
246
+ && Math.max(N, 1) === 1) return N;
247
+
248
+ if (isOnePointZero(N)) n = '100%';
242
249
 
243
- // Handle hue angles
244
- if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
250
+ const processPercent = isPercentage(n);
251
+ n = max === 360
252
+ ? parseFloat(n)
253
+ : Math.min(max, Math.max(0, parseFloat(n)));
245
254
 
246
255
  // Automatically convert percentage into number
247
- if (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
256
+ if (processPercent) n = (n * max) / 100;
248
257
 
249
258
  // Handle floating point rounding errors
250
259
  if (Math.abs(n - max) < 0.000001) {
@@ -255,11 +264,11 @@ function bound01(N, max) {
255
264
  // If n is a hue given in degrees,
256
265
  // wrap around out-of-range values into [0, 360] range
257
266
  // then convert into [0, 1].
258
- n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max));
267
+ n = (n < 0 ? (n % max) + max : n % max) / max;
259
268
  } else {
260
269
  // If n not a hue given in degrees
261
270
  // Convert into [0, 1] range if it isn't already.
262
- n = (n % max) / parseFloat(String(max));
271
+ n = (n % max) / max;
263
272
  }
264
273
  return n;
265
274
  }
@@ -294,7 +303,6 @@ function clamp01(v) {
294
303
  * @returns {string}
295
304
  */
296
305
  function getRGBFromName(name) {
297
- const documentHead = getDocumentHead();
298
306
  setElementStyle(documentHead, { color: name });
299
307
  const colorName = getElementStyle(documentHead, 'color');
300
308
  setElementStyle(documentHead, { color: '' });
@@ -340,15 +348,12 @@ function pad2(c) {
340
348
  /**
341
349
  * Converts an RGB colour value to HSL.
342
350
  *
343
- * @param {number} R Red component [0, 255]
344
- * @param {number} G Green component [0, 255]
345
- * @param {number} B Blue component [0, 255]
351
+ * @param {number} r Red component [0, 1]
352
+ * @param {number} g Green component [0, 1]
353
+ * @param {number} b Blue component [0, 1]
346
354
  * @returns {CP.HSL} {h,s,l} object with [0, 1] ranged values
347
355
  */
348
- function rgbToHsl(R, G, B) {
349
- const r = R / 255;
350
- const g = G / 255;
351
- const b = B / 255;
356
+ function rgbToHsl(r, g, b) {
352
357
  const max = Math.max(r, g, b);
353
358
  const min = Math.min(r, g, b);
354
359
  let h = 0;
@@ -360,17 +365,10 @@ function rgbToHsl(R, G, B) {
360
365
  } else {
361
366
  const d = max - min;
362
367
  s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
363
- switch (max) {
364
- case r:
365
- h = (g - b) / d + (g < b ? 6 : 0);
366
- break;
367
- case g:
368
- h = (b - r) / d + 2;
369
- break;
370
- case b:
371
- h = (r - g) / d + 4;
372
- break;
373
- }
368
+ if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
369
+ if (max === g) h = (b - r) / d + 2;
370
+ if (max === b) h = (r - g) / d + 4;
371
+
374
372
  h /= 6;
375
373
  }
376
374
  return { h, s, l };
@@ -393,21 +391,46 @@ function hueToRgb(p, q, t) {
393
391
  return p;
394
392
  }
395
393
 
394
+ /**
395
+ * Converts an HSL colour value to RGB.
396
+ *
397
+ * @param {number} h Hue Angle [0, 1]
398
+ * @param {number} s Saturation [0, 1]
399
+ * @param {number} l Lightness Angle [0, 1]
400
+ * @returns {CP.RGB} {r,g,b} object with [0, 1] ranged values
401
+ */
402
+ function hslToRgb(h, s, l) {
403
+ let r = 0;
404
+ let g = 0;
405
+ let b = 0;
406
+
407
+ if (s === 0) {
408
+ // achromatic
409
+ g = l;
410
+ b = l;
411
+ r = l;
412
+ } else {
413
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
414
+ const p = 2 * l - q;
415
+ r = hueToRgb(p, q, h + 1 / 3);
416
+ g = hueToRgb(p, q, h);
417
+ b = hueToRgb(p, q, h - 1 / 3);
418
+ }
419
+
420
+ return { r, g, b };
421
+ }
422
+
396
423
  /**
397
424
  * Returns an HWB colour object from an RGB colour object.
398
425
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
399
426
  * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
400
427
  *
401
- * @param {number} R Red component [0, 255]
402
- * @param {number} G Green [0, 255]
403
- * @param {number} B Blue [0, 255]
428
+ * @param {number} r Red component [0, 1]
429
+ * @param {number} g Green [0, 1]
430
+ * @param {number} b Blue [0, 1]
404
431
  * @return {CP.HWB} {h,w,b} object with [0, 1] ranged values
405
432
  */
406
- function rgbToHwb(R, G, B) {
407
- const r = R / 255;
408
- const g = G / 255;
409
- const b = B / 255;
410
-
433
+ function rgbToHwb(r, g, b) {
411
434
  let f = 0;
412
435
  let i = 0;
413
436
  const whiteness = Math.min(r, g, b);
@@ -437,50 +460,18 @@ function rgbToHwb(R, G, B) {
437
460
  * @param {number} H Hue Angle [0, 1]
438
461
  * @param {number} W Whiteness [0, 1]
439
462
  * @param {number} B Blackness [0, 1]
440
- * @return {CP.RGB} {r,g,b} object with [0, 255] ranged values
463
+ * @return {CP.RGB} {r,g,b} object with [0, 1] ranged values
441
464
  *
442
465
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
443
466
  * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
444
467
  */
445
468
  function hwbToRgb(H, W, B) {
446
469
  if (W + B >= 1) {
447
- const gray = (W / (W + B)) * 255;
470
+ const gray = W / (W + B);
448
471
  return { r: gray, g: gray, b: gray };
449
472
  }
450
473
  let { r, g, b } = hslToRgb(H, 1, 0.5);
451
- [r, g, b] = [r, g, b]
452
- .map((v) => (v / 255) * (1 - W - B) + W)
453
- .map((v) => v * 255);
454
-
455
- return { r, g, b };
456
- }
457
-
458
- /**
459
- * Converts an HSL colour value to RGB.
460
- *
461
- * @param {number} h Hue Angle [0, 1]
462
- * @param {number} s Saturation [0, 1]
463
- * @param {number} l Lightness Angle [0, 1]
464
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
465
- */
466
- function hslToRgb(h, s, l) {
467
- let r = 0;
468
- let g = 0;
469
- let b = 0;
470
-
471
- if (s === 0) {
472
- // achromatic
473
- g = l;
474
- b = l;
475
- r = l;
476
- } else {
477
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
478
- const p = 2 * l - q;
479
- r = hueToRgb(p, q, h + 1 / 3);
480
- g = hueToRgb(p, q, h);
481
- b = hueToRgb(p, q, h - 1 / 3);
482
- }
483
- [r, g, b] = [r, g, b].map((x) => x * 255);
474
+ [r, g, b] = [r, g, b].map((v) => v * (1 - W - B) + W);
484
475
 
485
476
  return { r, g, b };
486
477
  }
@@ -488,15 +479,12 @@ function hslToRgb(h, s, l) {
488
479
  /**
489
480
  * Converts an RGB colour value to HSV.
490
481
  *
491
- * @param {number} R Red component [0, 255]
492
- * @param {number} G Green [0, 255]
493
- * @param {number} B Blue [0, 255]
482
+ * @param {number} r Red component [0, 1]
483
+ * @param {number} g Green [0, 1]
484
+ * @param {number} b Blue [0, 1]
494
485
  * @returns {CP.HSV} {h,s,v} object with [0, 1] ranged values
495
486
  */
496
- function rgbToHsv(R, G, B) {
497
- const r = R / 255;
498
- const g = G / 255;
499
- const b = B / 255;
487
+ function rgbToHsv(r, g, b) {
500
488
  const max = Math.max(r, g, b);
501
489
  const min = Math.min(r, g, b);
502
490
  let h = 0;
@@ -506,17 +494,10 @@ function rgbToHsv(R, G, B) {
506
494
  if (max === min) {
507
495
  h = 0; // achromatic
508
496
  } else {
509
- switch (max) {
510
- case r:
511
- h = (g - b) / d + (g < b ? 6 : 0);
512
- break;
513
- case g:
514
- h = (b - r) / d + 2;
515
- break;
516
- case b:
517
- h = (r - g) / d + 4;
518
- break;
519
- }
497
+ if (r === max) h = (g - b) / d + (g < b ? 6 : 0);
498
+ if (g === max) h = (b - r) / d + 2;
499
+ if (b === max) h = (r - g) / d + 4;
500
+
520
501
  h /= 6;
521
502
  }
522
503
  return { h, s, v };
@@ -543,7 +524,7 @@ function hsvToRgb(H, S, V) {
543
524
  const r = [v, q, p, p, t, v][mod];
544
525
  const g = [t, v, v, q, p, p][mod];
545
526
  const b = [p, p, t, v, v, q][mod];
546
- return { r: r * 255, g: g * 255, b: b * 255 };
527
+ return { r, g, b };
547
528
  }
548
529
 
549
530
  /**
@@ -567,7 +548,7 @@ function rgbToHex(r, g, b, allow3Char) {
567
548
  // Return a 3 character hex if possible
568
549
  if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
569
550
  && hex[1].charAt(0) === hex[1].charAt(1)
570
- && hex[2].charAt(0) === hex[2].charAt(1)) {
551
+ && hex[2].charAt(0) === hex[2].charAt(1)) {
571
552
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
572
553
  }
573
554
 
@@ -595,51 +576,34 @@ function rgbaToHex(r, g, b, a, allow4Char) {
595
576
  // Return a 4 character hex if possible
596
577
  if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
597
578
  && hex[1].charAt(0) === hex[1].charAt(1)
598
- && hex[2].charAt(0) === hex[2].charAt(1)
599
- && hex[3].charAt(0) === hex[3].charAt(1)) {
579
+ && hex[2].charAt(0) === hex[2].charAt(1)
580
+ && hex[3].charAt(0) === hex[3].charAt(1)) {
600
581
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
601
582
  }
602
583
  return hex.join('');
603
584
  }
604
585
 
605
- /**
606
- * Returns a colour object corresponding to a given number.
607
- * @param {number} color input number
608
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
609
- */
610
- function numberInputToObject(color) {
611
- /* eslint-disable no-bitwise */
612
- return {
613
- r: color >> 16,
614
- g: (color & 0xff00) >> 8,
615
- b: color & 0xff,
616
- };
617
- /* eslint-enable no-bitwise */
618
- }
619
-
620
586
  /**
621
587
  * Permissive string parsing. Take in a number of formats, and output an object
622
588
  * based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
623
589
  * @param {string} input colour value in any format
624
- * @returns {Record<string, (number | string)> | false} an object matching the RegExp
590
+ * @returns {Record<string, (number | string | boolean)> | false} an object matching the RegExp
625
591
  */
626
592
  function stringInputToObject(input) {
627
- let color = input.trim().toLowerCase();
593
+ let color = toLowerCase(input.trim());
594
+
628
595
  if (color.length === 0) {
629
596
  return {
630
- r: 0, g: 0, b: 0, a: 0,
597
+ r: 0, g: 0, b: 0, a: 1,
631
598
  };
632
599
  }
633
- let named = false;
600
+
634
601
  if (isColorName(color)) {
635
602
  color = getRGBFromName(color);
636
- named = true;
637
603
  } else if (nonColors.includes(color)) {
638
- const isTransparent = color === 'transparent';
639
- const rgb = isTransparent ? 0 : 255;
640
- const a = isTransparent ? 0 : 1;
604
+ const a = color === 'transparent' ? 0 : 1;
641
605
  return {
642
- r: rgb, g: rgb, b: rgb, a, format: 'rgb',
606
+ r: 0, g: 0, b: 0, a, format: 'rgb', ok: true,
643
607
  };
644
608
  }
645
609
 
@@ -654,24 +618,28 @@ function stringInputToObject(input) {
654
618
  r: m1, g: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'rgb',
655
619
  };
656
620
  }
621
+
657
622
  [, m1, m2, m3, m4] = matchers.hsl.exec(color) || [];
658
623
  if (m1 && m2 && m3/* && m4 */) {
659
624
  return {
660
625
  h: m1, s: m2, l: m3, a: m4 !== undefined ? m4 : 1, format: 'hsl',
661
626
  };
662
627
  }
628
+
663
629
  [, m1, m2, m3, m4] = matchers.hsv.exec(color) || [];
664
630
  if (m1 && m2 && m3/* && m4 */) {
665
631
  return {
666
632
  h: m1, s: m2, v: m3, a: m4 !== undefined ? m4 : 1, format: 'hsv',
667
633
  };
668
634
  }
635
+
669
636
  [, m1, m2, m3, m4] = matchers.hwb.exec(color) || [];
670
637
  if (m1 && m2 && m3) {
671
638
  return {
672
639
  h: m1, w: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'hwb',
673
640
  };
674
641
  }
642
+
675
643
  [, m1, m2, m3, m4] = matchers.hex8.exec(color) || [];
676
644
  if (m1 && m2 && m3 && m4) {
677
645
  return {
@@ -679,19 +647,20 @@ function stringInputToObject(input) {
679
647
  g: parseIntFromHex(m2),
680
648
  b: parseIntFromHex(m3),
681
649
  a: convertHexToDecimal(m4),
682
- // format: named ? 'rgb' : 'hex8',
683
- format: named ? 'rgb' : 'hex',
650
+ format: 'hex',
684
651
  };
685
652
  }
653
+
686
654
  [, m1, m2, m3] = matchers.hex6.exec(color) || [];
687
655
  if (m1 && m2 && m3) {
688
656
  return {
689
657
  r: parseIntFromHex(m1),
690
658
  g: parseIntFromHex(m2),
691
659
  b: parseIntFromHex(m3),
692
- format: named ? 'rgb' : 'hex',
660
+ format: 'hex',
693
661
  };
694
662
  }
663
+
695
664
  [, m1, m2, m3, m4] = matchers.hex4.exec(color) || [];
696
665
  if (m1 && m2 && m3 && m4) {
697
666
  return {
@@ -699,19 +668,20 @@ function stringInputToObject(input) {
699
668
  g: parseIntFromHex(m2 + m2),
700
669
  b: parseIntFromHex(m3 + m3),
701
670
  a: convertHexToDecimal(m4 + m4),
702
- // format: named ? 'rgb' : 'hex8',
703
- format: named ? 'rgb' : 'hex',
671
+ format: 'hex',
704
672
  };
705
673
  }
674
+
706
675
  [, m1, m2, m3] = matchers.hex3.exec(color) || [];
707
676
  if (m1 && m2 && m3) {
708
677
  return {
709
678
  r: parseIntFromHex(m1 + m1),
710
679
  g: parseIntFromHex(m2 + m2),
711
680
  b: parseIntFromHex(m3 + m3),
712
- format: named ? 'rgb' : 'hex',
681
+ format: 'hex',
713
682
  };
714
683
  }
684
+
715
685
  return false;
716
686
  }
717
687
 
@@ -742,7 +712,9 @@ function stringInputToObject(input) {
742
712
  */
743
713
  function inputToRGB(input) {
744
714
  let rgb = { r: 0, g: 0, b: 0 };
715
+ /** @type {*} */
745
716
  let color = input;
717
+ /** @type {string | number} */
746
718
  let a = 1;
747
719
  let s = null;
748
720
  let v = null;
@@ -753,58 +725,67 @@ function inputToRGB(input) {
753
725
  let r = null;
754
726
  let g = null;
755
727
  let ok = false;
756
- let format = 'hex';
728
+ const inputFormat = typeof color === 'object' && color.format;
729
+ let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
757
730
 
758
731
  if (typeof input === 'string') {
759
- // @ts-ignore -- this now is converted to object
760
732
  color = stringInputToObject(input);
761
733
  if (color) ok = true;
762
734
  }
763
735
  if (typeof color === 'object') {
764
736
  if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
765
737
  ({ r, g, b } = color);
766
- // RGB values now are all in [0, 255] range
767
- [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255);
738
+ // RGB values now are all in [0, 1] range
739
+ [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255));
768
740
  rgb = { r, g, b };
769
741
  ok = true;
770
- format = 'rgb';
771
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
742
+ format = color.format || 'rgb';
743
+ }
744
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
772
745
  ({ h, s, v } = color);
773
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
774
- s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
775
- v = typeof v === 'number' ? v : bound01(v, 100); // brightness can be `5%` or a [0, 1] value
746
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
747
+ s = bound01(s, 100); // saturation can be `5%` or a [0, 1] value
748
+ v = bound01(v, 100); // brightness can be `5%` or a [0, 1] value
776
749
  rgb = hsvToRgb(h, s, v);
777
750
  ok = true;
778
751
  format = 'hsv';
779
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
752
+ }
753
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
780
754
  ({ h, s, l } = color);
781
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
782
- s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
783
- l = typeof l === 'number' ? l : bound01(l, 100); // lightness can be `5%` or a [0, 1] value
755
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
756
+ s = bound01(s, 100); // saturation can be `5%` or a [0, 1] value
757
+ l = bound01(l, 100); // lightness can be `5%` or a [0, 1] value
784
758
  rgb = hslToRgb(h, s, l);
785
759
  ok = true;
786
760
  format = 'hsl';
787
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
761
+ }
762
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
788
763
  ({ h, w, b } = color);
789
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
790
- w = typeof w === 'number' ? w : bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
791
- b = typeof b === 'number' ? b : bound01(b, 100); // blackness can be `5%` or a [0, 1] value
764
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
765
+ w = bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
766
+ b = bound01(b, 100); // blackness can be `5%` or a [0, 1] value
792
767
  rgb = hwbToRgb(h, w, b);
793
768
  ok = true;
794
769
  format = 'hwb';
795
770
  }
796
771
  if (isValidCSSUnit(color.a)) {
797
- a = color.a;
798
- a = isPercentage(`${a}`) ? bound01(a, 100) : a;
772
+ a = color.a; // @ts-ignore -- `parseFloat` works with numbers too
773
+ a = isPercentage(`${a}`) || parseFloat(a) > 1 ? bound01(a, 100) : a;
799
774
  }
800
775
  }
776
+ if (typeof color === 'undefined') {
777
+ ok = true;
778
+ }
801
779
 
802
780
  return {
803
- ok, // @ts-ignore
804
- format: color.format || format,
805
- r: Math.min(255, Math.max(rgb.r, 0)),
806
- g: Math.min(255, Math.max(rgb.g, 0)),
807
- b: Math.min(255, Math.max(rgb.b, 0)),
781
+ ok,
782
+ format,
783
+ // r: Math.min(255, Math.max(rgb.r, 0)),
784
+ // g: Math.min(255, Math.max(rgb.g, 0)),
785
+ // b: Math.min(255, Math.max(rgb.b, 0)),
786
+ r: rgb.r,
787
+ g: rgb.g,
788
+ b: rgb.b,
808
789
  a: boundAlpha(a),
809
790
  };
810
791
  }
@@ -823,15 +804,13 @@ class Color {
823
804
  constructor(input, config) {
824
805
  let color = input;
825
806
  const configFormat = config && COLOR_FORMAT.includes(config)
826
- ? config : 'rgb';
807
+ ? config : '';
827
808
 
828
- // If input is already a `Color`, return itself
809
+ // If input is already a `Color`, clone its values
829
810
  if (color instanceof Color) {
830
811
  color = inputToRGB(color);
831
812
  }
832
- if (typeof color === 'number') {
833
- color = numberInputToObject(color);
834
- }
813
+
835
814
  const {
836
815
  r, g, b, a, ok, format,
837
816
  } = inputToRGB(color);
@@ -840,7 +819,7 @@ class Color {
840
819
  const self = this;
841
820
 
842
821
  /** @type {CP.ColorInput} */
843
- self.originalInput = color;
822
+ self.originalInput = input;
844
823
  /** @type {number} */
845
824
  self.r = r;
846
825
  /** @type {number} */
@@ -881,24 +860,21 @@ class Color {
881
860
  let R = 0;
882
861
  let G = 0;
883
862
  let B = 0;
884
- const rp = r / 255;
885
- const rg = g / 255;
886
- const rb = b / 255;
887
863
 
888
- if (rp <= 0.03928) {
889
- R = rp / 12.92;
864
+ if (r <= 0.03928) {
865
+ R = r / 12.92;
890
866
  } else {
891
- R = ((rp + 0.055) / 1.055) ** 2.4;
867
+ R = ((r + 0.055) / 1.055) ** 2.4;
892
868
  }
893
- if (rg <= 0.03928) {
894
- G = rg / 12.92;
869
+ if (g <= 0.03928) {
870
+ G = g / 12.92;
895
871
  } else {
896
- G = ((rg + 0.055) / 1.055) ** 2.4;
872
+ G = ((g + 0.055) / 1.055) ** 2.4;
897
873
  }
898
- if (rb <= 0.03928) {
899
- B = rb / 12.92;
874
+ if (b <= 0.03928) {
875
+ B = b / 12.92;
900
876
  } else {
901
- B = ((rb + 0.055) / 1.055) ** 2.4;
877
+ B = ((b + 0.055) / 1.055) ** 2.4;
902
878
  }
903
879
  return 0.2126 * R + 0.7152 * G + 0.0722 * B;
904
880
  }
@@ -908,7 +884,7 @@ class Color {
908
884
  * @returns {number} a number in the [0, 255] range
909
885
  */
910
886
  get brightness() {
911
- const { r, g, b } = this;
887
+ const { r, g, b } = this.toRgb();
912
888
  return (r * 299 + g * 587 + b * 114) / 1000;
913
889
  }
914
890
 
@@ -917,12 +893,14 @@ class Color {
917
893
  * @returns {CP.RGBA} an {r,g,b,a} object with [0, 255] ranged values
918
894
  */
919
895
  toRgb() {
920
- const {
896
+ let {
921
897
  r, g, b, a,
922
898
  } = this;
923
899
 
900
+ [r, g, b] = [r, g, b].map((n) => roundPart(n * 255 * 100) / 100);
901
+ a = roundPart(a * 100) / 100;
924
902
  return {
925
- r, g, b, a: roundPart(a * 100) / 100,
903
+ r, g, b, a,
926
904
  };
927
905
  }
928
906
 
@@ -1016,7 +994,7 @@ class Color {
1016
994
  toHsv() {
1017
995
  const {
1018
996
  r, g, b, a,
1019
- } = this.toRgb();
997
+ } = this;
1020
998
  const { h, s, v } = rgbToHsv(r, g, b);
1021
999
 
1022
1000
  return {
@@ -1031,7 +1009,7 @@ class Color {
1031
1009
  toHsl() {
1032
1010
  const {
1033
1011
  r, g, b, a,
1034
- } = this.toRgb();
1012
+ } = this;
1035
1013
  const { h, s, l } = rgbToHsl(r, g, b);
1036
1014
 
1037
1015
  return {
@@ -1116,6 +1094,7 @@ class Color {
1116
1094
  */
1117
1095
  setAlpha(alpha) {
1118
1096
  const self = this;
1097
+ if (typeof alpha !== 'number') return self;
1119
1098
  self.a = boundAlpha(alpha);
1120
1099
  return self;
1121
1100
  }
@@ -1230,6 +1209,7 @@ ObjectAssign(Color, {
1230
1209
  isOnePointZero,
1231
1210
  isPercentage,
1232
1211
  isValidCSSUnit,
1212
+ isColorName,
1233
1213
  pad2,
1234
1214
  clamp01,
1235
1215
  bound01,
@@ -1247,10 +1227,11 @@ ObjectAssign(Color, {
1247
1227
  hueToRgb,
1248
1228
  hwbToRgb,
1249
1229
  parseIntFromHex,
1250
- numberInputToObject,
1251
1230
  stringInputToObject,
1252
1231
  inputToRGB,
1253
1232
  roundPart,
1233
+ getElementStyle,
1234
+ setElementStyle,
1254
1235
  ObjectAssign,
1255
1236
  });
1256
1237
 
@@ -1380,24 +1361,6 @@ const ariaValueText = 'aria-valuetext';
1380
1361
  */
1381
1362
  const ariaValueNow = 'aria-valuenow';
1382
1363
 
1383
- /**
1384
- * A global namespace for aria-haspopup.
1385
- * @type {string}
1386
- */
1387
- const ariaHasPopup = 'aria-haspopup';
1388
-
1389
- /**
1390
- * A global namespace for aria-hidden.
1391
- * @type {string}
1392
- */
1393
- const ariaHidden = 'aria-hidden';
1394
-
1395
- /**
1396
- * A global namespace for aria-labelledby.
1397
- * @type {string}
1398
- */
1399
- const ariaLabelledBy = 'aria-labelledby';
1400
-
1401
1364
  /**
1402
1365
  * A global namespace for `ArrowDown` key.
1403
1366
  * @type {string} e.which = 40 equivalent
@@ -1524,37 +1487,6 @@ const resizeEvent = 'resize';
1524
1487
  */
1525
1488
  const focusoutEvent = 'focusout';
1526
1489
 
1527
- // @ts-ignore
1528
- const { userAgentData: uaDATA } = navigator;
1529
-
1530
- /**
1531
- * A global namespace for `userAgentData` object.
1532
- */
1533
- const userAgentData = uaDATA;
1534
-
1535
- const { userAgent: userAgentString } = navigator;
1536
-
1537
- /**
1538
- * A global namespace for `navigator.userAgent` string.
1539
- */
1540
- const userAgent = userAgentString;
1541
-
1542
- const mobileBrands = /iPhone|iPad|iPod|Android/i;
1543
- let isMobileCheck = false;
1544
-
1545
- if (userAgentData) {
1546
- isMobileCheck = userAgentData.brands
1547
- .some((/** @type {Record<String, any>} */x) => mobileBrands.test(x.brand));
1548
- } else {
1549
- isMobileCheck = mobileBrands.test(userAgent);
1550
- }
1551
-
1552
- /**
1553
- * A global `boolean` for mobile detection.
1554
- * @type {boolean}
1555
- */
1556
- const isMobile = isMobileCheck;
1557
-
1558
1490
  /**
1559
1491
  * Returns the `document.documentElement` or the `<html>` element.
1560
1492
  *
@@ -1739,30 +1671,6 @@ function getElementsByClassName(selector, parent) {
1739
1671
  return lookUp.getElementsByClassName(selector);
1740
1672
  }
1741
1673
 
1742
- /**
1743
- * This is a shortie for `document.createElementNS` method
1744
- * which allows you to create a new `HTMLElement` for a given `tagName`
1745
- * or based on an object with specific non-readonly attributes:
1746
- * `id`, `className`, `textContent`, `style`, etc.
1747
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
1748
- *
1749
- * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
1750
- * @param {Record<string, string> | string} param `tagName` or object
1751
- * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
1752
- */
1753
- function createElementNS(namespace, param) {
1754
- if (typeof param === 'string') {
1755
- return getDocument().createElementNS(namespace, param);
1756
- }
1757
-
1758
- const { tagName } = param;
1759
- const attr = { ...param };
1760
- const newElement = createElementNS(namespace, tagName);
1761
- delete attr.tagName;
1762
- ObjectAssign(newElement, attr);
1763
- return newElement;
1764
- }
1765
-
1766
1674
  /**
1767
1675
  * Shortcut for the `Element.dispatchEvent(Event)` method.
1768
1676
  *
@@ -1880,12 +1788,11 @@ function normalizeValue(value) {
1880
1788
  }
1881
1789
 
1882
1790
  /**
1883
- * Shortcut for `String.toLowerCase()`.
1884
- *
1885
- * @param {string} source input string
1886
- * @returns {string} lowercase output string
1791
+ * Shortcut for `Object.keys()` static method.
1792
+ * @param {Record<string, any>} obj a target object
1793
+ * @returns {string[]}
1887
1794
  */
1888
- const toLowerCase = (source) => source.toLowerCase();
1795
+ const ObjectKeys = (obj) => Object.keys(obj);
1889
1796
 
1890
1797
  /**
1891
1798
  * Utility to normalize component options.
@@ -1990,6 +1897,77 @@ function removeClass(element, classNAME) {
1990
1897
  */
1991
1898
  const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
1992
1899
 
1900
+ /**
1901
+ * @class
1902
+ * Returns a color palette with a given set of parameters.
1903
+ * @example
1904
+ * new ColorPalette(0, 12, 10);
1905
+ * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: Array<Color> }
1906
+ */
1907
+ class ColorPalette {
1908
+ /**
1909
+ * The `hue` parameter is optional, which would be set to 0.
1910
+ * @param {number[]} args represeinting hue, hueSteps, lightSteps
1911
+ * * `args.hue` the starting Hue [0, 360]
1912
+ * * `args.hueSteps` Hue Steps Count [5, 24]
1913
+ * * `args.lightSteps` Lightness Steps Count [5, 12]
1914
+ */
1915
+ constructor(...args) {
1916
+ let hue = 0;
1917
+ let hueSteps = 12;
1918
+ let lightSteps = 10;
1919
+ let lightnessArray = [0.5];
1920
+
1921
+ if (args.length === 3) {
1922
+ [hue, hueSteps, lightSteps] = args;
1923
+ } else if (args.length === 2) {
1924
+ [hueSteps, lightSteps] = args;
1925
+ if ([hueSteps, lightSteps].some((n) => n < 1)) {
1926
+ throw TypeError('ColorPalette: both arguments must be higher than 0.');
1927
+ }
1928
+ }
1929
+
1930
+ /** @type {*} */
1931
+ const colors = [];
1932
+ const hueStep = 360 / hueSteps;
1933
+ const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
1934
+ const steps1To13 = [0.25, 0.2, 0.15, 0.11, 0.09, 0.075];
1935
+ const lightSets = [[1, 2, 3], [4, 5], [6, 7], [8, 9], [10, 11], [12, 13]];
1936
+ const closestSet = lightSets.find((set) => set.includes(lightSteps));
1937
+
1938
+ // find a lightStep that won't go beyond black and white
1939
+ // something within the [10-90] range of lightness
1940
+ const lightStep = closestSet
1941
+ ? steps1To13[lightSets.indexOf(closestSet)]
1942
+ : (100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100);
1943
+
1944
+ // light tints
1945
+ for (let i = 1; i < half + 1; i += 1) {
1946
+ lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
1947
+ }
1948
+
1949
+ // dark tints
1950
+ for (let i = 1; i < lightSteps - half; i += 1) {
1951
+ lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
1952
+ }
1953
+
1954
+ // feed `colors` Array
1955
+ for (let i = 0; i < hueSteps; i += 1) {
1956
+ const currentHue = ((hue + i * hueStep) % 360) / 360;
1957
+ lightnessArray.forEach((l) => {
1958
+ colors.push(new Color({ h: currentHue, s: 1, l }));
1959
+ });
1960
+ }
1961
+
1962
+ this.hue = hue;
1963
+ this.hueSteps = hueSteps;
1964
+ this.lightSteps = lightSteps;
1965
+ this.colors = colors;
1966
+ }
1967
+ }
1968
+
1969
+ ObjectAssign(ColorPalette, { Color });
1970
+
1993
1971
  /** @type {Record<string, string>} */
1994
1972
  const colorPickerLabels = {
1995
1973
  pickerLabel: 'Colour Picker',
@@ -2017,14 +1995,72 @@ const colorPickerLabels = {
2017
1995
  */
2018
1996
  const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
2019
1997
 
1998
+ const tabIndex = 'tabindex';
1999
+
2020
2000
  /**
2021
- * Shortcut for `String.toUpperCase()`.
2022
- *
2023
- * @param {string} source input string
2024
- * @returns {string} uppercase output string
2001
+ * Check if a string is valid JSON string.
2002
+ * @param {string} str the string input
2003
+ * @returns {boolean} the query result
2004
+ */
2005
+ function isValidJSON(str) {
2006
+ try {
2007
+ JSON.parse(str);
2008
+ } catch (e) {
2009
+ return false;
2010
+ }
2011
+ return true;
2012
+ }
2013
+
2014
+ /**
2015
+ * Shortcut for `String.toUpperCase()`.
2016
+ *
2017
+ * @param {string} source input string
2018
+ * @returns {string} uppercase output string
2025
2019
  */
2026
2020
  const toUpperCase = (source) => source.toUpperCase();
2027
2021
 
2022
+ /**
2023
+ * A global namespace for aria-haspopup.
2024
+ * @type {string}
2025
+ */
2026
+ const ariaHasPopup = 'aria-haspopup';
2027
+
2028
+ /**
2029
+ * A global namespace for aria-hidden.
2030
+ * @type {string}
2031
+ */
2032
+ const ariaHidden = 'aria-hidden';
2033
+
2034
+ /**
2035
+ * A global namespace for aria-labelledby.
2036
+ * @type {string}
2037
+ */
2038
+ const ariaLabelledBy = 'aria-labelledby';
2039
+
2040
+ /**
2041
+ * This is a shortie for `document.createElementNS` method
2042
+ * which allows you to create a new `HTMLElement` for a given `tagName`
2043
+ * or based on an object with specific non-readonly attributes:
2044
+ * `id`, `className`, `textContent`, `style`, etc.
2045
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
2046
+ *
2047
+ * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
2048
+ * @param {Record<string, string> | string} param `tagName` or object
2049
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
2050
+ */
2051
+ function createElementNS(namespace, param) {
2052
+ if (typeof param === 'string') {
2053
+ return getDocument().createElementNS(namespace, param);
2054
+ }
2055
+
2056
+ const { tagName } = param;
2057
+ const attr = { ...param };
2058
+ const newElement = createElementNS(namespace, tagName);
2059
+ delete attr.tagName;
2060
+ ObjectAssign(newElement, attr);
2061
+ return newElement;
2062
+ }
2063
+
2028
2064
  const vHidden = 'v-hidden';
2029
2065
 
2030
2066
  /**
@@ -2104,8 +2140,6 @@ const ariaValueMin = 'aria-valuemin';
2104
2140
  */
2105
2141
  const ariaValueMax = 'aria-valuemax';
2106
2142
 
2107
- const tabIndex = 'tabindex';
2108
-
2109
2143
  /**
2110
2144
  * Returns all color controls for `ColorPicker`.
2111
2145
  *
@@ -2213,75 +2247,6 @@ function setCSSProperties(element, props) {
2213
2247
  });
2214
2248
  }
2215
2249
 
2216
- /**
2217
- * @class
2218
- * Returns a color palette with a given set of parameters.
2219
- * @example
2220
- * new ColorPalette(0, 12, 10);
2221
- * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
2222
- */
2223
- class ColorPalette {
2224
- /**
2225
- * The `hue` parameter is optional, which would be set to 0.
2226
- * @param {number[]} args represeinting hue, hueSteps, lightSteps
2227
- * * `args.hue` the starting Hue [0, 360]
2228
- * * `args.hueSteps` Hue Steps Count [5, 24]
2229
- * * `args.lightSteps` Lightness Steps Count [5, 12]
2230
- */
2231
- constructor(...args) {
2232
- let hue = 0;
2233
- let hueSteps = 12;
2234
- let lightSteps = 10;
2235
- let lightnessArray = [0.5];
2236
-
2237
- if (args.length === 3) {
2238
- [hue, hueSteps, lightSteps] = args;
2239
- } else if (args.length === 2) {
2240
- [hueSteps, lightSteps] = args;
2241
- } else {
2242
- throw TypeError('ColorPalette requires minimum 2 arguments');
2243
- }
2244
-
2245
- /** @type {string[]} */
2246
- const colors = [];
2247
-
2248
- const hueStep = 360 / hueSteps;
2249
- const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
2250
- const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
2251
-
2252
- let lightStep = 0.25;
2253
- lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
2254
- lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
2255
- lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
2256
- lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
2257
- lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
2258
- lightStep = lightSteps > 13 ? estimatedStep : lightStep;
2259
-
2260
- // light tints
2261
- for (let i = 1; i < half + 1; i += 1) {
2262
- lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
2263
- }
2264
-
2265
- // dark tints
2266
- for (let i = 1; i < lightSteps - half; i += 1) {
2267
- lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
2268
- }
2269
-
2270
- // feed `colors` Array
2271
- for (let i = 0; i < hueSteps; i += 1) {
2272
- const currentHue = ((hue + i * hueStep) % 360) / 360;
2273
- lightnessArray.forEach((l) => {
2274
- colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
2275
- });
2276
- }
2277
-
2278
- this.hue = hue;
2279
- this.hueSteps = hueSteps;
2280
- this.lightSteps = lightSteps;
2281
- this.colors = colors;
2282
- }
2283
- }
2284
-
2285
2250
  /**
2286
2251
  * Returns a color-defaults with given values and class.
2287
2252
  * @param {CP.ColorPicker} self
@@ -2315,7 +2280,8 @@ function getColorMenu(self, colorsSource, menuClass) {
2315
2280
  optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2316
2281
  const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2317
2282
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2318
-
2283
+ /** @type {HTMLUListElement} */
2284
+ // @ts-ignore -- <UL> is an `HTMLElement`
2319
2285
  const menu = createElement({
2320
2286
  tagName: 'ul',
2321
2287
  className: finalClass,
@@ -2323,7 +2289,7 @@ function getColorMenu(self, colorsSource, menuClass) {
2323
2289
  setAttribute(menu, 'role', 'listbox');
2324
2290
  setAttribute(menu, ariaLabel, menuLabel);
2325
2291
 
2326
- if (isScrollable) { // @ts-ignore
2292
+ if (isScrollable) {
2327
2293
  setCSSProperties(menu, {
2328
2294
  '--grid-item-size': `${optionSize}rem`,
2329
2295
  '--grid-fit': fit,
@@ -2334,15 +2300,19 @@ function getColorMenu(self, colorsSource, menuClass) {
2334
2300
  }
2335
2301
 
2336
2302
  colorsArray.forEach((x) => {
2337
- const [value, label] = x.trim().split(':');
2338
- const xRealColor = new Color(value, format).toString();
2339
- const isActive = xRealColor === getAttribute(input, 'value');
2303
+ let [value, label] = typeof x === 'string' ? x.trim().split(':') : [];
2304
+ if (x instanceof Color) {
2305
+ value = x.toHexString();
2306
+ label = value;
2307
+ }
2308
+ const color = new Color(x instanceof Color ? x : value, format);
2309
+ const isActive = color.toString() === getAttribute(input, 'value');
2340
2310
  const active = isActive ? ' active' : '';
2341
2311
 
2342
2312
  const option = createElement({
2343
2313
  tagName: 'li',
2344
2314
  className: `color-option${active}`,
2345
- innerText: `${label || x}`,
2315
+ innerText: `${label || value}`,
2346
2316
  });
2347
2317
 
2348
2318
  setAttribute(option, tabIndex, '0');
@@ -2351,7 +2321,7 @@ function getColorMenu(self, colorsSource, menuClass) {
2351
2321
  setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2352
2322
 
2353
2323
  if (isOptionsMenu) {
2354
- setElementStyle(option, { backgroundColor: x });
2324
+ setElementStyle(option, { backgroundColor: value });
2355
2325
  }
2356
2326
 
2357
2327
  menu.append(option);
@@ -2360,55 +2330,10 @@ function getColorMenu(self, colorsSource, menuClass) {
2360
2330
  }
2361
2331
 
2362
2332
  /**
2363
- * Check if a string is valid JSON string.
2364
- * @param {string} str the string input
2365
- * @returns {boolean} the query result
2366
- */
2367
- function isValidJSON(str) {
2368
- try {
2369
- JSON.parse(str);
2370
- } catch (e) {
2371
- return false;
2372
- }
2373
- return true;
2374
- }
2375
-
2376
- var version = "0.0.1";
2377
-
2378
- // @ts-ignore
2379
-
2380
- const Version = version;
2381
-
2382
- // ColorPicker GC
2383
- // ==============
2384
- const colorPickerString = 'color-picker';
2385
- const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2386
- const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2387
- const colorPickerDefaults = {
2388
- componentLabels: colorPickerLabels,
2389
- colorLabels: colorNames,
2390
- format: 'rgb',
2391
- colorPresets: false,
2392
- colorKeywords: false,
2393
- };
2394
-
2395
- // ColorPicker Static Methods
2396
- // ==========================
2397
-
2398
- /** @type {CP.GetInstance<ColorPicker>} */
2399
- const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2400
-
2401
- /** @type {CP.InitCallback<ColorPicker>} */
2402
- const initColorPicker = (element) => new ColorPicker(element);
2403
-
2404
- // ColorPicker Private Methods
2405
- // ===========================
2406
-
2407
- /**
2408
- * Generate HTML markup and update instance properties.
2409
- * @param {ColorPicker} self
2410
- */
2411
- function initCallback(self) {
2333
+ * Generate HTML markup and update instance properties.
2334
+ * @param {CP.ColorPicker} self
2335
+ */
2336
+ function setMarkup(self) {
2412
2337
  const {
2413
2338
  input, parent, format, id, componentLabels, colorKeywords, colorPresets,
2414
2339
  } = self;
@@ -2423,9 +2348,7 @@ function initCallback(self) {
2423
2348
  self.color = new Color(color, format);
2424
2349
 
2425
2350
  // set initial controls dimensions
2426
- // make the controls smaller on mobile
2427
- const dropClass = isMobile ? ' mobile' : '';
2428
- const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
2351
+ const formatString = format === 'hex' ? hexLabel : toUpperCase(format);
2429
2352
 
2430
2353
  const pickerBtn = createElement({
2431
2354
  id: `picker-btn-${id}`,
@@ -2442,7 +2365,7 @@ function initCallback(self) {
2442
2365
 
2443
2366
  const pickerDropdown = createElement({
2444
2367
  tagName: 'div',
2445
- className: `color-dropdown picker${dropClass}`,
2368
+ className: 'color-dropdown picker',
2446
2369
  });
2447
2370
  setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
2448
2371
  setAttribute(pickerDropdown, 'role', 'group');
@@ -2458,7 +2381,7 @@ function initCallback(self) {
2458
2381
  if (colorKeywords || colorPresets) {
2459
2382
  const presetsDropdown = createElement({
2460
2383
  tagName: 'div',
2461
- className: `color-dropdown scrollable menu${dropClass}`,
2384
+ className: 'color-dropdown scrollable menu',
2462
2385
  });
2463
2386
 
2464
2387
  // color presets
@@ -2508,6 +2431,37 @@ function initCallback(self) {
2508
2431
  setAttribute(input, tabIndex, '-1');
2509
2432
  }
2510
2433
 
2434
+ var version = "0.0.2alpha3";
2435
+
2436
+ // @ts-ignore
2437
+
2438
+ const Version = version;
2439
+
2440
+ // ColorPicker GC
2441
+ // ==============
2442
+ const colorPickerString = 'color-picker';
2443
+ const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2444
+ const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2445
+ const colorPickerDefaults = {
2446
+ componentLabels: colorPickerLabels,
2447
+ colorLabels: colorNames,
2448
+ format: 'rgb',
2449
+ colorPresets: false,
2450
+ colorKeywords: false,
2451
+ };
2452
+
2453
+ // ColorPicker Static Methods
2454
+ // ==========================
2455
+
2456
+ /** @type {CP.GetInstance<ColorPicker>} */
2457
+ const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2458
+
2459
+ /** @type {CP.InitCallback<ColorPicker>} */
2460
+ const initColorPicker = (element) => new ColorPicker(element);
2461
+
2462
+ // ColorPicker Private Methods
2463
+ // ===========================
2464
+
2511
2465
  /**
2512
2466
  * Add / remove `ColorPicker` main event listeners.
2513
2467
  * @param {ColorPicker} self
@@ -2520,8 +2474,6 @@ function toggleEvents(self, action) {
2520
2474
  fn(input, focusinEvent, self.showPicker);
2521
2475
  fn(pickerToggle, mouseclickEvent, self.togglePicker);
2522
2476
 
2523
- fn(input, keydownEvent, self.keyToggle);
2524
-
2525
2477
  if (menuToggle) {
2526
2478
  fn(menuToggle, mouseclickEvent, self.toggleMenu);
2527
2479
  }
@@ -2559,8 +2511,7 @@ function toggleEventsOnShown(self, action) {
2559
2511
  fn(doc, pointerEvents.move, self.pointerMove);
2560
2512
  fn(doc, pointerEvents.up, self.pointerUp);
2561
2513
  fn(parent, focusoutEvent, self.handleFocusOut);
2562
- // @ts-ignore -- this is `Window`
2563
- fn(win, keyupEvent, self.handleDismiss);
2514
+ fn(doc, keyupEvent, self.handleDismiss);
2564
2515
  }
2565
2516
 
2566
2517
  /**
@@ -2644,7 +2595,7 @@ class ColorPicker {
2644
2595
  const input = querySelector(target);
2645
2596
 
2646
2597
  // invalidate
2647
- if (!input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
2598
+ if (!input) throw new TypeError(`ColorPicker target "${target}" cannot be found.`);
2648
2599
  self.input = input;
2649
2600
 
2650
2601
  const parent = closest(input, colorPickerParentSelector);
@@ -2691,15 +2642,14 @@ class ColorPicker {
2691
2642
  });
2692
2643
 
2693
2644
  // update and expose component labels
2694
- const tempLabels = ObjectAssign({}, colorPickerLabels);
2695
- const jsonLabels = componentLabels && isValidJSON(componentLabels)
2696
- ? JSON.parse(componentLabels) : componentLabels || {};
2645
+ const tempComponentLabels = componentLabels && isValidJSON(componentLabels)
2646
+ ? JSON.parse(componentLabels) : componentLabels;
2697
2647
 
2698
2648
  /** @type {Record<string, string>} */
2699
- self.componentLabels = ObjectAssign(tempLabels, jsonLabels);
2649
+ self.componentLabels = ObjectAssign(colorPickerLabels, tempComponentLabels);
2700
2650
 
2701
2651
  /** @type {Color} */
2702
- self.color = new Color('white', format);
2652
+ self.color = new Color(input.value || '#fff', format);
2703
2653
 
2704
2654
  /** @type {CP.ColorFormats} */
2705
2655
  self.format = format;
@@ -2708,7 +2658,7 @@ class ColorPicker {
2708
2658
  if (colorKeywords instanceof Array) {
2709
2659
  self.colorKeywords = colorKeywords;
2710
2660
  } else if (typeof colorKeywords === 'string' && colorKeywords.length) {
2711
- self.colorKeywords = colorKeywords.split(',');
2661
+ self.colorKeywords = colorKeywords.split(',').map((x) => x.trim());
2712
2662
  }
2713
2663
 
2714
2664
  // set colour presets
@@ -2737,11 +2687,10 @@ class ColorPicker {
2737
2687
  self.handleFocusOut = self.handleFocusOut.bind(self);
2738
2688
  self.changeHandler = self.changeHandler.bind(self);
2739
2689
  self.handleDismiss = self.handleDismiss.bind(self);
2740
- self.keyToggle = self.keyToggle.bind(self);
2741
2690
  self.handleKnobs = self.handleKnobs.bind(self);
2742
2691
 
2743
2692
  // generate markup
2744
- initCallback(self);
2693
+ setMarkup(self);
2745
2694
 
2746
2695
  const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
2747
2696
  // set main elements
@@ -2829,76 +2778,83 @@ class ColorPicker {
2829
2778
  return inputValue !== '' && new Color(inputValue).isValid;
2830
2779
  }
2831
2780
 
2781
+ /** Returns the colour appearance, usually the closest colour name for the current value. */
2782
+ get appearance() {
2783
+ const {
2784
+ colorLabels, hsl, hsv, format,
2785
+ } = this;
2786
+
2787
+ const hue = roundPart(hsl.h * 360);
2788
+ const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
2789
+ const saturation = roundPart(saturationSource * 100);
2790
+ const lightness = roundPart(hsl.l * 100);
2791
+ const hsvl = hsv.v * 100;
2792
+
2793
+ let colorName;
2794
+
2795
+ // determine color appearance
2796
+ if (lightness === 100 && saturation === 0) {
2797
+ colorName = colorLabels.white;
2798
+ } else if (lightness === 0) {
2799
+ colorName = colorLabels.black;
2800
+ } else if (saturation === 0) {
2801
+ colorName = colorLabels.grey;
2802
+ } else if (hue < 15 || hue >= 345) {
2803
+ colorName = colorLabels.red;
2804
+ } else if (hue >= 15 && hue < 45) {
2805
+ colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
2806
+ } else if (hue >= 45 && hue < 75) {
2807
+ const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
2808
+ const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
2809
+ colorName = isGold ? colorLabels.gold : colorLabels.yellow;
2810
+ colorName = isOlive ? colorLabels.olive : colorName;
2811
+ } else if (hue >= 75 && hue < 155) {
2812
+ colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
2813
+ } else if (hue >= 155 && hue < 175) {
2814
+ colorName = colorLabels.teal;
2815
+ } else if (hue >= 175 && hue < 195) {
2816
+ colorName = colorLabels.cyan;
2817
+ } else if (hue >= 195 && hue < 255) {
2818
+ colorName = colorLabels.blue;
2819
+ } else if (hue >= 255 && hue < 270) {
2820
+ colorName = colorLabels.violet;
2821
+ } else if (hue >= 270 && hue < 295) {
2822
+ colorName = colorLabels.magenta;
2823
+ } else if (hue >= 295 && hue < 345) {
2824
+ colorName = colorLabels.pink;
2825
+ }
2826
+ return colorName;
2827
+ }
2828
+
2832
2829
  /** Updates `ColorPicker` visuals. */
2833
2830
  updateVisuals() {
2834
2831
  const self = this;
2835
2832
  const {
2836
- format, controlPositions, visuals,
2833
+ controlPositions, visuals,
2837
2834
  } = self;
2838
2835
  const [v1, v2, v3] = visuals;
2839
- const { offsetWidth, offsetHeight } = v1;
2840
- const hue = format === 'hsl'
2841
- ? controlPositions.c1x / offsetWidth
2842
- : controlPositions.c2y / offsetHeight;
2843
- // @ts-ignore - `hslToRgb` is assigned to `Color` as static method
2844
- const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
2836
+ const { offsetHeight } = v1;
2837
+ const hue = controlPositions.c2y / offsetHeight;
2838
+ const { r, g, b } = new Color({ h: hue, s: 1, l: 0.5 }).toRgb();
2845
2839
  const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
2846
2840
  const alpha = 1 - controlPositions.c3y / offsetHeight;
2847
2841
  const roundA = roundPart((alpha * 100)) / 100;
2848
2842
 
2849
- if (format !== 'hsl') {
2850
- const fill = new Color({
2851
- h: hue, s: 1, l: 0.5, a: alpha,
2852
- }).toRgbString();
2853
- const hueGradient = `linear-gradient(
2854
- rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2855
- rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2856
- rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2857
- rgb(255,0,0) 100%)`;
2858
- setElementStyle(v1, {
2859
- background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2860
- linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2861
- ${whiteGrad}`,
2862
- });
2863
- setElementStyle(v2, { background: hueGradient });
2864
- } else {
2865
- const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
2866
- const fill0 = new Color({
2867
- r: 255, g: 0, b: 0, a: alpha,
2868
- }).saturate(-saturation).toRgbString();
2869
- const fill1 = new Color({
2870
- r: 255, g: 255, b: 0, a: alpha,
2871
- }).saturate(-saturation).toRgbString();
2872
- const fill2 = new Color({
2873
- r: 0, g: 255, b: 0, a: alpha,
2874
- }).saturate(-saturation).toRgbString();
2875
- const fill3 = new Color({
2876
- r: 0, g: 255, b: 255, a: alpha,
2877
- }).saturate(-saturation).toRgbString();
2878
- const fill4 = new Color({
2879
- r: 0, g: 0, b: 255, a: alpha,
2880
- }).saturate(-saturation).toRgbString();
2881
- const fill5 = new Color({
2882
- r: 255, g: 0, b: 255, a: alpha,
2883
- }).saturate(-saturation).toRgbString();
2884
- const fill6 = new Color({
2885
- r: 255, g: 0, b: 0, a: alpha,
2886
- }).saturate(-saturation).toRgbString();
2887
- const fillGradient = `linear-gradient(to right,
2888
- ${fill0} 0%, ${fill1} 16.67%, ${fill2} 33.33%, ${fill3} 50%,
2889
- ${fill4} 66.67%, ${fill5} 83.33%, ${fill6} 100%)`;
2890
- const lightGrad = `linear-gradient(rgba(255,255,255,${roundA}) 0%, rgba(255,255,255,0) 50%),
2891
- linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,${roundA}) 100%)`;
2892
-
2893
- setElementStyle(v1, { background: `${lightGrad},${fillGradient},${whiteGrad}` });
2894
- const {
2895
- r: gr, g: gg, b: gb,
2896
- } = new Color({ r, g, b }).greyscale().toRgb();
2843
+ const fill = new Color({
2844
+ h: hue, s: 1, l: 0.5, a: alpha,
2845
+ }).toRgbString();
2846
+ const hueGradient = `linear-gradient(
2847
+ rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2848
+ rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2849
+ rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2850
+ rgb(255,0,0) 100%)`;
2851
+ setElementStyle(v1, {
2852
+ background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2853
+ linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2854
+ ${whiteGrad}`,
2855
+ });
2856
+ setElementStyle(v2, { background: hueGradient });
2897
2857
 
2898
- setElementStyle(v2, {
2899
- background: `linear-gradient(rgb(${r},${g},${b}) 0%, rgb(${gr},${gg},${gb}) 100%)`,
2900
- });
2901
- }
2902
2858
  setElementStyle(v3, {
2903
2859
  background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
2904
2860
  });
@@ -2937,7 +2893,7 @@ class ColorPicker {
2937
2893
  const self = this;
2938
2894
  const { activeElement } = getDocument(self.input);
2939
2895
 
2940
- if ((isMobile && self.dragElement)
2896
+ if ((e.type === touchmoveEvent && self.dragElement)
2941
2897
  || (activeElement && self.controlKnobs.includes(activeElement))) {
2942
2898
  e.stopPropagation();
2943
2899
  e.preventDefault();
@@ -3048,13 +3004,13 @@ class ColorPicker {
3048
3004
  const [v1, v2, v3] = visuals;
3049
3005
  const [c1, c2, c3] = controlKnobs;
3050
3006
  /** @type {HTMLElement} */
3051
- const visual = hasClass(target, 'visual-control')
3052
- ? target : querySelector('.visual-control', target.parentElement);
3007
+ const visual = controlKnobs.includes(target) ? target.previousElementSibling : target;
3053
3008
  const visualRect = getBoundingClientRect(visual);
3009
+ const html = getDocumentElement(v1);
3054
3010
  const X = type === 'touchstart' ? touches[0].pageX : pageX;
3055
3011
  const Y = type === 'touchstart' ? touches[0].pageY : pageY;
3056
- const offsetX = X - window.pageXOffset - visualRect.left;
3057
- const offsetY = Y - window.pageYOffset - visualRect.top;
3012
+ const offsetX = X - html.scrollLeft - visualRect.left;
3013
+ const offsetY = Y - html.scrollTop - visualRect.top;
3058
3014
 
3059
3015
  if (target === v1 || target === c1) {
3060
3016
  self.dragElement = visual;
@@ -3114,10 +3070,11 @@ class ColorPicker {
3114
3070
  if (!dragElement) return;
3115
3071
 
3116
3072
  const controlRect = getBoundingClientRect(dragElement);
3117
- const X = type === 'touchmove' ? touches[0].pageX : pageX;
3118
- const Y = type === 'touchmove' ? touches[0].pageY : pageY;
3119
- const offsetX = X - window.pageXOffset - controlRect.left;
3120
- const offsetY = Y - window.pageYOffset - controlRect.top;
3073
+ const win = getDocumentElement(v1);
3074
+ const X = type === touchmoveEvent ? touches[0].pageX : pageX;
3075
+ const Y = type === touchmoveEvent ? touches[0].pageY : pageY;
3076
+ const offsetX = X - win.scrollLeft - controlRect.left;
3077
+ const offsetY = Y - win.scrollTop - controlRect.top;
3121
3078
 
3122
3079
  if (dragElement === v1) {
3123
3080
  self.changeControl1(offsetX, offsetY);
@@ -3144,19 +3101,19 @@ class ColorPicker {
3144
3101
  if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
3145
3102
  e.preventDefault();
3146
3103
 
3147
- const { format, controlKnobs, visuals } = self;
3104
+ const { controlKnobs, visuals } = self;
3148
3105
  const { offsetWidth, offsetHeight } = visuals[0];
3149
3106
  const [c1, c2, c3] = controlKnobs;
3150
3107
  const { activeElement } = getDocument(c1);
3151
3108
  const currentKnob = controlKnobs.find((x) => x === activeElement);
3152
- const yRatio = offsetHeight / (format === 'hsl' ? 100 : 360);
3109
+ const yRatio = offsetHeight / 360;
3153
3110
 
3154
3111
  if (currentKnob) {
3155
3112
  let offsetX = 0;
3156
3113
  let offsetY = 0;
3157
3114
 
3158
3115
  if (target === c1) {
3159
- const xRatio = offsetWidth / (format === 'hsl' ? 360 : 100);
3116
+ const xRatio = offsetWidth / 100;
3160
3117
 
3161
3118
  if ([keyArrowLeft, keyArrowRight].includes(code)) {
3162
3119
  self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
@@ -3206,7 +3163,7 @@ class ColorPicker {
3206
3163
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
3207
3164
  if (activeElement === input) {
3208
3165
  if (isNonColorValue) {
3209
- colorSource = 'white';
3166
+ colorSource = currentValue === 'transparent' ? 'rgba(0,0,0,0)' : 'rgb(0,0,0)';
3210
3167
  } else {
3211
3168
  colorSource = currentValue;
3212
3169
  }
@@ -3257,9 +3214,7 @@ class ColorPicker {
3257
3214
  changeControl1(X, Y) {
3258
3215
  const self = this;
3259
3216
  let [offsetX, offsetY] = [0, 0];
3260
- const {
3261
- format, controlPositions, visuals,
3262
- } = self;
3217
+ const { controlPositions, visuals } = self;
3263
3218
  const { offsetHeight, offsetWidth } = visuals[0];
3264
3219
 
3265
3220
  if (X > offsetWidth) offsetX = offsetWidth;
@@ -3268,29 +3223,19 @@ class ColorPicker {
3268
3223
  if (Y > offsetHeight) offsetY = offsetHeight;
3269
3224
  else if (Y >= 0) offsetY = Y;
3270
3225
 
3271
- const hue = format === 'hsl'
3272
- ? offsetX / offsetWidth
3273
- : controlPositions.c2y / offsetHeight;
3226
+ const hue = controlPositions.c2y / offsetHeight;
3274
3227
 
3275
- const saturation = format === 'hsl'
3276
- ? 1 - controlPositions.c2y / offsetHeight
3277
- : offsetX / offsetWidth;
3228
+ const saturation = offsetX / offsetWidth;
3278
3229
 
3279
3230
  const lightness = 1 - offsetY / offsetHeight;
3280
3231
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3281
3232
 
3282
- const colorObject = format === 'hsl'
3283
- ? {
3284
- h: hue, s: saturation, l: lightness, a: alpha,
3285
- }
3286
- : {
3287
- h: hue, s: saturation, v: lightness, a: alpha,
3288
- };
3289
-
3290
3233
  // new color
3291
3234
  const {
3292
3235
  r, g, b, a,
3293
- } = new Color(colorObject);
3236
+ } = new Color({
3237
+ h: hue, s: saturation, v: lightness, a: alpha,
3238
+ });
3294
3239
 
3295
3240
  ObjectAssign(self.color, {
3296
3241
  r, g, b, a,
@@ -3317,7 +3262,7 @@ class ColorPicker {
3317
3262
  changeControl2(Y) {
3318
3263
  const self = this;
3319
3264
  const {
3320
- format, controlPositions, visuals,
3265
+ controlPositions, visuals,
3321
3266
  } = self;
3322
3267
  const { offsetHeight, offsetWidth } = visuals[0];
3323
3268
 
@@ -3326,26 +3271,17 @@ class ColorPicker {
3326
3271
  if (Y > offsetHeight) offsetY = offsetHeight;
3327
3272
  else if (Y >= 0) offsetY = Y;
3328
3273
 
3329
- const hue = format === 'hsl'
3330
- ? controlPositions.c1x / offsetWidth
3331
- : offsetY / offsetHeight;
3332
- const saturation = format === 'hsl'
3333
- ? 1 - offsetY / offsetHeight
3334
- : controlPositions.c1x / offsetWidth;
3274
+ const hue = offsetY / offsetHeight;
3275
+ const saturation = controlPositions.c1x / offsetWidth;
3335
3276
  const lightness = 1 - controlPositions.c1y / offsetHeight;
3336
3277
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3337
- const colorObject = format === 'hsl'
3338
- ? {
3339
- h: hue, s: saturation, l: lightness, a: alpha,
3340
- }
3341
- : {
3342
- h: hue, s: saturation, v: lightness, a: alpha,
3343
- };
3344
3278
 
3345
3279
  // new color
3346
3280
  const {
3347
3281
  r, g, b, a,
3348
- } = new Color(colorObject);
3282
+ } = new Color({
3283
+ h: hue, s: saturation, v: lightness, a: alpha,
3284
+ });
3349
3285
 
3350
3286
  ObjectAssign(self.color, {
3351
3287
  r, g, b, a,
@@ -3432,18 +3368,18 @@ class ColorPicker {
3432
3368
  setControlPositions() {
3433
3369
  const self = this;
3434
3370
  const {
3435
- format, visuals, color, hsl, hsv,
3371
+ visuals, color, hsv,
3436
3372
  } = self;
3437
3373
  const { offsetHeight, offsetWidth } = visuals[0];
3438
3374
  const alpha = color.a;
3439
- const hue = hsl.h;
3375
+ const hue = hsv.h;
3440
3376
 
3441
- const saturation = format !== 'hsl' ? hsv.s : hsl.s;
3442
- const lightness = format !== 'hsl' ? hsv.v : hsl.l;
3377
+ const saturation = hsv.s;
3378
+ const lightness = hsv.v;
3443
3379
 
3444
- self.controlPositions.c1x = format !== 'hsl' ? saturation * offsetWidth : hue * offsetWidth;
3380
+ self.controlPositions.c1x = saturation * offsetWidth;
3445
3381
  self.controlPositions.c1y = (1 - lightness) * offsetHeight;
3446
- self.controlPositions.c2y = format !== 'hsl' ? hue * offsetHeight : (1 - saturation) * offsetHeight;
3382
+ self.controlPositions.c2y = hue * offsetHeight;
3447
3383
  self.controlPositions.c3y = (1 - alpha) * offsetHeight;
3448
3384
  }
3449
3385
 
@@ -3451,78 +3387,40 @@ class ColorPicker {
3451
3387
  updateAppearance() {
3452
3388
  const self = this;
3453
3389
  const {
3454
- componentLabels, colorLabels, color, parent,
3455
- hsl, hsv, hex, format, controlKnobs,
3390
+ componentLabels, color, parent,
3391
+ hsv, hex, format, controlKnobs,
3456
3392
  } = self;
3457
3393
  const {
3458
3394
  appearanceLabel, hexLabel, valueLabel,
3459
3395
  } = componentLabels;
3460
- const { r, g, b } = color.toRgb();
3396
+ let { r, g, b } = color.toRgb();
3461
3397
  const [knob1, knob2, knob3] = controlKnobs;
3462
- const hue = roundPart(hsl.h * 360);
3398
+ const hue = roundPart(hsv.h * 360);
3463
3399
  const alpha = color.a;
3464
- const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
3465
- const saturation = roundPart(saturationSource * 100);
3466
- const lightness = roundPart(hsl.l * 100);
3467
- const hsvl = hsv.v * 100;
3468
- let colorName;
3469
-
3470
- // determine color appearance
3471
- if (lightness === 100 && saturation === 0) {
3472
- colorName = colorLabels.white;
3473
- } else if (lightness === 0) {
3474
- colorName = colorLabels.black;
3475
- } else if (saturation === 0) {
3476
- colorName = colorLabels.grey;
3477
- } else if (hue < 15 || hue >= 345) {
3478
- colorName = colorLabels.red;
3479
- } else if (hue >= 15 && hue < 45) {
3480
- colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
3481
- } else if (hue >= 45 && hue < 75) {
3482
- const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
3483
- const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
3484
- colorName = isGold ? colorLabels.gold : colorLabels.yellow;
3485
- colorName = isOlive ? colorLabels.olive : colorName;
3486
- } else if (hue >= 75 && hue < 155) {
3487
- colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
3488
- } else if (hue >= 155 && hue < 175) {
3489
- colorName = colorLabels.teal;
3490
- } else if (hue >= 175 && hue < 195) {
3491
- colorName = colorLabels.cyan;
3492
- } else if (hue >= 195 && hue < 255) {
3493
- colorName = colorLabels.blue;
3494
- } else if (hue >= 255 && hue < 270) {
3495
- colorName = colorLabels.violet;
3496
- } else if (hue >= 270 && hue < 295) {
3497
- colorName = colorLabels.magenta;
3498
- } else if (hue >= 295 && hue < 345) {
3499
- colorName = colorLabels.pink;
3500
- }
3400
+ const saturation = roundPart(hsv.s * 100);
3401
+ const lightness = roundPart(hsv.v * 100);
3402
+ const colorName = self.appearance;
3501
3403
 
3502
3404
  let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
3503
3405
 
3504
- if (format === 'hsl') {
3505
- colorLabel = `HSL: ${hue}°, ${saturation}%, ${lightness}%`;
3506
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3507
- setAttribute(knob1, ariaValueText, `${hue}° & ${lightness}%`);
3508
- setAttribute(knob1, ariaValueNow, `${hue}`);
3509
- setAttribute(knob2, ariaValueText, `${saturation}%`);
3510
- setAttribute(knob2, ariaValueNow, `${saturation}`);
3511
- } else if (format === 'hwb') {
3406
+ if (format === 'hwb') {
3512
3407
  const { hwb } = self;
3513
3408
  const whiteness = roundPart(hwb.w * 100);
3514
3409
  const blackness = roundPart(hwb.b * 100);
3515
3410
  colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
3516
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3517
3411
  setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
3518
3412
  setAttribute(knob1, ariaValueNow, `${whiteness}`);
3413
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3519
3414
  setAttribute(knob2, ariaValueText, `${hue}%`);
3520
3415
  setAttribute(knob2, ariaValueNow, `${hue}`);
3521
3416
  } else {
3417
+ [r, g, b] = [r, g, b].map(roundPart);
3418
+ colorLabel = format === 'hsl' ? `HSL: ${hue}°, ${saturation}%, ${lightness}%` : colorLabel;
3522
3419
  colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
3523
- setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3420
+
3524
3421
  setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
3525
3422
  setAttribute(knob1, ariaValueNow, `${lightness}`);
3423
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3526
3424
  setAttribute(knob2, ariaValueText, `${hue}°`);
3527
3425
  setAttribute(knob2, ariaValueNow, `${hue}`);
3528
3426
  }
@@ -3617,37 +3515,13 @@ class ColorPicker {
3617
3515
  }
3618
3516
  }
3619
3517
 
3620
- /**
3621
- * The `Space` & `Enter` keys specific event listener.
3622
- * Toggle visibility of the `ColorPicker` / the presets menu, showing one will hide the other.
3623
- * @param {KeyboardEvent} e
3624
- * @this {ColorPicker}
3625
- */
3626
- keyToggle(e) {
3627
- const self = this;
3628
- const { menuToggle } = self;
3629
- const { activeElement } = getDocument(menuToggle);
3630
- const { code } = e;
3631
-
3632
- if ([keyEnter, keySpace].includes(code)) {
3633
- if ((menuToggle && activeElement === menuToggle) || !activeElement) {
3634
- e.preventDefault();
3635
- if (!activeElement) {
3636
- self.togglePicker(e);
3637
- } else {
3638
- self.toggleMenu();
3639
- }
3640
- }
3641
- }
3642
- }
3643
-
3644
3518
  /**
3645
3519
  * Toggle the `ColorPicker` dropdown visibility.
3646
- * @param {Event} e
3520
+ * @param {Event=} e
3647
3521
  * @this {ColorPicker}
3648
3522
  */
3649
3523
  togglePicker(e) {
3650
- e.preventDefault();
3524
+ if (e) e.preventDefault();
3651
3525
  const self = this;
3652
3526
  const { colorPicker } = self;
3653
3527
 
@@ -3668,8 +3542,13 @@ class ColorPicker {
3668
3542
  }
3669
3543
  }
3670
3544
 
3671
- /** Toggles the visibility of the `ColorPicker` presets menu. */
3672
- toggleMenu() {
3545
+ /**
3546
+ * Toggles the visibility of the `ColorPicker` presets menu.
3547
+ * @param {Event=} e
3548
+ * @this {ColorPicker}
3549
+ */
3550
+ toggleMenu(e) {
3551
+ if (e) e.preventDefault();
3673
3552
  const self = this;
3674
3553
  const { colorMenu } = self;
3675
3554
 
@@ -3695,6 +3574,10 @@ class ColorPicker {
3695
3574
  const relatedBtn = openPicker ? pickerToggle : menuToggle;
3696
3575
  const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
3697
3576
 
3577
+ // if (!self.isValid) {
3578
+ self.value = self.color.toString(true);
3579
+ // }
3580
+
3698
3581
  if (openDropdown) {
3699
3582
  removeClass(openDropdown, 'show');
3700
3583
  setAttribute(relatedBtn, ariaExpanded, 'false');
@@ -3708,9 +3591,6 @@ class ColorPicker {
3708
3591
  }, animationDuration);
3709
3592
  }
3710
3593
 
3711
- if (!self.isValid) {
3712
- self.value = self.color.toString();
3713
- }
3714
3594
  if (!focusPrevented) {
3715
3595
  focus(pickerToggle);
3716
3596
  }
@@ -3759,93 +3639,85 @@ let CPID = 0;
3759
3639
  * `ColorPickerElement` Web Component.
3760
3640
  * @example
3761
3641
  * <label for="UNIQUE_ID">Label</label>
3762
- * <color-picker data-format="hex" data-value="#075">
3763
- * <input id="UNIQUE_ID" type="text" class="color-preview btn-appearance">
3642
+ * <color-picker>
3643
+ * <input id="UNIQUE_ID" value="red" format="hex" class="color-preview btn-appearance">
3764
3644
  * </color-picker>
3645
+ * // or
3646
+ * <label for="UNIQUE_ID">Label</label>
3647
+ * <color-picker data-id="UNIQUE_ID" data-value="red" data-format="hex"></color-picker>
3765
3648
  */
3766
3649
  class ColorPickerElement extends HTMLElement {
3767
3650
  constructor() {
3768
3651
  super();
3769
- /** @type {boolean} */
3770
- this.isDisconnected = true;
3771
3652
  this.attachShadow({ mode: 'open' });
3772
3653
  }
3773
3654
 
3774
3655
  /**
3775
3656
  * Returns the current color value.
3776
- * @returns {string?}
3657
+ * @returns {string | undefined}
3777
3658
  */
3778
- get value() { return this.input ? this.input.value : null; }
3659
+ get value() { return this.input && this.input.value; }
3779
3660
 
3780
3661
  connectedCallback() {
3781
- if (this.colorPicker) {
3782
- if (this.isDisconnected) {
3783
- this.isDisconnected = false;
3784
- }
3785
- return;
3786
- }
3662
+ if (this.input) return;
3787
3663
 
3788
- const inputs = getElementsByTagName('input', this);
3664
+ let [input] = getElementsByTagName('input', this);
3665
+ const value = (input && getAttribute(input, 'value')) || getAttribute(this, 'data-value') || '#fff';
3666
+ const format = (input && getAttribute(input, 'format')) || getAttribute(this, 'data-format') || 'rgb';
3667
+ let id = (input && getAttribute(input, 'id')) || getAttribute(this, 'data-id');
3668
+
3669
+ if (!id) {
3670
+ id = `color-picker-${format}-${CPID}`;
3671
+ CPID += 1;
3672
+ }
3789
3673
 
3790
- if (!inputs.length) {
3791
- const label = getAttribute(this, 'data-label');
3792
- const value = getAttribute(this, 'data-value') || '#069';
3793
- const format = getAttribute(this, 'data-format') || 'rgb';
3794
- const newInput = createElement({
3674
+ if (!input) {
3675
+ input = createElement({
3795
3676
  tagName: 'input',
3796
3677
  type: 'text',
3797
3678
  className: 'color-preview btn-appearance',
3798
3679
  });
3799
- let id = getAttribute(this, 'data-id');
3800
- if (!id) {
3801
- id = `color-picker-${format}-${CPID}`;
3802
- CPID += 1;
3803
- }
3804
3680
 
3805
- const labelElement = createElement({ tagName: 'label', innerText: label || 'Color Picker' });
3806
- this.before(labelElement);
3807
- setAttribute(labelElement, 'for', id);
3808
- setAttribute(newInput, 'id', id);
3809
- setAttribute(newInput, 'name', id);
3810
- setAttribute(newInput, 'autocomplete', 'off');
3811
- setAttribute(newInput, 'spellcheck', 'false');
3812
- setAttribute(newInput, 'value', value);
3813
- this.append(newInput);
3681
+ setAttribute(input, 'id', id);
3682
+ setAttribute(input, 'name', id);
3683
+ setAttribute(input, 'autocomplete', 'off');
3684
+ setAttribute(input, 'spellcheck', 'false');
3685
+ setAttribute(input, 'value', value);
3686
+ this.append(input);
3814
3687
  }
3688
+ /** @type {HTMLInputElement} */
3689
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3690
+ this.input = input;
3815
3691
 
3816
- const [input] = inputs;
3817
-
3818
- if (input) {
3819
- /** @type {HTMLInputElement} */
3820
- // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3821
- this.input = input;
3822
-
3823
- // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3824
- this.colorPicker = new ColorPicker(input);
3825
- this.color = this.colorPicker.color;
3826
-
3827
- if (this.shadowRoot) {
3828
- this.shadowRoot.append(createElement('slot'));
3829
- }
3692
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3693
+ this.colorPicker = new ColorPicker(input);
3830
3694
 
3831
- this.isDisconnected = false;
3832
- }
3695
+ // @ts-ignore - `shadowRoot` is defined in the constructor
3696
+ this.shadowRoot.append(createElement('slot'));
3833
3697
  }
3834
3698
 
3699
+ /** @this {ColorPickerElement} */
3835
3700
  disconnectedCallback() {
3836
- if (this.colorPicker) this.colorPicker.dispose();
3837
- this.isDisconnected = true;
3701
+ const { input, colorPicker, shadowRoot } = this;
3702
+ if (colorPicker) colorPicker.dispose();
3703
+ if (input) input.remove();
3704
+ if (shadowRoot) shadowRoot.innerHTML = '';
3705
+
3706
+ ObjectAssign(this, {
3707
+ colorPicker: undefined,
3708
+ input: undefined,
3709
+ });
3838
3710
  }
3839
3711
  }
3840
3712
 
3841
3713
  ObjectAssign(ColorPickerElement, {
3842
3714
  Color,
3843
3715
  ColorPicker,
3844
- ColorPalette,
3845
- getInstance: getColorPickerInstance,
3716
+ ColorPalette, // @ts-ignore
3717
+ getInstance: ColorPicker.getInstance,
3846
3718
  Version,
3847
3719
  });
3848
3720
 
3849
3721
  customElements.define('color-picker', ColorPickerElement);
3850
3722
 
3851
- export default ColorPickerElement;
3723
+ export { ColorPickerElement as default };