@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
  */
@@ -218,6 +218,8 @@
218
218
  if (nonColors.includes(color)
219
219
  || ['#', ...COLOR_FORMAT].some((f) => color.includes(f))) return false;
220
220
 
221
+ if (['black', 'white'].includes(color)) return true;
222
+
221
223
  return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
222
224
  setElementStyle(documentHead, { color });
223
225
  const computedColor = getElementStyle(documentHead, 'color');
@@ -244,6 +246,11 @@
244
246
  */
245
247
  function bound01(N, max) {
246
248
  let n = N;
249
+
250
+ if (typeof N === 'number'
251
+ && Math.min(N, 0) === 0 // round values to 6 decimals Math.round(N * (10 ** 6)) / 10 ** 6
252
+ && Math.max(N, 1) === 1) return N;
253
+
247
254
  if (isOnePointZero(N)) n = '100%';
248
255
 
249
256
  const processPercent = isPercentage(n);
@@ -347,15 +354,12 @@
347
354
  /**
348
355
  * Converts an RGB colour value to HSL.
349
356
  *
350
- * @param {number} R Red component [0, 255]
351
- * @param {number} G Green component [0, 255]
352
- * @param {number} B Blue component [0, 255]
357
+ * @param {number} r Red component [0, 1]
358
+ * @param {number} g Green component [0, 1]
359
+ * @param {number} b Blue component [0, 1]
353
360
  * @returns {CP.HSL} {h,s,l} object with [0, 1] ranged values
354
361
  */
355
- function rgbToHsl(R, G, B) {
356
- const r = R / 255;
357
- const g = G / 255;
358
- const b = B / 255;
362
+ function rgbToHsl(r, g, b) {
359
363
  const max = Math.max(r, g, b);
360
364
  const min = Math.min(r, g, b);
361
365
  let h = 0;
@@ -367,17 +371,10 @@
367
371
  } else {
368
372
  const d = max - min;
369
373
  s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
370
- switch (max) {
371
- case r:
372
- h = (g - b) / d + (g < b ? 6 : 0);
373
- break;
374
- case g:
375
- h = (b - r) / d + 2;
376
- break;
377
- case b:
378
- h = (r - g) / d + 4;
379
- break;
380
- }
374
+ if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
375
+ if (max === g) h = (b - r) / d + 2;
376
+ if (max === b) h = (r - g) / d + 4;
377
+
381
378
  h /= 6;
382
379
  }
383
380
  return { h, s, l };
@@ -406,7 +403,7 @@
406
403
  * @param {number} h Hue Angle [0, 1]
407
404
  * @param {number} s Saturation [0, 1]
408
405
  * @param {number} l Lightness Angle [0, 1]
409
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
406
+ * @returns {CP.RGB} {r,g,b} object with [0, 1] ranged values
410
407
  */
411
408
  function hslToRgb(h, s, l) {
412
409
  let r = 0;
@@ -425,7 +422,6 @@
425
422
  g = hueToRgb(p, q, h);
426
423
  b = hueToRgb(p, q, h - 1 / 3);
427
424
  }
428
- [r, g, b] = [r, g, b].map((x) => x * 255);
429
425
 
430
426
  return { r, g, b };
431
427
  }
@@ -435,16 +431,12 @@
435
431
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
436
432
  * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
437
433
  *
438
- * @param {number} R Red component [0, 255]
439
- * @param {number} G Green [0, 255]
440
- * @param {number} B Blue [0, 255]
434
+ * @param {number} r Red component [0, 1]
435
+ * @param {number} g Green [0, 1]
436
+ * @param {number} b Blue [0, 1]
441
437
  * @return {CP.HWB} {h,w,b} object with [0, 1] ranged values
442
438
  */
443
- function rgbToHwb(R, G, B) {
444
- const r = R / 255;
445
- const g = G / 255;
446
- const b = B / 255;
447
-
439
+ function rgbToHwb(r, g, b) {
448
440
  let f = 0;
449
441
  let i = 0;
450
442
  const whiteness = Math.min(r, g, b);
@@ -474,20 +466,18 @@
474
466
  * @param {number} H Hue Angle [0, 1]
475
467
  * @param {number} W Whiteness [0, 1]
476
468
  * @param {number} B Blackness [0, 1]
477
- * @return {CP.RGB} {r,g,b} object with [0, 255] ranged values
469
+ * @return {CP.RGB} {r,g,b} object with [0, 1] ranged values
478
470
  *
479
471
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
480
472
  * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
481
473
  */
482
474
  function hwbToRgb(H, W, B) {
483
475
  if (W + B >= 1) {
484
- const gray = (W / (W + B)) * 255;
476
+ const gray = W / (W + B);
485
477
  return { r: gray, g: gray, b: gray };
486
478
  }
487
479
  let { r, g, b } = hslToRgb(H, 1, 0.5);
488
- [r, g, b] = [r, g, b]
489
- .map((v) => (v / 255) * (1 - W - B) + W)
490
- .map((v) => v * 255);
480
+ [r, g, b] = [r, g, b].map((v) => v * (1 - W - B) + W);
491
481
 
492
482
  return { r, g, b };
493
483
  }
@@ -495,15 +485,12 @@
495
485
  /**
496
486
  * Converts an RGB colour value to HSV.
497
487
  *
498
- * @param {number} R Red component [0, 255]
499
- * @param {number} G Green [0, 255]
500
- * @param {number} B Blue [0, 255]
488
+ * @param {number} r Red component [0, 1]
489
+ * @param {number} g Green [0, 1]
490
+ * @param {number} b Blue [0, 1]
501
491
  * @returns {CP.HSV} {h,s,v} object with [0, 1] ranged values
502
492
  */
503
- function rgbToHsv(R, G, B) {
504
- const r = R / 255;
505
- const g = G / 255;
506
- const b = B / 255;
493
+ function rgbToHsv(r, g, b) {
507
494
  const max = Math.max(r, g, b);
508
495
  const min = Math.min(r, g, b);
509
496
  let h = 0;
@@ -513,17 +500,10 @@
513
500
  if (max === min) {
514
501
  h = 0; // achromatic
515
502
  } else {
516
- switch (max) {
517
- case r:
518
- h = (g - b) / d + (g < b ? 6 : 0);
519
- break;
520
- case g:
521
- h = (b - r) / d + 2;
522
- break;
523
- case b:
524
- h = (r - g) / d + 4;
525
- break;
526
- }
503
+ if (r === max) h = (g - b) / d + (g < b ? 6 : 0);
504
+ if (g === max) h = (b - r) / d + 2;
505
+ if (b === max) h = (r - g) / d + 4;
506
+
527
507
  h /= 6;
528
508
  }
529
509
  return { h, s, v };
@@ -547,10 +527,9 @@
547
527
  const q = v * (1 - f * s);
548
528
  const t = v * (1 - (1 - f) * s);
549
529
  const mod = i % 6;
550
- let r = [v, q, p, p, t, v][mod];
551
- let g = [t, v, v, q, p, p][mod];
552
- let b = [p, p, t, v, v, q][mod];
553
- [r, g, b] = [r, g, b].map((n) => n * 255);
530
+ const r = [v, q, p, p, t, v][mod];
531
+ const g = [t, v, v, q, p, p][mod];
532
+ const b = [p, p, t, v, v, q][mod];
554
533
  return { r, g, b };
555
534
  }
556
535
 
@@ -618,15 +597,15 @@
618
597
  */
619
598
  function stringInputToObject(input) {
620
599
  let color = toLowerCase(input.trim());
600
+
621
601
  if (color.length === 0) {
622
602
  return {
623
603
  r: 0, g: 0, b: 0, a: 1,
624
604
  };
625
605
  }
626
- let named = false;
606
+
627
607
  if (isColorName(color)) {
628
608
  color = getRGBFromName(color);
629
- named = true;
630
609
  } else if (nonColors.includes(color)) {
631
610
  const a = color === 'transparent' ? 0 : 1;
632
611
  return {
@@ -645,24 +624,28 @@
645
624
  r: m1, g: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'rgb',
646
625
  };
647
626
  }
627
+
648
628
  [, m1, m2, m3, m4] = matchers.hsl.exec(color) || [];
649
629
  if (m1 && m2 && m3/* && m4 */) {
650
630
  return {
651
631
  h: m1, s: m2, l: m3, a: m4 !== undefined ? m4 : 1, format: 'hsl',
652
632
  };
653
633
  }
634
+
654
635
  [, m1, m2, m3, m4] = matchers.hsv.exec(color) || [];
655
636
  if (m1 && m2 && m3/* && m4 */) {
656
637
  return {
657
638
  h: m1, s: m2, v: m3, a: m4 !== undefined ? m4 : 1, format: 'hsv',
658
639
  };
659
640
  }
641
+
660
642
  [, m1, m2, m3, m4] = matchers.hwb.exec(color) || [];
661
643
  if (m1 && m2 && m3) {
662
644
  return {
663
645
  h: m1, w: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'hwb',
664
646
  };
665
647
  }
648
+
666
649
  [, m1, m2, m3, m4] = matchers.hex8.exec(color) || [];
667
650
  if (m1 && m2 && m3 && m4) {
668
651
  return {
@@ -670,18 +653,20 @@
670
653
  g: parseIntFromHex(m2),
671
654
  b: parseIntFromHex(m3),
672
655
  a: convertHexToDecimal(m4),
673
- format: named ? 'rgb' : 'hex',
656
+ format: 'hex',
674
657
  };
675
658
  }
659
+
676
660
  [, m1, m2, m3] = matchers.hex6.exec(color) || [];
677
661
  if (m1 && m2 && m3) {
678
662
  return {
679
663
  r: parseIntFromHex(m1),
680
664
  g: parseIntFromHex(m2),
681
665
  b: parseIntFromHex(m3),
682
- format: named ? 'rgb' : 'hex',
666
+ format: 'hex',
683
667
  };
684
668
  }
669
+
685
670
  [, m1, m2, m3, m4] = matchers.hex4.exec(color) || [];
686
671
  if (m1 && m2 && m3 && m4) {
687
672
  return {
@@ -689,19 +674,20 @@
689
674
  g: parseIntFromHex(m2 + m2),
690
675
  b: parseIntFromHex(m3 + m3),
691
676
  a: convertHexToDecimal(m4 + m4),
692
- // format: named ? 'rgb' : 'hex8',
693
- format: named ? 'rgb' : 'hex',
677
+ format: 'hex',
694
678
  };
695
679
  }
680
+
696
681
  [, m1, m2, m3] = matchers.hex3.exec(color) || [];
697
682
  if (m1 && m2 && m3) {
698
683
  return {
699
684
  r: parseIntFromHex(m1 + m1),
700
685
  g: parseIntFromHex(m2 + m2),
701
686
  b: parseIntFromHex(m3 + m3),
702
- format: named ? 'rgb' : 'hex',
687
+ format: 'hex',
703
688
  };
704
689
  }
690
+
705
691
  return false;
706
692
  }
707
693
 
@@ -732,6 +718,7 @@
732
718
  */
733
719
  function inputToRGB(input) {
734
720
  let rgb = { r: 0, g: 0, b: 0 };
721
+ /** @type {*} */
735
722
  let color = input;
736
723
  /** @type {string | number} */
737
724
  let a = 1;
@@ -748,39 +735,41 @@
748
735
  let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
749
736
 
750
737
  if (typeof input === 'string') {
751
- // @ts-ignore -- this now is converted to object
752
738
  color = stringInputToObject(input);
753
739
  if (color) ok = true;
754
740
  }
755
741
  if (typeof color === 'object') {
756
742
  if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
757
743
  ({ r, g, b } = color);
758
- // RGB values now are all in [0, 255] range
759
- [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255);
744
+ // RGB values now are all in [0, 1] range
745
+ [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255));
760
746
  rgb = { r, g, b };
761
747
  ok = true;
762
- format = 'rgb';
763
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
748
+ format = color.format || 'rgb';
749
+ }
750
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
764
751
  ({ h, s, v } = color);
765
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
766
- s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
767
- v = typeof v === 'number' ? v : bound01(v, 100); // brightness can be `5%` or a [0, 1] value
752
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
753
+ s = bound01(s, 100); // saturation can be `5%` or a [0, 1] value
754
+ v = bound01(v, 100); // brightness can be `5%` or a [0, 1] value
768
755
  rgb = hsvToRgb(h, s, v);
769
756
  ok = true;
770
757
  format = 'hsv';
771
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
758
+ }
759
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
772
760
  ({ h, s, l } = color);
773
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
774
- s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
775
- l = typeof l === 'number' ? l : bound01(l, 100); // lightness can be `5%` or a [0, 1] value
761
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
762
+ s = bound01(s, 100); // saturation can be `5%` or a [0, 1] value
763
+ l = bound01(l, 100); // lightness can be `5%` or a [0, 1] value
776
764
  rgb = hslToRgb(h, s, l);
777
765
  ok = true;
778
766
  format = 'hsl';
779
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
767
+ }
768
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
780
769
  ({ h, w, b } = color);
781
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
782
- w = typeof w === 'number' ? w : bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
783
- b = typeof b === 'number' ? b : bound01(b, 100); // blackness can be `5%` or a [0, 1] value
770
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
771
+ w = bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
772
+ b = bound01(b, 100); // blackness can be `5%` or a [0, 1] value
784
773
  rgb = hwbToRgb(h, w, b);
785
774
  ok = true;
786
775
  format = 'hwb';
@@ -797,9 +786,12 @@
797
786
  return {
798
787
  ok,
799
788
  format,
800
- r: Math.min(255, Math.max(rgb.r, 0)),
801
- g: Math.min(255, Math.max(rgb.g, 0)),
802
- b: Math.min(255, Math.max(rgb.b, 0)),
789
+ // r: Math.min(255, Math.max(rgb.r, 0)),
790
+ // g: Math.min(255, Math.max(rgb.g, 0)),
791
+ // b: Math.min(255, Math.max(rgb.b, 0)),
792
+ r: rgb.r,
793
+ g: rgb.g,
794
+ b: rgb.b,
803
795
  a: boundAlpha(a),
804
796
  };
805
797
  }
@@ -818,16 +810,13 @@
818
810
  constructor(input, config) {
819
811
  let color = input;
820
812
  const configFormat = config && COLOR_FORMAT.includes(config)
821
- ? config : 'rgb';
813
+ ? config : '';
822
814
 
823
- // If input is already a `Color`, return itself
815
+ // If input is already a `Color`, clone its values
824
816
  if (color instanceof Color) {
825
817
  color = inputToRGB(color);
826
818
  }
827
- if (typeof color === 'number') {
828
- const len = `${color}`.length;
829
- color = `#${(len === 2 ? '0' : '00')}${color}`;
830
- }
819
+
831
820
  const {
832
821
  r, g, b, a, ok, format,
833
822
  } = inputToRGB(color);
@@ -877,24 +866,21 @@
877
866
  let R = 0;
878
867
  let G = 0;
879
868
  let B = 0;
880
- const rp = r / 255;
881
- const rg = g / 255;
882
- const rb = b / 255;
883
869
 
884
- if (rp <= 0.03928) {
885
- R = rp / 12.92;
870
+ if (r <= 0.03928) {
871
+ R = r / 12.92;
886
872
  } else {
887
- R = ((rp + 0.055) / 1.055) ** 2.4;
873
+ R = ((r + 0.055) / 1.055) ** 2.4;
888
874
  }
889
- if (rg <= 0.03928) {
890
- G = rg / 12.92;
875
+ if (g <= 0.03928) {
876
+ G = g / 12.92;
891
877
  } else {
892
- G = ((rg + 0.055) / 1.055) ** 2.4;
878
+ G = ((g + 0.055) / 1.055) ** 2.4;
893
879
  }
894
- if (rb <= 0.03928) {
895
- B = rb / 12.92;
880
+ if (b <= 0.03928) {
881
+ B = b / 12.92;
896
882
  } else {
897
- B = ((rb + 0.055) / 1.055) ** 2.4;
883
+ B = ((b + 0.055) / 1.055) ** 2.4;
898
884
  }
899
885
  return 0.2126 * R + 0.7152 * G + 0.0722 * B;
900
886
  }
@@ -904,7 +890,7 @@
904
890
  * @returns {number} a number in the [0, 255] range
905
891
  */
906
892
  get brightness() {
907
- const { r, g, b } = this;
893
+ const { r, g, b } = this.toRgb();
908
894
  return (r * 299 + g * 587 + b * 114) / 1000;
909
895
  }
910
896
 
@@ -913,12 +899,14 @@
913
899
  * @returns {CP.RGBA} an {r,g,b,a} object with [0, 255] ranged values
914
900
  */
915
901
  toRgb() {
916
- const {
902
+ let {
917
903
  r, g, b, a,
918
904
  } = this;
919
905
 
906
+ [r, g, b] = [r, g, b].map((n) => roundPart(n * 255 * 100) / 100);
907
+ a = roundPart(a * 100) / 100;
920
908
  return {
921
- r, g, b, a: roundPart(a * 100) / 100,
909
+ r, g, b, a,
922
910
  };
923
911
  }
924
912
 
@@ -1012,7 +1000,7 @@
1012
1000
  toHsv() {
1013
1001
  const {
1014
1002
  r, g, b, a,
1015
- } = this.toRgb();
1003
+ } = this;
1016
1004
  const { h, s, v } = rgbToHsv(r, g, b);
1017
1005
 
1018
1006
  return {
@@ -1027,7 +1015,7 @@
1027
1015
  toHsl() {
1028
1016
  const {
1029
1017
  r, g, b, a,
1030
- } = this.toRgb();
1018
+ } = this;
1031
1019
  const { h, s, l } = rgbToHsl(r, g, b);
1032
1020
 
1033
1021
  return {
@@ -1112,6 +1100,7 @@
1112
1100
  */
1113
1101
  setAlpha(alpha) {
1114
1102
  const self = this;
1103
+ if (typeof alpha !== 'number') return self;
1115
1104
  self.a = boundAlpha(alpha);
1116
1105
  return self;
1117
1106
  }
@@ -1940,26 +1929,23 @@
1940
1929
  } else if (args.length === 2) {
1941
1930
  [hueSteps, lightSteps] = args;
1942
1931
  if ([hueSteps, lightSteps].some((n) => n < 1)) {
1943
- throw TypeError('ColorPalette: when 2 arguments used, both must be larger than 0.');
1932
+ throw TypeError('ColorPalette: both arguments must be higher than 0.');
1944
1933
  }
1945
- } else {
1946
- throw TypeError('ColorPalette requires minimum 2 arguments');
1947
1934
  }
1948
1935
 
1949
- /** @type {Color[]} */
1936
+ /** @type {*} */
1950
1937
  const colors = [];
1951
-
1952
1938
  const hueStep = 360 / hueSteps;
1953
1939
  const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
1954
- const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
1940
+ const steps1To13 = [0.25, 0.2, 0.15, 0.11, 0.09, 0.075];
1941
+ const lightSets = [[1, 2, 3], [4, 5], [6, 7], [8, 9], [10, 11], [12, 13]];
1942
+ const closestSet = lightSets.find((set) => set.includes(lightSteps));
1955
1943
 
1956
- let lightStep = 0.25;
1957
- lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
1958
- lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
1959
- lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
1960
- lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
1961
- lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
1962
- lightStep = lightSteps > 13 ? estimatedStep : lightStep;
1944
+ // find a lightStep that won't go beyond black and white
1945
+ // something within the [10-90] range of lightness
1946
+ const lightStep = closestSet
1947
+ ? steps1To13[lightSets.indexOf(closestSet)]
1948
+ : (100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100);
1963
1949
 
1964
1950
  // light tints
1965
1951
  for (let i = 1; i < half + 1; i += 1) {
@@ -2451,7 +2437,7 @@
2451
2437
  setAttribute(input, tabIndex, '-1');
2452
2438
  }
2453
2439
 
2454
- var version = "0.0.2alpha1";
2440
+ var version = "0.0.2alpha4";
2455
2441
 
2456
2442
  // @ts-ignore
2457
2443
 
@@ -2494,8 +2480,6 @@
2494
2480
  fn(input, focusinEvent, self.showPicker);
2495
2481
  fn(pickerToggle, mouseclickEvent, self.togglePicker);
2496
2482
 
2497
- fn(input, keydownEvent, self.keyToggle);
2498
-
2499
2483
  if (menuToggle) {
2500
2484
  fn(menuToggle, mouseclickEvent, self.toggleMenu);
2501
2485
  }
@@ -2533,8 +2517,7 @@
2533
2517
  fn(doc, pointerEvents.move, self.pointerMove);
2534
2518
  fn(doc, pointerEvents.up, self.pointerUp);
2535
2519
  fn(parent, focusoutEvent, self.handleFocusOut);
2536
- // @ts-ignore -- this is `Window`
2537
- fn(win, keyupEvent, self.handleDismiss);
2520
+ fn(doc, keyupEvent, self.handleDismiss);
2538
2521
  }
2539
2522
 
2540
2523
  /**
@@ -2618,7 +2601,7 @@
2618
2601
  const input = querySelector(target);
2619
2602
 
2620
2603
  // invalidate
2621
- if (!input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
2604
+ if (!input) throw new TypeError(`ColorPicker target "${target}" cannot be found.`);
2622
2605
  self.input = input;
2623
2606
 
2624
2607
  const parent = closest(input, colorPickerParentSelector);
@@ -2665,15 +2648,14 @@
2665
2648
  });
2666
2649
 
2667
2650
  // update and expose component labels
2668
- const tempLabels = ObjectAssign({}, colorPickerLabels);
2669
- const jsonLabels = componentLabels && isValidJSON(componentLabels)
2670
- ? JSON.parse(componentLabels) : componentLabels || {};
2651
+ const tempComponentLabels = componentLabels && isValidJSON(componentLabels)
2652
+ ? JSON.parse(componentLabels) : componentLabels;
2671
2653
 
2672
2654
  /** @type {Record<string, string>} */
2673
- self.componentLabels = ObjectAssign(tempLabels, jsonLabels);
2655
+ self.componentLabels = ObjectAssign(colorPickerLabels, tempComponentLabels);
2674
2656
 
2675
2657
  /** @type {Color} */
2676
- self.color = new Color('white', format);
2658
+ self.color = new Color(input.value || '#fff', format);
2677
2659
 
2678
2660
  /** @type {CP.ColorFormats} */
2679
2661
  self.format = format;
@@ -2682,7 +2664,7 @@
2682
2664
  if (colorKeywords instanceof Array) {
2683
2665
  self.colorKeywords = colorKeywords;
2684
2666
  } else if (typeof colorKeywords === 'string' && colorKeywords.length) {
2685
- self.colorKeywords = colorKeywords.split(',');
2667
+ self.colorKeywords = colorKeywords.split(',').map((x) => x.trim());
2686
2668
  }
2687
2669
 
2688
2670
  // set colour presets
@@ -2711,7 +2693,6 @@
2711
2693
  self.handleFocusOut = self.handleFocusOut.bind(self);
2712
2694
  self.changeHandler = self.changeHandler.bind(self);
2713
2695
  self.handleDismiss = self.handleDismiss.bind(self);
2714
- self.keyToggle = self.keyToggle.bind(self);
2715
2696
  self.handleKnobs = self.handleKnobs.bind(self);
2716
2697
 
2717
2698
  // generate markup
@@ -2803,76 +2784,83 @@
2803
2784
  return inputValue !== '' && new Color(inputValue).isValid;
2804
2785
  }
2805
2786
 
2787
+ /** Returns the colour appearance, usually the closest colour name for the current value. */
2788
+ get appearance() {
2789
+ const {
2790
+ colorLabels, hsl, hsv, format,
2791
+ } = this;
2792
+
2793
+ const hue = roundPart(hsl.h * 360);
2794
+ const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
2795
+ const saturation = roundPart(saturationSource * 100);
2796
+ const lightness = roundPart(hsl.l * 100);
2797
+ const hsvl = hsv.v * 100;
2798
+
2799
+ let colorName;
2800
+
2801
+ // determine color appearance
2802
+ if (lightness === 100 && saturation === 0) {
2803
+ colorName = colorLabels.white;
2804
+ } else if (lightness === 0) {
2805
+ colorName = colorLabels.black;
2806
+ } else if (saturation === 0) {
2807
+ colorName = colorLabels.grey;
2808
+ } else if (hue < 15 || hue >= 345) {
2809
+ colorName = colorLabels.red;
2810
+ } else if (hue >= 15 && hue < 45) {
2811
+ colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
2812
+ } else if (hue >= 45 && hue < 75) {
2813
+ const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
2814
+ const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
2815
+ colorName = isGold ? colorLabels.gold : colorLabels.yellow;
2816
+ colorName = isOlive ? colorLabels.olive : colorName;
2817
+ } else if (hue >= 75 && hue < 155) {
2818
+ colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
2819
+ } else if (hue >= 155 && hue < 175) {
2820
+ colorName = colorLabels.teal;
2821
+ } else if (hue >= 175 && hue < 195) {
2822
+ colorName = colorLabels.cyan;
2823
+ } else if (hue >= 195 && hue < 255) {
2824
+ colorName = colorLabels.blue;
2825
+ } else if (hue >= 255 && hue < 270) {
2826
+ colorName = colorLabels.violet;
2827
+ } else if (hue >= 270 && hue < 295) {
2828
+ colorName = colorLabels.magenta;
2829
+ } else if (hue >= 295 && hue < 345) {
2830
+ colorName = colorLabels.pink;
2831
+ }
2832
+ return colorName;
2833
+ }
2834
+
2806
2835
  /** Updates `ColorPicker` visuals. */
2807
2836
  updateVisuals() {
2808
2837
  const self = this;
2809
2838
  const {
2810
- format, controlPositions, visuals,
2839
+ controlPositions, visuals,
2811
2840
  } = self;
2812
2841
  const [v1, v2, v3] = visuals;
2813
- const { offsetWidth, offsetHeight } = v1;
2814
- const hue = format === 'hsl'
2815
- ? controlPositions.c1x / offsetWidth
2816
- : controlPositions.c2y / offsetHeight;
2817
- // @ts-ignore - `hslToRgb` is assigned to `Color` as static method
2818
- const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
2842
+ const { offsetHeight } = v1;
2843
+ const hue = controlPositions.c2y / offsetHeight;
2844
+ const { r, g, b } = new Color({ h: hue, s: 1, l: 0.5 }).toRgb();
2819
2845
  const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
2820
2846
  const alpha = 1 - controlPositions.c3y / offsetHeight;
2821
2847
  const roundA = roundPart((alpha * 100)) / 100;
2822
2848
 
2823
- if (format !== 'hsl') {
2824
- const fill = new Color({
2825
- h: hue, s: 1, l: 0.5, a: alpha,
2826
- }).toRgbString();
2827
- const hueGradient = `linear-gradient(
2828
- rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2829
- rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2830
- rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2831
- rgb(255,0,0) 100%)`;
2832
- setElementStyle(v1, {
2833
- background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2834
- linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2835
- ${whiteGrad}`,
2836
- });
2837
- setElementStyle(v2, { background: hueGradient });
2838
- } else {
2839
- const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
2840
- const fill0 = new Color({
2841
- r: 255, g: 0, b: 0, a: alpha,
2842
- }).saturate(-saturation).toRgbString();
2843
- const fill1 = new Color({
2844
- r: 255, g: 255, b: 0, a: alpha,
2845
- }).saturate(-saturation).toRgbString();
2846
- const fill2 = new Color({
2847
- r: 0, g: 255, b: 0, a: alpha,
2848
- }).saturate(-saturation).toRgbString();
2849
- const fill3 = new Color({
2850
- r: 0, g: 255, b: 255, a: alpha,
2851
- }).saturate(-saturation).toRgbString();
2852
- const fill4 = new Color({
2853
- r: 0, g: 0, b: 255, a: alpha,
2854
- }).saturate(-saturation).toRgbString();
2855
- const fill5 = new Color({
2856
- r: 255, g: 0, b: 255, a: alpha,
2857
- }).saturate(-saturation).toRgbString();
2858
- const fill6 = new Color({
2859
- r: 255, g: 0, b: 0, a: alpha,
2860
- }).saturate(-saturation).toRgbString();
2861
- const fillGradient = `linear-gradient(to right,
2862
- ${fill0} 0%, ${fill1} 16.67%, ${fill2} 33.33%, ${fill3} 50%,
2863
- ${fill4} 66.67%, ${fill5} 83.33%, ${fill6} 100%)`;
2864
- const lightGrad = `linear-gradient(rgba(255,255,255,${roundA}) 0%, rgba(255,255,255,0) 50%),
2865
- linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,${roundA}) 100%)`;
2866
-
2867
- setElementStyle(v1, { background: `${lightGrad},${fillGradient},${whiteGrad}` });
2868
- const {
2869
- r: gr, g: gg, b: gb,
2870
- } = new Color({ r, g, b }).greyscale().toRgb();
2849
+ const fill = new Color({
2850
+ h: hue, s: 1, l: 0.5, a: alpha,
2851
+ }).toRgbString();
2852
+ const hueGradient = `linear-gradient(
2853
+ rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2854
+ rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2855
+ rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2856
+ rgb(255,0,0) 100%)`;
2857
+ setElementStyle(v1, {
2858
+ background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2859
+ linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2860
+ ${whiteGrad}`,
2861
+ });
2862
+ setElementStyle(v2, { background: hueGradient });
2871
2863
 
2872
- setElementStyle(v2, {
2873
- background: `linear-gradient(rgb(${r},${g},${b}) 0%, rgb(${gr},${gg},${gb}) 100%)`,
2874
- });
2875
- }
2876
2864
  setElementStyle(v3, {
2877
2865
  background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
2878
2866
  });
@@ -3022,13 +3010,13 @@
3022
3010
  const [v1, v2, v3] = visuals;
3023
3011
  const [c1, c2, c3] = controlKnobs;
3024
3012
  /** @type {HTMLElement} */
3025
- const visual = hasClass(target, 'visual-control')
3026
- ? target : querySelector('.visual-control', target.parentElement);
3013
+ const visual = controlKnobs.includes(target) ? target.previousElementSibling : target;
3027
3014
  const visualRect = getBoundingClientRect(visual);
3015
+ const html = getDocumentElement(v1);
3028
3016
  const X = type === 'touchstart' ? touches[0].pageX : pageX;
3029
3017
  const Y = type === 'touchstart' ? touches[0].pageY : pageY;
3030
- const offsetX = X - window.pageXOffset - visualRect.left;
3031
- const offsetY = Y - window.pageYOffset - visualRect.top;
3018
+ const offsetX = X - html.scrollLeft - visualRect.left;
3019
+ const offsetY = Y - html.scrollTop - visualRect.top;
3032
3020
 
3033
3021
  if (target === v1 || target === c1) {
3034
3022
  self.dragElement = visual;
@@ -3088,10 +3076,11 @@
3088
3076
  if (!dragElement) return;
3089
3077
 
3090
3078
  const controlRect = getBoundingClientRect(dragElement);
3091
- const X = type === 'touchmove' ? touches[0].pageX : pageX;
3092
- const Y = type === 'touchmove' ? touches[0].pageY : pageY;
3093
- const offsetX = X - window.pageXOffset - controlRect.left;
3094
- const offsetY = Y - window.pageYOffset - controlRect.top;
3079
+ const win = getDocumentElement(v1);
3080
+ const X = type === touchmoveEvent ? touches[0].pageX : pageX;
3081
+ const Y = type === touchmoveEvent ? touches[0].pageY : pageY;
3082
+ const offsetX = X - win.scrollLeft - controlRect.left;
3083
+ const offsetY = Y - win.scrollTop - controlRect.top;
3095
3084
 
3096
3085
  if (dragElement === v1) {
3097
3086
  self.changeControl1(offsetX, offsetY);
@@ -3118,19 +3107,19 @@
3118
3107
  if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
3119
3108
  e.preventDefault();
3120
3109
 
3121
- const { format, controlKnobs, visuals } = self;
3110
+ const { controlKnobs, visuals } = self;
3122
3111
  const { offsetWidth, offsetHeight } = visuals[0];
3123
3112
  const [c1, c2, c3] = controlKnobs;
3124
3113
  const { activeElement } = getDocument(c1);
3125
3114
  const currentKnob = controlKnobs.find((x) => x === activeElement);
3126
- const yRatio = offsetHeight / (format === 'hsl' ? 100 : 360);
3115
+ const yRatio = offsetHeight / 360;
3127
3116
 
3128
3117
  if (currentKnob) {
3129
3118
  let offsetX = 0;
3130
3119
  let offsetY = 0;
3131
3120
 
3132
3121
  if (target === c1) {
3133
- const xRatio = offsetWidth / (format === 'hsl' ? 360 : 100);
3122
+ const xRatio = offsetWidth / 100;
3134
3123
 
3135
3124
  if ([keyArrowLeft, keyArrowRight].includes(code)) {
3136
3125
  self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
@@ -3180,7 +3169,7 @@
3180
3169
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
3181
3170
  if (activeElement === input) {
3182
3171
  if (isNonColorValue) {
3183
- colorSource = 'white';
3172
+ colorSource = currentValue === 'transparent' ? 'rgba(0,0,0,0)' : 'rgb(0,0,0)';
3184
3173
  } else {
3185
3174
  colorSource = currentValue;
3186
3175
  }
@@ -3231,9 +3220,7 @@
3231
3220
  changeControl1(X, Y) {
3232
3221
  const self = this;
3233
3222
  let [offsetX, offsetY] = [0, 0];
3234
- const {
3235
- format, controlPositions, visuals,
3236
- } = self;
3223
+ const { controlPositions, visuals } = self;
3237
3224
  const { offsetHeight, offsetWidth } = visuals[0];
3238
3225
 
3239
3226
  if (X > offsetWidth) offsetX = offsetWidth;
@@ -3242,29 +3229,19 @@
3242
3229
  if (Y > offsetHeight) offsetY = offsetHeight;
3243
3230
  else if (Y >= 0) offsetY = Y;
3244
3231
 
3245
- const hue = format === 'hsl'
3246
- ? offsetX / offsetWidth
3247
- : controlPositions.c2y / offsetHeight;
3232
+ const hue = controlPositions.c2y / offsetHeight;
3248
3233
 
3249
- const saturation = format === 'hsl'
3250
- ? 1 - controlPositions.c2y / offsetHeight
3251
- : offsetX / offsetWidth;
3234
+ const saturation = offsetX / offsetWidth;
3252
3235
 
3253
3236
  const lightness = 1 - offsetY / offsetHeight;
3254
3237
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3255
3238
 
3256
- const colorObject = format === 'hsl'
3257
- ? {
3258
- h: hue, s: saturation, l: lightness, a: alpha,
3259
- }
3260
- : {
3261
- h: hue, s: saturation, v: lightness, a: alpha,
3262
- };
3263
-
3264
3239
  // new color
3265
3240
  const {
3266
3241
  r, g, b, a,
3267
- } = new Color(colorObject);
3242
+ } = new Color({
3243
+ h: hue, s: saturation, v: lightness, a: alpha,
3244
+ });
3268
3245
 
3269
3246
  ObjectAssign(self.color, {
3270
3247
  r, g, b, a,
@@ -3291,7 +3268,7 @@
3291
3268
  changeControl2(Y) {
3292
3269
  const self = this;
3293
3270
  const {
3294
- format, controlPositions, visuals,
3271
+ controlPositions, visuals,
3295
3272
  } = self;
3296
3273
  const { offsetHeight, offsetWidth } = visuals[0];
3297
3274
 
@@ -3300,26 +3277,17 @@
3300
3277
  if (Y > offsetHeight) offsetY = offsetHeight;
3301
3278
  else if (Y >= 0) offsetY = Y;
3302
3279
 
3303
- const hue = format === 'hsl'
3304
- ? controlPositions.c1x / offsetWidth
3305
- : offsetY / offsetHeight;
3306
- const saturation = format === 'hsl'
3307
- ? 1 - offsetY / offsetHeight
3308
- : controlPositions.c1x / offsetWidth;
3280
+ const hue = offsetY / offsetHeight;
3281
+ const saturation = controlPositions.c1x / offsetWidth;
3309
3282
  const lightness = 1 - controlPositions.c1y / offsetHeight;
3310
3283
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3311
- const colorObject = format === 'hsl'
3312
- ? {
3313
- h: hue, s: saturation, l: lightness, a: alpha,
3314
- }
3315
- : {
3316
- h: hue, s: saturation, v: lightness, a: alpha,
3317
- };
3318
3284
 
3319
3285
  // new color
3320
3286
  const {
3321
3287
  r, g, b, a,
3322
- } = new Color(colorObject);
3288
+ } = new Color({
3289
+ h: hue, s: saturation, v: lightness, a: alpha,
3290
+ });
3323
3291
 
3324
3292
  ObjectAssign(self.color, {
3325
3293
  r, g, b, a,
@@ -3406,18 +3374,18 @@
3406
3374
  setControlPositions() {
3407
3375
  const self = this;
3408
3376
  const {
3409
- format, visuals, color, hsl, hsv,
3377
+ visuals, color, hsv,
3410
3378
  } = self;
3411
3379
  const { offsetHeight, offsetWidth } = visuals[0];
3412
3380
  const alpha = color.a;
3413
- const hue = hsl.h;
3381
+ const hue = hsv.h;
3414
3382
 
3415
- const saturation = format !== 'hsl' ? hsv.s : hsl.s;
3416
- const lightness = format !== 'hsl' ? hsv.v : hsl.l;
3383
+ const saturation = hsv.s;
3384
+ const lightness = hsv.v;
3417
3385
 
3418
- self.controlPositions.c1x = format !== 'hsl' ? saturation * offsetWidth : hue * offsetWidth;
3386
+ self.controlPositions.c1x = saturation * offsetWidth;
3419
3387
  self.controlPositions.c1y = (1 - lightness) * offsetHeight;
3420
- self.controlPositions.c2y = format !== 'hsl' ? hue * offsetHeight : (1 - saturation) * offsetHeight;
3388
+ self.controlPositions.c2y = hue * offsetHeight;
3421
3389
  self.controlPositions.c3y = (1 - alpha) * offsetHeight;
3422
3390
  }
3423
3391
 
@@ -3425,78 +3393,40 @@
3425
3393
  updateAppearance() {
3426
3394
  const self = this;
3427
3395
  const {
3428
- componentLabels, colorLabels, color, parent,
3429
- hsl, hsv, hex, format, controlKnobs,
3396
+ componentLabels, color, parent,
3397
+ hsv, hex, format, controlKnobs,
3430
3398
  } = self;
3431
3399
  const {
3432
3400
  appearanceLabel, hexLabel, valueLabel,
3433
3401
  } = componentLabels;
3434
- const { r, g, b } = color.toRgb();
3402
+ let { r, g, b } = color.toRgb();
3435
3403
  const [knob1, knob2, knob3] = controlKnobs;
3436
- const hue = roundPart(hsl.h * 360);
3404
+ const hue = roundPart(hsv.h * 360);
3437
3405
  const alpha = color.a;
3438
- const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
3439
- const saturation = roundPart(saturationSource * 100);
3440
- const lightness = roundPart(hsl.l * 100);
3441
- const hsvl = hsv.v * 100;
3442
- let colorName;
3443
-
3444
- // determine color appearance
3445
- if (lightness === 100 && saturation === 0) {
3446
- colorName = colorLabels.white;
3447
- } else if (lightness === 0) {
3448
- colorName = colorLabels.black;
3449
- } else if (saturation === 0) {
3450
- colorName = colorLabels.grey;
3451
- } else if (hue < 15 || hue >= 345) {
3452
- colorName = colorLabels.red;
3453
- } else if (hue >= 15 && hue < 45) {
3454
- colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
3455
- } else if (hue >= 45 && hue < 75) {
3456
- const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
3457
- const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
3458
- colorName = isGold ? colorLabels.gold : colorLabels.yellow;
3459
- colorName = isOlive ? colorLabels.olive : colorName;
3460
- } else if (hue >= 75 && hue < 155) {
3461
- colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
3462
- } else if (hue >= 155 && hue < 175) {
3463
- colorName = colorLabels.teal;
3464
- } else if (hue >= 175 && hue < 195) {
3465
- colorName = colorLabels.cyan;
3466
- } else if (hue >= 195 && hue < 255) {
3467
- colorName = colorLabels.blue;
3468
- } else if (hue >= 255 && hue < 270) {
3469
- colorName = colorLabels.violet;
3470
- } else if (hue >= 270 && hue < 295) {
3471
- colorName = colorLabels.magenta;
3472
- } else if (hue >= 295 && hue < 345) {
3473
- colorName = colorLabels.pink;
3474
- }
3406
+ const saturation = roundPart(hsv.s * 100);
3407
+ const lightness = roundPart(hsv.v * 100);
3408
+ const colorName = self.appearance;
3475
3409
 
3476
3410
  let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
3477
3411
 
3478
- if (format === 'hsl') {
3479
- colorLabel = `HSL: ${hue}°, ${saturation}%, ${lightness}%`;
3480
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3481
- setAttribute(knob1, ariaValueText, `${hue}° & ${lightness}%`);
3482
- setAttribute(knob1, ariaValueNow, `${hue}`);
3483
- setAttribute(knob2, ariaValueText, `${saturation}%`);
3484
- setAttribute(knob2, ariaValueNow, `${saturation}`);
3485
- } else if (format === 'hwb') {
3412
+ if (format === 'hwb') {
3486
3413
  const { hwb } = self;
3487
3414
  const whiteness = roundPart(hwb.w * 100);
3488
3415
  const blackness = roundPart(hwb.b * 100);
3489
3416
  colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
3490
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3491
3417
  setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
3492
3418
  setAttribute(knob1, ariaValueNow, `${whiteness}`);
3419
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3493
3420
  setAttribute(knob2, ariaValueText, `${hue}%`);
3494
3421
  setAttribute(knob2, ariaValueNow, `${hue}`);
3495
3422
  } else {
3423
+ [r, g, b] = [r, g, b].map(roundPart);
3424
+ colorLabel = format === 'hsl' ? `HSL: ${hue}°, ${saturation}%, ${lightness}%` : colorLabel;
3496
3425
  colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
3497
- setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3426
+
3498
3427
  setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
3499
3428
  setAttribute(knob1, ariaValueNow, `${lightness}`);
3429
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3500
3430
  setAttribute(knob2, ariaValueText, `${hue}°`);
3501
3431
  setAttribute(knob2, ariaValueNow, `${hue}`);
3502
3432
  }
@@ -3591,37 +3521,13 @@
3591
3521
  }
3592
3522
  }
3593
3523
 
3594
- /**
3595
- * The `Space` & `Enter` keys specific event listener.
3596
- * Toggle visibility of the `ColorPicker` / the presets menu, showing one will hide the other.
3597
- * @param {KeyboardEvent} e
3598
- * @this {ColorPicker}
3599
- */
3600
- keyToggle(e) {
3601
- const self = this;
3602
- const { menuToggle } = self;
3603
- const { activeElement } = getDocument(menuToggle);
3604
- const { code } = e;
3605
-
3606
- if ([keyEnter, keySpace].includes(code)) {
3607
- if ((menuToggle && activeElement === menuToggle) || !activeElement) {
3608
- e.preventDefault();
3609
- if (!activeElement) {
3610
- self.togglePicker(e);
3611
- } else {
3612
- self.toggleMenu();
3613
- }
3614
- }
3615
- }
3616
- }
3617
-
3618
3524
  /**
3619
3525
  * Toggle the `ColorPicker` dropdown visibility.
3620
- * @param {Event} e
3526
+ * @param {Event=} e
3621
3527
  * @this {ColorPicker}
3622
3528
  */
3623
3529
  togglePicker(e) {
3624
- e.preventDefault();
3530
+ if (e) e.preventDefault();
3625
3531
  const self = this;
3626
3532
  const { colorPicker } = self;
3627
3533
 
@@ -3642,8 +3548,13 @@
3642
3548
  }
3643
3549
  }
3644
3550
 
3645
- /** Toggles the visibility of the `ColorPicker` presets menu. */
3646
- toggleMenu() {
3551
+ /**
3552
+ * Toggles the visibility of the `ColorPicker` presets menu.
3553
+ * @param {Event=} e
3554
+ * @this {ColorPicker}
3555
+ */
3556
+ toggleMenu(e) {
3557
+ if (e) e.preventDefault();
3647
3558
  const self = this;
3648
3559
  const { colorMenu } = self;
3649
3560
 
@@ -3669,6 +3580,10 @@
3669
3580
  const relatedBtn = openPicker ? pickerToggle : menuToggle;
3670
3581
  const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
3671
3582
 
3583
+ // if (!self.isValid) {
3584
+ self.value = self.color.toString(true);
3585
+ // }
3586
+
3672
3587
  if (openDropdown) {
3673
3588
  removeClass(openDropdown, 'show');
3674
3589
  setAttribute(relatedBtn, ariaExpanded, 'false');
@@ -3682,9 +3597,6 @@
3682
3597
  }, animationDuration);
3683
3598
  }
3684
3599
 
3685
- if (!self.isValid) {
3686
- self.value = self.color.toString();
3687
- }
3688
3600
  if (!focusPrevented) {
3689
3601
  focus(pickerToggle);
3690
3602
  }
@@ -3733,90 +3645,82 @@
3733
3645
  * `ColorPickerElement` Web Component.
3734
3646
  * @example
3735
3647
  * <label for="UNIQUE_ID">Label</label>
3736
- * <color-picker data-format="hex" data-value="#075">
3737
- * <input id="UNIQUE_ID" type="text" class="color-preview btn-appearance">
3648
+ * <color-picker>
3649
+ * <input id="UNIQUE_ID" value="red" format="hex" class="color-preview btn-appearance">
3738
3650
  * </color-picker>
3651
+ * // or
3652
+ * <label for="UNIQUE_ID">Label</label>
3653
+ * <color-picker data-id="UNIQUE_ID" data-value="red" data-format="hex"></color-picker>
3739
3654
  */
3740
3655
  class ColorPickerElement extends HTMLElement {
3741
3656
  constructor() {
3742
3657
  super();
3743
- /** @type {boolean} */
3744
- this.isDisconnected = true;
3745
3658
  this.attachShadow({ mode: 'open' });
3746
3659
  }
3747
3660
 
3748
3661
  /**
3749
3662
  * Returns the current color value.
3750
- * @returns {string?}
3663
+ * @returns {string | undefined}
3751
3664
  */
3752
- get value() { return this.input ? this.input.value : null; }
3665
+ get value() { return this.input && this.input.value; }
3753
3666
 
3754
3667
  connectedCallback() {
3755
- if (this.colorPicker) {
3756
- if (this.isDisconnected) {
3757
- this.isDisconnected = false;
3758
- }
3759
- return;
3760
- }
3668
+ if (this.input) return;
3761
3669
 
3762
- const inputs = getElementsByTagName('input', this);
3670
+ let [input] = getElementsByTagName('input', this);
3671
+ const value = (input && getAttribute(input, 'value')) || getAttribute(this, 'data-value') || '#fff';
3672
+ const format = (input && getAttribute(input, 'format')) || getAttribute(this, 'data-format') || 'rgb';
3673
+ let id = (input && getAttribute(input, 'id')) || getAttribute(this, 'data-id');
3674
+
3675
+ if (!id) {
3676
+ id = `color-picker-${format}-${CPID}`;
3677
+ CPID += 1;
3678
+ }
3763
3679
 
3764
- if (!inputs.length) {
3765
- const label = getAttribute(this, 'data-label');
3766
- const value = getAttribute(this, 'data-value') || '#069';
3767
- const format = getAttribute(this, 'data-format') || 'rgb';
3768
- const newInput = createElement({
3680
+ if (!input) {
3681
+ input = createElement({
3769
3682
  tagName: 'input',
3770
3683
  type: 'text',
3771
3684
  className: 'color-preview btn-appearance',
3772
3685
  });
3773
- let id = getAttribute(this, 'data-id');
3774
- if (!id) {
3775
- id = `color-picker-${format}-${CPID}`;
3776
- CPID += 1;
3777
- }
3778
3686
 
3779
- const labelElement = createElement({ tagName: 'label', innerText: label || 'Color Picker' });
3780
- this.before(labelElement);
3781
- setAttribute(labelElement, 'for', id);
3782
- setAttribute(newInput, 'id', id);
3783
- setAttribute(newInput, 'name', id);
3784
- setAttribute(newInput, 'autocomplete', 'off');
3785
- setAttribute(newInput, 'spellcheck', 'false');
3786
- setAttribute(newInput, 'value', value);
3787
- this.append(newInput);
3687
+ setAttribute(input, 'id', id);
3688
+ setAttribute(input, 'name', id);
3689
+ setAttribute(input, 'autocomplete', 'off');
3690
+ setAttribute(input, 'spellcheck', 'false');
3691
+ setAttribute(input, 'value', value);
3692
+ this.append(input);
3788
3693
  }
3694
+ /** @type {HTMLInputElement} */
3695
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3696
+ this.input = input;
3789
3697
 
3790
- const [input] = inputs;
3791
-
3792
- if (input) {
3793
- /** @type {HTMLInputElement} */
3794
- // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3795
- this.input = input;
3796
-
3797
- // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3798
- this.colorPicker = new ColorPicker(input);
3799
- this.color = this.colorPicker.color;
3800
-
3801
- if (this.shadowRoot) {
3802
- this.shadowRoot.append(createElement('slot'));
3803
- }
3698
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3699
+ this.colorPicker = new ColorPicker(input);
3804
3700
 
3805
- this.isDisconnected = false;
3806
- }
3701
+ // @ts-ignore - `shadowRoot` is defined in the constructor
3702
+ this.shadowRoot.append(createElement('slot'));
3807
3703
  }
3808
3704
 
3705
+ /** @this {ColorPickerElement} */
3809
3706
  disconnectedCallback() {
3810
- if (this.colorPicker) this.colorPicker.dispose();
3811
- this.isDisconnected = true;
3707
+ const { input, colorPicker, shadowRoot } = this;
3708
+ if (colorPicker) colorPicker.dispose();
3709
+ if (input) input.remove();
3710
+ if (shadowRoot) shadowRoot.innerHTML = '';
3711
+
3712
+ ObjectAssign(this, {
3713
+ colorPicker: undefined,
3714
+ input: undefined,
3715
+ });
3812
3716
  }
3813
3717
  }
3814
3718
 
3815
3719
  ObjectAssign(ColorPickerElement, {
3816
3720
  Color,
3817
3721
  ColorPicker,
3818
- ColorPalette,
3819
- getInstance: getColorPickerInstance,
3722
+ ColorPalette, // @ts-ignore
3723
+ getInstance: ColorPicker.getInstance,
3820
3724
  Version,
3821
3725
  });
3822
3726