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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * ColorPickerElement v0.0.2alpha1 (http://thednp.github.io/color-picker)
2
+ * ColorPickerElement v0.0.2alpha4 (http://thednp.github.io/color-picker)
3
3
  * Copyright 2022 © thednp
4
4
  * Licensed under MIT (https://github.com/thednp/color-picker/blob/master/LICENSE)
5
5
  */
@@ -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