@thednp/color-picker 0.0.1 → 0.0.2-alpha1

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.2alpha1 (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,97 @@ 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
+ return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
841
+ setElementStyle(documentHead, { color });
842
+ const computedColor = getElementStyle(documentHead, 'color');
843
+ setElementStyle(documentHead, { color: '' });
844
+ return computedColor !== c;
845
+ });
1164
846
  }
1165
847
 
1166
848
  /**
@@ -1181,15 +863,15 @@ function isValidCSSUnit(color) {
1181
863
  */
1182
864
  function bound01(N, max) {
1183
865
  let n = N;
1184
- if (isOnePointZero(n)) n = '100%';
1185
-
1186
- n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
866
+ if (isOnePointZero(N)) n = '100%';
1187
867
 
1188
- // Handle hue angles
1189
- if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
868
+ const processPercent = isPercentage(n);
869
+ n = max === 360
870
+ ? parseFloat(n)
871
+ : Math.min(max, Math.max(0, parseFloat(n)));
1190
872
 
1191
873
  // Automatically convert percentage into number
1192
- if (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
874
+ if (processPercent) n = (n * max) / 100;
1193
875
 
1194
876
  // Handle floating point rounding errors
1195
877
  if (Math.abs(n - max) < 0.000001) {
@@ -1200,11 +882,11 @@ function bound01(N, max) {
1200
882
  // If n is a hue given in degrees,
1201
883
  // wrap around out-of-range values into [0, 360] range
1202
884
  // then convert into [0, 1].
1203
- n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max));
885
+ n = (n < 0 ? (n % max) + max : n % max) / max;
1204
886
  } else {
1205
887
  // If n not a hue given in degrees
1206
888
  // Convert into [0, 1] range if it isn't already.
1207
- n = (n % max) / parseFloat(String(max));
889
+ n = (n % max) / max;
1208
890
  }
1209
891
  return n;
1210
892
  }
@@ -1239,7 +921,6 @@ function clamp01(v) {
1239
921
  * @returns {string}
1240
922
  */
1241
923
  function getRGBFromName(name) {
1242
- const documentHead = getDocumentHead();
1243
924
  setElementStyle(documentHead, { color: name });
1244
925
  const colorName = getElementStyle(documentHead, 'color');
1245
926
  setElementStyle(documentHead, { color: '' });
@@ -1338,6 +1019,36 @@ function hueToRgb(p, q, t) {
1338
1019
  return p;
1339
1020
  }
1340
1021
 
1022
+ /**
1023
+ * Converts an HSL colour value to RGB.
1024
+ *
1025
+ * @param {number} h Hue Angle [0, 1]
1026
+ * @param {number} s Saturation [0, 1]
1027
+ * @param {number} l Lightness Angle [0, 1]
1028
+ * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
1029
+ */
1030
+ function hslToRgb(h, s, l) {
1031
+ let r = 0;
1032
+ let g = 0;
1033
+ let b = 0;
1034
+
1035
+ if (s === 0) {
1036
+ // achromatic
1037
+ g = l;
1038
+ b = l;
1039
+ r = l;
1040
+ } else {
1041
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
1042
+ const p = 2 * l - q;
1043
+ r = hueToRgb(p, q, h + 1 / 3);
1044
+ g = hueToRgb(p, q, h);
1045
+ b = hueToRgb(p, q, h - 1 / 3);
1046
+ }
1047
+ [r, g, b] = [r, g, b].map((x) => x * 255);
1048
+
1049
+ return { r, g, b };
1050
+ }
1051
+
1341
1052
  /**
1342
1053
  * Returns an HWB colour object from an RGB colour object.
1343
1054
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
@@ -1400,36 +1111,6 @@ function hwbToRgb(H, W, B) {
1400
1111
  return { r, g, b };
1401
1112
  }
1402
1113
 
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);
1429
-
1430
- return { r, g, b };
1431
- }
1432
-
1433
1114
  /**
1434
1115
  * Converts an RGB colour value to HSV.
1435
1116
  *
@@ -1485,10 +1166,11 @@ function hsvToRgb(H, S, V) {
1485
1166
  const q = v * (1 - f * s);
1486
1167
  const t = v * (1 - (1 - f) * s);
1487
1168
  const mod = i % 6;
1488
- const r = [v, q, p, p, t, v][mod];
1489
- const g = [t, v, v, q, p, p][mod];
1490
- const b = [p, p, t, v, v, q][mod];
1491
- return { r: r * 255, g: g * 255, b: b * 255 };
1169
+ let r = [v, q, p, p, t, v][mod];
1170
+ let g = [t, v, v, q, p, p][mod];
1171
+ let b = [p, p, t, v, v, q][mod];
1172
+ [r, g, b] = [r, g, b].map((n) => n * 255);
1173
+ return { r, g, b };
1492
1174
  }
1493
1175
 
1494
1176
  /**
@@ -1512,7 +1194,7 @@ function rgbToHex(r, g, b, allow3Char) {
1512
1194
  // Return a 3 character hex if possible
1513
1195
  if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
1514
1196
  && hex[1].charAt(0) === hex[1].charAt(1)
1515
- && hex[2].charAt(0) === hex[2].charAt(1)) {
1197
+ && hex[2].charAt(0) === hex[2].charAt(1)) {
1516
1198
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
1517
1199
  }
1518
1200
 
@@ -1540,39 +1222,24 @@ function rgbaToHex(r, g, b, a, allow4Char) {
1540
1222
  // Return a 4 character hex if possible
1541
1223
  if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
1542
1224
  && 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)) {
1225
+ && hex[2].charAt(0) === hex[2].charAt(1)
1226
+ && hex[3].charAt(0) === hex[3].charAt(1)) {
1545
1227
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
1546
1228
  }
1547
1229
  return hex.join('');
1548
1230
  }
1549
1231
 
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
1232
  /**
1566
1233
  * Permissive string parsing. Take in a number of formats, and output an object
1567
1234
  * based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
1568
1235
  * @param {string} input colour value in any format
1569
- * @returns {Record<string, (number | string)> | false} an object matching the RegExp
1236
+ * @returns {Record<string, (number | string | boolean)> | false} an object matching the RegExp
1570
1237
  */
1571
1238
  function stringInputToObject(input) {
1572
- let color = input.trim().toLowerCase();
1239
+ let color = toLowerCase(input.trim());
1573
1240
  if (color.length === 0) {
1574
1241
  return {
1575
- r: 0, g: 0, b: 0, a: 0,
1242
+ r: 0, g: 0, b: 0, a: 1,
1576
1243
  };
1577
1244
  }
1578
1245
  let named = false;
@@ -1580,11 +1247,9 @@ function stringInputToObject(input) {
1580
1247
  color = getRGBFromName(color);
1581
1248
  named = true;
1582
1249
  } else if (nonColors.includes(color)) {
1583
- const isTransparent = color === 'transparent';
1584
- const rgb = isTransparent ? 0 : 255;
1585
- const a = isTransparent ? 0 : 1;
1250
+ const a = color === 'transparent' ? 0 : 1;
1586
1251
  return {
1587
- r: rgb, g: rgb, b: rgb, a, format: 'rgb',
1252
+ r: 0, g: 0, b: 0, a, format: 'rgb', ok: true,
1588
1253
  };
1589
1254
  }
1590
1255
 
@@ -1624,7 +1289,6 @@ function stringInputToObject(input) {
1624
1289
  g: parseIntFromHex(m2),
1625
1290
  b: parseIntFromHex(m3),
1626
1291
  a: convertHexToDecimal(m4),
1627
- // format: named ? 'rgb' : 'hex8',
1628
1292
  format: named ? 'rgb' : 'hex',
1629
1293
  };
1630
1294
  }
@@ -1688,6 +1352,7 @@ function stringInputToObject(input) {
1688
1352
  function inputToRGB(input) {
1689
1353
  let rgb = { r: 0, g: 0, b: 0 };
1690
1354
  let color = input;
1355
+ /** @type {string | number} */
1691
1356
  let a = 1;
1692
1357
  let s = null;
1693
1358
  let v = null;
@@ -1698,7 +1363,8 @@ function inputToRGB(input) {
1698
1363
  let r = null;
1699
1364
  let g = null;
1700
1365
  let ok = false;
1701
- let format = 'hex';
1366
+ const inputFormat = typeof color === 'object' && color.format;
1367
+ let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
1702
1368
 
1703
1369
  if (typeof input === 'string') {
1704
1370
  // @ts-ignore -- this now is converted to object
@@ -1739,14 +1405,17 @@ function inputToRGB(input) {
1739
1405
  format = 'hwb';
1740
1406
  }
1741
1407
  if (isValidCSSUnit(color.a)) {
1742
- a = color.a;
1743
- a = isPercentage(`${a}`) ? bound01(a, 100) : a;
1408
+ a = color.a; // @ts-ignore -- `parseFloat` works with numbers too
1409
+ a = isPercentage(`${a}`) || parseFloat(a) > 1 ? bound01(a, 100) : a;
1744
1410
  }
1745
1411
  }
1412
+ if (typeof color === 'undefined') {
1413
+ ok = true;
1414
+ }
1746
1415
 
1747
1416
  return {
1748
- ok, // @ts-ignore
1749
- format: color.format || format,
1417
+ ok,
1418
+ format,
1750
1419
  r: Math.min(255, Math.max(rgb.r, 0)),
1751
1420
  g: Math.min(255, Math.max(rgb.g, 0)),
1752
1421
  b: Math.min(255, Math.max(rgb.b, 0)),
@@ -1775,7 +1444,8 @@ class Color {
1775
1444
  color = inputToRGB(color);
1776
1445
  }
1777
1446
  if (typeof color === 'number') {
1778
- color = numberInputToObject(color);
1447
+ const len = `${color}`.length;
1448
+ color = `#${(len === 2 ? '0' : '00')}${color}`;
1779
1449
  }
1780
1450
  const {
1781
1451
  r, g, b, a, ok, format,
@@ -1785,7 +1455,7 @@ class Color {
1785
1455
  const self = this;
1786
1456
 
1787
1457
  /** @type {CP.ColorInput} */
1788
- self.originalInput = color;
1458
+ self.originalInput = input;
1789
1459
  /** @type {number} */
1790
1460
  self.r = r;
1791
1461
  /** @type {number} */
@@ -2175,6 +1845,7 @@ ObjectAssign(Color, {
2175
1845
  isOnePointZero,
2176
1846
  isPercentage,
2177
1847
  isValidCSSUnit,
1848
+ isColorName,
2178
1849
  pad2,
2179
1850
  clamp01,
2180
1851
  bound01,
@@ -2192,10 +1863,11 @@ ObjectAssign(Color, {
2192
1863
  hueToRgb,
2193
1864
  hwbToRgb,
2194
1865
  parseIntFromHex,
2195
- numberInputToObject,
2196
1866
  stringInputToObject,
2197
1867
  inputToRGB,
2198
1868
  roundPart,
1869
+ getElementStyle,
1870
+ setElementStyle,
2199
1871
  ObjectAssign,
2200
1872
  });
2201
1873
 
@@ -2204,7 +1876,7 @@ ObjectAssign(Color, {
2204
1876
  * Returns a color palette with a given set of parameters.
2205
1877
  * @example
2206
1878
  * new ColorPalette(0, 12, 10);
2207
- * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
1879
+ * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: Array<Color> }
2208
1880
  */
2209
1881
  class ColorPalette {
2210
1882
  /**
@@ -2224,11 +1896,14 @@ class ColorPalette {
2224
1896
  [hue, hueSteps, lightSteps] = args;
2225
1897
  } else if (args.length === 2) {
2226
1898
  [hueSteps, lightSteps] = args;
1899
+ if ([hueSteps, lightSteps].some((n) => n < 1)) {
1900
+ throw TypeError('ColorPalette: when 2 arguments used, both must be larger than 0.');
1901
+ }
2227
1902
  } else {
2228
1903
  throw TypeError('ColorPalette requires minimum 2 arguments');
2229
1904
  }
2230
1905
 
2231
- /** @type {string[]} */
1906
+ /** @type {Color[]} */
2232
1907
  const colors = [];
2233
1908
 
2234
1909
  const hueStep = 360 / hueSteps;
@@ -2257,7 +1932,7 @@ class ColorPalette {
2257
1932
  for (let i = 0; i < hueSteps; i += 1) {
2258
1933
  const currentHue = ((hue + i * hueStep) % 360) / 360;
2259
1934
  lightnessArray.forEach((l) => {
2260
- colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
1935
+ colors.push(new Color({ h: currentHue, s: 1, l }));
2261
1936
  });
2262
1937
  }
2263
1938
 
@@ -2268,6 +1943,310 @@ class ColorPalette {
2268
1943
  }
2269
1944
  }
2270
1945
 
1946
+ ObjectAssign(ColorPalette, { Color });
1947
+
1948
+ /** @type {Record<string, string>} */
1949
+ const colorPickerLabels = {
1950
+ pickerLabel: 'Colour Picker',
1951
+ appearanceLabel: 'Colour Appearance',
1952
+ valueLabel: 'Colour Value',
1953
+ toggleLabel: 'Select Colour',
1954
+ presetsLabel: 'Colour Presets',
1955
+ defaultsLabel: 'Colour Defaults',
1956
+ formatLabel: 'Format',
1957
+ alphaLabel: 'Alpha',
1958
+ hexLabel: 'Hexadecimal',
1959
+ hueLabel: 'Hue',
1960
+ whitenessLabel: 'Whiteness',
1961
+ blacknessLabel: 'Blackness',
1962
+ saturationLabel: 'Saturation',
1963
+ lightnessLabel: 'Lightness',
1964
+ redLabel: 'Red',
1965
+ greenLabel: 'Green',
1966
+ blueLabel: 'Blue',
1967
+ };
1968
+
1969
+ /**
1970
+ * A list of 17 color names used for WAI-ARIA compliance.
1971
+ * @type {string[]}
1972
+ */
1973
+ const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
1974
+
1975
+ const tabIndex = 'tabindex';
1976
+
1977
+ /**
1978
+ * Check if a string is valid JSON string.
1979
+ * @param {string} str the string input
1980
+ * @returns {boolean} the query result
1981
+ */
1982
+ function isValidJSON(str) {
1983
+ try {
1984
+ JSON.parse(str);
1985
+ } catch (e) {
1986
+ return false;
1987
+ }
1988
+ return true;
1989
+ }
1990
+
1991
+ /**
1992
+ * Shortcut for `String.toUpperCase()`.
1993
+ *
1994
+ * @param {string} source input string
1995
+ * @returns {string} uppercase output string
1996
+ */
1997
+ const toUpperCase = (source) => source.toUpperCase();
1998
+
1999
+ /**
2000
+ * A global namespace for aria-haspopup.
2001
+ * @type {string}
2002
+ */
2003
+ const ariaHasPopup = 'aria-haspopup';
2004
+
2005
+ /**
2006
+ * A global namespace for aria-hidden.
2007
+ * @type {string}
2008
+ */
2009
+ const ariaHidden = 'aria-hidden';
2010
+
2011
+ /**
2012
+ * A global namespace for aria-labelledby.
2013
+ * @type {string}
2014
+ */
2015
+ const ariaLabelledBy = 'aria-labelledby';
2016
+
2017
+ /**
2018
+ * This is a shortie for `document.createElement` method
2019
+ * which allows you to create a new `HTMLElement` for a given `tagName`
2020
+ * or based on an object with specific non-readonly attributes:
2021
+ * `id`, `className`, `textContent`, `style`, etc.
2022
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
2023
+ *
2024
+ * @param {Record<string, string> | string} param `tagName` or object
2025
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
2026
+ */
2027
+ function createElement(param) {
2028
+ if (typeof param === 'string') {
2029
+ return getDocument().createElement(param);
2030
+ }
2031
+
2032
+ const { tagName } = param;
2033
+ const attr = { ...param };
2034
+ const newElement = createElement(tagName);
2035
+ delete attr.tagName;
2036
+ ObjectAssign(newElement, attr);
2037
+ return newElement;
2038
+ }
2039
+
2040
+ /**
2041
+ * This is a shortie for `document.createElementNS` method
2042
+ * which allows you to create a new `HTMLElement` for a given `tagName`
2043
+ * or based on an object with specific non-readonly attributes:
2044
+ * `id`, `className`, `textContent`, `style`, etc.
2045
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
2046
+ *
2047
+ * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
2048
+ * @param {Record<string, string> | string} param `tagName` or object
2049
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
2050
+ */
2051
+ function createElementNS(namespace, param) {
2052
+ if (typeof param === 'string') {
2053
+ return getDocument().createElementNS(namespace, param);
2054
+ }
2055
+
2056
+ const { tagName } = param;
2057
+ const attr = { ...param };
2058
+ const newElement = createElementNS(namespace, tagName);
2059
+ delete attr.tagName;
2060
+ ObjectAssign(newElement, attr);
2061
+ return newElement;
2062
+ }
2063
+
2064
+ const vHidden = 'v-hidden';
2065
+
2066
+ /**
2067
+ * Returns the color form for `ColorPicker`.
2068
+ *
2069
+ * @param {CP.ColorPicker} self the `ColorPicker` instance
2070
+ * @returns {HTMLElement | Element} a new `<div>` element with color component `<input>`
2071
+ */
2072
+ function getColorForm(self) {
2073
+ const { format, id, componentLabels } = self;
2074
+ const colorForm = createElement({
2075
+ tagName: 'div',
2076
+ className: `color-form ${format}`,
2077
+ });
2078
+
2079
+ let components = ['hex'];
2080
+ if (format === 'rgb') components = ['red', 'green', 'blue', 'alpha'];
2081
+ else if (format === 'hsl') components = ['hue', 'saturation', 'lightness', 'alpha'];
2082
+ else if (format === 'hwb') components = ['hue', 'whiteness', 'blackness', 'alpha'];
2083
+
2084
+ components.forEach((c) => {
2085
+ const [C] = format === 'hex' ? ['#'] : toUpperCase(c).split('');
2086
+ const cID = `color_${format}_${c}_${id}`;
2087
+ const formatLabel = componentLabels[`${c}Label`];
2088
+ const cInputLabel = createElement({ tagName: 'label' });
2089
+ setAttribute(cInputLabel, 'for', cID);
2090
+ cInputLabel.append(
2091
+ createElement({ tagName: 'span', ariaHidden: 'true', innerText: `${C}:` }),
2092
+ createElement({ tagName: 'span', className: vHidden, innerText: formatLabel }),
2093
+ );
2094
+ const cInput = createElement({
2095
+ tagName: 'input',
2096
+ id: cID,
2097
+ // name: cID, - prevent saving the value to a form
2098
+ type: format === 'hex' ? 'text' : 'number',
2099
+ value: c === 'alpha' ? '100' : '0',
2100
+ className: `color-input ${c}`,
2101
+ });
2102
+ setAttribute(cInput, 'autocomplete', 'off');
2103
+ setAttribute(cInput, 'spellcheck', 'false');
2104
+
2105
+ // alpha
2106
+ let max = '100';
2107
+ let step = '1';
2108
+ if (c !== 'alpha') {
2109
+ if (format === 'rgb') {
2110
+ max = '255'; step = '1';
2111
+ } else if (c === 'hue') {
2112
+ max = '360'; step = '1';
2113
+ }
2114
+ }
2115
+ ObjectAssign(cInput, {
2116
+ min: '0',
2117
+ max,
2118
+ step,
2119
+ });
2120
+ colorForm.append(cInputLabel, cInput);
2121
+ });
2122
+ return colorForm;
2123
+ }
2124
+
2125
+ /**
2126
+ * A global namespace for aria-label.
2127
+ * @type {string}
2128
+ */
2129
+ const ariaLabel = 'aria-label';
2130
+
2131
+ /**
2132
+ * A global namespace for aria-valuemin.
2133
+ * @type {string}
2134
+ */
2135
+ const ariaValueMin = 'aria-valuemin';
2136
+
2137
+ /**
2138
+ * A global namespace for aria-valuemax.
2139
+ * @type {string}
2140
+ */
2141
+ const ariaValueMax = 'aria-valuemax';
2142
+
2143
+ /**
2144
+ * Returns all color controls for `ColorPicker`.
2145
+ *
2146
+ * @param {CP.ColorPicker} self the `ColorPicker` instance
2147
+ * @returns {HTMLElement | Element} color controls
2148
+ */
2149
+ function getColorControls(self) {
2150
+ const { format, componentLabels } = self;
2151
+ const {
2152
+ hueLabel, alphaLabel, lightnessLabel, saturationLabel,
2153
+ whitenessLabel, blacknessLabel,
2154
+ } = componentLabels;
2155
+
2156
+ const max1 = format === 'hsl' ? 360 : 100;
2157
+ const max2 = format === 'hsl' ? 100 : 360;
2158
+ const max3 = 100;
2159
+
2160
+ let ctrl1Label = format === 'hsl'
2161
+ ? `${hueLabel} & ${lightnessLabel}`
2162
+ : `${lightnessLabel} & ${saturationLabel}`;
2163
+
2164
+ ctrl1Label = format === 'hwb'
2165
+ ? `${whitenessLabel} & ${blacknessLabel}`
2166
+ : ctrl1Label;
2167
+
2168
+ const ctrl2Label = format === 'hsl'
2169
+ ? `${saturationLabel}`
2170
+ : `${hueLabel}`;
2171
+
2172
+ const colorControls = createElement({
2173
+ tagName: 'div',
2174
+ className: `color-controls ${format}`,
2175
+ });
2176
+
2177
+ const colorPointer = 'color-pointer';
2178
+ const colorSlider = 'color-slider';
2179
+
2180
+ const controls = [
2181
+ {
2182
+ i: 1,
2183
+ c: colorPointer,
2184
+ l: ctrl1Label,
2185
+ min: 0,
2186
+ max: max1,
2187
+ },
2188
+ {
2189
+ i: 2,
2190
+ c: colorSlider,
2191
+ l: ctrl2Label,
2192
+ min: 0,
2193
+ max: max2,
2194
+ },
2195
+ {
2196
+ i: 3,
2197
+ c: colorSlider,
2198
+ l: alphaLabel,
2199
+ min: 0,
2200
+ max: max3,
2201
+ },
2202
+ ];
2203
+
2204
+ controls.forEach((template) => {
2205
+ const {
2206
+ i, c, l, min, max,
2207
+ } = template;
2208
+ const control = createElement({
2209
+ tagName: 'div',
2210
+ className: 'color-control',
2211
+ });
2212
+ setAttribute(control, 'role', 'presentation');
2213
+
2214
+ control.append(
2215
+ createElement({
2216
+ tagName: 'div',
2217
+ className: `visual-control visual-control${i}`,
2218
+ }),
2219
+ );
2220
+
2221
+ const knob = createElement({
2222
+ tagName: 'div',
2223
+ className: `${c} knob`,
2224
+ ariaLive: 'polite',
2225
+ });
2226
+
2227
+ setAttribute(knob, ariaLabel, l);
2228
+ setAttribute(knob, 'role', 'slider');
2229
+ setAttribute(knob, tabIndex, '0');
2230
+ setAttribute(knob, ariaValueMin, `${min}`);
2231
+ setAttribute(knob, ariaValueMax, `${max}`);
2232
+ control.append(knob);
2233
+ colorControls.append(control);
2234
+ });
2235
+
2236
+ return colorControls;
2237
+ }
2238
+
2239
+ /**
2240
+ * Helps setting CSS variables to the color-menu.
2241
+ * @param {HTMLElement} element
2242
+ * @param {Record<string,any>} props
2243
+ */
2244
+ function setCSSProperties(element, props) {
2245
+ ObjectKeys(props).forEach((prop) => {
2246
+ element.style.setProperty(prop, props[prop]);
2247
+ });
2248
+ }
2249
+
2271
2250
  /**
2272
2251
  * Returns a color-defaults with given values and class.
2273
2252
  * @param {CP.ColorPicker} self
@@ -2301,7 +2280,8 @@ function getColorMenu(self, colorsSource, menuClass) {
2301
2280
  optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2302
2281
  const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2303
2282
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2304
-
2283
+ /** @type {HTMLUListElement} */
2284
+ // @ts-ignore -- <UL> is an `HTMLElement`
2305
2285
  const menu = createElement({
2306
2286
  tagName: 'ul',
2307
2287
  className: finalClass,
@@ -2309,7 +2289,7 @@ function getColorMenu(self, colorsSource, menuClass) {
2309
2289
  setAttribute(menu, 'role', 'listbox');
2310
2290
  setAttribute(menu, ariaLabel, menuLabel);
2311
2291
 
2312
- if (isScrollable) { // @ts-ignore
2292
+ if (isScrollable) {
2313
2293
  setCSSProperties(menu, {
2314
2294
  '--grid-item-size': `${optionSize}rem`,
2315
2295
  '--grid-fit': fit,
@@ -2320,15 +2300,19 @@ function getColorMenu(self, colorsSource, menuClass) {
2320
2300
  }
2321
2301
 
2322
2302
  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');
2303
+ let [value, label] = typeof x === 'string' ? x.trim().split(':') : [];
2304
+ if (x instanceof Color) {
2305
+ value = x.toHexString();
2306
+ label = value;
2307
+ }
2308
+ const color = new Color(x instanceof Color ? x : value, format);
2309
+ const isActive = color.toString() === getAttribute(input, 'value');
2326
2310
  const active = isActive ? ' active' : '';
2327
2311
 
2328
2312
  const option = createElement({
2329
2313
  tagName: 'li',
2330
2314
  className: `color-option${active}`,
2331
- innerText: `${label || x}`,
2315
+ innerText: `${label || value}`,
2332
2316
  });
2333
2317
 
2334
2318
  setAttribute(option, tabIndex, '0');
@@ -2337,7 +2321,7 @@ function getColorMenu(self, colorsSource, menuClass) {
2337
2321
  setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2338
2322
 
2339
2323
  if (isOptionsMenu) {
2340
- setElementStyle(option, { backgroundColor: x });
2324
+ setElementStyle(option, { backgroundColor: value });
2341
2325
  }
2342
2326
 
2343
2327
  menu.append(option);
@@ -2346,55 +2330,10 @@ function getColorMenu(self, colorsSource, menuClass) {
2346
2330
  }
2347
2331
 
2348
2332
  /**
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) {
2333
+ * Generate HTML markup and update instance properties.
2334
+ * @param {CP.ColorPicker} self
2335
+ */
2336
+ function setMarkup(self) {
2398
2337
  const {
2399
2338
  input, parent, format, id, componentLabels, colorKeywords, colorPresets,
2400
2339
  } = self;
@@ -2409,9 +2348,7 @@ function initCallback(self) {
2409
2348
  self.color = new Color(color, format);
2410
2349
 
2411
2350
  // set initial controls dimensions
2412
- // make the controls smaller on mobile
2413
- const dropClass = isMobile ? ' mobile' : '';
2414
- const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
2351
+ const formatString = format === 'hex' ? hexLabel : toUpperCase(format);
2415
2352
 
2416
2353
  const pickerBtn = createElement({
2417
2354
  id: `picker-btn-${id}`,
@@ -2428,7 +2365,7 @@ function initCallback(self) {
2428
2365
 
2429
2366
  const pickerDropdown = createElement({
2430
2367
  tagName: 'div',
2431
- className: `color-dropdown picker${dropClass}`,
2368
+ className: 'color-dropdown picker',
2432
2369
  });
2433
2370
  setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
2434
2371
  setAttribute(pickerDropdown, 'role', 'group');
@@ -2444,7 +2381,7 @@ function initCallback(self) {
2444
2381
  if (colorKeywords || colorPresets) {
2445
2382
  const presetsDropdown = createElement({
2446
2383
  tagName: 'div',
2447
- className: `color-dropdown scrollable menu${dropClass}`,
2384
+ className: 'color-dropdown scrollable menu',
2448
2385
  });
2449
2386
 
2450
2387
  // color presets
@@ -2494,6 +2431,37 @@ function initCallback(self) {
2494
2431
  setAttribute(input, tabIndex, '-1');
2495
2432
  }
2496
2433
 
2434
+ var version = "0.0.2alpha1";
2435
+
2436
+ // @ts-ignore
2437
+
2438
+ const Version = version;
2439
+
2440
+ // ColorPicker GC
2441
+ // ==============
2442
+ const colorPickerString = 'color-picker';
2443
+ const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2444
+ const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2445
+ const colorPickerDefaults = {
2446
+ componentLabels: colorPickerLabels,
2447
+ colorLabels: colorNames,
2448
+ format: 'rgb',
2449
+ colorPresets: false,
2450
+ colorKeywords: false,
2451
+ };
2452
+
2453
+ // ColorPicker Static Methods
2454
+ // ==========================
2455
+
2456
+ /** @type {CP.GetInstance<ColorPicker>} */
2457
+ const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2458
+
2459
+ /** @type {CP.InitCallback<ColorPicker>} */
2460
+ const initColorPicker = (element) => new ColorPicker(element);
2461
+
2462
+ // ColorPicker Private Methods
2463
+ // ===========================
2464
+
2497
2465
  /**
2498
2466
  * Add / remove `ColorPicker` main event listeners.
2499
2467
  * @param {ColorPicker} self
@@ -2727,7 +2695,7 @@ class ColorPicker {
2727
2695
  self.handleKnobs = self.handleKnobs.bind(self);
2728
2696
 
2729
2697
  // generate markup
2730
- initCallback(self);
2698
+ setMarkup(self);
2731
2699
 
2732
2700
  const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
2733
2701
  // set main elements
@@ -2923,7 +2891,7 @@ class ColorPicker {
2923
2891
  const self = this;
2924
2892
  const { activeElement } = getDocument(self.input);
2925
2893
 
2926
- if ((isMobile && self.dragElement)
2894
+ if ((e.type === touchmoveEvent && self.dragElement)
2927
2895
  || (activeElement && self.controlKnobs.includes(activeElement))) {
2928
2896
  e.stopPropagation();
2929
2897
  e.preventDefault();
@@ -3739,4 +3707,4 @@ ObjectAssign(ColorPicker, {
3739
3707
  getBoundingClientRect,
3740
3708
  });
3741
3709
 
3742
- export default ColorPicker;
3710
+ export { ColorPicker as default };