@thednp/color-picker 0.0.1 → 0.0.2-alpha3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 };