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

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.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