@thednp/color-picker 0.0.1-alpha3 → 0.0.2-alpha2

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
- * ColorPicker v0.0.1alpha3 (http://thednp.github.io/color-picker)
2
+ * ColorPicker v0.0.2alpha2 (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
  */
@@ -129,24 +129,6 @@ const ariaValueText = 'aria-valuetext';
129
129
  */
130
130
  const ariaValueNow = 'aria-valuenow';
131
131
 
132
- /**
133
- * A global namespace for aria-haspopup.
134
- * @type {string}
135
- */
136
- const ariaHasPopup = 'aria-haspopup';
137
-
138
- /**
139
- * A global namespace for aria-hidden.
140
- * @type {string}
141
- */
142
- const ariaHidden = 'aria-hidden';
143
-
144
- /**
145
- * A global namespace for aria-labelledby.
146
- * @type {string}
147
- */
148
- const ariaLabelledBy = 'aria-labelledby';
149
-
150
132
  /**
151
133
  * A global namespace for `ArrowDown` key.
152
134
  * @type {string} e.which = 40 equivalent
@@ -273,37 +255,6 @@ const resizeEvent = 'resize';
273
255
  */
274
256
  const focusoutEvent = 'focusout';
275
257
 
276
- // @ts-ignore
277
- const { userAgentData: uaDATA } = navigator;
278
-
279
- /**
280
- * A global namespace for `userAgentData` object.
281
- */
282
- const userAgentData = uaDATA;
283
-
284
- const { userAgent: userAgentString } = navigator;
285
-
286
- /**
287
- * A global namespace for `navigator.userAgent` string.
288
- */
289
- const userAgent = userAgentString;
290
-
291
- const mobileBrands = /iPhone|iPad|iPod|Android/i;
292
- let isMobileCheck = false;
293
-
294
- if (userAgentData) {
295
- isMobileCheck = userAgentData.brands
296
- .some((/** @type {Record<String, any>} */x) => mobileBrands.test(x.brand));
297
- } else {
298
- isMobileCheck = mobileBrands.test(userAgent);
299
- }
300
-
301
- /**
302
- * A global `boolean` for mobile detection.
303
- * @type {boolean}
304
- */
305
- const isMobile = isMobileCheck;
306
-
307
258
  /**
308
259
  * Returns the `document` or the `#document` element.
309
260
  * @see https://github.com/floating-ui/floating-ui
@@ -524,60 +475,6 @@ function getElementsByClassName(selector, parent) {
524
475
  return lookUp.getElementsByClassName(selector);
525
476
  }
526
477
 
527
- /**
528
- * Shortcut for `Object.assign()` static method.
529
- * @param {Record<string, any>} obj a target object
530
- * @param {Record<string, any>} source a source object
531
- */
532
- const ObjectAssign = (obj, source) => Object.assign(obj, source);
533
-
534
- /**
535
- * This is a shortie for `document.createElement` method
536
- * which allows you to create a new `HTMLElement` for a given `tagName`
537
- * or based on an object with specific non-readonly attributes:
538
- * `id`, `className`, `textContent`, `style`, etc.
539
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
540
- *
541
- * @param {Record<string, string> | string} param `tagName` or object
542
- * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
543
- */
544
- function createElement(param) {
545
- if (typeof param === 'string') {
546
- return getDocument().createElement(param);
547
- }
548
-
549
- const { tagName } = param;
550
- const attr = { ...param };
551
- const newElement = createElement(tagName);
552
- delete attr.tagName;
553
- ObjectAssign(newElement, attr);
554
- return newElement;
555
- }
556
-
557
- /**
558
- * This is a shortie for `document.createElementNS` method
559
- * which allows you to create a new `HTMLElement` for a given `tagName`
560
- * or based on an object with specific non-readonly attributes:
561
- * `id`, `className`, `textContent`, `style`, etc.
562
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
563
- *
564
- * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
565
- * @param {Record<string, string> | string} param `tagName` or object
566
- * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
567
- */
568
- function createElementNS(namespace, param) {
569
- if (typeof param === 'string') {
570
- return getDocument().createElementNS(namespace, param);
571
- }
572
-
573
- const { tagName } = param;
574
- const attr = { ...param };
575
- const newElement = createElementNS(namespace, tagName);
576
- delete attr.tagName;
577
- ObjectAssign(newElement, attr);
578
- return newElement;
579
- }
580
-
581
478
  /**
582
479
  * Shortcut for the `Element.dispatchEvent(Event)` method.
583
480
  *
@@ -586,6 +483,13 @@ function createElementNS(namespace, param) {
586
483
  */
587
484
  const dispatchEvent = (element, event) => element.dispatchEvent(event);
588
485
 
486
+ /**
487
+ * Shortcut for `Object.assign()` static method.
488
+ * @param {Record<string, any>} obj a target object
489
+ * @param {Record<string, any>} source a source object
490
+ */
491
+ const ObjectAssign = (obj, source) => Object.assign(obj, source);
492
+
589
493
  /** @type {Map<string, Map<HTMLElement | Element, Record<string, any>>>} */
590
494
  const componentData = new Map();
591
495
  /**
@@ -661,20 +565,13 @@ const Data = {
661
565
  */
662
566
  const getInstance = (target, component) => Data.get(target, component);
663
567
 
664
- /**
665
- * Shortcut for `Object.keys()` static method.
666
- * @param {Record<string, any>} obj a target object
667
- * @returns {string[]}
668
- */
669
- const ObjectKeys = (obj) => Object.keys(obj);
670
-
671
568
  /**
672
569
  * Shortcut for multiple uses of `HTMLElement.style.propertyName` method.
673
570
  * @param {HTMLElement | Element} element target element
674
571
  * @param {Partial<CSSStyleDeclaration>} styles attribute value
675
572
  */
676
573
  // @ts-ignore
677
- const setElementStyle = (element, styles) => ObjectAssign(element.style, styles);
574
+ const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
678
575
 
679
576
  /**
680
577
  * Shortcut for `HTMLElement.getAttribute()` method.
@@ -717,6 +614,13 @@ function normalizeValue(value) {
717
614
  return value;
718
615
  }
719
616
 
617
+ /**
618
+ * Shortcut for `Object.keys()` static method.
619
+ * @param {Record<string, any>} obj a target object
620
+ * @returns {string[]}
621
+ */
622
+ const ObjectKeys = (obj) => Object.keys(obj);
623
+
720
624
  /**
721
625
  * Shortcut for `String.toLowerCase()`.
722
626
  *
@@ -837,32 +741,10 @@ const setAttribute = (element, attribute, value) => element.setAttribute(attribu
837
741
  */
838
742
  const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
839
743
 
840
- /** @type {Record<string, string>} */
841
- const colorPickerLabels = {
842
- pickerLabel: 'Colour Picker',
843
- appearanceLabel: 'Colour Appearance',
844
- valueLabel: 'Colour Value',
845
- toggleLabel: 'Select Colour',
846
- presetsLabel: 'Colour Presets',
847
- defaultsLabel: 'Colour Defaults',
848
- formatLabel: 'Format',
849
- alphaLabel: 'Alpha',
850
- hexLabel: 'Hexadecimal',
851
- hueLabel: 'Hue',
852
- whitenessLabel: 'Whiteness',
853
- blacknessLabel: 'Blackness',
854
- saturationLabel: 'Saturation',
855
- lightnessLabel: 'Lightness',
856
- redLabel: 'Red',
857
- greenLabel: 'Green',
858
- blueLabel: 'Blue',
859
- };
860
-
861
744
  /**
862
- * A list of 17 color names used for WAI-ARIA compliance.
863
- * @type {string[]}
745
+ * A global namespace for `document.head`.
864
746
  */
865
- const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
747
+ const { head: documentHead } = document;
866
748
 
867
749
  /**
868
750
  * A list of explicit default non-color values.
@@ -870,297 +752,99 @@ const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold',
870
752
  const nonColors = ['transparent', 'currentColor', 'inherit', 'revert', 'initial'];
871
753
 
872
754
  /**
873
- * Shortcut for `String.toUpperCase()`.
874
- *
875
- * @param {string} source input string
876
- * @returns {string} uppercase output string
755
+ * Round colour components, for all formats except HEX.
756
+ * @param {number} v one of the colour components
757
+ * @returns {number} the rounded number
877
758
  */
878
- const toUpperCase = (source) => source.toUpperCase();
759
+ function roundPart(v) {
760
+ const floor = Math.floor(v);
761
+ return v - floor < 0.5 ? floor : Math.round(v);
762
+ }
879
763
 
880
- const vHidden = 'v-hidden';
764
+ // Color supported formats
765
+ const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsv', 'hwb'];
881
766
 
882
- /**
883
- * Returns the color form for `ColorPicker`.
884
- *
885
- * @param {CP.ColorPicker} self the `ColorPicker` instance
886
- * @returns {HTMLElement | Element} a new `<div>` element with color component `<input>`
887
- */
888
- function getColorForm(self) {
889
- const { format, id, componentLabels } = self;
890
- const colorForm = createElement({
891
- tagName: 'div',
892
- className: `color-form ${format}`,
893
- });
767
+ // Hue angles
768
+ const ANGLES = 'deg|rad|grad|turn';
894
769
 
895
- let components = ['hex'];
896
- if (format === 'rgb') components = ['red', 'green', 'blue', 'alpha'];
897
- else if (format === 'hsl') components = ['hue', 'saturation', 'lightness', 'alpha'];
898
- else if (format === 'hwb') components = ['hue', 'whiteness', 'blackness', 'alpha'];
770
+ // <http://www.w3.org/TR/css3-values/#integers>
771
+ const CSS_INTEGER = '[-\\+]?\\d+%?';
899
772
 
900
- components.forEach((c) => {
901
- const [C] = format === 'hex' ? ['#'] : toUpperCase(c).split('');
902
- const cID = `color_${format}_${c}_${id}`;
903
- const formatLabel = componentLabels[`${c}Label`];
904
- const cInputLabel = createElement({ tagName: 'label' });
905
- setAttribute(cInputLabel, 'for', cID);
906
- cInputLabel.append(
907
- createElement({ tagName: 'span', ariaHidden: 'true', innerText: `${C}:` }),
908
- createElement({ tagName: 'span', className: vHidden, innerText: formatLabel }),
909
- );
910
- const cInput = createElement({
911
- tagName: 'input',
912
- id: cID,
913
- // name: cID, - prevent saving the value to a form
914
- type: format === 'hex' ? 'text' : 'number',
915
- value: c === 'alpha' ? '100' : '0',
916
- className: `color-input ${c}`,
917
- });
918
- setAttribute(cInput, 'autocomplete', 'off');
919
- setAttribute(cInput, 'spellcheck', 'false');
773
+ // Include CSS3 Module
774
+ // <http://www.w3.org/TR/css3-values/#number-value>
775
+ const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
920
776
 
921
- // alpha
922
- let max = '100';
923
- let step = '1';
924
- if (c !== 'alpha') {
925
- if (format === 'rgb') {
926
- max = '255'; step = '1';
927
- } else if (c === 'hue') {
928
- max = '360'; step = '1';
929
- }
930
- }
931
- ObjectAssign(cInput, {
932
- min: '0',
933
- max,
934
- step,
935
- });
936
- colorForm.append(cInputLabel, cInput);
937
- });
938
- return colorForm;
939
- }
777
+ // Include CSS4 Module Hue degrees unit
778
+ // <https://www.w3.org/TR/css3-values/#angle-value>
779
+ const CSS_ANGLE = `[-\\+]?\\d*\\.?\\d+(?:${ANGLES})?`;
780
+
781
+ // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
782
+ const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
783
+
784
+ // Add angles to the mix
785
+ const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
786
+
787
+ // Start & end
788
+ const START_MATCH = '(?:[\\s|\\(\\s|\\s\\(\\s]+)?';
789
+ const END_MATCH = '(?:[\\s|\\)\\s]+)?';
790
+ // Components separation
791
+ const SEP = '(?:[,|\\s]+)';
792
+ const SEP2 = '(?:[,|\\/\\s]*)?';
793
+
794
+ // Actual matching.
795
+ // Parentheses and commas are optional, but not required.
796
+ // Whitespace can take the place of commas or opening paren
797
+ const PERMISSIVE_MATCH = `${START_MATCH}(${CSS_UNIT2})${SEP}(${CSS_UNIT})${SEP}(${CSS_UNIT})${SEP2}(${CSS_UNIT})?${END_MATCH}`;
798
+
799
+ const matchers = {
800
+ CSS_UNIT: new RegExp(CSS_UNIT2),
801
+ hwb: new RegExp(`hwb${PERMISSIVE_MATCH}`),
802
+ rgb: new RegExp(`rgb(?:a)?${PERMISSIVE_MATCH}`),
803
+ hsl: new RegExp(`hsl(?:a)?${PERMISSIVE_MATCH}`),
804
+ hsv: new RegExp(`hsv(?:a)?${PERMISSIVE_MATCH}`),
805
+ hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
806
+ hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
807
+ hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
808
+ hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
809
+ };
940
810
 
941
811
  /**
942
- * A global namespace for aria-label.
943
- * @type {string}
812
+ * Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
813
+ * <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
814
+ * @param {string} n testing number
815
+ * @returns {boolean} the query result
944
816
  */
945
- const ariaLabel = 'aria-label';
817
+ function isOnePointZero(n) {
818
+ return `${n}`.includes('.') && parseFloat(n) === 1;
819
+ }
946
820
 
947
821
  /**
948
- * A global namespace for aria-valuemin.
949
- * @type {string}
822
+ * Check to see if string passed in is a percentage
823
+ * @param {string} n testing number
824
+ * @returns {boolean} the query result
950
825
  */
951
- const ariaValueMin = 'aria-valuemin';
952
-
953
- /**
954
- * A global namespace for aria-valuemax.
955
- * @type {string}
956
- */
957
- const ariaValueMax = 'aria-valuemax';
958
-
959
- const tabIndex = 'tabindex';
960
-
961
- /**
962
- * Returns all color controls for `ColorPicker`.
963
- *
964
- * @param {CP.ColorPicker} self the `ColorPicker` instance
965
- * @returns {HTMLElement | Element} color controls
966
- */
967
- function getColorControls(self) {
968
- const { format, componentLabels } = self;
969
- const {
970
- hueLabel, alphaLabel, lightnessLabel, saturationLabel,
971
- whitenessLabel, blacknessLabel,
972
- } = componentLabels;
973
-
974
- const max1 = format === 'hsl' ? 360 : 100;
975
- const max2 = format === 'hsl' ? 100 : 360;
976
- const max3 = 100;
977
-
978
- let ctrl1Label = format === 'hsl'
979
- ? `${hueLabel} & ${lightnessLabel}`
980
- : `${lightnessLabel} & ${saturationLabel}`;
981
-
982
- ctrl1Label = format === 'hwb'
983
- ? `${whitenessLabel} & ${blacknessLabel}`
984
- : ctrl1Label;
985
-
986
- const ctrl2Label = format === 'hsl'
987
- ? `${saturationLabel}`
988
- : `${hueLabel}`;
989
-
990
- const colorControls = createElement({
991
- tagName: 'div',
992
- className: `color-controls ${format}`,
993
- });
994
-
995
- const colorPointer = 'color-pointer';
996
- const colorSlider = 'color-slider';
997
-
998
- const controls = [
999
- {
1000
- i: 1,
1001
- c: colorPointer,
1002
- l: ctrl1Label,
1003
- min: 0,
1004
- max: max1,
1005
- },
1006
- {
1007
- i: 2,
1008
- c: colorSlider,
1009
- l: ctrl2Label,
1010
- min: 0,
1011
- max: max2,
1012
- },
1013
- {
1014
- i: 3,
1015
- c: colorSlider,
1016
- l: alphaLabel,
1017
- min: 0,
1018
- max: max3,
1019
- },
1020
- ];
1021
-
1022
- controls.forEach((template) => {
1023
- const {
1024
- i, c, l, min, max,
1025
- } = template;
1026
- const control = createElement({
1027
- tagName: 'div',
1028
- className: 'color-control',
1029
- });
1030
- setAttribute(control, 'role', 'presentation');
1031
-
1032
- control.append(
1033
- createElement({
1034
- tagName: 'div',
1035
- className: `visual-control visual-control${i}`,
1036
- }),
1037
- );
1038
-
1039
- const knob = createElement({
1040
- tagName: 'div',
1041
- className: `${c} knob`,
1042
- ariaLive: 'polite',
1043
- });
1044
-
1045
- setAttribute(knob, ariaLabel, l);
1046
- setAttribute(knob, 'role', 'slider');
1047
- setAttribute(knob, tabIndex, '0');
1048
- setAttribute(knob, ariaValueMin, `${min}`);
1049
- setAttribute(knob, ariaValueMax, `${max}`);
1050
- control.append(knob);
1051
- colorControls.append(control);
1052
- });
1053
-
1054
- return colorControls;
1055
- }
1056
-
1057
- /**
1058
- * Helps setting CSS variables to the color-menu.
1059
- * @param {HTMLElement} element
1060
- * @param {Record<string,any>} props
1061
- */
1062
- function setCSSProperties(element, props) {
1063
- ObjectKeys(props).forEach((prop) => {
1064
- element.style.setProperty(prop, props[prop]);
1065
- });
1066
- }
1067
-
1068
- /**
1069
- * Returns the `document.head` or the `<head>` element.
1070
- *
1071
- * @param {(Node | HTMLElement | Element | globalThis)=} node
1072
- * @returns {HTMLElement | HTMLHeadElement}
1073
- */
1074
- function getDocumentHead(node) {
1075
- return getDocument(node).head;
1076
- }
1077
-
1078
- /**
1079
- * Round colour components, for all formats except HEX.
1080
- * @param {number} v one of the colour components
1081
- * @returns {number} the rounded number
1082
- */
1083
- function roundPart(v) {
1084
- const floor = Math.floor(v);
1085
- return v - floor < 0.5 ? floor : Math.round(v);
1086
- }
1087
-
1088
- // Color supported formats
1089
- const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
1090
-
1091
- // Hue angles
1092
- const ANGLES = 'deg|rad|grad|turn';
1093
-
1094
- // <http://www.w3.org/TR/css3-values/#integers>
1095
- const CSS_INTEGER = '[-\\+]?\\d+%?';
1096
-
1097
- // Include CSS3 Module
1098
- // <http://www.w3.org/TR/css3-values/#number-value>
1099
- const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
1100
-
1101
- // Include CSS4 Module Hue degrees unit
1102
- // <https://www.w3.org/TR/css3-values/#angle-value>
1103
- const CSS_ANGLE = `[-\\+]?\\d*\\.?\\d+(?:${ANGLES})?`;
1104
-
1105
- // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
1106
- const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
1107
-
1108
- // Add angles to the mix
1109
- const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
1110
-
1111
- // Actual matching.
1112
- // Parentheses and commas are optional, but not required.
1113
- // Whitespace can take the place of commas or opening paren
1114
- const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
1115
-
1116
- const matchers = {
1117
- CSS_UNIT: new RegExp(CSS_UNIT2),
1118
- hwb: new RegExp(`hwb${PERMISSIVE_MATCH}`),
1119
- rgb: new RegExp(`rgb(?:a)?${PERMISSIVE_MATCH}`),
1120
- hsl: new RegExp(`hsl(?:a)?${PERMISSIVE_MATCH}`),
1121
- hsv: new RegExp(`hsv(?:a)?${PERMISSIVE_MATCH}`),
1122
- hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
1123
- hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
1124
- hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
1125
- hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
1126
- };
1127
-
1128
- /**
1129
- * Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
1130
- * <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
1131
- * @param {string} n testing number
1132
- * @returns {boolean} the query result
1133
- */
1134
- function isOnePointZero(n) {
1135
- return `${n}`.includes('.') && parseFloat(n) === 1;
1136
- }
1137
-
1138
- /**
1139
- * Check to see if string passed in is a percentage
1140
- * @param {string} n testing number
1141
- * @returns {boolean} the query result
1142
- */
1143
- function isPercentage(n) {
1144
- return `${n}`.includes('%');
1145
- }
1146
-
1147
- /**
1148
- * Check to see if string passed in is an angle
1149
- * @param {string} n testing string
1150
- * @returns {boolean} the query result
1151
- */
1152
- function isAngle(n) {
1153
- return ANGLES.split('|').some((a) => `${n}`.includes(a));
1154
- }
826
+ function isPercentage(n) {
827
+ return `${n}`.includes('%');
828
+ }
1155
829
 
1156
830
  /**
1157
831
  * Check to see if string passed is a web safe colour.
832
+ * @see https://stackoverflow.com/a/16994164
1158
833
  * @param {string} color a colour name, EG: *red*
1159
834
  * @returns {boolean} the query result
1160
835
  */
1161
836
  function isColorName(color) {
1162
- return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
1163
- && !/[0-9]/.test(color);
837
+ if (nonColors.includes(color)
838
+ || ['#', ...COLOR_FORMAT].some((f) => color.includes(f))) return false;
839
+
840
+ if (['black', 'white'].includes(color)) return true;
841
+
842
+ return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
843
+ setElementStyle(documentHead, { color });
844
+ const computedColor = getElementStyle(documentHead, 'color');
845
+ setElementStyle(documentHead, { color: '' });
846
+ return computedColor !== c;
847
+ });
1164
848
  }
1165
849
 
1166
850
  /**
@@ -1181,15 +865,20 @@ function isValidCSSUnit(color) {
1181
865
  */
1182
866
  function bound01(N, max) {
1183
867
  let n = N;
1184
- if (isOnePointZero(n)) n = '100%';
1185
868
 
1186
- n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
869
+ if (typeof N === 'number'
870
+ && Math.min(N, 0) === 0 // round values to 6 decimals Math.round(N * (10 ** 6)) / 10 ** 6
871
+ && Math.max(N, 1) === 1) return N;
872
+
873
+ if (isOnePointZero(N)) n = '100%';
1187
874
 
1188
- // Handle hue angles
1189
- if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
875
+ const processPercent = isPercentage(n);
876
+ n = max === 360
877
+ ? parseFloat(n)
878
+ : Math.min(max, Math.max(0, parseFloat(n)));
1190
879
 
1191
880
  // Automatically convert percentage into number
1192
- if (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
881
+ if (processPercent) n = (n * max) / 100;
1193
882
 
1194
883
  // Handle floating point rounding errors
1195
884
  if (Math.abs(n - max) < 0.000001) {
@@ -1200,11 +889,11 @@ function bound01(N, max) {
1200
889
  // If n is a hue given in degrees,
1201
890
  // wrap around out-of-range values into [0, 360] range
1202
891
  // then convert into [0, 1].
1203
- n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max));
892
+ n = (n < 0 ? (n % max) + max : n % max) / max;
1204
893
  } else {
1205
894
  // If n not a hue given in degrees
1206
895
  // Convert into [0, 1] range if it isn't already.
1207
- n = (n % max) / parseFloat(String(max));
896
+ n = (n % max) / max;
1208
897
  }
1209
898
  return n;
1210
899
  }
@@ -1239,7 +928,6 @@ function clamp01(v) {
1239
928
  * @returns {string}
1240
929
  */
1241
930
  function getRGBFromName(name) {
1242
- const documentHead = getDocumentHead();
1243
931
  setElementStyle(documentHead, { color: name });
1244
932
  const colorName = getElementStyle(documentHead, 'color');
1245
933
  setElementStyle(documentHead, { color: '' });
@@ -1285,15 +973,12 @@ function pad2(c) {
1285
973
  /**
1286
974
  * Converts an RGB colour value to HSL.
1287
975
  *
1288
- * @param {number} R Red component [0, 255]
1289
- * @param {number} G Green component [0, 255]
1290
- * @param {number} B Blue component [0, 255]
976
+ * @param {number} r Red component [0, 1]
977
+ * @param {number} g Green component [0, 1]
978
+ * @param {number} b Blue component [0, 1]
1291
979
  * @returns {CP.HSL} {h,s,l} object with [0, 1] ranged values
1292
980
  */
1293
- function rgbToHsl(R, G, B) {
1294
- const r = R / 255;
1295
- const g = G / 255;
1296
- const b = B / 255;
981
+ function rgbToHsl(r, g, b) {
1297
982
  const max = Math.max(r, g, b);
1298
983
  const min = Math.min(r, g, b);
1299
984
  let h = 0;
@@ -1305,17 +990,10 @@ function rgbToHsl(R, G, B) {
1305
990
  } else {
1306
991
  const d = max - min;
1307
992
  s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
1308
- switch (max) {
1309
- case r:
1310
- h = (g - b) / d + (g < b ? 6 : 0);
1311
- break;
1312
- case g:
1313
- h = (b - r) / d + 2;
1314
- break;
1315
- case b:
1316
- h = (r - g) / d + 4;
1317
- break;
1318
- }
993
+ if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
994
+ if (max === g) h = (b - r) / d + 2;
995
+ if (max === b) h = (r - g) / d + 4;
996
+
1319
997
  h /= 6;
1320
998
  }
1321
999
  return { h, s, l };
@@ -1338,21 +1016,46 @@ function hueToRgb(p, q, t) {
1338
1016
  return p;
1339
1017
  }
1340
1018
 
1019
+ /**
1020
+ * Converts an HSL colour value to RGB.
1021
+ *
1022
+ * @param {number} h Hue Angle [0, 1]
1023
+ * @param {number} s Saturation [0, 1]
1024
+ * @param {number} l Lightness Angle [0, 1]
1025
+ * @returns {CP.RGB} {r,g,b} object with [0, 1] ranged values
1026
+ */
1027
+ function hslToRgb(h, s, l) {
1028
+ let r = 0;
1029
+ let g = 0;
1030
+ let b = 0;
1031
+
1032
+ if (s === 0) {
1033
+ // achromatic
1034
+ g = l;
1035
+ b = l;
1036
+ r = l;
1037
+ } else {
1038
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
1039
+ const p = 2 * l - q;
1040
+ r = hueToRgb(p, q, h + 1 / 3);
1041
+ g = hueToRgb(p, q, h);
1042
+ b = hueToRgb(p, q, h - 1 / 3);
1043
+ }
1044
+
1045
+ return { r, g, b };
1046
+ }
1047
+
1341
1048
  /**
1342
1049
  * Returns an HWB colour object from an RGB colour object.
1343
1050
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
1344
1051
  * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
1345
1052
  *
1346
- * @param {number} R Red component [0, 255]
1347
- * @param {number} G Green [0, 255]
1348
- * @param {number} B Blue [0, 255]
1053
+ * @param {number} r Red component [0, 1]
1054
+ * @param {number} g Green [0, 1]
1055
+ * @param {number} b Blue [0, 1]
1349
1056
  * @return {CP.HWB} {h,w,b} object with [0, 1] ranged values
1350
1057
  */
1351
- function rgbToHwb(R, G, B) {
1352
- const r = R / 255;
1353
- const g = G / 255;
1354
- const b = B / 255;
1355
-
1058
+ function rgbToHwb(r, g, b) {
1356
1059
  let f = 0;
1357
1060
  let i = 0;
1358
1061
  const whiteness = Math.min(r, g, b);
@@ -1382,50 +1085,18 @@ function rgbToHwb(R, G, B) {
1382
1085
  * @param {number} H Hue Angle [0, 1]
1383
1086
  * @param {number} W Whiteness [0, 1]
1384
1087
  * @param {number} B Blackness [0, 1]
1385
- * @return {CP.RGB} {r,g,b} object with [0, 255] ranged values
1088
+ * @return {CP.RGB} {r,g,b} object with [0, 1] ranged values
1386
1089
  *
1387
1090
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
1388
1091
  * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
1389
1092
  */
1390
1093
  function hwbToRgb(H, W, B) {
1391
1094
  if (W + B >= 1) {
1392
- const gray = (W / (W + B)) * 255;
1095
+ const gray = W / (W + B);
1393
1096
  return { r: gray, g: gray, b: gray };
1394
1097
  }
1395
1098
  let { r, g, b } = hslToRgb(H, 1, 0.5);
1396
- [r, g, b] = [r, g, b]
1397
- .map((v) => (v / 255) * (1 - W - B) + W)
1398
- .map((v) => v * 255);
1399
-
1400
- return { r, g, b };
1401
- }
1402
-
1403
- /**
1404
- * Converts an HSL colour value to RGB.
1405
- *
1406
- * @param {number} h Hue Angle [0, 1]
1407
- * @param {number} s Saturation [0, 1]
1408
- * @param {number} l Lightness Angle [0, 1]
1409
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
1410
- */
1411
- function hslToRgb(h, s, l) {
1412
- let r = 0;
1413
- let g = 0;
1414
- let b = 0;
1415
-
1416
- if (s === 0) {
1417
- // achromatic
1418
- g = l;
1419
- b = l;
1420
- r = l;
1421
- } else {
1422
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
1423
- const p = 2 * l - q;
1424
- r = hueToRgb(p, q, h + 1 / 3);
1425
- g = hueToRgb(p, q, h);
1426
- b = hueToRgb(p, q, h - 1 / 3);
1427
- }
1428
- [r, g, b] = [r, g, b].map((x) => x * 255);
1099
+ [r, g, b] = [r, g, b].map((v) => v * (1 - W - B) + W);
1429
1100
 
1430
1101
  return { r, g, b };
1431
1102
  }
@@ -1433,15 +1104,12 @@ function hslToRgb(h, s, l) {
1433
1104
  /**
1434
1105
  * Converts an RGB colour value to HSV.
1435
1106
  *
1436
- * @param {number} R Red component [0, 255]
1437
- * @param {number} G Green [0, 255]
1438
- * @param {number} B Blue [0, 255]
1107
+ * @param {number} r Red component [0, 1]
1108
+ * @param {number} g Green [0, 1]
1109
+ * @param {number} b Blue [0, 1]
1439
1110
  * @returns {CP.HSV} {h,s,v} object with [0, 1] ranged values
1440
1111
  */
1441
- function rgbToHsv(R, G, B) {
1442
- const r = R / 255;
1443
- const g = G / 255;
1444
- const b = B / 255;
1112
+ function rgbToHsv(r, g, b) {
1445
1113
  const max = Math.max(r, g, b);
1446
1114
  const min = Math.min(r, g, b);
1447
1115
  let h = 0;
@@ -1451,17 +1119,10 @@ function rgbToHsv(R, G, B) {
1451
1119
  if (max === min) {
1452
1120
  h = 0; // achromatic
1453
1121
  } else {
1454
- switch (max) {
1455
- case r:
1456
- h = (g - b) / d + (g < b ? 6 : 0);
1457
- break;
1458
- case g:
1459
- h = (b - r) / d + 2;
1460
- break;
1461
- case b:
1462
- h = (r - g) / d + 4;
1463
- break;
1464
- }
1122
+ if (r === max) h = (g - b) / d + (g < b ? 6 : 0);
1123
+ if (g === max) h = (b - r) / d + 2;
1124
+ if (b === max) h = (r - g) / d + 4;
1125
+
1465
1126
  h /= 6;
1466
1127
  }
1467
1128
  return { h, s, v };
@@ -1488,7 +1149,7 @@ function hsvToRgb(H, S, V) {
1488
1149
  const r = [v, q, p, p, t, v][mod];
1489
1150
  const g = [t, v, v, q, p, p][mod];
1490
1151
  const b = [p, p, t, v, v, q][mod];
1491
- return { r: r * 255, g: g * 255, b: b * 255 };
1152
+ return { r, g, b };
1492
1153
  }
1493
1154
 
1494
1155
  /**
@@ -1512,7 +1173,7 @@ function rgbToHex(r, g, b, allow3Char) {
1512
1173
  // Return a 3 character hex if possible
1513
1174
  if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
1514
1175
  && hex[1].charAt(0) === hex[1].charAt(1)
1515
- && hex[2].charAt(0) === hex[2].charAt(1)) {
1176
+ && hex[2].charAt(0) === hex[2].charAt(1)) {
1516
1177
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
1517
1178
  }
1518
1179
 
@@ -1540,51 +1201,34 @@ function rgbaToHex(r, g, b, a, allow4Char) {
1540
1201
  // Return a 4 character hex if possible
1541
1202
  if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
1542
1203
  && hex[1].charAt(0) === hex[1].charAt(1)
1543
- && hex[2].charAt(0) === hex[2].charAt(1)
1544
- && hex[3].charAt(0) === hex[3].charAt(1)) {
1204
+ && hex[2].charAt(0) === hex[2].charAt(1)
1205
+ && hex[3].charAt(0) === hex[3].charAt(1)) {
1545
1206
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
1546
1207
  }
1547
1208
  return hex.join('');
1548
1209
  }
1549
1210
 
1550
- /**
1551
- * Returns a colour object corresponding to a given number.
1552
- * @param {number} color input number
1553
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
1554
- */
1555
- function numberInputToObject(color) {
1556
- /* eslint-disable no-bitwise */
1557
- return {
1558
- r: color >> 16,
1559
- g: (color & 0xff00) >> 8,
1560
- b: color & 0xff,
1561
- };
1562
- /* eslint-enable no-bitwise */
1563
- }
1564
-
1565
1211
  /**
1566
1212
  * Permissive string parsing. Take in a number of formats, and output an object
1567
1213
  * based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
1568
1214
  * @param {string} input colour value in any format
1569
- * @returns {Record<string, (number | string)> | false} an object matching the RegExp
1215
+ * @returns {Record<string, (number | string | boolean)> | false} an object matching the RegExp
1570
1216
  */
1571
1217
  function stringInputToObject(input) {
1572
- let color = input.trim().toLowerCase();
1218
+ let color = toLowerCase(input.trim());
1219
+
1573
1220
  if (color.length === 0) {
1574
1221
  return {
1575
- r: 0, g: 0, b: 0, a: 0,
1222
+ r: 0, g: 0, b: 0, a: 1,
1576
1223
  };
1577
1224
  }
1578
- let named = false;
1225
+
1579
1226
  if (isColorName(color)) {
1580
1227
  color = getRGBFromName(color);
1581
- named = true;
1582
1228
  } else if (nonColors.includes(color)) {
1583
- const isTransparent = color === 'transparent';
1584
- const rgb = isTransparent ? 0 : 255;
1585
- const a = isTransparent ? 0 : 1;
1229
+ const a = color === 'transparent' ? 0 : 1;
1586
1230
  return {
1587
- r: rgb, g: rgb, b: rgb, a, format: 'rgb',
1231
+ r: 0, g: 0, b: 0, a, format: 'rgb', ok: true,
1588
1232
  };
1589
1233
  }
1590
1234
 
@@ -1599,24 +1243,28 @@ function stringInputToObject(input) {
1599
1243
  r: m1, g: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'rgb',
1600
1244
  };
1601
1245
  }
1246
+
1602
1247
  [, m1, m2, m3, m4] = matchers.hsl.exec(color) || [];
1603
1248
  if (m1 && m2 && m3/* && m4 */) {
1604
1249
  return {
1605
1250
  h: m1, s: m2, l: m3, a: m4 !== undefined ? m4 : 1, format: 'hsl',
1606
1251
  };
1607
1252
  }
1253
+
1608
1254
  [, m1, m2, m3, m4] = matchers.hsv.exec(color) || [];
1609
1255
  if (m1 && m2 && m3/* && m4 */) {
1610
1256
  return {
1611
1257
  h: m1, s: m2, v: m3, a: m4 !== undefined ? m4 : 1, format: 'hsv',
1612
1258
  };
1613
1259
  }
1260
+
1614
1261
  [, m1, m2, m3, m4] = matchers.hwb.exec(color) || [];
1615
1262
  if (m1 && m2 && m3) {
1616
1263
  return {
1617
1264
  h: m1, w: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'hwb',
1618
1265
  };
1619
1266
  }
1267
+
1620
1268
  [, m1, m2, m3, m4] = matchers.hex8.exec(color) || [];
1621
1269
  if (m1 && m2 && m3 && m4) {
1622
1270
  return {
@@ -1624,19 +1272,20 @@ function stringInputToObject(input) {
1624
1272
  g: parseIntFromHex(m2),
1625
1273
  b: parseIntFromHex(m3),
1626
1274
  a: convertHexToDecimal(m4),
1627
- // format: named ? 'rgb' : 'hex8',
1628
- format: named ? 'rgb' : 'hex',
1275
+ format: 'hex',
1629
1276
  };
1630
1277
  }
1278
+
1631
1279
  [, m1, m2, m3] = matchers.hex6.exec(color) || [];
1632
1280
  if (m1 && m2 && m3) {
1633
1281
  return {
1634
1282
  r: parseIntFromHex(m1),
1635
1283
  g: parseIntFromHex(m2),
1636
1284
  b: parseIntFromHex(m3),
1637
- format: named ? 'rgb' : 'hex',
1285
+ format: 'hex',
1638
1286
  };
1639
1287
  }
1288
+
1640
1289
  [, m1, m2, m3, m4] = matchers.hex4.exec(color) || [];
1641
1290
  if (m1 && m2 && m3 && m4) {
1642
1291
  return {
@@ -1644,19 +1293,20 @@ function stringInputToObject(input) {
1644
1293
  g: parseIntFromHex(m2 + m2),
1645
1294
  b: parseIntFromHex(m3 + m3),
1646
1295
  a: convertHexToDecimal(m4 + m4),
1647
- // format: named ? 'rgb' : 'hex8',
1648
- format: named ? 'rgb' : 'hex',
1296
+ format: 'hex',
1649
1297
  };
1650
1298
  }
1299
+
1651
1300
  [, m1, m2, m3] = matchers.hex3.exec(color) || [];
1652
1301
  if (m1 && m2 && m3) {
1653
1302
  return {
1654
1303
  r: parseIntFromHex(m1 + m1),
1655
1304
  g: parseIntFromHex(m2 + m2),
1656
1305
  b: parseIntFromHex(m3 + m3),
1657
- format: named ? 'rgb' : 'hex',
1306
+ format: 'hex',
1658
1307
  };
1659
1308
  }
1309
+
1660
1310
  return false;
1661
1311
  }
1662
1312
 
@@ -1687,7 +1337,9 @@ function stringInputToObject(input) {
1687
1337
  */
1688
1338
  function inputToRGB(input) {
1689
1339
  let rgb = { r: 0, g: 0, b: 0 };
1340
+ /** @type {*} */
1690
1341
  let color = input;
1342
+ /** @type {string | number} */
1691
1343
  let a = 1;
1692
1344
  let s = null;
1693
1345
  let v = null;
@@ -1698,58 +1350,67 @@ function inputToRGB(input) {
1698
1350
  let r = null;
1699
1351
  let g = null;
1700
1352
  let ok = false;
1701
- let format = 'hex';
1353
+ const inputFormat = typeof color === 'object' && color.format;
1354
+ let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
1702
1355
 
1703
1356
  if (typeof input === 'string') {
1704
- // @ts-ignore -- this now is converted to object
1705
1357
  color = stringInputToObject(input);
1706
1358
  if (color) ok = true;
1707
1359
  }
1708
1360
  if (typeof color === 'object') {
1709
1361
  if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
1710
1362
  ({ r, g, b } = color);
1711
- [r, g, b] = [...[r, g, b]]
1712
- .map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255).map(roundPart);
1713
- rgb = { r, g, b }; // RGB values now are all in [0, 255] range
1363
+ // RGB values now are all in [0, 1] range
1364
+ [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255));
1365
+ rgb = { r, g, b };
1714
1366
  ok = true;
1715
- format = 'rgb';
1716
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
1367
+ format = color.format || 'rgb';
1368
+ }
1369
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
1717
1370
  ({ h, s, v } = color);
1718
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
1719
- s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
1720
- v = typeof v === 'number' ? v : bound01(v, 100); // brightness can be `5%` or a [0, 1] value
1371
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
1372
+ s = bound01(s, 100); // saturation can be `5%` or a [0, 1] value
1373
+ v = bound01(v, 100); // brightness can be `5%` or a [0, 1] value
1721
1374
  rgb = hsvToRgb(h, s, v);
1722
1375
  ok = true;
1723
1376
  format = 'hsv';
1724
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
1377
+ }
1378
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
1725
1379
  ({ h, s, l } = color);
1726
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
1727
- s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
1728
- l = typeof l === 'number' ? l : bound01(l, 100); // lightness can be `5%` or a [0, 1] value
1380
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
1381
+ s = bound01(s, 100); // saturation can be `5%` or a [0, 1] value
1382
+ l = bound01(l, 100); // lightness can be `5%` or a [0, 1] value
1729
1383
  rgb = hslToRgb(h, s, l);
1730
1384
  ok = true;
1731
1385
  format = 'hsl';
1732
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
1386
+ }
1387
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
1733
1388
  ({ h, w, b } = color);
1734
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
1735
- w = typeof w === 'number' ? w : bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
1736
- b = typeof b === 'number' ? b : bound01(b, 100); // blackness can be `5%` or a [0, 1] value
1389
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
1390
+ w = bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
1391
+ b = bound01(b, 100); // blackness can be `5%` or a [0, 1] value
1737
1392
  rgb = hwbToRgb(h, w, b);
1738
1393
  ok = true;
1739
1394
  format = 'hwb';
1740
1395
  }
1741
1396
  if (isValidCSSUnit(color.a)) {
1742
- a = color.a;
1743
- a = isPercentage(`${a}`) ? bound01(a, 100) : a;
1397
+ a = color.a; // @ts-ignore -- `parseFloat` works with numbers too
1398
+ a = isPercentage(`${a}`) || parseFloat(a) > 1 ? bound01(a, 100) : a;
1744
1399
  }
1745
1400
  }
1401
+ if (typeof color === 'undefined') {
1402
+ ok = true;
1403
+ }
1746
1404
 
1747
1405
  return {
1748
- ok, // @ts-ignore
1749
- format: color.format || format,
1750
- r: Math.min(255, Math.max(rgb.r, 0)),
1751
- g: Math.min(255, Math.max(rgb.g, 0)),
1752
- b: Math.min(255, Math.max(rgb.b, 0)),
1406
+ ok,
1407
+ format,
1408
+ // r: Math.min(255, Math.max(rgb.r, 0)),
1409
+ // g: Math.min(255, Math.max(rgb.g, 0)),
1410
+ // b: Math.min(255, Math.max(rgb.b, 0)),
1411
+ r: rgb.r,
1412
+ g: rgb.g,
1413
+ b: rgb.b,
1753
1414
  a: boundAlpha(a),
1754
1415
  };
1755
1416
  }
@@ -1768,15 +1429,13 @@ class Color {
1768
1429
  constructor(input, config) {
1769
1430
  let color = input;
1770
1431
  const configFormat = config && COLOR_FORMAT.includes(config)
1771
- ? config : 'rgb';
1432
+ ? config : '';
1772
1433
 
1773
- // If input is already a `Color`, return itself
1434
+ // If input is already a `Color`, clone its values
1774
1435
  if (color instanceof Color) {
1775
1436
  color = inputToRGB(color);
1776
1437
  }
1777
- if (typeof color === 'number') {
1778
- color = numberInputToObject(color);
1779
- }
1438
+
1780
1439
  const {
1781
1440
  r, g, b, a, ok, format,
1782
1441
  } = inputToRGB(color);
@@ -1785,7 +1444,7 @@ class Color {
1785
1444
  const self = this;
1786
1445
 
1787
1446
  /** @type {CP.ColorInput} */
1788
- self.originalInput = color;
1447
+ self.originalInput = input;
1789
1448
  /** @type {number} */
1790
1449
  self.r = r;
1791
1450
  /** @type {number} */
@@ -1826,24 +1485,21 @@ class Color {
1826
1485
  let R = 0;
1827
1486
  let G = 0;
1828
1487
  let B = 0;
1829
- const rp = r / 255;
1830
- const rg = g / 255;
1831
- const rb = b / 255;
1832
1488
 
1833
- if (rp <= 0.03928) {
1834
- R = rp / 12.92;
1489
+ if (r <= 0.03928) {
1490
+ R = r / 12.92;
1835
1491
  } else {
1836
- R = ((rp + 0.055) / 1.055) ** 2.4;
1492
+ R = ((r + 0.055) / 1.055) ** 2.4;
1837
1493
  }
1838
- if (rg <= 0.03928) {
1839
- G = rg / 12.92;
1494
+ if (g <= 0.03928) {
1495
+ G = g / 12.92;
1840
1496
  } else {
1841
- G = ((rg + 0.055) / 1.055) ** 2.4;
1497
+ G = ((g + 0.055) / 1.055) ** 2.4;
1842
1498
  }
1843
- if (rb <= 0.03928) {
1844
- B = rb / 12.92;
1499
+ if (b <= 0.03928) {
1500
+ B = b / 12.92;
1845
1501
  } else {
1846
- B = ((rb + 0.055) / 1.055) ** 2.4;
1502
+ B = ((b + 0.055) / 1.055) ** 2.4;
1847
1503
  }
1848
1504
  return 0.2126 * R + 0.7152 * G + 0.0722 * B;
1849
1505
  }
@@ -1853,7 +1509,7 @@ class Color {
1853
1509
  * @returns {number} a number in the [0, 255] range
1854
1510
  */
1855
1511
  get brightness() {
1856
- const { r, g, b } = this;
1512
+ const { r, g, b } = this.toRgb();
1857
1513
  return (r * 299 + g * 587 + b * 114) / 1000;
1858
1514
  }
1859
1515
 
@@ -1862,16 +1518,14 @@ class Color {
1862
1518
  * @returns {CP.RGBA} an {r,g,b,a} object with [0, 255] ranged values
1863
1519
  */
1864
1520
  toRgb() {
1865
- const {
1521
+ let {
1866
1522
  r, g, b, a,
1867
1523
  } = this;
1868
- const [R, G, B] = [r, g, b].map((x) => roundPart(x));
1869
1524
 
1525
+ [r, g, b] = [r, g, b].map((n) => roundPart(n * 255 * 100) / 100);
1526
+ a = roundPart(a * 100) / 100;
1870
1527
  return {
1871
- r: R,
1872
- g: G,
1873
- b: B,
1874
- a: roundPart(a * 100) / 100,
1528
+ r, g, b, a,
1875
1529
  };
1876
1530
  }
1877
1531
 
@@ -1885,10 +1539,11 @@ class Color {
1885
1539
  const {
1886
1540
  r, g, b, a,
1887
1541
  } = this.toRgb();
1542
+ const [R, G, B] = [r, g, b].map(roundPart);
1888
1543
 
1889
1544
  return a === 1
1890
- ? `rgb(${r}, ${g}, ${b})`
1891
- : `rgba(${r}, ${g}, ${b}, ${a})`;
1545
+ ? `rgb(${R}, ${G}, ${B})`
1546
+ : `rgba(${R}, ${G}, ${B}, ${a})`;
1892
1547
  }
1893
1548
 
1894
1549
  /**
@@ -1901,9 +1556,10 @@ class Color {
1901
1556
  const {
1902
1557
  r, g, b, a,
1903
1558
  } = this.toRgb();
1559
+ const [R, G, B] = [r, g, b].map(roundPart);
1904
1560
  const A = a === 1 ? '' : ` / ${roundPart(a * 100)}%`;
1905
1561
 
1906
- return `rgb(${r} ${g} ${b}${A})`;
1562
+ return `rgb(${R} ${G} ${B}${A})`;
1907
1563
  }
1908
1564
 
1909
1565
  /**
@@ -1963,7 +1619,7 @@ class Color {
1963
1619
  toHsv() {
1964
1620
  const {
1965
1621
  r, g, b, a,
1966
- } = this.toRgb();
1622
+ } = this;
1967
1623
  const { h, s, v } = rgbToHsv(r, g, b);
1968
1624
 
1969
1625
  return {
@@ -1978,7 +1634,7 @@ class Color {
1978
1634
  toHsl() {
1979
1635
  const {
1980
1636
  r, g, b, a,
1981
- } = this.toRgb();
1637
+ } = this;
1982
1638
  const { h, s, l } = rgbToHsl(r, g, b);
1983
1639
 
1984
1640
  return {
@@ -2063,6 +1719,7 @@ class Color {
2063
1719
  */
2064
1720
  setAlpha(alpha) {
2065
1721
  const self = this;
1722
+ if (typeof alpha !== 'number') return self;
2066
1723
  self.a = boundAlpha(alpha);
2067
1724
  return self;
2068
1725
  }
@@ -2177,6 +1834,7 @@ ObjectAssign(Color, {
2177
1834
  isOnePointZero,
2178
1835
  isPercentage,
2179
1836
  isValidCSSUnit,
1837
+ isColorName,
2180
1838
  pad2,
2181
1839
  clamp01,
2182
1840
  bound01,
@@ -2194,10 +1852,11 @@ ObjectAssign(Color, {
2194
1852
  hueToRgb,
2195
1853
  hwbToRgb,
2196
1854
  parseIntFromHex,
2197
- numberInputToObject,
2198
1855
  stringInputToObject,
2199
1856
  inputToRGB,
2200
1857
  roundPart,
1858
+ getElementStyle,
1859
+ setElementStyle,
2201
1860
  ObjectAssign,
2202
1861
  });
2203
1862
 
@@ -2206,7 +1865,7 @@ ObjectAssign(Color, {
2206
1865
  * Returns a color palette with a given set of parameters.
2207
1866
  * @example
2208
1867
  * new ColorPalette(0, 12, 10);
2209
- * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
1868
+ * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: Array<Color> }
2210
1869
  */
2211
1870
  class ColorPalette {
2212
1871
  /**
@@ -2226,48 +1885,352 @@ class ColorPalette {
2226
1885
  [hue, hueSteps, lightSteps] = args;
2227
1886
  } else if (args.length === 2) {
2228
1887
  [hueSteps, lightSteps] = args;
2229
- } else {
2230
- throw TypeError('ColorPalette requires minimum 2 arguments');
1888
+ if ([hueSteps, lightSteps].some((n) => n < 1)) {
1889
+ throw TypeError('ColorPalette: both arguments must be higher than 0.');
1890
+ }
2231
1891
  }
2232
1892
 
2233
- /** @type {string[]} */
1893
+ /** @type {*} */
2234
1894
  const colors = [];
2235
-
2236
1895
  const hueStep = 360 / hueSteps;
2237
1896
  const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
2238
- const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
1897
+ const steps1To13 = [0.25, 0.2, 0.15, 0.11, 0.09, 0.075];
1898
+ const lightSets = [[1, 2, 3], [4, 5], [6, 7], [8, 9], [10, 11], [12, 13]];
1899
+ const closestSet = lightSets.find((set) => set.includes(lightSteps));
1900
+
1901
+ // find a lightStep that won't go beyond black and white
1902
+ // something within the [10-90] range of lightness
1903
+ const lightStep = closestSet
1904
+ ? steps1To13[lightSets.indexOf(closestSet)]
1905
+ : (100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100);
1906
+
1907
+ // light tints
1908
+ for (let i = 1; i < half + 1; i += 1) {
1909
+ lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
1910
+ }
1911
+
1912
+ // dark tints
1913
+ for (let i = 1; i < lightSteps - half; i += 1) {
1914
+ lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
1915
+ }
1916
+
1917
+ // feed `colors` Array
1918
+ for (let i = 0; i < hueSteps; i += 1) {
1919
+ const currentHue = ((hue + i * hueStep) % 360) / 360;
1920
+ lightnessArray.forEach((l) => {
1921
+ colors.push(new Color({ h: currentHue, s: 1, l }));
1922
+ });
1923
+ }
1924
+
1925
+ this.hue = hue;
1926
+ this.hueSteps = hueSteps;
1927
+ this.lightSteps = lightSteps;
1928
+ this.colors = colors;
1929
+ }
1930
+ }
1931
+
1932
+ ObjectAssign(ColorPalette, { Color });
1933
+
1934
+ /** @type {Record<string, string>} */
1935
+ const colorPickerLabels = {
1936
+ pickerLabel: 'Colour Picker',
1937
+ appearanceLabel: 'Colour Appearance',
1938
+ valueLabel: 'Colour Value',
1939
+ toggleLabel: 'Select Colour',
1940
+ presetsLabel: 'Colour Presets',
1941
+ defaultsLabel: 'Colour Defaults',
1942
+ formatLabel: 'Format',
1943
+ alphaLabel: 'Alpha',
1944
+ hexLabel: 'Hexadecimal',
1945
+ hueLabel: 'Hue',
1946
+ whitenessLabel: 'Whiteness',
1947
+ blacknessLabel: 'Blackness',
1948
+ saturationLabel: 'Saturation',
1949
+ lightnessLabel: 'Lightness',
1950
+ redLabel: 'Red',
1951
+ greenLabel: 'Green',
1952
+ blueLabel: 'Blue',
1953
+ };
1954
+
1955
+ /**
1956
+ * A list of 17 color names used for WAI-ARIA compliance.
1957
+ * @type {string[]}
1958
+ */
1959
+ const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
1960
+
1961
+ const tabIndex = 'tabindex';
1962
+
1963
+ /**
1964
+ * Check if a string is valid JSON string.
1965
+ * @param {string} str the string input
1966
+ * @returns {boolean} the query result
1967
+ */
1968
+ function isValidJSON(str) {
1969
+ try {
1970
+ JSON.parse(str);
1971
+ } catch (e) {
1972
+ return false;
1973
+ }
1974
+ return true;
1975
+ }
1976
+
1977
+ /**
1978
+ * Shortcut for `String.toUpperCase()`.
1979
+ *
1980
+ * @param {string} source input string
1981
+ * @returns {string} uppercase output string
1982
+ */
1983
+ const toUpperCase = (source) => source.toUpperCase();
1984
+
1985
+ /**
1986
+ * A global namespace for aria-haspopup.
1987
+ * @type {string}
1988
+ */
1989
+ const ariaHasPopup = 'aria-haspopup';
1990
+
1991
+ /**
1992
+ * A global namespace for aria-hidden.
1993
+ * @type {string}
1994
+ */
1995
+ const ariaHidden = 'aria-hidden';
1996
+
1997
+ /**
1998
+ * A global namespace for aria-labelledby.
1999
+ * @type {string}
2000
+ */
2001
+ const ariaLabelledBy = 'aria-labelledby';
2002
+
2003
+ /**
2004
+ * This is a shortie for `document.createElement` method
2005
+ * which allows you to create a new `HTMLElement` for a given `tagName`
2006
+ * or based on an object with specific non-readonly attributes:
2007
+ * `id`, `className`, `textContent`, `style`, etc.
2008
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
2009
+ *
2010
+ * @param {Record<string, string> | string} param `tagName` or object
2011
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
2012
+ */
2013
+ function createElement(param) {
2014
+ if (typeof param === 'string') {
2015
+ return getDocument().createElement(param);
2016
+ }
2017
+
2018
+ const { tagName } = param;
2019
+ const attr = { ...param };
2020
+ const newElement = createElement(tagName);
2021
+ delete attr.tagName;
2022
+ ObjectAssign(newElement, attr);
2023
+ return newElement;
2024
+ }
2025
+
2026
+ /**
2027
+ * This is a shortie for `document.createElementNS` method
2028
+ * which allows you to create a new `HTMLElement` for a given `tagName`
2029
+ * or based on an object with specific non-readonly attributes:
2030
+ * `id`, `className`, `textContent`, `style`, etc.
2031
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
2032
+ *
2033
+ * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
2034
+ * @param {Record<string, string> | string} param `tagName` or object
2035
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
2036
+ */
2037
+ function createElementNS(namespace, param) {
2038
+ if (typeof param === 'string') {
2039
+ return getDocument().createElementNS(namespace, param);
2040
+ }
2041
+
2042
+ const { tagName } = param;
2043
+ const attr = { ...param };
2044
+ const newElement = createElementNS(namespace, tagName);
2045
+ delete attr.tagName;
2046
+ ObjectAssign(newElement, attr);
2047
+ return newElement;
2048
+ }
2049
+
2050
+ const vHidden = 'v-hidden';
2051
+
2052
+ /**
2053
+ * Returns the color form for `ColorPicker`.
2054
+ *
2055
+ * @param {CP.ColorPicker} self the `ColorPicker` instance
2056
+ * @returns {HTMLElement | Element} a new `<div>` element with color component `<input>`
2057
+ */
2058
+ function getColorForm(self) {
2059
+ const { format, id, componentLabels } = self;
2060
+ const colorForm = createElement({
2061
+ tagName: 'div',
2062
+ className: `color-form ${format}`,
2063
+ });
2064
+
2065
+ let components = ['hex'];
2066
+ if (format === 'rgb') components = ['red', 'green', 'blue', 'alpha'];
2067
+ else if (format === 'hsl') components = ['hue', 'saturation', 'lightness', 'alpha'];
2068
+ else if (format === 'hwb') components = ['hue', 'whiteness', 'blackness', 'alpha'];
2069
+
2070
+ components.forEach((c) => {
2071
+ const [C] = format === 'hex' ? ['#'] : toUpperCase(c).split('');
2072
+ const cID = `color_${format}_${c}_${id}`;
2073
+ const formatLabel = componentLabels[`${c}Label`];
2074
+ const cInputLabel = createElement({ tagName: 'label' });
2075
+ setAttribute(cInputLabel, 'for', cID);
2076
+ cInputLabel.append(
2077
+ createElement({ tagName: 'span', ariaHidden: 'true', innerText: `${C}:` }),
2078
+ createElement({ tagName: 'span', className: vHidden, innerText: formatLabel }),
2079
+ );
2080
+ const cInput = createElement({
2081
+ tagName: 'input',
2082
+ id: cID,
2083
+ // name: cID, - prevent saving the value to a form
2084
+ type: format === 'hex' ? 'text' : 'number',
2085
+ value: c === 'alpha' ? '100' : '0',
2086
+ className: `color-input ${c}`,
2087
+ });
2088
+ setAttribute(cInput, 'autocomplete', 'off');
2089
+ setAttribute(cInput, 'spellcheck', 'false');
2090
+
2091
+ // alpha
2092
+ let max = '100';
2093
+ let step = '1';
2094
+ if (c !== 'alpha') {
2095
+ if (format === 'rgb') {
2096
+ max = '255'; step = '1';
2097
+ } else if (c === 'hue') {
2098
+ max = '360'; step = '1';
2099
+ }
2100
+ }
2101
+ ObjectAssign(cInput, {
2102
+ min: '0',
2103
+ max,
2104
+ step,
2105
+ });
2106
+ colorForm.append(cInputLabel, cInput);
2107
+ });
2108
+ return colorForm;
2109
+ }
2110
+
2111
+ /**
2112
+ * A global namespace for aria-label.
2113
+ * @type {string}
2114
+ */
2115
+ const ariaLabel = 'aria-label';
2116
+
2117
+ /**
2118
+ * A global namespace for aria-valuemin.
2119
+ * @type {string}
2120
+ */
2121
+ const ariaValueMin = 'aria-valuemin';
2122
+
2123
+ /**
2124
+ * A global namespace for aria-valuemax.
2125
+ * @type {string}
2126
+ */
2127
+ const ariaValueMax = 'aria-valuemax';
2128
+
2129
+ /**
2130
+ * Returns all color controls for `ColorPicker`.
2131
+ *
2132
+ * @param {CP.ColorPicker} self the `ColorPicker` instance
2133
+ * @returns {HTMLElement | Element} color controls
2134
+ */
2135
+ function getColorControls(self) {
2136
+ const { format, componentLabels } = self;
2137
+ const {
2138
+ hueLabel, alphaLabel, lightnessLabel, saturationLabel,
2139
+ whitenessLabel, blacknessLabel,
2140
+ } = componentLabels;
2141
+
2142
+ const max1 = format === 'hsl' ? 360 : 100;
2143
+ const max2 = format === 'hsl' ? 100 : 360;
2144
+ const max3 = 100;
2145
+
2146
+ let ctrl1Label = format === 'hsl'
2147
+ ? `${hueLabel} & ${lightnessLabel}`
2148
+ : `${lightnessLabel} & ${saturationLabel}`;
2149
+
2150
+ ctrl1Label = format === 'hwb'
2151
+ ? `${whitenessLabel} & ${blacknessLabel}`
2152
+ : ctrl1Label;
2153
+
2154
+ const ctrl2Label = format === 'hsl'
2155
+ ? `${saturationLabel}`
2156
+ : `${hueLabel}`;
2157
+
2158
+ const colorControls = createElement({
2159
+ tagName: 'div',
2160
+ className: `color-controls ${format}`,
2161
+ });
2162
+
2163
+ const colorPointer = 'color-pointer';
2164
+ const colorSlider = 'color-slider';
2165
+
2166
+ const controls = [
2167
+ {
2168
+ i: 1,
2169
+ c: colorPointer,
2170
+ l: ctrl1Label,
2171
+ min: 0,
2172
+ max: max1,
2173
+ },
2174
+ {
2175
+ i: 2,
2176
+ c: colorSlider,
2177
+ l: ctrl2Label,
2178
+ min: 0,
2179
+ max: max2,
2180
+ },
2181
+ {
2182
+ i: 3,
2183
+ c: colorSlider,
2184
+ l: alphaLabel,
2185
+ min: 0,
2186
+ max: max3,
2187
+ },
2188
+ ];
2189
+
2190
+ controls.forEach((template) => {
2191
+ const {
2192
+ i, c, l, min, max,
2193
+ } = template;
2194
+ const control = createElement({
2195
+ tagName: 'div',
2196
+ className: 'color-control',
2197
+ });
2198
+ setAttribute(control, 'role', 'presentation');
2239
2199
 
2240
- let lightStep = 0.25;
2241
- lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
2242
- lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
2243
- lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
2244
- lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
2245
- lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
2246
- lightStep = lightSteps > 13 ? estimatedStep : lightStep;
2200
+ control.append(
2201
+ createElement({
2202
+ tagName: 'div',
2203
+ className: `visual-control visual-control${i}`,
2204
+ }),
2205
+ );
2247
2206
 
2248
- // light tints
2249
- for (let i = 1; i < half + 1; i += 1) {
2250
- lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
2251
- }
2207
+ const knob = createElement({
2208
+ tagName: 'div',
2209
+ className: `${c} knob`,
2210
+ ariaLive: 'polite',
2211
+ });
2252
2212
 
2253
- // dark tints
2254
- for (let i = 1; i < lightSteps - half; i += 1) {
2255
- lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
2256
- }
2213
+ setAttribute(knob, ariaLabel, l);
2214
+ setAttribute(knob, 'role', 'slider');
2215
+ setAttribute(knob, tabIndex, '0');
2216
+ setAttribute(knob, ariaValueMin, `${min}`);
2217
+ setAttribute(knob, ariaValueMax, `${max}`);
2218
+ control.append(knob);
2219
+ colorControls.append(control);
2220
+ });
2257
2221
 
2258
- // feed `colors` Array
2259
- for (let i = 0; i < hueSteps; i += 1) {
2260
- const currentHue = ((hue + i * hueStep) % 360) / 360;
2261
- lightnessArray.forEach((l) => {
2262
- colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
2263
- });
2264
- }
2222
+ return colorControls;
2223
+ }
2265
2224
 
2266
- this.hue = hue;
2267
- this.hueSteps = hueSteps;
2268
- this.lightSteps = lightSteps;
2269
- this.colors = colors;
2270
- }
2225
+ /**
2226
+ * Helps setting CSS variables to the color-menu.
2227
+ * @param {HTMLElement} element
2228
+ * @param {Record<string,any>} props
2229
+ */
2230
+ function setCSSProperties(element, props) {
2231
+ ObjectKeys(props).forEach((prop) => {
2232
+ element.style.setProperty(prop, props[prop]);
2233
+ });
2271
2234
  }
2272
2235
 
2273
2236
  /**
@@ -2303,7 +2266,8 @@ function getColorMenu(self, colorsSource, menuClass) {
2303
2266
  optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2304
2267
  const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2305
2268
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2306
-
2269
+ /** @type {HTMLUListElement} */
2270
+ // @ts-ignore -- <UL> is an `HTMLElement`
2307
2271
  const menu = createElement({
2308
2272
  tagName: 'ul',
2309
2273
  className: finalClass,
@@ -2311,7 +2275,7 @@ function getColorMenu(self, colorsSource, menuClass) {
2311
2275
  setAttribute(menu, 'role', 'listbox');
2312
2276
  setAttribute(menu, ariaLabel, menuLabel);
2313
2277
 
2314
- if (isScrollable) { // @ts-ignore
2278
+ if (isScrollable) {
2315
2279
  setCSSProperties(menu, {
2316
2280
  '--grid-item-size': `${optionSize}rem`,
2317
2281
  '--grid-fit': fit,
@@ -2322,15 +2286,19 @@ function getColorMenu(self, colorsSource, menuClass) {
2322
2286
  }
2323
2287
 
2324
2288
  colorsArray.forEach((x) => {
2325
- const [value, label] = x.trim().split(':');
2326
- const xRealColor = new Color(value, format).toString();
2327
- const isActive = xRealColor === getAttribute(input, 'value');
2289
+ let [value, label] = typeof x === 'string' ? x.trim().split(':') : [];
2290
+ if (x instanceof Color) {
2291
+ value = x.toHexString();
2292
+ label = value;
2293
+ }
2294
+ const color = new Color(x instanceof Color ? x : value, format);
2295
+ const isActive = color.toString() === getAttribute(input, 'value');
2328
2296
  const active = isActive ? ' active' : '';
2329
2297
 
2330
2298
  const option = createElement({
2331
2299
  tagName: 'li',
2332
2300
  className: `color-option${active}`,
2333
- innerText: `${label || x}`,
2301
+ innerText: `${label || value}`,
2334
2302
  });
2335
2303
 
2336
2304
  setAttribute(option, tabIndex, '0');
@@ -2339,7 +2307,7 @@ function getColorMenu(self, colorsSource, menuClass) {
2339
2307
  setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2340
2308
 
2341
2309
  if (isOptionsMenu) {
2342
- setElementStyle(option, { backgroundColor: x });
2310
+ setElementStyle(option, { backgroundColor: value });
2343
2311
  }
2344
2312
 
2345
2313
  menu.append(option);
@@ -2348,55 +2316,10 @@ function getColorMenu(self, colorsSource, menuClass) {
2348
2316
  }
2349
2317
 
2350
2318
  /**
2351
- * Check if a string is valid JSON string.
2352
- * @param {string} str the string input
2353
- * @returns {boolean} the query result
2354
- */
2355
- function isValidJSON(str) {
2356
- try {
2357
- JSON.parse(str);
2358
- } catch (e) {
2359
- return false;
2360
- }
2361
- return true;
2362
- }
2363
-
2364
- var version = "0.0.1alpha3";
2365
-
2366
- // @ts-ignore
2367
-
2368
- const Version = version;
2369
-
2370
- // ColorPicker GC
2371
- // ==============
2372
- const colorPickerString = 'color-picker';
2373
- const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2374
- const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2375
- const colorPickerDefaults = {
2376
- componentLabels: colorPickerLabels,
2377
- colorLabels: colorNames,
2378
- format: 'rgb',
2379
- colorPresets: false,
2380
- colorKeywords: false,
2381
- };
2382
-
2383
- // ColorPicker Static Methods
2384
- // ==========================
2385
-
2386
- /** @type {CP.GetInstance<ColorPicker>} */
2387
- const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2388
-
2389
- /** @type {CP.InitCallback<ColorPicker>} */
2390
- const initColorPicker = (element) => new ColorPicker(element);
2391
-
2392
- // ColorPicker Private Methods
2393
- // ===========================
2394
-
2395
- /**
2396
- * Generate HTML markup and update instance properties.
2397
- * @param {ColorPicker} self
2398
- */
2399
- function initCallback(self) {
2319
+ * Generate HTML markup and update instance properties.
2320
+ * @param {CP.ColorPicker} self
2321
+ */
2322
+ function setMarkup(self) {
2400
2323
  const {
2401
2324
  input, parent, format, id, componentLabels, colorKeywords, colorPresets,
2402
2325
  } = self;
@@ -2411,9 +2334,7 @@ function initCallback(self) {
2411
2334
  self.color = new Color(color, format);
2412
2335
 
2413
2336
  // set initial controls dimensions
2414
- // make the controls smaller on mobile
2415
- const dropClass = isMobile ? ' mobile' : '';
2416
- const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
2337
+ const formatString = format === 'hex' ? hexLabel : toUpperCase(format);
2417
2338
 
2418
2339
  const pickerBtn = createElement({
2419
2340
  id: `picker-btn-${id}`,
@@ -2430,7 +2351,7 @@ function initCallback(self) {
2430
2351
 
2431
2352
  const pickerDropdown = createElement({
2432
2353
  tagName: 'div',
2433
- className: `color-dropdown picker${dropClass}`,
2354
+ className: 'color-dropdown picker',
2434
2355
  });
2435
2356
  setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
2436
2357
  setAttribute(pickerDropdown, 'role', 'group');
@@ -2446,7 +2367,7 @@ function initCallback(self) {
2446
2367
  if (colorKeywords || colorPresets) {
2447
2368
  const presetsDropdown = createElement({
2448
2369
  tagName: 'div',
2449
- className: `color-dropdown scrollable menu${dropClass}`,
2370
+ className: 'color-dropdown scrollable menu',
2450
2371
  });
2451
2372
 
2452
2373
  // color presets
@@ -2496,6 +2417,37 @@ function initCallback(self) {
2496
2417
  setAttribute(input, tabIndex, '-1');
2497
2418
  }
2498
2419
 
2420
+ var version = "0.0.2alpha2";
2421
+
2422
+ // @ts-ignore
2423
+
2424
+ const Version = version;
2425
+
2426
+ // ColorPicker GC
2427
+ // ==============
2428
+ const colorPickerString = 'color-picker';
2429
+ const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2430
+ const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2431
+ const colorPickerDefaults = {
2432
+ componentLabels: colorPickerLabels,
2433
+ colorLabels: colorNames,
2434
+ format: 'rgb',
2435
+ colorPresets: false,
2436
+ colorKeywords: false,
2437
+ };
2438
+
2439
+ // ColorPicker Static Methods
2440
+ // ==========================
2441
+
2442
+ /** @type {CP.GetInstance<ColorPicker>} */
2443
+ const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2444
+
2445
+ /** @type {CP.InitCallback<ColorPicker>} */
2446
+ const initColorPicker = (element) => new ColorPicker(element);
2447
+
2448
+ // ColorPicker Private Methods
2449
+ // ===========================
2450
+
2499
2451
  /**
2500
2452
  * Add / remove `ColorPicker` main event listeners.
2501
2453
  * @param {ColorPicker} self
@@ -2508,8 +2460,6 @@ function toggleEvents(self, action) {
2508
2460
  fn(input, focusinEvent, self.showPicker);
2509
2461
  fn(pickerToggle, mouseclickEvent, self.togglePicker);
2510
2462
 
2511
- fn(input, keydownEvent, self.keyToggle);
2512
-
2513
2463
  if (menuToggle) {
2514
2464
  fn(menuToggle, mouseclickEvent, self.toggleMenu);
2515
2465
  }
@@ -2547,8 +2497,7 @@ function toggleEventsOnShown(self, action) {
2547
2497
  fn(doc, pointerEvents.move, self.pointerMove);
2548
2498
  fn(doc, pointerEvents.up, self.pointerUp);
2549
2499
  fn(parent, focusoutEvent, self.handleFocusOut);
2550
- // @ts-ignore -- this is `Window`
2551
- fn(win, keyupEvent, self.handleDismiss);
2500
+ fn(doc, keyupEvent, self.handleDismiss);
2552
2501
  }
2553
2502
 
2554
2503
  /**
@@ -2632,7 +2581,7 @@ class ColorPicker {
2632
2581
  const input = querySelector(target);
2633
2582
 
2634
2583
  // invalidate
2635
- if (!input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
2584
+ if (!input) throw new TypeError(`ColorPicker target "${target}" cannot be found.`);
2636
2585
  self.input = input;
2637
2586
 
2638
2587
  const parent = closest(input, colorPickerParentSelector);
@@ -2679,15 +2628,14 @@ class ColorPicker {
2679
2628
  });
2680
2629
 
2681
2630
  // update and expose component labels
2682
- const tempLabels = ObjectAssign({}, colorPickerLabels);
2683
- const jsonLabels = componentLabels && isValidJSON(componentLabels)
2684
- ? JSON.parse(componentLabels) : componentLabels || {};
2631
+ const tempComponentLabels = componentLabels && isValidJSON(componentLabels)
2632
+ ? JSON.parse(componentLabels) : componentLabels;
2685
2633
 
2686
2634
  /** @type {Record<string, string>} */
2687
- self.componentLabels = ObjectAssign(tempLabels, jsonLabels);
2635
+ self.componentLabels = ObjectAssign(colorPickerLabels, tempComponentLabels);
2688
2636
 
2689
2637
  /** @type {Color} */
2690
- self.color = new Color('white', format);
2638
+ self.color = new Color(input.value || '#fff', format);
2691
2639
 
2692
2640
  /** @type {CP.ColorFormats} */
2693
2641
  self.format = format;
@@ -2696,7 +2644,7 @@ class ColorPicker {
2696
2644
  if (colorKeywords instanceof Array) {
2697
2645
  self.colorKeywords = colorKeywords;
2698
2646
  } else if (typeof colorKeywords === 'string' && colorKeywords.length) {
2699
- self.colorKeywords = colorKeywords.split(',');
2647
+ self.colorKeywords = colorKeywords.split(',').map((x) => x.trim());
2700
2648
  }
2701
2649
 
2702
2650
  // set colour presets
@@ -2725,11 +2673,10 @@ class ColorPicker {
2725
2673
  self.handleFocusOut = self.handleFocusOut.bind(self);
2726
2674
  self.changeHandler = self.changeHandler.bind(self);
2727
2675
  self.handleDismiss = self.handleDismiss.bind(self);
2728
- self.keyToggle = self.keyToggle.bind(self);
2729
2676
  self.handleKnobs = self.handleKnobs.bind(self);
2730
2677
 
2731
2678
  // generate markup
2732
- initCallback(self);
2679
+ setMarkup(self);
2733
2680
 
2734
2681
  const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
2735
2682
  // set main elements
@@ -2817,76 +2764,83 @@ class ColorPicker {
2817
2764
  return inputValue !== '' && new Color(inputValue).isValid;
2818
2765
  }
2819
2766
 
2767
+ /** Returns the colour appearance, usually the closest colour name for the current value. */
2768
+ get appearance() {
2769
+ const {
2770
+ colorLabels, hsl, hsv, format,
2771
+ } = this;
2772
+
2773
+ const hue = roundPart(hsl.h * 360);
2774
+ const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
2775
+ const saturation = roundPart(saturationSource * 100);
2776
+ const lightness = roundPart(hsl.l * 100);
2777
+ const hsvl = hsv.v * 100;
2778
+
2779
+ let colorName;
2780
+
2781
+ // determine color appearance
2782
+ if (lightness === 100 && saturation === 0) {
2783
+ colorName = colorLabels.white;
2784
+ } else if (lightness === 0) {
2785
+ colorName = colorLabels.black;
2786
+ } else if (saturation === 0) {
2787
+ colorName = colorLabels.grey;
2788
+ } else if (hue < 15 || hue >= 345) {
2789
+ colorName = colorLabels.red;
2790
+ } else if (hue >= 15 && hue < 45) {
2791
+ colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
2792
+ } else if (hue >= 45 && hue < 75) {
2793
+ const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
2794
+ const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
2795
+ colorName = isGold ? colorLabels.gold : colorLabels.yellow;
2796
+ colorName = isOlive ? colorLabels.olive : colorName;
2797
+ } else if (hue >= 75 && hue < 155) {
2798
+ colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
2799
+ } else if (hue >= 155 && hue < 175) {
2800
+ colorName = colorLabels.teal;
2801
+ } else if (hue >= 175 && hue < 195) {
2802
+ colorName = colorLabels.cyan;
2803
+ } else if (hue >= 195 && hue < 255) {
2804
+ colorName = colorLabels.blue;
2805
+ } else if (hue >= 255 && hue < 270) {
2806
+ colorName = colorLabels.violet;
2807
+ } else if (hue >= 270 && hue < 295) {
2808
+ colorName = colorLabels.magenta;
2809
+ } else if (hue >= 295 && hue < 345) {
2810
+ colorName = colorLabels.pink;
2811
+ }
2812
+ return colorName;
2813
+ }
2814
+
2820
2815
  /** Updates `ColorPicker` visuals. */
2821
2816
  updateVisuals() {
2822
2817
  const self = this;
2823
2818
  const {
2824
- format, controlPositions, visuals,
2819
+ controlPositions, visuals,
2825
2820
  } = self;
2826
2821
  const [v1, v2, v3] = visuals;
2827
- const { offsetWidth, offsetHeight } = v1;
2828
- const hue = format === 'hsl'
2829
- ? controlPositions.c1x / offsetWidth
2830
- : controlPositions.c2y / offsetHeight;
2831
- // @ts-ignore - `hslToRgb` is assigned to `Color` as static method
2832
- const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
2822
+ const { offsetHeight } = v1;
2823
+ const hue = controlPositions.c2y / offsetHeight;
2824
+ const { r, g, b } = new Color({ h: hue, s: 1, l: 0.5 }).toRgb();
2833
2825
  const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
2834
2826
  const alpha = 1 - controlPositions.c3y / offsetHeight;
2835
2827
  const roundA = roundPart((alpha * 100)) / 100;
2836
2828
 
2837
- if (format !== 'hsl') {
2838
- const fill = new Color({
2839
- h: hue, s: 1, l: 0.5, a: alpha,
2840
- }).toRgbString();
2841
- const hueGradient = `linear-gradient(
2842
- rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2843
- rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2844
- rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2845
- rgb(255,0,0) 100%)`;
2846
- setElementStyle(v1, {
2847
- background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2848
- linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2849
- ${whiteGrad}`,
2850
- });
2851
- setElementStyle(v2, { background: hueGradient });
2852
- } else {
2853
- const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
2854
- const fill0 = new Color({
2855
- r: 255, g: 0, b: 0, a: alpha,
2856
- }).saturate(-saturation).toRgbString();
2857
- const fill1 = new Color({
2858
- r: 255, g: 255, b: 0, a: alpha,
2859
- }).saturate(-saturation).toRgbString();
2860
- const fill2 = new Color({
2861
- r: 0, g: 255, b: 0, a: alpha,
2862
- }).saturate(-saturation).toRgbString();
2863
- const fill3 = new Color({
2864
- r: 0, g: 255, b: 255, a: alpha,
2865
- }).saturate(-saturation).toRgbString();
2866
- const fill4 = new Color({
2867
- r: 0, g: 0, b: 255, a: alpha,
2868
- }).saturate(-saturation).toRgbString();
2869
- const fill5 = new Color({
2870
- r: 255, g: 0, b: 255, a: alpha,
2871
- }).saturate(-saturation).toRgbString();
2872
- const fill6 = new Color({
2873
- r: 255, g: 0, b: 0, a: alpha,
2874
- }).saturate(-saturation).toRgbString();
2875
- const fillGradient = `linear-gradient(to right,
2876
- ${fill0} 0%, ${fill1} 16.67%, ${fill2} 33.33%, ${fill3} 50%,
2877
- ${fill4} 66.67%, ${fill5} 83.33%, ${fill6} 100%)`;
2878
- const lightGrad = `linear-gradient(rgba(255,255,255,${roundA}) 0%, rgba(255,255,255,0) 50%),
2879
- linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,${roundA}) 100%)`;
2880
-
2881
- setElementStyle(v1, { background: `${lightGrad},${fillGradient},${whiteGrad}` });
2882
- const {
2883
- r: gr, g: gg, b: gb,
2884
- } = new Color({ r, g, b }).greyscale().toRgb();
2829
+ const fill = new Color({
2830
+ h: hue, s: 1, l: 0.5, a: alpha,
2831
+ }).toRgbString();
2832
+ const hueGradient = `linear-gradient(
2833
+ rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2834
+ rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2835
+ rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2836
+ rgb(255,0,0) 100%)`;
2837
+ setElementStyle(v1, {
2838
+ background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2839
+ linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2840
+ ${whiteGrad}`,
2841
+ });
2842
+ setElementStyle(v2, { background: hueGradient });
2885
2843
 
2886
- setElementStyle(v2, {
2887
- background: `linear-gradient(rgb(${r},${g},${b}) 0%, rgb(${gr},${gg},${gb}) 100%)`,
2888
- });
2889
- }
2890
2844
  setElementStyle(v3, {
2891
2845
  background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
2892
2846
  });
@@ -2925,7 +2879,7 @@ class ColorPicker {
2925
2879
  const self = this;
2926
2880
  const { activeElement } = getDocument(self.input);
2927
2881
 
2928
- if ((isMobile && self.dragElement)
2882
+ if ((e.type === touchmoveEvent && self.dragElement)
2929
2883
  || (activeElement && self.controlKnobs.includes(activeElement))) {
2930
2884
  e.stopPropagation();
2931
2885
  e.preventDefault();
@@ -3036,13 +2990,13 @@ class ColorPicker {
3036
2990
  const [v1, v2, v3] = visuals;
3037
2991
  const [c1, c2, c3] = controlKnobs;
3038
2992
  /** @type {HTMLElement} */
3039
- const visual = hasClass(target, 'visual-control')
3040
- ? target : querySelector('.visual-control', target.parentElement);
2993
+ const visual = controlKnobs.includes(target) ? target.previousElementSibling : target;
3041
2994
  const visualRect = getBoundingClientRect(visual);
2995
+ const html = getDocumentElement(v1);
3042
2996
  const X = type === 'touchstart' ? touches[0].pageX : pageX;
3043
2997
  const Y = type === 'touchstart' ? touches[0].pageY : pageY;
3044
- const offsetX = X - window.pageXOffset - visualRect.left;
3045
- const offsetY = Y - window.pageYOffset - visualRect.top;
2998
+ const offsetX = X - html.scrollLeft - visualRect.left;
2999
+ const offsetY = Y - html.scrollTop - visualRect.top;
3046
3000
 
3047
3001
  if (target === v1 || target === c1) {
3048
3002
  self.dragElement = visual;
@@ -3102,10 +3056,11 @@ class ColorPicker {
3102
3056
  if (!dragElement) return;
3103
3057
 
3104
3058
  const controlRect = getBoundingClientRect(dragElement);
3105
- const X = type === 'touchmove' ? touches[0].pageX : pageX;
3106
- const Y = type === 'touchmove' ? touches[0].pageY : pageY;
3107
- const offsetX = X - window.pageXOffset - controlRect.left;
3108
- const offsetY = Y - window.pageYOffset - controlRect.top;
3059
+ const win = getDocumentElement(v1);
3060
+ const X = type === touchmoveEvent ? touches[0].pageX : pageX;
3061
+ const Y = type === touchmoveEvent ? touches[0].pageY : pageY;
3062
+ const offsetX = X - win.scrollLeft - controlRect.left;
3063
+ const offsetY = Y - win.scrollTop - controlRect.top;
3109
3064
 
3110
3065
  if (dragElement === v1) {
3111
3066
  self.changeControl1(offsetX, offsetY);
@@ -3132,30 +3087,41 @@ class ColorPicker {
3132
3087
  if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
3133
3088
  e.preventDefault();
3134
3089
 
3135
- const { controlKnobs } = self;
3090
+ const { controlKnobs, visuals } = self;
3091
+ const { offsetWidth, offsetHeight } = visuals[0];
3136
3092
  const [c1, c2, c3] = controlKnobs;
3137
3093
  const { activeElement } = getDocument(c1);
3138
3094
  const currentKnob = controlKnobs.find((x) => x === activeElement);
3095
+ const yRatio = offsetHeight / 360;
3139
3096
 
3140
3097
  if (currentKnob) {
3141
3098
  let offsetX = 0;
3142
3099
  let offsetY = 0;
3100
+
3143
3101
  if (target === c1) {
3102
+ const xRatio = offsetWidth / 100;
3103
+
3144
3104
  if ([keyArrowLeft, keyArrowRight].includes(code)) {
3145
- self.controlPositions.c1x += code === keyArrowRight ? +1 : -1;
3105
+ self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
3146
3106
  } else if ([keyArrowUp, keyArrowDown].includes(code)) {
3147
- self.controlPositions.c1y += code === keyArrowDown ? +1 : -1;
3107
+ self.controlPositions.c1y += code === keyArrowDown ? yRatio : -yRatio;
3148
3108
  }
3149
3109
 
3150
3110
  offsetX = self.controlPositions.c1x;
3151
3111
  offsetY = self.controlPositions.c1y;
3152
3112
  self.changeControl1(offsetX, offsetY);
3153
3113
  } else if (target === c2) {
3154
- self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
3114
+ self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code)
3115
+ ? yRatio
3116
+ : -yRatio;
3117
+
3155
3118
  offsetY = self.controlPositions.c2y;
3156
3119
  self.changeControl2(offsetY);
3157
3120
  } else if (target === c3) {
3158
- self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
3121
+ self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code)
3122
+ ? yRatio
3123
+ : -yRatio;
3124
+
3159
3125
  offsetY = self.controlPositions.c3y;
3160
3126
  self.changeAlpha(offsetY);
3161
3127
  }
@@ -3183,7 +3149,7 @@ class ColorPicker {
3183
3149
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
3184
3150
  if (activeElement === input) {
3185
3151
  if (isNonColorValue) {
3186
- colorSource = 'white';
3152
+ colorSource = currentValue === 'transparent' ? 'rgba(0,0,0,0)' : 'rgb(0,0,0)';
3187
3153
  } else {
3188
3154
  colorSource = currentValue;
3189
3155
  }
@@ -3234,9 +3200,7 @@ class ColorPicker {
3234
3200
  changeControl1(X, Y) {
3235
3201
  const self = this;
3236
3202
  let [offsetX, offsetY] = [0, 0];
3237
- const {
3238
- format, controlPositions, visuals,
3239
- } = self;
3203
+ const { controlPositions, visuals } = self;
3240
3204
  const { offsetHeight, offsetWidth } = visuals[0];
3241
3205
 
3242
3206
  if (X > offsetWidth) offsetX = offsetWidth;
@@ -3245,29 +3209,19 @@ class ColorPicker {
3245
3209
  if (Y > offsetHeight) offsetY = offsetHeight;
3246
3210
  else if (Y >= 0) offsetY = Y;
3247
3211
 
3248
- const hue = format === 'hsl'
3249
- ? offsetX / offsetWidth
3250
- : controlPositions.c2y / offsetHeight;
3212
+ const hue = controlPositions.c2y / offsetHeight;
3251
3213
 
3252
- const saturation = format === 'hsl'
3253
- ? 1 - controlPositions.c2y / offsetHeight
3254
- : offsetX / offsetWidth;
3214
+ const saturation = offsetX / offsetWidth;
3255
3215
 
3256
3216
  const lightness = 1 - offsetY / offsetHeight;
3257
3217
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3258
3218
 
3259
- const colorObject = format === 'hsl'
3260
- ? {
3261
- h: hue, s: saturation, l: lightness, a: alpha,
3262
- }
3263
- : {
3264
- h: hue, s: saturation, v: lightness, a: alpha,
3265
- };
3266
-
3267
3219
  // new color
3268
3220
  const {
3269
3221
  r, g, b, a,
3270
- } = new Color(colorObject);
3222
+ } = new Color({
3223
+ h: hue, s: saturation, v: lightness, a: alpha,
3224
+ });
3271
3225
 
3272
3226
  ObjectAssign(self.color, {
3273
3227
  r, g, b, a,
@@ -3294,7 +3248,7 @@ class ColorPicker {
3294
3248
  changeControl2(Y) {
3295
3249
  const self = this;
3296
3250
  const {
3297
- format, controlPositions, visuals,
3251
+ controlPositions, visuals,
3298
3252
  } = self;
3299
3253
  const { offsetHeight, offsetWidth } = visuals[0];
3300
3254
 
@@ -3303,26 +3257,17 @@ class ColorPicker {
3303
3257
  if (Y > offsetHeight) offsetY = offsetHeight;
3304
3258
  else if (Y >= 0) offsetY = Y;
3305
3259
 
3306
- const hue = format === 'hsl'
3307
- ? controlPositions.c1x / offsetWidth
3308
- : offsetY / offsetHeight;
3309
- const saturation = format === 'hsl'
3310
- ? 1 - offsetY / offsetHeight
3311
- : controlPositions.c1x / offsetWidth;
3260
+ const hue = offsetY / offsetHeight;
3261
+ const saturation = controlPositions.c1x / offsetWidth;
3312
3262
  const lightness = 1 - controlPositions.c1y / offsetHeight;
3313
3263
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3314
- const colorObject = format === 'hsl'
3315
- ? {
3316
- h: hue, s: saturation, l: lightness, a: alpha,
3317
- }
3318
- : {
3319
- h: hue, s: saturation, v: lightness, a: alpha,
3320
- };
3321
3264
 
3322
3265
  // new color
3323
3266
  const {
3324
3267
  r, g, b, a,
3325
- } = new Color(colorObject);
3268
+ } = new Color({
3269
+ h: hue, s: saturation, v: lightness, a: alpha,
3270
+ });
3326
3271
 
3327
3272
  ObjectAssign(self.color, {
3328
3273
  r, g, b, a,
@@ -3409,18 +3354,18 @@ class ColorPicker {
3409
3354
  setControlPositions() {
3410
3355
  const self = this;
3411
3356
  const {
3412
- format, visuals, color, hsl, hsv,
3357
+ visuals, color, hsv,
3413
3358
  } = self;
3414
3359
  const { offsetHeight, offsetWidth } = visuals[0];
3415
3360
  const alpha = color.a;
3416
- const hue = hsl.h;
3361
+ const hue = hsv.h;
3417
3362
 
3418
- const saturation = format !== 'hsl' ? hsv.s : hsl.s;
3419
- const lightness = format !== 'hsl' ? hsv.v : hsl.l;
3363
+ const saturation = hsv.s;
3364
+ const lightness = hsv.v;
3420
3365
 
3421
- self.controlPositions.c1x = format !== 'hsl' ? saturation * offsetWidth : hue * offsetWidth;
3366
+ self.controlPositions.c1x = saturation * offsetWidth;
3422
3367
  self.controlPositions.c1y = (1 - lightness) * offsetHeight;
3423
- self.controlPositions.c2y = format !== 'hsl' ? hue * offsetHeight : (1 - saturation) * offsetHeight;
3368
+ self.controlPositions.c2y = hue * offsetHeight;
3424
3369
  self.controlPositions.c3y = (1 - alpha) * offsetHeight;
3425
3370
  }
3426
3371
 
@@ -3428,78 +3373,40 @@ class ColorPicker {
3428
3373
  updateAppearance() {
3429
3374
  const self = this;
3430
3375
  const {
3431
- componentLabels, colorLabels, color, parent,
3432
- hsl, hsv, hex, format, controlKnobs,
3376
+ componentLabels, color, parent,
3377
+ hsv, hex, format, controlKnobs,
3433
3378
  } = self;
3434
3379
  const {
3435
3380
  appearanceLabel, hexLabel, valueLabel,
3436
3381
  } = componentLabels;
3437
- const { r, g, b } = color.toRgb();
3382
+ let { r, g, b } = color.toRgb();
3438
3383
  const [knob1, knob2, knob3] = controlKnobs;
3439
- const hue = roundPart(hsl.h * 360);
3384
+ const hue = roundPart(hsv.h * 360);
3440
3385
  const alpha = color.a;
3441
- const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
3442
- const saturation = roundPart(saturationSource * 100);
3443
- const lightness = roundPart(hsl.l * 100);
3444
- const hsvl = hsv.v * 100;
3445
- let colorName;
3446
-
3447
- // determine color appearance
3448
- if (lightness === 100 && saturation === 0) {
3449
- colorName = colorLabels.white;
3450
- } else if (lightness === 0) {
3451
- colorName = colorLabels.black;
3452
- } else if (saturation === 0) {
3453
- colorName = colorLabels.grey;
3454
- } else if (hue < 15 || hue >= 345) {
3455
- colorName = colorLabels.red;
3456
- } else if (hue >= 15 && hue < 45) {
3457
- colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
3458
- } else if (hue >= 45 && hue < 75) {
3459
- const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
3460
- const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
3461
- colorName = isGold ? colorLabels.gold : colorLabels.yellow;
3462
- colorName = isOlive ? colorLabels.olive : colorName;
3463
- } else if (hue >= 75 && hue < 155) {
3464
- colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
3465
- } else if (hue >= 155 && hue < 175) {
3466
- colorName = colorLabels.teal;
3467
- } else if (hue >= 175 && hue < 195) {
3468
- colorName = colorLabels.cyan;
3469
- } else if (hue >= 195 && hue < 255) {
3470
- colorName = colorLabels.blue;
3471
- } else if (hue >= 255 && hue < 270) {
3472
- colorName = colorLabels.violet;
3473
- } else if (hue >= 270 && hue < 295) {
3474
- colorName = colorLabels.magenta;
3475
- } else if (hue >= 295 && hue < 345) {
3476
- colorName = colorLabels.pink;
3477
- }
3386
+ const saturation = roundPart(hsv.s * 100);
3387
+ const lightness = roundPart(hsv.v * 100);
3388
+ const colorName = self.appearance;
3478
3389
 
3479
3390
  let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
3480
3391
 
3481
- if (format === 'hsl') {
3482
- colorLabel = `HSL: ${hue}°, ${saturation}%, ${lightness}%`;
3483
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3484
- setAttribute(knob1, ariaValueText, `${hue}° & ${lightness}%`);
3485
- setAttribute(knob1, ariaValueNow, `${hue}`);
3486
- setAttribute(knob2, ariaValueText, `${saturation}%`);
3487
- setAttribute(knob2, ariaValueNow, `${saturation}`);
3488
- } else if (format === 'hwb') {
3392
+ if (format === 'hwb') {
3489
3393
  const { hwb } = self;
3490
3394
  const whiteness = roundPart(hwb.w * 100);
3491
3395
  const blackness = roundPart(hwb.b * 100);
3492
3396
  colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
3493
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3494
3397
  setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
3495
3398
  setAttribute(knob1, ariaValueNow, `${whiteness}`);
3399
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3496
3400
  setAttribute(knob2, ariaValueText, `${hue}%`);
3497
3401
  setAttribute(knob2, ariaValueNow, `${hue}`);
3498
3402
  } else {
3403
+ [r, g, b] = [r, g, b].map(roundPart);
3404
+ colorLabel = format === 'hsl' ? `HSL: ${hue}°, ${saturation}%, ${lightness}%` : colorLabel;
3499
3405
  colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
3500
- setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3406
+
3501
3407
  setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
3502
3408
  setAttribute(knob1, ariaValueNow, `${lightness}`);
3409
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3503
3410
  setAttribute(knob2, ariaValueText, `${hue}°`);
3504
3411
  setAttribute(knob2, ariaValueNow, `${hue}`);
3505
3412
  }
@@ -3527,10 +3434,12 @@ class ColorPicker {
3527
3434
  /** Updates the control knobs actual positions. */
3528
3435
  updateControls() {
3529
3436
  const { controlKnobs, controlPositions } = this;
3530
- const {
3437
+ let {
3531
3438
  c1x, c1y, c2y, c3y,
3532
3439
  } = controlPositions;
3533
3440
  const [control1, control2, control3] = controlKnobs;
3441
+ // round control positions
3442
+ [c1x, c1y, c2y, c3y] = [c1x, c1y, c2y, c3y].map(roundPart);
3534
3443
 
3535
3444
  setElementStyle(control1, { transform: `translate3d(${c1x - 4}px,${c1y - 4}px,0)` });
3536
3445
  setElementStyle(control2, { transform: `translate3d(0,${c2y - 4}px,0)` });
@@ -3573,7 +3482,8 @@ class ColorPicker {
3573
3482
  i3.value = `${blackness}`;
3574
3483
  i4.value = `${alpha}`;
3575
3484
  } else if (format === 'rgb') {
3576
- const { r, g, b } = self.rgb;
3485
+ let { r, g, b } = self.rgb;
3486
+ [r, g, b] = [r, g, b].map(roundPart);
3577
3487
 
3578
3488
  newColor = self.color.toRgbString();
3579
3489
  i1.value = `${r}`;
@@ -3591,37 +3501,13 @@ class ColorPicker {
3591
3501
  }
3592
3502
  }
3593
3503
 
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
3504
  /**
3619
3505
  * Toggle the `ColorPicker` dropdown visibility.
3620
- * @param {Event} e
3506
+ * @param {Event=} e
3621
3507
  * @this {ColorPicker}
3622
3508
  */
3623
3509
  togglePicker(e) {
3624
- e.preventDefault();
3510
+ if (e) e.preventDefault();
3625
3511
  const self = this;
3626
3512
  const { colorPicker } = self;
3627
3513
 
@@ -3642,8 +3528,13 @@ class ColorPicker {
3642
3528
  }
3643
3529
  }
3644
3530
 
3645
- /** Toggles the visibility of the `ColorPicker` presets menu. */
3646
- toggleMenu() {
3531
+ /**
3532
+ * Toggles the visibility of the `ColorPicker` presets menu.
3533
+ * @param {Event=} e
3534
+ * @this {ColorPicker}
3535
+ */
3536
+ toggleMenu(e) {
3537
+ if (e) e.preventDefault();
3647
3538
  const self = this;
3648
3539
  const { colorMenu } = self;
3649
3540
 
@@ -3669,6 +3560,10 @@ class ColorPicker {
3669
3560
  const relatedBtn = openPicker ? pickerToggle : menuToggle;
3670
3561
  const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
3671
3562
 
3563
+ // if (!self.isValid) {
3564
+ self.value = self.color.toString(true);
3565
+ // }
3566
+
3672
3567
  if (openDropdown) {
3673
3568
  removeClass(openDropdown, 'show');
3674
3569
  setAttribute(relatedBtn, ariaExpanded, 'false');
@@ -3682,9 +3577,6 @@ class ColorPicker {
3682
3577
  }, animationDuration);
3683
3578
  }
3684
3579
 
3685
- if (!self.isValid) {
3686
- self.value = self.color.toString();
3687
- }
3688
3580
  if (!focusPrevented) {
3689
3581
  focus(pickerToggle);
3690
3582
  }
@@ -3727,4 +3619,4 @@ ObjectAssign(ColorPicker, {
3727
3619
  getBoundingClientRect,
3728
3620
  });
3729
3621
 
3730
- export default ColorPicker;
3622
+ export { ColorPicker as default };