@thednp/color-picker 0.0.2-alpha1 → 0.0.2-alpha4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * ColorPickerElement v0.0.2alpha1 (http://thednp.github.io/color-picker)
2
+ * ColorPickerElement v0.0.2alpha4 (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
  */
@@ -212,6 +212,8 @@ function isColorName(color) {
212
212
  if (nonColors.includes(color)
213
213
  || ['#', ...COLOR_FORMAT].some((f) => color.includes(f))) return false;
214
214
 
215
+ if (['black', 'white'].includes(color)) return true;
216
+
215
217
  return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
216
218
  setElementStyle(documentHead, { color });
217
219
  const computedColor = getElementStyle(documentHead, 'color');
@@ -238,6 +240,11 @@ function isValidCSSUnit(color) {
238
240
  */
239
241
  function bound01(N, max) {
240
242
  let n = N;
243
+
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
+
241
248
  if (isOnePointZero(N)) n = '100%';
242
249
 
243
250
  const processPercent = isPercentage(n);
@@ -341,15 +348,12 @@ function pad2(c) {
341
348
  /**
342
349
  * Converts an RGB colour value to HSL.
343
350
  *
344
- * @param {number} R Red component [0, 255]
345
- * @param {number} G Green component [0, 255]
346
- * @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]
347
354
  * @returns {CP.HSL} {h,s,l} object with [0, 1] ranged values
348
355
  */
349
- function rgbToHsl(R, G, B) {
350
- const r = R / 255;
351
- const g = G / 255;
352
- const b = B / 255;
356
+ function rgbToHsl(r, g, b) {
353
357
  const max = Math.max(r, g, b);
354
358
  const min = Math.min(r, g, b);
355
359
  let h = 0;
@@ -361,17 +365,10 @@ function rgbToHsl(R, G, B) {
361
365
  } else {
362
366
  const d = max - min;
363
367
  s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
364
- switch (max) {
365
- case r:
366
- h = (g - b) / d + (g < b ? 6 : 0);
367
- break;
368
- case g:
369
- h = (b - r) / d + 2;
370
- break;
371
- case b:
372
- h = (r - g) / d + 4;
373
- break;
374
- }
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
+
375
372
  h /= 6;
376
373
  }
377
374
  return { h, s, l };
@@ -400,7 +397,7 @@ function hueToRgb(p, q, t) {
400
397
  * @param {number} h Hue Angle [0, 1]
401
398
  * @param {number} s Saturation [0, 1]
402
399
  * @param {number} l Lightness Angle [0, 1]
403
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
400
+ * @returns {CP.RGB} {r,g,b} object with [0, 1] ranged values
404
401
  */
405
402
  function hslToRgb(h, s, l) {
406
403
  let r = 0;
@@ -419,7 +416,6 @@ function hslToRgb(h, s, l) {
419
416
  g = hueToRgb(p, q, h);
420
417
  b = hueToRgb(p, q, h - 1 / 3);
421
418
  }
422
- [r, g, b] = [r, g, b].map((x) => x * 255);
423
419
 
424
420
  return { r, g, b };
425
421
  }
@@ -429,16 +425,12 @@ function hslToRgb(h, s, l) {
429
425
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
430
426
  * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
431
427
  *
432
- * @param {number} R Red component [0, 255]
433
- * @param {number} G Green [0, 255]
434
- * @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]
435
431
  * @return {CP.HWB} {h,w,b} object with [0, 1] ranged values
436
432
  */
437
- function rgbToHwb(R, G, B) {
438
- const r = R / 255;
439
- const g = G / 255;
440
- const b = B / 255;
441
-
433
+ function rgbToHwb(r, g, b) {
442
434
  let f = 0;
443
435
  let i = 0;
444
436
  const whiteness = Math.min(r, g, b);
@@ -468,20 +460,18 @@ function rgbToHwb(R, G, B) {
468
460
  * @param {number} H Hue Angle [0, 1]
469
461
  * @param {number} W Whiteness [0, 1]
470
462
  * @param {number} B Blackness [0, 1]
471
- * @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
472
464
  *
473
465
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
474
466
  * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
475
467
  */
476
468
  function hwbToRgb(H, W, B) {
477
469
  if (W + B >= 1) {
478
- const gray = (W / (W + B)) * 255;
470
+ const gray = W / (W + B);
479
471
  return { r: gray, g: gray, b: gray };
480
472
  }
481
473
  let { r, g, b } = hslToRgb(H, 1, 0.5);
482
- [r, g, b] = [r, g, b]
483
- .map((v) => (v / 255) * (1 - W - B) + W)
484
- .map((v) => v * 255);
474
+ [r, g, b] = [r, g, b].map((v) => v * (1 - W - B) + W);
485
475
 
486
476
  return { r, g, b };
487
477
  }
@@ -489,15 +479,12 @@ function hwbToRgb(H, W, B) {
489
479
  /**
490
480
  * Converts an RGB colour value to HSV.
491
481
  *
492
- * @param {number} R Red component [0, 255]
493
- * @param {number} G Green [0, 255]
494
- * @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]
495
485
  * @returns {CP.HSV} {h,s,v} object with [0, 1] ranged values
496
486
  */
497
- function rgbToHsv(R, G, B) {
498
- const r = R / 255;
499
- const g = G / 255;
500
- const b = B / 255;
487
+ function rgbToHsv(r, g, b) {
501
488
  const max = Math.max(r, g, b);
502
489
  const min = Math.min(r, g, b);
503
490
  let h = 0;
@@ -507,17 +494,10 @@ function rgbToHsv(R, G, B) {
507
494
  if (max === min) {
508
495
  h = 0; // achromatic
509
496
  } else {
510
- switch (max) {
511
- case r:
512
- h = (g - b) / d + (g < b ? 6 : 0);
513
- break;
514
- case g:
515
- h = (b - r) / d + 2;
516
- break;
517
- case b:
518
- h = (r - g) / d + 4;
519
- break;
520
- }
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
+
521
501
  h /= 6;
522
502
  }
523
503
  return { h, s, v };
@@ -541,10 +521,9 @@ function hsvToRgb(H, S, V) {
541
521
  const q = v * (1 - f * s);
542
522
  const t = v * (1 - (1 - f) * s);
543
523
  const mod = i % 6;
544
- let r = [v, q, p, p, t, v][mod];
545
- let g = [t, v, v, q, p, p][mod];
546
- let b = [p, p, t, v, v, q][mod];
547
- [r, g, b] = [r, g, b].map((n) => n * 255);
524
+ const r = [v, q, p, p, t, v][mod];
525
+ const g = [t, v, v, q, p, p][mod];
526
+ const b = [p, p, t, v, v, q][mod];
548
527
  return { r, g, b };
549
528
  }
550
529
 
@@ -612,15 +591,15 @@ function rgbaToHex(r, g, b, a, allow4Char) {
612
591
  */
613
592
  function stringInputToObject(input) {
614
593
  let color = toLowerCase(input.trim());
594
+
615
595
  if (color.length === 0) {
616
596
  return {
617
597
  r: 0, g: 0, b: 0, a: 1,
618
598
  };
619
599
  }
620
- let named = false;
600
+
621
601
  if (isColorName(color)) {
622
602
  color = getRGBFromName(color);
623
- named = true;
624
603
  } else if (nonColors.includes(color)) {
625
604
  const a = color === 'transparent' ? 0 : 1;
626
605
  return {
@@ -639,24 +618,28 @@ function stringInputToObject(input) {
639
618
  r: m1, g: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'rgb',
640
619
  };
641
620
  }
621
+
642
622
  [, m1, m2, m3, m4] = matchers.hsl.exec(color) || [];
643
623
  if (m1 && m2 && m3/* && m4 */) {
644
624
  return {
645
625
  h: m1, s: m2, l: m3, a: m4 !== undefined ? m4 : 1, format: 'hsl',
646
626
  };
647
627
  }
628
+
648
629
  [, m1, m2, m3, m4] = matchers.hsv.exec(color) || [];
649
630
  if (m1 && m2 && m3/* && m4 */) {
650
631
  return {
651
632
  h: m1, s: m2, v: m3, a: m4 !== undefined ? m4 : 1, format: 'hsv',
652
633
  };
653
634
  }
635
+
654
636
  [, m1, m2, m3, m4] = matchers.hwb.exec(color) || [];
655
637
  if (m1 && m2 && m3) {
656
638
  return {
657
639
  h: m1, w: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'hwb',
658
640
  };
659
641
  }
642
+
660
643
  [, m1, m2, m3, m4] = matchers.hex8.exec(color) || [];
661
644
  if (m1 && m2 && m3 && m4) {
662
645
  return {
@@ -664,18 +647,20 @@ function stringInputToObject(input) {
664
647
  g: parseIntFromHex(m2),
665
648
  b: parseIntFromHex(m3),
666
649
  a: convertHexToDecimal(m4),
667
- format: named ? 'rgb' : 'hex',
650
+ format: 'hex',
668
651
  };
669
652
  }
653
+
670
654
  [, m1, m2, m3] = matchers.hex6.exec(color) || [];
671
655
  if (m1 && m2 && m3) {
672
656
  return {
673
657
  r: parseIntFromHex(m1),
674
658
  g: parseIntFromHex(m2),
675
659
  b: parseIntFromHex(m3),
676
- format: named ? 'rgb' : 'hex',
660
+ format: 'hex',
677
661
  };
678
662
  }
663
+
679
664
  [, m1, m2, m3, m4] = matchers.hex4.exec(color) || [];
680
665
  if (m1 && m2 && m3 && m4) {
681
666
  return {
@@ -683,19 +668,20 @@ function stringInputToObject(input) {
683
668
  g: parseIntFromHex(m2 + m2),
684
669
  b: parseIntFromHex(m3 + m3),
685
670
  a: convertHexToDecimal(m4 + m4),
686
- // format: named ? 'rgb' : 'hex8',
687
- format: named ? 'rgb' : 'hex',
671
+ format: 'hex',
688
672
  };
689
673
  }
674
+
690
675
  [, m1, m2, m3] = matchers.hex3.exec(color) || [];
691
676
  if (m1 && m2 && m3) {
692
677
  return {
693
678
  r: parseIntFromHex(m1 + m1),
694
679
  g: parseIntFromHex(m2 + m2),
695
680
  b: parseIntFromHex(m3 + m3),
696
- format: named ? 'rgb' : 'hex',
681
+ format: 'hex',
697
682
  };
698
683
  }
684
+
699
685
  return false;
700
686
  }
701
687
 
@@ -726,6 +712,7 @@ function stringInputToObject(input) {
726
712
  */
727
713
  function inputToRGB(input) {
728
714
  let rgb = { r: 0, g: 0, b: 0 };
715
+ /** @type {*} */
729
716
  let color = input;
730
717
  /** @type {string | number} */
731
718
  let a = 1;
@@ -742,39 +729,41 @@ function inputToRGB(input) {
742
729
  let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
743
730
 
744
731
  if (typeof input === 'string') {
745
- // @ts-ignore -- this now is converted to object
746
732
  color = stringInputToObject(input);
747
733
  if (color) ok = true;
748
734
  }
749
735
  if (typeof color === 'object') {
750
736
  if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
751
737
  ({ r, g, b } = color);
752
- // RGB values now are all in [0, 255] range
753
- [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));
754
740
  rgb = { r, g, b };
755
741
  ok = true;
756
- format = 'rgb';
757
- } 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)) {
758
745
  ({ h, s, v } = color);
759
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
760
- s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
761
- 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
762
749
  rgb = hsvToRgb(h, s, v);
763
750
  ok = true;
764
751
  format = 'hsv';
765
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
752
+ }
753
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
766
754
  ({ h, s, l } = color);
767
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
768
- s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
769
- 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
770
758
  rgb = hslToRgb(h, s, l);
771
759
  ok = true;
772
760
  format = 'hsl';
773
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
761
+ }
762
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
774
763
  ({ h, w, b } = color);
775
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
776
- w = typeof w === 'number' ? w : bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
777
- 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
778
767
  rgb = hwbToRgb(h, w, b);
779
768
  ok = true;
780
769
  format = 'hwb';
@@ -791,9 +780,12 @@ function inputToRGB(input) {
791
780
  return {
792
781
  ok,
793
782
  format,
794
- r: Math.min(255, Math.max(rgb.r, 0)),
795
- g: Math.min(255, Math.max(rgb.g, 0)),
796
- b: Math.min(255, Math.max(rgb.b, 0)),
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,
797
789
  a: boundAlpha(a),
798
790
  };
799
791
  }
@@ -812,16 +804,13 @@ class Color {
812
804
  constructor(input, config) {
813
805
  let color = input;
814
806
  const configFormat = config && COLOR_FORMAT.includes(config)
815
- ? config : 'rgb';
807
+ ? config : '';
816
808
 
817
- // If input is already a `Color`, return itself
809
+ // If input is already a `Color`, clone its values
818
810
  if (color instanceof Color) {
819
811
  color = inputToRGB(color);
820
812
  }
821
- if (typeof color === 'number') {
822
- const len = `${color}`.length;
823
- color = `#${(len === 2 ? '0' : '00')}${color}`;
824
- }
813
+
825
814
  const {
826
815
  r, g, b, a, ok, format,
827
816
  } = inputToRGB(color);
@@ -871,24 +860,21 @@ class Color {
871
860
  let R = 0;
872
861
  let G = 0;
873
862
  let B = 0;
874
- const rp = r / 255;
875
- const rg = g / 255;
876
- const rb = b / 255;
877
863
 
878
- if (rp <= 0.03928) {
879
- R = rp / 12.92;
864
+ if (r <= 0.03928) {
865
+ R = r / 12.92;
880
866
  } else {
881
- R = ((rp + 0.055) / 1.055) ** 2.4;
867
+ R = ((r + 0.055) / 1.055) ** 2.4;
882
868
  }
883
- if (rg <= 0.03928) {
884
- G = rg / 12.92;
869
+ if (g <= 0.03928) {
870
+ G = g / 12.92;
885
871
  } else {
886
- G = ((rg + 0.055) / 1.055) ** 2.4;
872
+ G = ((g + 0.055) / 1.055) ** 2.4;
887
873
  }
888
- if (rb <= 0.03928) {
889
- B = rb / 12.92;
874
+ if (b <= 0.03928) {
875
+ B = b / 12.92;
890
876
  } else {
891
- B = ((rb + 0.055) / 1.055) ** 2.4;
877
+ B = ((b + 0.055) / 1.055) ** 2.4;
892
878
  }
893
879
  return 0.2126 * R + 0.7152 * G + 0.0722 * B;
894
880
  }
@@ -898,7 +884,7 @@ class Color {
898
884
  * @returns {number} a number in the [0, 255] range
899
885
  */
900
886
  get brightness() {
901
- const { r, g, b } = this;
887
+ const { r, g, b } = this.toRgb();
902
888
  return (r * 299 + g * 587 + b * 114) / 1000;
903
889
  }
904
890
 
@@ -907,12 +893,14 @@ class Color {
907
893
  * @returns {CP.RGBA} an {r,g,b,a} object with [0, 255] ranged values
908
894
  */
909
895
  toRgb() {
910
- const {
896
+ let {
911
897
  r, g, b, a,
912
898
  } = this;
913
899
 
900
+ [r, g, b] = [r, g, b].map((n) => roundPart(n * 255 * 100) / 100);
901
+ a = roundPart(a * 100) / 100;
914
902
  return {
915
- r, g, b, a: roundPart(a * 100) / 100,
903
+ r, g, b, a,
916
904
  };
917
905
  }
918
906
 
@@ -1006,7 +994,7 @@ class Color {
1006
994
  toHsv() {
1007
995
  const {
1008
996
  r, g, b, a,
1009
- } = this.toRgb();
997
+ } = this;
1010
998
  const { h, s, v } = rgbToHsv(r, g, b);
1011
999
 
1012
1000
  return {
@@ -1021,7 +1009,7 @@ class Color {
1021
1009
  toHsl() {
1022
1010
  const {
1023
1011
  r, g, b, a,
1024
- } = this.toRgb();
1012
+ } = this;
1025
1013
  const { h, s, l } = rgbToHsl(r, g, b);
1026
1014
 
1027
1015
  return {
@@ -1106,6 +1094,7 @@ class Color {
1106
1094
  */
1107
1095
  setAlpha(alpha) {
1108
1096
  const self = this;
1097
+ if (typeof alpha !== 'number') return self;
1109
1098
  self.a = boundAlpha(alpha);
1110
1099
  return self;
1111
1100
  }
@@ -1934,26 +1923,23 @@ class ColorPalette {
1934
1923
  } else if (args.length === 2) {
1935
1924
  [hueSteps, lightSteps] = args;
1936
1925
  if ([hueSteps, lightSteps].some((n) => n < 1)) {
1937
- throw TypeError('ColorPalette: when 2 arguments used, both must be larger than 0.');
1926
+ throw TypeError('ColorPalette: both arguments must be higher than 0.');
1938
1927
  }
1939
- } else {
1940
- throw TypeError('ColorPalette requires minimum 2 arguments');
1941
1928
  }
1942
1929
 
1943
- /** @type {Color[]} */
1930
+ /** @type {*} */
1944
1931
  const colors = [];
1945
-
1946
1932
  const hueStep = 360 / hueSteps;
1947
1933
  const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
1948
- const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
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));
1949
1937
 
1950
- let lightStep = 0.25;
1951
- lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
1952
- lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
1953
- lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
1954
- lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
1955
- lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
1956
- lightStep = lightSteps > 13 ? estimatedStep : lightStep;
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);
1957
1943
 
1958
1944
  // light tints
1959
1945
  for (let i = 1; i < half + 1; i += 1) {
@@ -2445,7 +2431,7 @@ function setMarkup(self) {
2445
2431
  setAttribute(input, tabIndex, '-1');
2446
2432
  }
2447
2433
 
2448
- var version = "0.0.2alpha1";
2434
+ var version = "0.0.2alpha4";
2449
2435
 
2450
2436
  // @ts-ignore
2451
2437
 
@@ -2488,8 +2474,6 @@ function toggleEvents(self, action) {
2488
2474
  fn(input, focusinEvent, self.showPicker);
2489
2475
  fn(pickerToggle, mouseclickEvent, self.togglePicker);
2490
2476
 
2491
- fn(input, keydownEvent, self.keyToggle);
2492
-
2493
2477
  if (menuToggle) {
2494
2478
  fn(menuToggle, mouseclickEvent, self.toggleMenu);
2495
2479
  }
@@ -2527,8 +2511,7 @@ function toggleEventsOnShown(self, action) {
2527
2511
  fn(doc, pointerEvents.move, self.pointerMove);
2528
2512
  fn(doc, pointerEvents.up, self.pointerUp);
2529
2513
  fn(parent, focusoutEvent, self.handleFocusOut);
2530
- // @ts-ignore -- this is `Window`
2531
- fn(win, keyupEvent, self.handleDismiss);
2514
+ fn(doc, keyupEvent, self.handleDismiss);
2532
2515
  }
2533
2516
 
2534
2517
  /**
@@ -2612,7 +2595,7 @@ class ColorPicker {
2612
2595
  const input = querySelector(target);
2613
2596
 
2614
2597
  // invalidate
2615
- if (!input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
2598
+ if (!input) throw new TypeError(`ColorPicker target "${target}" cannot be found.`);
2616
2599
  self.input = input;
2617
2600
 
2618
2601
  const parent = closest(input, colorPickerParentSelector);
@@ -2659,15 +2642,14 @@ class ColorPicker {
2659
2642
  });
2660
2643
 
2661
2644
  // update and expose component labels
2662
- const tempLabels = ObjectAssign({}, colorPickerLabels);
2663
- const jsonLabels = componentLabels && isValidJSON(componentLabels)
2664
- ? JSON.parse(componentLabels) : componentLabels || {};
2645
+ const tempComponentLabels = componentLabels && isValidJSON(componentLabels)
2646
+ ? JSON.parse(componentLabels) : componentLabels;
2665
2647
 
2666
2648
  /** @type {Record<string, string>} */
2667
- self.componentLabels = ObjectAssign(tempLabels, jsonLabels);
2649
+ self.componentLabels = ObjectAssign(colorPickerLabels, tempComponentLabels);
2668
2650
 
2669
2651
  /** @type {Color} */
2670
- self.color = new Color('white', format);
2652
+ self.color = new Color(input.value || '#fff', format);
2671
2653
 
2672
2654
  /** @type {CP.ColorFormats} */
2673
2655
  self.format = format;
@@ -2676,7 +2658,7 @@ class ColorPicker {
2676
2658
  if (colorKeywords instanceof Array) {
2677
2659
  self.colorKeywords = colorKeywords;
2678
2660
  } else if (typeof colorKeywords === 'string' && colorKeywords.length) {
2679
- self.colorKeywords = colorKeywords.split(',');
2661
+ self.colorKeywords = colorKeywords.split(',').map((x) => x.trim());
2680
2662
  }
2681
2663
 
2682
2664
  // set colour presets
@@ -2705,7 +2687,6 @@ class ColorPicker {
2705
2687
  self.handleFocusOut = self.handleFocusOut.bind(self);
2706
2688
  self.changeHandler = self.changeHandler.bind(self);
2707
2689
  self.handleDismiss = self.handleDismiss.bind(self);
2708
- self.keyToggle = self.keyToggle.bind(self);
2709
2690
  self.handleKnobs = self.handleKnobs.bind(self);
2710
2691
 
2711
2692
  // generate markup
@@ -2797,76 +2778,83 @@ class ColorPicker {
2797
2778
  return inputValue !== '' && new Color(inputValue).isValid;
2798
2779
  }
2799
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
+
2800
2829
  /** Updates `ColorPicker` visuals. */
2801
2830
  updateVisuals() {
2802
2831
  const self = this;
2803
2832
  const {
2804
- format, controlPositions, visuals,
2833
+ controlPositions, visuals,
2805
2834
  } = self;
2806
2835
  const [v1, v2, v3] = visuals;
2807
- const { offsetWidth, offsetHeight } = v1;
2808
- const hue = format === 'hsl'
2809
- ? controlPositions.c1x / offsetWidth
2810
- : controlPositions.c2y / offsetHeight;
2811
- // @ts-ignore - `hslToRgb` is assigned to `Color` as static method
2812
- 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();
2813
2839
  const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
2814
2840
  const alpha = 1 - controlPositions.c3y / offsetHeight;
2815
2841
  const roundA = roundPart((alpha * 100)) / 100;
2816
2842
 
2817
- if (format !== 'hsl') {
2818
- const fill = new Color({
2819
- h: hue, s: 1, l: 0.5, a: alpha,
2820
- }).toRgbString();
2821
- const hueGradient = `linear-gradient(
2822
- rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2823
- rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2824
- rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2825
- rgb(255,0,0) 100%)`;
2826
- setElementStyle(v1, {
2827
- background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2828
- linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2829
- ${whiteGrad}`,
2830
- });
2831
- setElementStyle(v2, { background: hueGradient });
2832
- } else {
2833
- const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
2834
- const fill0 = new Color({
2835
- r: 255, g: 0, b: 0, a: alpha,
2836
- }).saturate(-saturation).toRgbString();
2837
- const fill1 = new Color({
2838
- r: 255, g: 255, b: 0, a: alpha,
2839
- }).saturate(-saturation).toRgbString();
2840
- const fill2 = new Color({
2841
- r: 0, g: 255, b: 0, a: alpha,
2842
- }).saturate(-saturation).toRgbString();
2843
- const fill3 = new Color({
2844
- r: 0, g: 255, b: 255, a: alpha,
2845
- }).saturate(-saturation).toRgbString();
2846
- const fill4 = new Color({
2847
- r: 0, g: 0, b: 255, a: alpha,
2848
- }).saturate(-saturation).toRgbString();
2849
- const fill5 = new Color({
2850
- r: 255, g: 0, b: 255, a: alpha,
2851
- }).saturate(-saturation).toRgbString();
2852
- const fill6 = new Color({
2853
- r: 255, g: 0, b: 0, a: alpha,
2854
- }).saturate(-saturation).toRgbString();
2855
- const fillGradient = `linear-gradient(to right,
2856
- ${fill0} 0%, ${fill1} 16.67%, ${fill2} 33.33%, ${fill3} 50%,
2857
- ${fill4} 66.67%, ${fill5} 83.33%, ${fill6} 100%)`;
2858
- const lightGrad = `linear-gradient(rgba(255,255,255,${roundA}) 0%, rgba(255,255,255,0) 50%),
2859
- linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,${roundA}) 100%)`;
2860
-
2861
- setElementStyle(v1, { background: `${lightGrad},${fillGradient},${whiteGrad}` });
2862
- const {
2863
- r: gr, g: gg, b: gb,
2864
- } = 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 });
2865
2857
 
2866
- setElementStyle(v2, {
2867
- background: `linear-gradient(rgb(${r},${g},${b}) 0%, rgb(${gr},${gg},${gb}) 100%)`,
2868
- });
2869
- }
2870
2858
  setElementStyle(v3, {
2871
2859
  background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
2872
2860
  });
@@ -3016,13 +3004,13 @@ class ColorPicker {
3016
3004
  const [v1, v2, v3] = visuals;
3017
3005
  const [c1, c2, c3] = controlKnobs;
3018
3006
  /** @type {HTMLElement} */
3019
- const visual = hasClass(target, 'visual-control')
3020
- ? target : querySelector('.visual-control', target.parentElement);
3007
+ const visual = controlKnobs.includes(target) ? target.previousElementSibling : target;
3021
3008
  const visualRect = getBoundingClientRect(visual);
3009
+ const html = getDocumentElement(v1);
3022
3010
  const X = type === 'touchstart' ? touches[0].pageX : pageX;
3023
3011
  const Y = type === 'touchstart' ? touches[0].pageY : pageY;
3024
- const offsetX = X - window.pageXOffset - visualRect.left;
3025
- const offsetY = Y - window.pageYOffset - visualRect.top;
3012
+ const offsetX = X - html.scrollLeft - visualRect.left;
3013
+ const offsetY = Y - html.scrollTop - visualRect.top;
3026
3014
 
3027
3015
  if (target === v1 || target === c1) {
3028
3016
  self.dragElement = visual;
@@ -3082,10 +3070,11 @@ class ColorPicker {
3082
3070
  if (!dragElement) return;
3083
3071
 
3084
3072
  const controlRect = getBoundingClientRect(dragElement);
3085
- const X = type === 'touchmove' ? touches[0].pageX : pageX;
3086
- const Y = type === 'touchmove' ? touches[0].pageY : pageY;
3087
- const offsetX = X - window.pageXOffset - controlRect.left;
3088
- 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;
3089
3078
 
3090
3079
  if (dragElement === v1) {
3091
3080
  self.changeControl1(offsetX, offsetY);
@@ -3112,19 +3101,19 @@ class ColorPicker {
3112
3101
  if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
3113
3102
  e.preventDefault();
3114
3103
 
3115
- const { format, controlKnobs, visuals } = self;
3104
+ const { controlKnobs, visuals } = self;
3116
3105
  const { offsetWidth, offsetHeight } = visuals[0];
3117
3106
  const [c1, c2, c3] = controlKnobs;
3118
3107
  const { activeElement } = getDocument(c1);
3119
3108
  const currentKnob = controlKnobs.find((x) => x === activeElement);
3120
- const yRatio = offsetHeight / (format === 'hsl' ? 100 : 360);
3109
+ const yRatio = offsetHeight / 360;
3121
3110
 
3122
3111
  if (currentKnob) {
3123
3112
  let offsetX = 0;
3124
3113
  let offsetY = 0;
3125
3114
 
3126
3115
  if (target === c1) {
3127
- const xRatio = offsetWidth / (format === 'hsl' ? 360 : 100);
3116
+ const xRatio = offsetWidth / 100;
3128
3117
 
3129
3118
  if ([keyArrowLeft, keyArrowRight].includes(code)) {
3130
3119
  self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
@@ -3174,7 +3163,7 @@ class ColorPicker {
3174
3163
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
3175
3164
  if (activeElement === input) {
3176
3165
  if (isNonColorValue) {
3177
- colorSource = 'white';
3166
+ colorSource = currentValue === 'transparent' ? 'rgba(0,0,0,0)' : 'rgb(0,0,0)';
3178
3167
  } else {
3179
3168
  colorSource = currentValue;
3180
3169
  }
@@ -3225,9 +3214,7 @@ class ColorPicker {
3225
3214
  changeControl1(X, Y) {
3226
3215
  const self = this;
3227
3216
  let [offsetX, offsetY] = [0, 0];
3228
- const {
3229
- format, controlPositions, visuals,
3230
- } = self;
3217
+ const { controlPositions, visuals } = self;
3231
3218
  const { offsetHeight, offsetWidth } = visuals[0];
3232
3219
 
3233
3220
  if (X > offsetWidth) offsetX = offsetWidth;
@@ -3236,29 +3223,19 @@ class ColorPicker {
3236
3223
  if (Y > offsetHeight) offsetY = offsetHeight;
3237
3224
  else if (Y >= 0) offsetY = Y;
3238
3225
 
3239
- const hue = format === 'hsl'
3240
- ? offsetX / offsetWidth
3241
- : controlPositions.c2y / offsetHeight;
3226
+ const hue = controlPositions.c2y / offsetHeight;
3242
3227
 
3243
- const saturation = format === 'hsl'
3244
- ? 1 - controlPositions.c2y / offsetHeight
3245
- : offsetX / offsetWidth;
3228
+ const saturation = offsetX / offsetWidth;
3246
3229
 
3247
3230
  const lightness = 1 - offsetY / offsetHeight;
3248
3231
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3249
3232
 
3250
- const colorObject = format === 'hsl'
3251
- ? {
3252
- h: hue, s: saturation, l: lightness, a: alpha,
3253
- }
3254
- : {
3255
- h: hue, s: saturation, v: lightness, a: alpha,
3256
- };
3257
-
3258
3233
  // new color
3259
3234
  const {
3260
3235
  r, g, b, a,
3261
- } = new Color(colorObject);
3236
+ } = new Color({
3237
+ h: hue, s: saturation, v: lightness, a: alpha,
3238
+ });
3262
3239
 
3263
3240
  ObjectAssign(self.color, {
3264
3241
  r, g, b, a,
@@ -3285,7 +3262,7 @@ class ColorPicker {
3285
3262
  changeControl2(Y) {
3286
3263
  const self = this;
3287
3264
  const {
3288
- format, controlPositions, visuals,
3265
+ controlPositions, visuals,
3289
3266
  } = self;
3290
3267
  const { offsetHeight, offsetWidth } = visuals[0];
3291
3268
 
@@ -3294,26 +3271,17 @@ class ColorPicker {
3294
3271
  if (Y > offsetHeight) offsetY = offsetHeight;
3295
3272
  else if (Y >= 0) offsetY = Y;
3296
3273
 
3297
- const hue = format === 'hsl'
3298
- ? controlPositions.c1x / offsetWidth
3299
- : offsetY / offsetHeight;
3300
- const saturation = format === 'hsl'
3301
- ? 1 - offsetY / offsetHeight
3302
- : controlPositions.c1x / offsetWidth;
3274
+ const hue = offsetY / offsetHeight;
3275
+ const saturation = controlPositions.c1x / offsetWidth;
3303
3276
  const lightness = 1 - controlPositions.c1y / offsetHeight;
3304
3277
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3305
- const colorObject = format === 'hsl'
3306
- ? {
3307
- h: hue, s: saturation, l: lightness, a: alpha,
3308
- }
3309
- : {
3310
- h: hue, s: saturation, v: lightness, a: alpha,
3311
- };
3312
3278
 
3313
3279
  // new color
3314
3280
  const {
3315
3281
  r, g, b, a,
3316
- } = new Color(colorObject);
3282
+ } = new Color({
3283
+ h: hue, s: saturation, v: lightness, a: alpha,
3284
+ });
3317
3285
 
3318
3286
  ObjectAssign(self.color, {
3319
3287
  r, g, b, a,
@@ -3400,18 +3368,18 @@ class ColorPicker {
3400
3368
  setControlPositions() {
3401
3369
  const self = this;
3402
3370
  const {
3403
- format, visuals, color, hsl, hsv,
3371
+ visuals, color, hsv,
3404
3372
  } = self;
3405
3373
  const { offsetHeight, offsetWidth } = visuals[0];
3406
3374
  const alpha = color.a;
3407
- const hue = hsl.h;
3375
+ const hue = hsv.h;
3408
3376
 
3409
- const saturation = format !== 'hsl' ? hsv.s : hsl.s;
3410
- const lightness = format !== 'hsl' ? hsv.v : hsl.l;
3377
+ const saturation = hsv.s;
3378
+ const lightness = hsv.v;
3411
3379
 
3412
- self.controlPositions.c1x = format !== 'hsl' ? saturation * offsetWidth : hue * offsetWidth;
3380
+ self.controlPositions.c1x = saturation * offsetWidth;
3413
3381
  self.controlPositions.c1y = (1 - lightness) * offsetHeight;
3414
- self.controlPositions.c2y = format !== 'hsl' ? hue * offsetHeight : (1 - saturation) * offsetHeight;
3382
+ self.controlPositions.c2y = hue * offsetHeight;
3415
3383
  self.controlPositions.c3y = (1 - alpha) * offsetHeight;
3416
3384
  }
3417
3385
 
@@ -3419,78 +3387,40 @@ class ColorPicker {
3419
3387
  updateAppearance() {
3420
3388
  const self = this;
3421
3389
  const {
3422
- componentLabels, colorLabels, color, parent,
3423
- hsl, hsv, hex, format, controlKnobs,
3390
+ componentLabels, color, parent,
3391
+ hsv, hex, format, controlKnobs,
3424
3392
  } = self;
3425
3393
  const {
3426
3394
  appearanceLabel, hexLabel, valueLabel,
3427
3395
  } = componentLabels;
3428
- const { r, g, b } = color.toRgb();
3396
+ let { r, g, b } = color.toRgb();
3429
3397
  const [knob1, knob2, knob3] = controlKnobs;
3430
- const hue = roundPart(hsl.h * 360);
3398
+ const hue = roundPart(hsv.h * 360);
3431
3399
  const alpha = color.a;
3432
- const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
3433
- const saturation = roundPart(saturationSource * 100);
3434
- const lightness = roundPart(hsl.l * 100);
3435
- const hsvl = hsv.v * 100;
3436
- let colorName;
3437
-
3438
- // determine color appearance
3439
- if (lightness === 100 && saturation === 0) {
3440
- colorName = colorLabels.white;
3441
- } else if (lightness === 0) {
3442
- colorName = colorLabels.black;
3443
- } else if (saturation === 0) {
3444
- colorName = colorLabels.grey;
3445
- } else if (hue < 15 || hue >= 345) {
3446
- colorName = colorLabels.red;
3447
- } else if (hue >= 15 && hue < 45) {
3448
- colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
3449
- } else if (hue >= 45 && hue < 75) {
3450
- const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
3451
- const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
3452
- colorName = isGold ? colorLabels.gold : colorLabels.yellow;
3453
- colorName = isOlive ? colorLabels.olive : colorName;
3454
- } else if (hue >= 75 && hue < 155) {
3455
- colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
3456
- } else if (hue >= 155 && hue < 175) {
3457
- colorName = colorLabels.teal;
3458
- } else if (hue >= 175 && hue < 195) {
3459
- colorName = colorLabels.cyan;
3460
- } else if (hue >= 195 && hue < 255) {
3461
- colorName = colorLabels.blue;
3462
- } else if (hue >= 255 && hue < 270) {
3463
- colorName = colorLabels.violet;
3464
- } else if (hue >= 270 && hue < 295) {
3465
- colorName = colorLabels.magenta;
3466
- } else if (hue >= 295 && hue < 345) {
3467
- colorName = colorLabels.pink;
3468
- }
3400
+ const saturation = roundPart(hsv.s * 100);
3401
+ const lightness = roundPart(hsv.v * 100);
3402
+ const colorName = self.appearance;
3469
3403
 
3470
3404
  let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
3471
3405
 
3472
- if (format === 'hsl') {
3473
- colorLabel = `HSL: ${hue}°, ${saturation}%, ${lightness}%`;
3474
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3475
- setAttribute(knob1, ariaValueText, `${hue}° & ${lightness}%`);
3476
- setAttribute(knob1, ariaValueNow, `${hue}`);
3477
- setAttribute(knob2, ariaValueText, `${saturation}%`);
3478
- setAttribute(knob2, ariaValueNow, `${saturation}`);
3479
- } else if (format === 'hwb') {
3406
+ if (format === 'hwb') {
3480
3407
  const { hwb } = self;
3481
3408
  const whiteness = roundPart(hwb.w * 100);
3482
3409
  const blackness = roundPart(hwb.b * 100);
3483
3410
  colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
3484
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3485
3411
  setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
3486
3412
  setAttribute(knob1, ariaValueNow, `${whiteness}`);
3413
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3487
3414
  setAttribute(knob2, ariaValueText, `${hue}%`);
3488
3415
  setAttribute(knob2, ariaValueNow, `${hue}`);
3489
3416
  } else {
3417
+ [r, g, b] = [r, g, b].map(roundPart);
3418
+ colorLabel = format === 'hsl' ? `HSL: ${hue}°, ${saturation}%, ${lightness}%` : colorLabel;
3490
3419
  colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
3491
- setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3420
+
3492
3421
  setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
3493
3422
  setAttribute(knob1, ariaValueNow, `${lightness}`);
3423
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3494
3424
  setAttribute(knob2, ariaValueText, `${hue}°`);
3495
3425
  setAttribute(knob2, ariaValueNow, `${hue}`);
3496
3426
  }
@@ -3585,37 +3515,13 @@ class ColorPicker {
3585
3515
  }
3586
3516
  }
3587
3517
 
3588
- /**
3589
- * The `Space` & `Enter` keys specific event listener.
3590
- * Toggle visibility of the `ColorPicker` / the presets menu, showing one will hide the other.
3591
- * @param {KeyboardEvent} e
3592
- * @this {ColorPicker}
3593
- */
3594
- keyToggle(e) {
3595
- const self = this;
3596
- const { menuToggle } = self;
3597
- const { activeElement } = getDocument(menuToggle);
3598
- const { code } = e;
3599
-
3600
- if ([keyEnter, keySpace].includes(code)) {
3601
- if ((menuToggle && activeElement === menuToggle) || !activeElement) {
3602
- e.preventDefault();
3603
- if (!activeElement) {
3604
- self.togglePicker(e);
3605
- } else {
3606
- self.toggleMenu();
3607
- }
3608
- }
3609
- }
3610
- }
3611
-
3612
3518
  /**
3613
3519
  * Toggle the `ColorPicker` dropdown visibility.
3614
- * @param {Event} e
3520
+ * @param {Event=} e
3615
3521
  * @this {ColorPicker}
3616
3522
  */
3617
3523
  togglePicker(e) {
3618
- e.preventDefault();
3524
+ if (e) e.preventDefault();
3619
3525
  const self = this;
3620
3526
  const { colorPicker } = self;
3621
3527
 
@@ -3636,8 +3542,13 @@ class ColorPicker {
3636
3542
  }
3637
3543
  }
3638
3544
 
3639
- /** Toggles the visibility of the `ColorPicker` presets menu. */
3640
- 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();
3641
3552
  const self = this;
3642
3553
  const { colorMenu } = self;
3643
3554
 
@@ -3663,6 +3574,10 @@ class ColorPicker {
3663
3574
  const relatedBtn = openPicker ? pickerToggle : menuToggle;
3664
3575
  const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
3665
3576
 
3577
+ // if (!self.isValid) {
3578
+ self.value = self.color.toString(true);
3579
+ // }
3580
+
3666
3581
  if (openDropdown) {
3667
3582
  removeClass(openDropdown, 'show');
3668
3583
  setAttribute(relatedBtn, ariaExpanded, 'false');
@@ -3676,9 +3591,6 @@ class ColorPicker {
3676
3591
  }, animationDuration);
3677
3592
  }
3678
3593
 
3679
- if (!self.isValid) {
3680
- self.value = self.color.toString();
3681
- }
3682
3594
  if (!focusPrevented) {
3683
3595
  focus(pickerToggle);
3684
3596
  }
@@ -3727,90 +3639,82 @@ let CPID = 0;
3727
3639
  * `ColorPickerElement` Web Component.
3728
3640
  * @example
3729
3641
  * <label for="UNIQUE_ID">Label</label>
3730
- * <color-picker data-format="hex" data-value="#075">
3731
- * <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">
3732
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>
3733
3648
  */
3734
3649
  class ColorPickerElement extends HTMLElement {
3735
3650
  constructor() {
3736
3651
  super();
3737
- /** @type {boolean} */
3738
- this.isDisconnected = true;
3739
3652
  this.attachShadow({ mode: 'open' });
3740
3653
  }
3741
3654
 
3742
3655
  /**
3743
3656
  * Returns the current color value.
3744
- * @returns {string?}
3657
+ * @returns {string | undefined}
3745
3658
  */
3746
- get value() { return this.input ? this.input.value : null; }
3659
+ get value() { return this.input && this.input.value; }
3747
3660
 
3748
3661
  connectedCallback() {
3749
- if (this.colorPicker) {
3750
- if (this.isDisconnected) {
3751
- this.isDisconnected = false;
3752
- }
3753
- return;
3754
- }
3662
+ if (this.input) return;
3755
3663
 
3756
- 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
+ }
3757
3673
 
3758
- if (!inputs.length) {
3759
- const label = getAttribute(this, 'data-label');
3760
- const value = getAttribute(this, 'data-value') || '#069';
3761
- const format = getAttribute(this, 'data-format') || 'rgb';
3762
- const newInput = createElement({
3674
+ if (!input) {
3675
+ input = createElement({
3763
3676
  tagName: 'input',
3764
3677
  type: 'text',
3765
3678
  className: 'color-preview btn-appearance',
3766
3679
  });
3767
- let id = getAttribute(this, 'data-id');
3768
- if (!id) {
3769
- id = `color-picker-${format}-${CPID}`;
3770
- CPID += 1;
3771
- }
3772
3680
 
3773
- const labelElement = createElement({ tagName: 'label', innerText: label || 'Color Picker' });
3774
- this.before(labelElement);
3775
- setAttribute(labelElement, 'for', id);
3776
- setAttribute(newInput, 'id', id);
3777
- setAttribute(newInput, 'name', id);
3778
- setAttribute(newInput, 'autocomplete', 'off');
3779
- setAttribute(newInput, 'spellcheck', 'false');
3780
- setAttribute(newInput, 'value', value);
3781
- 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);
3782
3687
  }
3688
+ /** @type {HTMLInputElement} */
3689
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3690
+ this.input = input;
3783
3691
 
3784
- const [input] = inputs;
3785
-
3786
- if (input) {
3787
- /** @type {HTMLInputElement} */
3788
- // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3789
- this.input = input;
3790
-
3791
- // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3792
- this.colorPicker = new ColorPicker(input);
3793
- this.color = this.colorPicker.color;
3794
-
3795
- if (this.shadowRoot) {
3796
- this.shadowRoot.append(createElement('slot'));
3797
- }
3692
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3693
+ this.colorPicker = new ColorPicker(input);
3798
3694
 
3799
- this.isDisconnected = false;
3800
- }
3695
+ // @ts-ignore - `shadowRoot` is defined in the constructor
3696
+ this.shadowRoot.append(createElement('slot'));
3801
3697
  }
3802
3698
 
3699
+ /** @this {ColorPickerElement} */
3803
3700
  disconnectedCallback() {
3804
- if (this.colorPicker) this.colorPicker.dispose();
3805
- 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
+ });
3806
3710
  }
3807
3711
  }
3808
3712
 
3809
3713
  ObjectAssign(ColorPickerElement, {
3810
3714
  Color,
3811
3715
  ColorPicker,
3812
- ColorPalette,
3813
- getInstance: getColorPickerInstance,
3716
+ ColorPalette, // @ts-ignore
3717
+ getInstance: ColorPicker.getInstance,
3814
3718
  Version,
3815
3719
  });
3816
3720