@thednp/color-picker 0.0.1 → 0.0.2-alpha3

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.1 (http://thednp.github.io/color-picker)
2
+ * ColorPicker v0.0.2alpha3 (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
- // RGB values now are all in [0, 255] range
1712
- [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255);
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));
1713
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,12 +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
1524
 
1525
+ [r, g, b] = [r, g, b].map((n) => roundPart(n * 255 * 100) / 100);
1526
+ a = roundPart(a * 100) / 100;
1869
1527
  return {
1870
- r, g, b, a: roundPart(a * 100) / 100,
1528
+ r, g, b, a,
1871
1529
  };
1872
1530
  }
1873
1531
 
@@ -1961,7 +1619,7 @@ class Color {
1961
1619
  toHsv() {
1962
1620
  const {
1963
1621
  r, g, b, a,
1964
- } = this.toRgb();
1622
+ } = this;
1965
1623
  const { h, s, v } = rgbToHsv(r, g, b);
1966
1624
 
1967
1625
  return {
@@ -1976,7 +1634,7 @@ class Color {
1976
1634
  toHsl() {
1977
1635
  const {
1978
1636
  r, g, b, a,
1979
- } = this.toRgb();
1637
+ } = this;
1980
1638
  const { h, s, l } = rgbToHsl(r, g, b);
1981
1639
 
1982
1640
  return {
@@ -2061,6 +1719,7 @@ class Color {
2061
1719
  */
2062
1720
  setAlpha(alpha) {
2063
1721
  const self = this;
1722
+ if (typeof alpha !== 'number') return self;
2064
1723
  self.a = boundAlpha(alpha);
2065
1724
  return self;
2066
1725
  }
@@ -2175,6 +1834,7 @@ ObjectAssign(Color, {
2175
1834
  isOnePointZero,
2176
1835
  isPercentage,
2177
1836
  isValidCSSUnit,
1837
+ isColorName,
2178
1838
  pad2,
2179
1839
  clamp01,
2180
1840
  bound01,
@@ -2192,10 +1852,11 @@ ObjectAssign(Color, {
2192
1852
  hueToRgb,
2193
1853
  hwbToRgb,
2194
1854
  parseIntFromHex,
2195
- numberInputToObject,
2196
1855
  stringInputToObject,
2197
1856
  inputToRGB,
2198
1857
  roundPart,
1858
+ getElementStyle,
1859
+ setElementStyle,
2199
1860
  ObjectAssign,
2200
1861
  });
2201
1862
 
@@ -2204,7 +1865,7 @@ ObjectAssign(Color, {
2204
1865
  * Returns a color palette with a given set of parameters.
2205
1866
  * @example
2206
1867
  * new ColorPalette(0, 12, 10);
2207
- * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
1868
+ * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: Array<Color> }
2208
1869
  */
2209
1870
  class ColorPalette {
2210
1871
  /**
@@ -2224,48 +1885,352 @@ class ColorPalette {
2224
1885
  [hue, hueSteps, lightSteps] = args;
2225
1886
  } else if (args.length === 2) {
2226
1887
  [hueSteps, lightSteps] = args;
2227
- } else {
2228
- 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
+ }
2229
1891
  }
2230
1892
 
2231
- /** @type {string[]} */
1893
+ /** @type {*} */
2232
1894
  const colors = [];
2233
-
2234
1895
  const hueStep = 360 / hueSteps;
2235
1896
  const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
2236
- 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');
2237
2199
 
2238
- let lightStep = 0.25;
2239
- lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
2240
- lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
2241
- lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
2242
- lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
2243
- lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
2244
- lightStep = lightSteps > 13 ? estimatedStep : lightStep;
2200
+ control.append(
2201
+ createElement({
2202
+ tagName: 'div',
2203
+ className: `visual-control visual-control${i}`,
2204
+ }),
2205
+ );
2245
2206
 
2246
- // light tints
2247
- for (let i = 1; i < half + 1; i += 1) {
2248
- lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
2249
- }
2207
+ const knob = createElement({
2208
+ tagName: 'div',
2209
+ className: `${c} knob`,
2210
+ ariaLive: 'polite',
2211
+ });
2250
2212
 
2251
- // dark tints
2252
- for (let i = 1; i < lightSteps - half; i += 1) {
2253
- lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
2254
- }
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
+ });
2255
2221
 
2256
- // feed `colors` Array
2257
- for (let i = 0; i < hueSteps; i += 1) {
2258
- const currentHue = ((hue + i * hueStep) % 360) / 360;
2259
- lightnessArray.forEach((l) => {
2260
- colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
2261
- });
2262
- }
2222
+ return colorControls;
2223
+ }
2263
2224
 
2264
- this.hue = hue;
2265
- this.hueSteps = hueSteps;
2266
- this.lightSteps = lightSteps;
2267
- this.colors = colors;
2268
- }
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
+ });
2269
2234
  }
2270
2235
 
2271
2236
  /**
@@ -2301,7 +2266,8 @@ function getColorMenu(self, colorsSource, menuClass) {
2301
2266
  optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2302
2267
  const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2303
2268
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2304
-
2269
+ /** @type {HTMLUListElement} */
2270
+ // @ts-ignore -- <UL> is an `HTMLElement`
2305
2271
  const menu = createElement({
2306
2272
  tagName: 'ul',
2307
2273
  className: finalClass,
@@ -2309,7 +2275,7 @@ function getColorMenu(self, colorsSource, menuClass) {
2309
2275
  setAttribute(menu, 'role', 'listbox');
2310
2276
  setAttribute(menu, ariaLabel, menuLabel);
2311
2277
 
2312
- if (isScrollable) { // @ts-ignore
2278
+ if (isScrollable) {
2313
2279
  setCSSProperties(menu, {
2314
2280
  '--grid-item-size': `${optionSize}rem`,
2315
2281
  '--grid-fit': fit,
@@ -2320,15 +2286,19 @@ function getColorMenu(self, colorsSource, menuClass) {
2320
2286
  }
2321
2287
 
2322
2288
  colorsArray.forEach((x) => {
2323
- const [value, label] = x.trim().split(':');
2324
- const xRealColor = new Color(value, format).toString();
2325
- 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');
2326
2296
  const active = isActive ? ' active' : '';
2327
2297
 
2328
2298
  const option = createElement({
2329
2299
  tagName: 'li',
2330
2300
  className: `color-option${active}`,
2331
- innerText: `${label || x}`,
2301
+ innerText: `${label || value}`,
2332
2302
  });
2333
2303
 
2334
2304
  setAttribute(option, tabIndex, '0');
@@ -2337,7 +2307,7 @@ function getColorMenu(self, colorsSource, menuClass) {
2337
2307
  setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2338
2308
 
2339
2309
  if (isOptionsMenu) {
2340
- setElementStyle(option, { backgroundColor: x });
2310
+ setElementStyle(option, { backgroundColor: value });
2341
2311
  }
2342
2312
 
2343
2313
  menu.append(option);
@@ -2346,55 +2316,10 @@ function getColorMenu(self, colorsSource, menuClass) {
2346
2316
  }
2347
2317
 
2348
2318
  /**
2349
- * Check if a string is valid JSON string.
2350
- * @param {string} str the string input
2351
- * @returns {boolean} the query result
2352
- */
2353
- function isValidJSON(str) {
2354
- try {
2355
- JSON.parse(str);
2356
- } catch (e) {
2357
- return false;
2358
- }
2359
- return true;
2360
- }
2361
-
2362
- var version = "0.0.1";
2363
-
2364
- // @ts-ignore
2365
-
2366
- const Version = version;
2367
-
2368
- // ColorPicker GC
2369
- // ==============
2370
- const colorPickerString = 'color-picker';
2371
- const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2372
- const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2373
- const colorPickerDefaults = {
2374
- componentLabels: colorPickerLabels,
2375
- colorLabels: colorNames,
2376
- format: 'rgb',
2377
- colorPresets: false,
2378
- colorKeywords: false,
2379
- };
2380
-
2381
- // ColorPicker Static Methods
2382
- // ==========================
2383
-
2384
- /** @type {CP.GetInstance<ColorPicker>} */
2385
- const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2386
-
2387
- /** @type {CP.InitCallback<ColorPicker>} */
2388
- const initColorPicker = (element) => new ColorPicker(element);
2389
-
2390
- // ColorPicker Private Methods
2391
- // ===========================
2392
-
2393
- /**
2394
- * Generate HTML markup and update instance properties.
2395
- * @param {ColorPicker} self
2396
- */
2397
- function initCallback(self) {
2319
+ * Generate HTML markup and update instance properties.
2320
+ * @param {CP.ColorPicker} self
2321
+ */
2322
+ function setMarkup(self) {
2398
2323
  const {
2399
2324
  input, parent, format, id, componentLabels, colorKeywords, colorPresets,
2400
2325
  } = self;
@@ -2409,9 +2334,7 @@ function initCallback(self) {
2409
2334
  self.color = new Color(color, format);
2410
2335
 
2411
2336
  // set initial controls dimensions
2412
- // make the controls smaller on mobile
2413
- const dropClass = isMobile ? ' mobile' : '';
2414
- const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
2337
+ const formatString = format === 'hex' ? hexLabel : toUpperCase(format);
2415
2338
 
2416
2339
  const pickerBtn = createElement({
2417
2340
  id: `picker-btn-${id}`,
@@ -2428,7 +2351,7 @@ function initCallback(self) {
2428
2351
 
2429
2352
  const pickerDropdown = createElement({
2430
2353
  tagName: 'div',
2431
- className: `color-dropdown picker${dropClass}`,
2354
+ className: 'color-dropdown picker',
2432
2355
  });
2433
2356
  setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
2434
2357
  setAttribute(pickerDropdown, 'role', 'group');
@@ -2444,7 +2367,7 @@ function initCallback(self) {
2444
2367
  if (colorKeywords || colorPresets) {
2445
2368
  const presetsDropdown = createElement({
2446
2369
  tagName: 'div',
2447
- className: `color-dropdown scrollable menu${dropClass}`,
2370
+ className: 'color-dropdown scrollable menu',
2448
2371
  });
2449
2372
 
2450
2373
  // color presets
@@ -2494,6 +2417,37 @@ function initCallback(self) {
2494
2417
  setAttribute(input, tabIndex, '-1');
2495
2418
  }
2496
2419
 
2420
+ var version = "0.0.2alpha3";
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
+
2497
2451
  /**
2498
2452
  * Add / remove `ColorPicker` main event listeners.
2499
2453
  * @param {ColorPicker} self
@@ -2506,8 +2460,6 @@ function toggleEvents(self, action) {
2506
2460
  fn(input, focusinEvent, self.showPicker);
2507
2461
  fn(pickerToggle, mouseclickEvent, self.togglePicker);
2508
2462
 
2509
- fn(input, keydownEvent, self.keyToggle);
2510
-
2511
2463
  if (menuToggle) {
2512
2464
  fn(menuToggle, mouseclickEvent, self.toggleMenu);
2513
2465
  }
@@ -2545,8 +2497,7 @@ function toggleEventsOnShown(self, action) {
2545
2497
  fn(doc, pointerEvents.move, self.pointerMove);
2546
2498
  fn(doc, pointerEvents.up, self.pointerUp);
2547
2499
  fn(parent, focusoutEvent, self.handleFocusOut);
2548
- // @ts-ignore -- this is `Window`
2549
- fn(win, keyupEvent, self.handleDismiss);
2500
+ fn(doc, keyupEvent, self.handleDismiss);
2550
2501
  }
2551
2502
 
2552
2503
  /**
@@ -2630,7 +2581,7 @@ class ColorPicker {
2630
2581
  const input = querySelector(target);
2631
2582
 
2632
2583
  // invalidate
2633
- if (!input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
2584
+ if (!input) throw new TypeError(`ColorPicker target "${target}" cannot be found.`);
2634
2585
  self.input = input;
2635
2586
 
2636
2587
  const parent = closest(input, colorPickerParentSelector);
@@ -2677,15 +2628,14 @@ class ColorPicker {
2677
2628
  });
2678
2629
 
2679
2630
  // update and expose component labels
2680
- const tempLabels = ObjectAssign({}, colorPickerLabels);
2681
- const jsonLabels = componentLabels && isValidJSON(componentLabels)
2682
- ? JSON.parse(componentLabels) : componentLabels || {};
2631
+ const tempComponentLabels = componentLabels && isValidJSON(componentLabels)
2632
+ ? JSON.parse(componentLabels) : componentLabels;
2683
2633
 
2684
2634
  /** @type {Record<string, string>} */
2685
- self.componentLabels = ObjectAssign(tempLabels, jsonLabels);
2635
+ self.componentLabels = ObjectAssign(colorPickerLabels, tempComponentLabels);
2686
2636
 
2687
2637
  /** @type {Color} */
2688
- self.color = new Color('white', format);
2638
+ self.color = new Color(input.value || '#fff', format);
2689
2639
 
2690
2640
  /** @type {CP.ColorFormats} */
2691
2641
  self.format = format;
@@ -2694,7 +2644,7 @@ class ColorPicker {
2694
2644
  if (colorKeywords instanceof Array) {
2695
2645
  self.colorKeywords = colorKeywords;
2696
2646
  } else if (typeof colorKeywords === 'string' && colorKeywords.length) {
2697
- self.colorKeywords = colorKeywords.split(',');
2647
+ self.colorKeywords = colorKeywords.split(',').map((x) => x.trim());
2698
2648
  }
2699
2649
 
2700
2650
  // set colour presets
@@ -2723,11 +2673,10 @@ class ColorPicker {
2723
2673
  self.handleFocusOut = self.handleFocusOut.bind(self);
2724
2674
  self.changeHandler = self.changeHandler.bind(self);
2725
2675
  self.handleDismiss = self.handleDismiss.bind(self);
2726
- self.keyToggle = self.keyToggle.bind(self);
2727
2676
  self.handleKnobs = self.handleKnobs.bind(self);
2728
2677
 
2729
2678
  // generate markup
2730
- initCallback(self);
2679
+ setMarkup(self);
2731
2680
 
2732
2681
  const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
2733
2682
  // set main elements
@@ -2815,76 +2764,83 @@ class ColorPicker {
2815
2764
  return inputValue !== '' && new Color(inputValue).isValid;
2816
2765
  }
2817
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
+
2818
2815
  /** Updates `ColorPicker` visuals. */
2819
2816
  updateVisuals() {
2820
2817
  const self = this;
2821
2818
  const {
2822
- format, controlPositions, visuals,
2819
+ controlPositions, visuals,
2823
2820
  } = self;
2824
2821
  const [v1, v2, v3] = visuals;
2825
- const { offsetWidth, offsetHeight } = v1;
2826
- const hue = format === 'hsl'
2827
- ? controlPositions.c1x / offsetWidth
2828
- : controlPositions.c2y / offsetHeight;
2829
- // @ts-ignore - `hslToRgb` is assigned to `Color` as static method
2830
- 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();
2831
2825
  const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
2832
2826
  const alpha = 1 - controlPositions.c3y / offsetHeight;
2833
2827
  const roundA = roundPart((alpha * 100)) / 100;
2834
2828
 
2835
- if (format !== 'hsl') {
2836
- const fill = new Color({
2837
- h: hue, s: 1, l: 0.5, a: alpha,
2838
- }).toRgbString();
2839
- const hueGradient = `linear-gradient(
2840
- rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2841
- rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2842
- rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2843
- rgb(255,0,0) 100%)`;
2844
- setElementStyle(v1, {
2845
- background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2846
- linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2847
- ${whiteGrad}`,
2848
- });
2849
- setElementStyle(v2, { background: hueGradient });
2850
- } else {
2851
- const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
2852
- const fill0 = new Color({
2853
- r: 255, g: 0, b: 0, a: alpha,
2854
- }).saturate(-saturation).toRgbString();
2855
- const fill1 = new Color({
2856
- r: 255, g: 255, b: 0, a: alpha,
2857
- }).saturate(-saturation).toRgbString();
2858
- const fill2 = new Color({
2859
- r: 0, g: 255, b: 0, a: alpha,
2860
- }).saturate(-saturation).toRgbString();
2861
- const fill3 = new Color({
2862
- r: 0, g: 255, b: 255, a: alpha,
2863
- }).saturate(-saturation).toRgbString();
2864
- const fill4 = new Color({
2865
- r: 0, g: 0, b: 255, a: alpha,
2866
- }).saturate(-saturation).toRgbString();
2867
- const fill5 = new Color({
2868
- r: 255, g: 0, b: 255, a: alpha,
2869
- }).saturate(-saturation).toRgbString();
2870
- const fill6 = new Color({
2871
- r: 255, g: 0, b: 0, a: alpha,
2872
- }).saturate(-saturation).toRgbString();
2873
- const fillGradient = `linear-gradient(to right,
2874
- ${fill0} 0%, ${fill1} 16.67%, ${fill2} 33.33%, ${fill3} 50%,
2875
- ${fill4} 66.67%, ${fill5} 83.33%, ${fill6} 100%)`;
2876
- const lightGrad = `linear-gradient(rgba(255,255,255,${roundA}) 0%, rgba(255,255,255,0) 50%),
2877
- linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,${roundA}) 100%)`;
2878
-
2879
- setElementStyle(v1, { background: `${lightGrad},${fillGradient},${whiteGrad}` });
2880
- const {
2881
- r: gr, g: gg, b: gb,
2882
- } = 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 });
2883
2843
 
2884
- setElementStyle(v2, {
2885
- background: `linear-gradient(rgb(${r},${g},${b}) 0%, rgb(${gr},${gg},${gb}) 100%)`,
2886
- });
2887
- }
2888
2844
  setElementStyle(v3, {
2889
2845
  background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
2890
2846
  });
@@ -2923,7 +2879,7 @@ class ColorPicker {
2923
2879
  const self = this;
2924
2880
  const { activeElement } = getDocument(self.input);
2925
2881
 
2926
- if ((isMobile && self.dragElement)
2882
+ if ((e.type === touchmoveEvent && self.dragElement)
2927
2883
  || (activeElement && self.controlKnobs.includes(activeElement))) {
2928
2884
  e.stopPropagation();
2929
2885
  e.preventDefault();
@@ -3034,13 +2990,13 @@ class ColorPicker {
3034
2990
  const [v1, v2, v3] = visuals;
3035
2991
  const [c1, c2, c3] = controlKnobs;
3036
2992
  /** @type {HTMLElement} */
3037
- const visual = hasClass(target, 'visual-control')
3038
- ? target : querySelector('.visual-control', target.parentElement);
2993
+ const visual = controlKnobs.includes(target) ? target.previousElementSibling : target;
3039
2994
  const visualRect = getBoundingClientRect(visual);
2995
+ const html = getDocumentElement(v1);
3040
2996
  const X = type === 'touchstart' ? touches[0].pageX : pageX;
3041
2997
  const Y = type === 'touchstart' ? touches[0].pageY : pageY;
3042
- const offsetX = X - window.pageXOffset - visualRect.left;
3043
- const offsetY = Y - window.pageYOffset - visualRect.top;
2998
+ const offsetX = X - html.scrollLeft - visualRect.left;
2999
+ const offsetY = Y - html.scrollTop - visualRect.top;
3044
3000
 
3045
3001
  if (target === v1 || target === c1) {
3046
3002
  self.dragElement = visual;
@@ -3100,10 +3056,11 @@ class ColorPicker {
3100
3056
  if (!dragElement) return;
3101
3057
 
3102
3058
  const controlRect = getBoundingClientRect(dragElement);
3103
- const X = type === 'touchmove' ? touches[0].pageX : pageX;
3104
- const Y = type === 'touchmove' ? touches[0].pageY : pageY;
3105
- const offsetX = X - window.pageXOffset - controlRect.left;
3106
- 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;
3107
3064
 
3108
3065
  if (dragElement === v1) {
3109
3066
  self.changeControl1(offsetX, offsetY);
@@ -3130,19 +3087,19 @@ class ColorPicker {
3130
3087
  if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
3131
3088
  e.preventDefault();
3132
3089
 
3133
- const { format, controlKnobs, visuals } = self;
3090
+ const { controlKnobs, visuals } = self;
3134
3091
  const { offsetWidth, offsetHeight } = visuals[0];
3135
3092
  const [c1, c2, c3] = controlKnobs;
3136
3093
  const { activeElement } = getDocument(c1);
3137
3094
  const currentKnob = controlKnobs.find((x) => x === activeElement);
3138
- const yRatio = offsetHeight / (format === 'hsl' ? 100 : 360);
3095
+ const yRatio = offsetHeight / 360;
3139
3096
 
3140
3097
  if (currentKnob) {
3141
3098
  let offsetX = 0;
3142
3099
  let offsetY = 0;
3143
3100
 
3144
3101
  if (target === c1) {
3145
- const xRatio = offsetWidth / (format === 'hsl' ? 360 : 100);
3102
+ const xRatio = offsetWidth / 100;
3146
3103
 
3147
3104
  if ([keyArrowLeft, keyArrowRight].includes(code)) {
3148
3105
  self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
@@ -3192,7 +3149,7 @@ class ColorPicker {
3192
3149
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
3193
3150
  if (activeElement === input) {
3194
3151
  if (isNonColorValue) {
3195
- colorSource = 'white';
3152
+ colorSource = currentValue === 'transparent' ? 'rgba(0,0,0,0)' : 'rgb(0,0,0)';
3196
3153
  } else {
3197
3154
  colorSource = currentValue;
3198
3155
  }
@@ -3243,9 +3200,7 @@ class ColorPicker {
3243
3200
  changeControl1(X, Y) {
3244
3201
  const self = this;
3245
3202
  let [offsetX, offsetY] = [0, 0];
3246
- const {
3247
- format, controlPositions, visuals,
3248
- } = self;
3203
+ const { controlPositions, visuals } = self;
3249
3204
  const { offsetHeight, offsetWidth } = visuals[0];
3250
3205
 
3251
3206
  if (X > offsetWidth) offsetX = offsetWidth;
@@ -3254,29 +3209,19 @@ class ColorPicker {
3254
3209
  if (Y > offsetHeight) offsetY = offsetHeight;
3255
3210
  else if (Y >= 0) offsetY = Y;
3256
3211
 
3257
- const hue = format === 'hsl'
3258
- ? offsetX / offsetWidth
3259
- : controlPositions.c2y / offsetHeight;
3212
+ const hue = controlPositions.c2y / offsetHeight;
3260
3213
 
3261
- const saturation = format === 'hsl'
3262
- ? 1 - controlPositions.c2y / offsetHeight
3263
- : offsetX / offsetWidth;
3214
+ const saturation = offsetX / offsetWidth;
3264
3215
 
3265
3216
  const lightness = 1 - offsetY / offsetHeight;
3266
3217
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3267
3218
 
3268
- const colorObject = format === 'hsl'
3269
- ? {
3270
- h: hue, s: saturation, l: lightness, a: alpha,
3271
- }
3272
- : {
3273
- h: hue, s: saturation, v: lightness, a: alpha,
3274
- };
3275
-
3276
3219
  // new color
3277
3220
  const {
3278
3221
  r, g, b, a,
3279
- } = new Color(colorObject);
3222
+ } = new Color({
3223
+ h: hue, s: saturation, v: lightness, a: alpha,
3224
+ });
3280
3225
 
3281
3226
  ObjectAssign(self.color, {
3282
3227
  r, g, b, a,
@@ -3303,7 +3248,7 @@ class ColorPicker {
3303
3248
  changeControl2(Y) {
3304
3249
  const self = this;
3305
3250
  const {
3306
- format, controlPositions, visuals,
3251
+ controlPositions, visuals,
3307
3252
  } = self;
3308
3253
  const { offsetHeight, offsetWidth } = visuals[0];
3309
3254
 
@@ -3312,26 +3257,17 @@ class ColorPicker {
3312
3257
  if (Y > offsetHeight) offsetY = offsetHeight;
3313
3258
  else if (Y >= 0) offsetY = Y;
3314
3259
 
3315
- const hue = format === 'hsl'
3316
- ? controlPositions.c1x / offsetWidth
3317
- : offsetY / offsetHeight;
3318
- const saturation = format === 'hsl'
3319
- ? 1 - offsetY / offsetHeight
3320
- : controlPositions.c1x / offsetWidth;
3260
+ const hue = offsetY / offsetHeight;
3261
+ const saturation = controlPositions.c1x / offsetWidth;
3321
3262
  const lightness = 1 - controlPositions.c1y / offsetHeight;
3322
3263
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3323
- const colorObject = format === 'hsl'
3324
- ? {
3325
- h: hue, s: saturation, l: lightness, a: alpha,
3326
- }
3327
- : {
3328
- h: hue, s: saturation, v: lightness, a: alpha,
3329
- };
3330
3264
 
3331
3265
  // new color
3332
3266
  const {
3333
3267
  r, g, b, a,
3334
- } = new Color(colorObject);
3268
+ } = new Color({
3269
+ h: hue, s: saturation, v: lightness, a: alpha,
3270
+ });
3335
3271
 
3336
3272
  ObjectAssign(self.color, {
3337
3273
  r, g, b, a,
@@ -3418,18 +3354,18 @@ class ColorPicker {
3418
3354
  setControlPositions() {
3419
3355
  const self = this;
3420
3356
  const {
3421
- format, visuals, color, hsl, hsv,
3357
+ visuals, color, hsv,
3422
3358
  } = self;
3423
3359
  const { offsetHeight, offsetWidth } = visuals[0];
3424
3360
  const alpha = color.a;
3425
- const hue = hsl.h;
3361
+ const hue = hsv.h;
3426
3362
 
3427
- const saturation = format !== 'hsl' ? hsv.s : hsl.s;
3428
- const lightness = format !== 'hsl' ? hsv.v : hsl.l;
3363
+ const saturation = hsv.s;
3364
+ const lightness = hsv.v;
3429
3365
 
3430
- self.controlPositions.c1x = format !== 'hsl' ? saturation * offsetWidth : hue * offsetWidth;
3366
+ self.controlPositions.c1x = saturation * offsetWidth;
3431
3367
  self.controlPositions.c1y = (1 - lightness) * offsetHeight;
3432
- self.controlPositions.c2y = format !== 'hsl' ? hue * offsetHeight : (1 - saturation) * offsetHeight;
3368
+ self.controlPositions.c2y = hue * offsetHeight;
3433
3369
  self.controlPositions.c3y = (1 - alpha) * offsetHeight;
3434
3370
  }
3435
3371
 
@@ -3437,78 +3373,40 @@ class ColorPicker {
3437
3373
  updateAppearance() {
3438
3374
  const self = this;
3439
3375
  const {
3440
- componentLabels, colorLabels, color, parent,
3441
- hsl, hsv, hex, format, controlKnobs,
3376
+ componentLabels, color, parent,
3377
+ hsv, hex, format, controlKnobs,
3442
3378
  } = self;
3443
3379
  const {
3444
3380
  appearanceLabel, hexLabel, valueLabel,
3445
3381
  } = componentLabels;
3446
- const { r, g, b } = color.toRgb();
3382
+ let { r, g, b } = color.toRgb();
3447
3383
  const [knob1, knob2, knob3] = controlKnobs;
3448
- const hue = roundPart(hsl.h * 360);
3384
+ const hue = roundPart(hsv.h * 360);
3449
3385
  const alpha = color.a;
3450
- const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
3451
- const saturation = roundPart(saturationSource * 100);
3452
- const lightness = roundPart(hsl.l * 100);
3453
- const hsvl = hsv.v * 100;
3454
- let colorName;
3455
-
3456
- // determine color appearance
3457
- if (lightness === 100 && saturation === 0) {
3458
- colorName = colorLabels.white;
3459
- } else if (lightness === 0) {
3460
- colorName = colorLabels.black;
3461
- } else if (saturation === 0) {
3462
- colorName = colorLabels.grey;
3463
- } else if (hue < 15 || hue >= 345) {
3464
- colorName = colorLabels.red;
3465
- } else if (hue >= 15 && hue < 45) {
3466
- colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
3467
- } else if (hue >= 45 && hue < 75) {
3468
- const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
3469
- const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
3470
- colorName = isGold ? colorLabels.gold : colorLabels.yellow;
3471
- colorName = isOlive ? colorLabels.olive : colorName;
3472
- } else if (hue >= 75 && hue < 155) {
3473
- colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
3474
- } else if (hue >= 155 && hue < 175) {
3475
- colorName = colorLabels.teal;
3476
- } else if (hue >= 175 && hue < 195) {
3477
- colorName = colorLabels.cyan;
3478
- } else if (hue >= 195 && hue < 255) {
3479
- colorName = colorLabels.blue;
3480
- } else if (hue >= 255 && hue < 270) {
3481
- colorName = colorLabels.violet;
3482
- } else if (hue >= 270 && hue < 295) {
3483
- colorName = colorLabels.magenta;
3484
- } else if (hue >= 295 && hue < 345) {
3485
- colorName = colorLabels.pink;
3486
- }
3386
+ const saturation = roundPart(hsv.s * 100);
3387
+ const lightness = roundPart(hsv.v * 100);
3388
+ const colorName = self.appearance;
3487
3389
 
3488
3390
  let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
3489
3391
 
3490
- if (format === 'hsl') {
3491
- colorLabel = `HSL: ${hue}°, ${saturation}%, ${lightness}%`;
3492
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3493
- setAttribute(knob1, ariaValueText, `${hue}° & ${lightness}%`);
3494
- setAttribute(knob1, ariaValueNow, `${hue}`);
3495
- setAttribute(knob2, ariaValueText, `${saturation}%`);
3496
- setAttribute(knob2, ariaValueNow, `${saturation}`);
3497
- } else if (format === 'hwb') {
3392
+ if (format === 'hwb') {
3498
3393
  const { hwb } = self;
3499
3394
  const whiteness = roundPart(hwb.w * 100);
3500
3395
  const blackness = roundPart(hwb.b * 100);
3501
3396
  colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
3502
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3503
3397
  setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
3504
3398
  setAttribute(knob1, ariaValueNow, `${whiteness}`);
3399
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3505
3400
  setAttribute(knob2, ariaValueText, `${hue}%`);
3506
3401
  setAttribute(knob2, ariaValueNow, `${hue}`);
3507
3402
  } else {
3403
+ [r, g, b] = [r, g, b].map(roundPart);
3404
+ colorLabel = format === 'hsl' ? `HSL: ${hue}°, ${saturation}%, ${lightness}%` : colorLabel;
3508
3405
  colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
3509
- setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3406
+
3510
3407
  setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
3511
3408
  setAttribute(knob1, ariaValueNow, `${lightness}`);
3409
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3512
3410
  setAttribute(knob2, ariaValueText, `${hue}°`);
3513
3411
  setAttribute(knob2, ariaValueNow, `${hue}`);
3514
3412
  }
@@ -3603,37 +3501,13 @@ class ColorPicker {
3603
3501
  }
3604
3502
  }
3605
3503
 
3606
- /**
3607
- * The `Space` & `Enter` keys specific event listener.
3608
- * Toggle visibility of the `ColorPicker` / the presets menu, showing one will hide the other.
3609
- * @param {KeyboardEvent} e
3610
- * @this {ColorPicker}
3611
- */
3612
- keyToggle(e) {
3613
- const self = this;
3614
- const { menuToggle } = self;
3615
- const { activeElement } = getDocument(menuToggle);
3616
- const { code } = e;
3617
-
3618
- if ([keyEnter, keySpace].includes(code)) {
3619
- if ((menuToggle && activeElement === menuToggle) || !activeElement) {
3620
- e.preventDefault();
3621
- if (!activeElement) {
3622
- self.togglePicker(e);
3623
- } else {
3624
- self.toggleMenu();
3625
- }
3626
- }
3627
- }
3628
- }
3629
-
3630
3504
  /**
3631
3505
  * Toggle the `ColorPicker` dropdown visibility.
3632
- * @param {Event} e
3506
+ * @param {Event=} e
3633
3507
  * @this {ColorPicker}
3634
3508
  */
3635
3509
  togglePicker(e) {
3636
- e.preventDefault();
3510
+ if (e) e.preventDefault();
3637
3511
  const self = this;
3638
3512
  const { colorPicker } = self;
3639
3513
 
@@ -3654,8 +3528,13 @@ class ColorPicker {
3654
3528
  }
3655
3529
  }
3656
3530
 
3657
- /** Toggles the visibility of the `ColorPicker` presets menu. */
3658
- 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();
3659
3538
  const self = this;
3660
3539
  const { colorMenu } = self;
3661
3540
 
@@ -3681,6 +3560,10 @@ class ColorPicker {
3681
3560
  const relatedBtn = openPicker ? pickerToggle : menuToggle;
3682
3561
  const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
3683
3562
 
3563
+ // if (!self.isValid) {
3564
+ self.value = self.color.toString(true);
3565
+ // }
3566
+
3684
3567
  if (openDropdown) {
3685
3568
  removeClass(openDropdown, 'show');
3686
3569
  setAttribute(relatedBtn, ariaExpanded, 'false');
@@ -3694,9 +3577,6 @@ class ColorPicker {
3694
3577
  }, animationDuration);
3695
3578
  }
3696
3579
 
3697
- if (!self.isValid) {
3698
- self.value = self.color.toString();
3699
- }
3700
3580
  if (!focusPrevented) {
3701
3581
  focus(pickerToggle);
3702
3582
  }
@@ -3739,4 +3619,4 @@ ObjectAssign(ColorPicker, {
3739
3619
  getBoundingClientRect,
3740
3620
  });
3741
3621
 
3742
- export default ColorPicker;
3622
+ export { ColorPicker as default };