@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,13 +1,13 @@
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
  */
6
6
  (function (global, factory) {
7
7
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
8
8
  typeof define === 'function' && define.amd ? define(factory) :
9
- (global = global || self, global.ColorPicker = factory());
10
- }(this, (function () { 'use strict';
9
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ColorPicker = factory());
10
+ })(this, (function () { 'use strict';
11
11
 
12
12
  /** @type {Record<string, any>} */
13
13
  const EventRegistry = {};
@@ -135,24 +135,6 @@
135
135
  */
136
136
  const ariaValueNow = 'aria-valuenow';
137
137
 
138
- /**
139
- * A global namespace for aria-haspopup.
140
- * @type {string}
141
- */
142
- const ariaHasPopup = 'aria-haspopup';
143
-
144
- /**
145
- * A global namespace for aria-hidden.
146
- * @type {string}
147
- */
148
- const ariaHidden = 'aria-hidden';
149
-
150
- /**
151
- * A global namespace for aria-labelledby.
152
- * @type {string}
153
- */
154
- const ariaLabelledBy = 'aria-labelledby';
155
-
156
138
  /**
157
139
  * A global namespace for `ArrowDown` key.
158
140
  * @type {string} e.which = 40 equivalent
@@ -279,37 +261,6 @@
279
261
  */
280
262
  const focusoutEvent = 'focusout';
281
263
 
282
- // @ts-ignore
283
- const { userAgentData: uaDATA } = navigator;
284
-
285
- /**
286
- * A global namespace for `userAgentData` object.
287
- */
288
- const userAgentData = uaDATA;
289
-
290
- const { userAgent: userAgentString } = navigator;
291
-
292
- /**
293
- * A global namespace for `navigator.userAgent` string.
294
- */
295
- const userAgent = userAgentString;
296
-
297
- const mobileBrands = /iPhone|iPad|iPod|Android/i;
298
- let isMobileCheck = false;
299
-
300
- if (userAgentData) {
301
- isMobileCheck = userAgentData.brands
302
- .some((/** @type {Record<String, any>} */x) => mobileBrands.test(x.brand));
303
- } else {
304
- isMobileCheck = mobileBrands.test(userAgent);
305
- }
306
-
307
- /**
308
- * A global `boolean` for mobile detection.
309
- * @type {boolean}
310
- */
311
- const isMobile = isMobileCheck;
312
-
313
264
  /**
314
265
  * Returns the `document` or the `#document` element.
315
266
  * @see https://github.com/floating-ui/floating-ui
@@ -530,60 +481,6 @@
530
481
  return lookUp.getElementsByClassName(selector);
531
482
  }
532
483
 
533
- /**
534
- * Shortcut for `Object.assign()` static method.
535
- * @param {Record<string, any>} obj a target object
536
- * @param {Record<string, any>} source a source object
537
- */
538
- const ObjectAssign = (obj, source) => Object.assign(obj, source);
539
-
540
- /**
541
- * This is a shortie for `document.createElement` method
542
- * which allows you to create a new `HTMLElement` for a given `tagName`
543
- * or based on an object with specific non-readonly attributes:
544
- * `id`, `className`, `textContent`, `style`, etc.
545
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
546
- *
547
- * @param {Record<string, string> | string} param `tagName` or object
548
- * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
549
- */
550
- function createElement(param) {
551
- if (typeof param === 'string') {
552
- return getDocument().createElement(param);
553
- }
554
-
555
- const { tagName } = param;
556
- const attr = { ...param };
557
- const newElement = createElement(tagName);
558
- delete attr.tagName;
559
- ObjectAssign(newElement, attr);
560
- return newElement;
561
- }
562
-
563
- /**
564
- * This is a shortie for `document.createElementNS` method
565
- * which allows you to create a new `HTMLElement` for a given `tagName`
566
- * or based on an object with specific non-readonly attributes:
567
- * `id`, `className`, `textContent`, `style`, etc.
568
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
569
- *
570
- * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
571
- * @param {Record<string, string> | string} param `tagName` or object
572
- * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
573
- */
574
- function createElementNS(namespace, param) {
575
- if (typeof param === 'string') {
576
- return getDocument().createElementNS(namespace, param);
577
- }
578
-
579
- const { tagName } = param;
580
- const attr = { ...param };
581
- const newElement = createElementNS(namespace, tagName);
582
- delete attr.tagName;
583
- ObjectAssign(newElement, attr);
584
- return newElement;
585
- }
586
-
587
484
  /**
588
485
  * Shortcut for the `Element.dispatchEvent(Event)` method.
589
486
  *
@@ -592,6 +489,13 @@
592
489
  */
593
490
  const dispatchEvent = (element, event) => element.dispatchEvent(event);
594
491
 
492
+ /**
493
+ * Shortcut for `Object.assign()` static method.
494
+ * @param {Record<string, any>} obj a target object
495
+ * @param {Record<string, any>} source a source object
496
+ */
497
+ const ObjectAssign = (obj, source) => Object.assign(obj, source);
498
+
595
499
  /** @type {Map<string, Map<HTMLElement | Element, Record<string, any>>>} */
596
500
  const componentData = new Map();
597
501
  /**
@@ -667,20 +571,13 @@
667
571
  */
668
572
  const getInstance = (target, component) => Data.get(target, component);
669
573
 
670
- /**
671
- * Shortcut for `Object.keys()` static method.
672
- * @param {Record<string, any>} obj a target object
673
- * @returns {string[]}
674
- */
675
- const ObjectKeys = (obj) => Object.keys(obj);
676
-
677
574
  /**
678
575
  * Shortcut for multiple uses of `HTMLElement.style.propertyName` method.
679
576
  * @param {HTMLElement | Element} element target element
680
577
  * @param {Partial<CSSStyleDeclaration>} styles attribute value
681
578
  */
682
579
  // @ts-ignore
683
- const setElementStyle = (element, styles) => ObjectAssign(element.style, styles);
580
+ const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
684
581
 
685
582
  /**
686
583
  * Shortcut for `HTMLElement.getAttribute()` method.
@@ -723,6 +620,13 @@
723
620
  return value;
724
621
  }
725
622
 
623
+ /**
624
+ * Shortcut for `Object.keys()` static method.
625
+ * @param {Record<string, any>} obj a target object
626
+ * @returns {string[]}
627
+ */
628
+ const ObjectKeys = (obj) => Object.keys(obj);
629
+
726
630
  /**
727
631
  * Shortcut for `String.toLowerCase()`.
728
632
  *
@@ -843,32 +747,10 @@
843
747
  */
844
748
  const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
845
749
 
846
- /** @type {Record<string, string>} */
847
- const colorPickerLabels = {
848
- pickerLabel: 'Colour Picker',
849
- appearanceLabel: 'Colour Appearance',
850
- valueLabel: 'Colour Value',
851
- toggleLabel: 'Select Colour',
852
- presetsLabel: 'Colour Presets',
853
- defaultsLabel: 'Colour Defaults',
854
- formatLabel: 'Format',
855
- alphaLabel: 'Alpha',
856
- hexLabel: 'Hexadecimal',
857
- hueLabel: 'Hue',
858
- whitenessLabel: 'Whiteness',
859
- blacknessLabel: 'Blackness',
860
- saturationLabel: 'Saturation',
861
- lightnessLabel: 'Lightness',
862
- redLabel: 'Red',
863
- greenLabel: 'Green',
864
- blueLabel: 'Blue',
865
- };
866
-
867
750
  /**
868
- * A list of 17 color names used for WAI-ARIA compliance.
869
- * @type {string[]}
751
+ * A global namespace for `document.head`.
870
752
  */
871
- const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
753
+ const { head: documentHead } = document;
872
754
 
873
755
  /**
874
756
  * A list of explicit default non-color values.
@@ -876,297 +758,97 @@
876
758
  const nonColors = ['transparent', 'currentColor', 'inherit', 'revert', 'initial'];
877
759
 
878
760
  /**
879
- * Shortcut for `String.toUpperCase()`.
880
- *
881
- * @param {string} source input string
882
- * @returns {string} uppercase output string
761
+ * Round colour components, for all formats except HEX.
762
+ * @param {number} v one of the colour components
763
+ * @returns {number} the rounded number
883
764
  */
884
- const toUpperCase = (source) => source.toUpperCase();
765
+ function roundPart(v) {
766
+ const floor = Math.floor(v);
767
+ return v - floor < 0.5 ? floor : Math.round(v);
768
+ }
885
769
 
886
- const vHidden = 'v-hidden';
770
+ // Color supported formats
771
+ const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsv', 'hwb'];
887
772
 
888
- /**
889
- * Returns the color form for `ColorPicker`.
890
- *
891
- * @param {CP.ColorPicker} self the `ColorPicker` instance
892
- * @returns {HTMLElement | Element} a new `<div>` element with color component `<input>`
893
- */
894
- function getColorForm(self) {
895
- const { format, id, componentLabels } = self;
896
- const colorForm = createElement({
897
- tagName: 'div',
898
- className: `color-form ${format}`,
899
- });
773
+ // Hue angles
774
+ const ANGLES = 'deg|rad|grad|turn';
900
775
 
901
- let components = ['hex'];
902
- if (format === 'rgb') components = ['red', 'green', 'blue', 'alpha'];
903
- else if (format === 'hsl') components = ['hue', 'saturation', 'lightness', 'alpha'];
904
- else if (format === 'hwb') components = ['hue', 'whiteness', 'blackness', 'alpha'];
776
+ // <http://www.w3.org/TR/css3-values/#integers>
777
+ const CSS_INTEGER = '[-\\+]?\\d+%?';
905
778
 
906
- components.forEach((c) => {
907
- const [C] = format === 'hex' ? ['#'] : toUpperCase(c).split('');
908
- const cID = `color_${format}_${c}_${id}`;
909
- const formatLabel = componentLabels[`${c}Label`];
910
- const cInputLabel = createElement({ tagName: 'label' });
911
- setAttribute(cInputLabel, 'for', cID);
912
- cInputLabel.append(
913
- createElement({ tagName: 'span', ariaHidden: 'true', innerText: `${C}:` }),
914
- createElement({ tagName: 'span', className: vHidden, innerText: formatLabel }),
915
- );
916
- const cInput = createElement({
917
- tagName: 'input',
918
- id: cID,
919
- // name: cID, - prevent saving the value to a form
920
- type: format === 'hex' ? 'text' : 'number',
921
- value: c === 'alpha' ? '100' : '0',
922
- className: `color-input ${c}`,
923
- });
924
- setAttribute(cInput, 'autocomplete', 'off');
925
- setAttribute(cInput, 'spellcheck', 'false');
779
+ // Include CSS3 Module
780
+ // <http://www.w3.org/TR/css3-values/#number-value>
781
+ const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
926
782
 
927
- // alpha
928
- let max = '100';
929
- let step = '1';
930
- if (c !== 'alpha') {
931
- if (format === 'rgb') {
932
- max = '255'; step = '1';
933
- } else if (c === 'hue') {
934
- max = '360'; step = '1';
935
- }
936
- }
937
- ObjectAssign(cInput, {
938
- min: '0',
939
- max,
940
- step,
941
- });
942
- colorForm.append(cInputLabel, cInput);
943
- });
944
- return colorForm;
945
- }
783
+ // Include CSS4 Module Hue degrees unit
784
+ // <https://www.w3.org/TR/css3-values/#angle-value>
785
+ const CSS_ANGLE = `[-\\+]?\\d*\\.?\\d+(?:${ANGLES})?`;
786
+
787
+ // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
788
+ const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
789
+
790
+ // Add angles to the mix
791
+ const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
792
+
793
+ // Start & end
794
+ const START_MATCH = '(?:[\\s|\\(\\s|\\s\\(\\s]+)?';
795
+ const END_MATCH = '(?:[\\s|\\)\\s]+)?';
796
+ // Components separation
797
+ const SEP = '(?:[,|\\s]+)';
798
+ const SEP2 = '(?:[,|\\/\\s]*)?';
799
+
800
+ // Actual matching.
801
+ // Parentheses and commas are optional, but not required.
802
+ // Whitespace can take the place of commas or opening paren
803
+ const PERMISSIVE_MATCH = `${START_MATCH}(${CSS_UNIT2})${SEP}(${CSS_UNIT})${SEP}(${CSS_UNIT})${SEP2}(${CSS_UNIT})?${END_MATCH}`;
804
+
805
+ const matchers = {
806
+ CSS_UNIT: new RegExp(CSS_UNIT2),
807
+ hwb: new RegExp(`hwb${PERMISSIVE_MATCH}`),
808
+ rgb: new RegExp(`rgb(?:a)?${PERMISSIVE_MATCH}`),
809
+ hsl: new RegExp(`hsl(?:a)?${PERMISSIVE_MATCH}`),
810
+ hsv: new RegExp(`hsv(?:a)?${PERMISSIVE_MATCH}`),
811
+ hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
812
+ hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
813
+ hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
814
+ hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
815
+ };
946
816
 
947
817
  /**
948
- * A global namespace for aria-label.
949
- * @type {string}
818
+ * Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
819
+ * <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
820
+ * @param {string} n testing number
821
+ * @returns {boolean} the query result
950
822
  */
951
- const ariaLabel = 'aria-label';
823
+ function isOnePointZero(n) {
824
+ return `${n}`.includes('.') && parseFloat(n) === 1;
825
+ }
952
826
 
953
827
  /**
954
- * A global namespace for aria-valuemin.
955
- * @type {string}
828
+ * Check to see if string passed in is a percentage
829
+ * @param {string} n testing number
830
+ * @returns {boolean} the query result
956
831
  */
957
- const ariaValueMin = 'aria-valuemin';
958
-
959
- /**
960
- * A global namespace for aria-valuemax.
961
- * @type {string}
962
- */
963
- const ariaValueMax = 'aria-valuemax';
964
-
965
- const tabIndex = 'tabindex';
966
-
967
- /**
968
- * Returns all color controls for `ColorPicker`.
969
- *
970
- * @param {CP.ColorPicker} self the `ColorPicker` instance
971
- * @returns {HTMLElement | Element} color controls
972
- */
973
- function getColorControls(self) {
974
- const { format, componentLabels } = self;
975
- const {
976
- hueLabel, alphaLabel, lightnessLabel, saturationLabel,
977
- whitenessLabel, blacknessLabel,
978
- } = componentLabels;
979
-
980
- const max1 = format === 'hsl' ? 360 : 100;
981
- const max2 = format === 'hsl' ? 100 : 360;
982
- const max3 = 100;
983
-
984
- let ctrl1Label = format === 'hsl'
985
- ? `${hueLabel} & ${lightnessLabel}`
986
- : `${lightnessLabel} & ${saturationLabel}`;
987
-
988
- ctrl1Label = format === 'hwb'
989
- ? `${whitenessLabel} & ${blacknessLabel}`
990
- : ctrl1Label;
991
-
992
- const ctrl2Label = format === 'hsl'
993
- ? `${saturationLabel}`
994
- : `${hueLabel}`;
995
-
996
- const colorControls = createElement({
997
- tagName: 'div',
998
- className: `color-controls ${format}`,
999
- });
1000
-
1001
- const colorPointer = 'color-pointer';
1002
- const colorSlider = 'color-slider';
1003
-
1004
- const controls = [
1005
- {
1006
- i: 1,
1007
- c: colorPointer,
1008
- l: ctrl1Label,
1009
- min: 0,
1010
- max: max1,
1011
- },
1012
- {
1013
- i: 2,
1014
- c: colorSlider,
1015
- l: ctrl2Label,
1016
- min: 0,
1017
- max: max2,
1018
- },
1019
- {
1020
- i: 3,
1021
- c: colorSlider,
1022
- l: alphaLabel,
1023
- min: 0,
1024
- max: max3,
1025
- },
1026
- ];
1027
-
1028
- controls.forEach((template) => {
1029
- const {
1030
- i, c, l, min, max,
1031
- } = template;
1032
- const control = createElement({
1033
- tagName: 'div',
1034
- className: 'color-control',
1035
- });
1036
- setAttribute(control, 'role', 'presentation');
1037
-
1038
- control.append(
1039
- createElement({
1040
- tagName: 'div',
1041
- className: `visual-control visual-control${i}`,
1042
- }),
1043
- );
1044
-
1045
- const knob = createElement({
1046
- tagName: 'div',
1047
- className: `${c} knob`,
1048
- ariaLive: 'polite',
1049
- });
1050
-
1051
- setAttribute(knob, ariaLabel, l);
1052
- setAttribute(knob, 'role', 'slider');
1053
- setAttribute(knob, tabIndex, '0');
1054
- setAttribute(knob, ariaValueMin, `${min}`);
1055
- setAttribute(knob, ariaValueMax, `${max}`);
1056
- control.append(knob);
1057
- colorControls.append(control);
1058
- });
1059
-
1060
- return colorControls;
1061
- }
1062
-
1063
- /**
1064
- * Helps setting CSS variables to the color-menu.
1065
- * @param {HTMLElement} element
1066
- * @param {Record<string,any>} props
1067
- */
1068
- function setCSSProperties(element, props) {
1069
- ObjectKeys(props).forEach((prop) => {
1070
- element.style.setProperty(prop, props[prop]);
1071
- });
1072
- }
1073
-
1074
- /**
1075
- * Returns the `document.head` or the `<head>` element.
1076
- *
1077
- * @param {(Node | HTMLElement | Element | globalThis)=} node
1078
- * @returns {HTMLElement | HTMLHeadElement}
1079
- */
1080
- function getDocumentHead(node) {
1081
- return getDocument(node).head;
1082
- }
1083
-
1084
- /**
1085
- * Round colour components, for all formats except HEX.
1086
- * @param {number} v one of the colour components
1087
- * @returns {number} the rounded number
1088
- */
1089
- function roundPart(v) {
1090
- const floor = Math.floor(v);
1091
- return v - floor < 0.5 ? floor : Math.round(v);
1092
- }
1093
-
1094
- // Color supported formats
1095
- const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
1096
-
1097
- // Hue angles
1098
- const ANGLES = 'deg|rad|grad|turn';
1099
-
1100
- // <http://www.w3.org/TR/css3-values/#integers>
1101
- const CSS_INTEGER = '[-\\+]?\\d+%?';
1102
-
1103
- // Include CSS3 Module
1104
- // <http://www.w3.org/TR/css3-values/#number-value>
1105
- const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
1106
-
1107
- // Include CSS4 Module Hue degrees unit
1108
- // <https://www.w3.org/TR/css3-values/#angle-value>
1109
- const CSS_ANGLE = `[-\\+]?\\d*\\.?\\d+(?:${ANGLES})?`;
1110
-
1111
- // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
1112
- const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
1113
-
1114
- // Add angles to the mix
1115
- const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
1116
-
1117
- // Actual matching.
1118
- // Parentheses and commas are optional, but not required.
1119
- // Whitespace can take the place of commas or opening paren
1120
- const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
1121
-
1122
- const matchers = {
1123
- CSS_UNIT: new RegExp(CSS_UNIT2),
1124
- hwb: new RegExp(`hwb${PERMISSIVE_MATCH}`),
1125
- rgb: new RegExp(`rgb(?:a)?${PERMISSIVE_MATCH}`),
1126
- hsl: new RegExp(`hsl(?:a)?${PERMISSIVE_MATCH}`),
1127
- hsv: new RegExp(`hsv(?:a)?${PERMISSIVE_MATCH}`),
1128
- hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
1129
- hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
1130
- hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
1131
- hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
1132
- };
1133
-
1134
- /**
1135
- * Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
1136
- * <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
1137
- * @param {string} n testing number
1138
- * @returns {boolean} the query result
1139
- */
1140
- function isOnePointZero(n) {
1141
- return `${n}`.includes('.') && parseFloat(n) === 1;
1142
- }
1143
-
1144
- /**
1145
- * Check to see if string passed in is a percentage
1146
- * @param {string} n testing number
1147
- * @returns {boolean} the query result
1148
- */
1149
- function isPercentage(n) {
1150
- return `${n}`.includes('%');
1151
- }
1152
-
1153
- /**
1154
- * Check to see if string passed in is an angle
1155
- * @param {string} n testing string
1156
- * @returns {boolean} the query result
1157
- */
1158
- function isAngle(n) {
1159
- return ANGLES.split('|').some((a) => `${n}`.includes(a));
1160
- }
832
+ function isPercentage(n) {
833
+ return `${n}`.includes('%');
834
+ }
1161
835
 
1162
836
  /**
1163
837
  * Check to see if string passed is a web safe colour.
838
+ * @see https://stackoverflow.com/a/16994164
1164
839
  * @param {string} color a colour name, EG: *red*
1165
840
  * @returns {boolean} the query result
1166
841
  */
1167
842
  function isColorName(color) {
1168
- return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
1169
- && !/[0-9]/.test(color);
843
+ if (nonColors.includes(color)
844
+ || ['#', ...COLOR_FORMAT].some((f) => color.includes(f))) return false;
845
+
846
+ return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
847
+ setElementStyle(documentHead, { color });
848
+ const computedColor = getElementStyle(documentHead, 'color');
849
+ setElementStyle(documentHead, { color: '' });
850
+ return computedColor !== c;
851
+ });
1170
852
  }
1171
853
 
1172
854
  /**
@@ -1187,15 +869,15 @@
1187
869
  */
1188
870
  function bound01(N, max) {
1189
871
  let n = N;
1190
- if (isOnePointZero(n)) n = '100%';
1191
-
1192
- n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
872
+ if (isOnePointZero(N)) n = '100%';
1193
873
 
1194
- // Handle hue angles
1195
- if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
874
+ const processPercent = isPercentage(n);
875
+ n = max === 360
876
+ ? parseFloat(n)
877
+ : Math.min(max, Math.max(0, parseFloat(n)));
1196
878
 
1197
879
  // Automatically convert percentage into number
1198
- if (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
880
+ if (processPercent) n = (n * max) / 100;
1199
881
 
1200
882
  // Handle floating point rounding errors
1201
883
  if (Math.abs(n - max) < 0.000001) {
@@ -1206,11 +888,11 @@
1206
888
  // If n is a hue given in degrees,
1207
889
  // wrap around out-of-range values into [0, 360] range
1208
890
  // then convert into [0, 1].
1209
- n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max));
891
+ n = (n < 0 ? (n % max) + max : n % max) / max;
1210
892
  } else {
1211
893
  // If n not a hue given in degrees
1212
894
  // Convert into [0, 1] range if it isn't already.
1213
- n = (n % max) / parseFloat(String(max));
895
+ n = (n % max) / max;
1214
896
  }
1215
897
  return n;
1216
898
  }
@@ -1245,7 +927,6 @@
1245
927
  * @returns {string}
1246
928
  */
1247
929
  function getRGBFromName(name) {
1248
- const documentHead = getDocumentHead();
1249
930
  setElementStyle(documentHead, { color: name });
1250
931
  const colorName = getElementStyle(documentHead, 'color');
1251
932
  setElementStyle(documentHead, { color: '' });
@@ -1344,6 +1025,36 @@
1344
1025
  return p;
1345
1026
  }
1346
1027
 
1028
+ /**
1029
+ * Converts an HSL colour value to RGB.
1030
+ *
1031
+ * @param {number} h Hue Angle [0, 1]
1032
+ * @param {number} s Saturation [0, 1]
1033
+ * @param {number} l Lightness Angle [0, 1]
1034
+ * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
1035
+ */
1036
+ function hslToRgb(h, s, l) {
1037
+ let r = 0;
1038
+ let g = 0;
1039
+ let b = 0;
1040
+
1041
+ if (s === 0) {
1042
+ // achromatic
1043
+ g = l;
1044
+ b = l;
1045
+ r = l;
1046
+ } else {
1047
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
1048
+ const p = 2 * l - q;
1049
+ r = hueToRgb(p, q, h + 1 / 3);
1050
+ g = hueToRgb(p, q, h);
1051
+ b = hueToRgb(p, q, h - 1 / 3);
1052
+ }
1053
+ [r, g, b] = [r, g, b].map((x) => x * 255);
1054
+
1055
+ return { r, g, b };
1056
+ }
1057
+
1347
1058
  /**
1348
1059
  * Returns an HWB colour object from an RGB colour object.
1349
1060
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
@@ -1406,36 +1117,6 @@
1406
1117
  return { r, g, b };
1407
1118
  }
1408
1119
 
1409
- /**
1410
- * Converts an HSL colour value to RGB.
1411
- *
1412
- * @param {number} h Hue Angle [0, 1]
1413
- * @param {number} s Saturation [0, 1]
1414
- * @param {number} l Lightness Angle [0, 1]
1415
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
1416
- */
1417
- function hslToRgb(h, s, l) {
1418
- let r = 0;
1419
- let g = 0;
1420
- let b = 0;
1421
-
1422
- if (s === 0) {
1423
- // achromatic
1424
- g = l;
1425
- b = l;
1426
- r = l;
1427
- } else {
1428
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
1429
- const p = 2 * l - q;
1430
- r = hueToRgb(p, q, h + 1 / 3);
1431
- g = hueToRgb(p, q, h);
1432
- b = hueToRgb(p, q, h - 1 / 3);
1433
- }
1434
- [r, g, b] = [r, g, b].map((x) => x * 255);
1435
-
1436
- return { r, g, b };
1437
- }
1438
-
1439
1120
  /**
1440
1121
  * Converts an RGB colour value to HSV.
1441
1122
  *
@@ -1491,10 +1172,11 @@
1491
1172
  const q = v * (1 - f * s);
1492
1173
  const t = v * (1 - (1 - f) * s);
1493
1174
  const mod = i % 6;
1494
- const r = [v, q, p, p, t, v][mod];
1495
- const g = [t, v, v, q, p, p][mod];
1496
- const b = [p, p, t, v, v, q][mod];
1497
- return { r: r * 255, g: g * 255, b: b * 255 };
1175
+ let r = [v, q, p, p, t, v][mod];
1176
+ let g = [t, v, v, q, p, p][mod];
1177
+ let b = [p, p, t, v, v, q][mod];
1178
+ [r, g, b] = [r, g, b].map((n) => n * 255);
1179
+ return { r, g, b };
1498
1180
  }
1499
1181
 
1500
1182
  /**
@@ -1518,7 +1200,7 @@
1518
1200
  // Return a 3 character hex if possible
1519
1201
  if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
1520
1202
  && hex[1].charAt(0) === hex[1].charAt(1)
1521
- && hex[2].charAt(0) === hex[2].charAt(1)) {
1203
+ && hex[2].charAt(0) === hex[2].charAt(1)) {
1522
1204
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
1523
1205
  }
1524
1206
 
@@ -1546,39 +1228,24 @@
1546
1228
  // Return a 4 character hex if possible
1547
1229
  if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
1548
1230
  && hex[1].charAt(0) === hex[1].charAt(1)
1549
- && hex[2].charAt(0) === hex[2].charAt(1)
1550
- && hex[3].charAt(0) === hex[3].charAt(1)) {
1231
+ && hex[2].charAt(0) === hex[2].charAt(1)
1232
+ && hex[3].charAt(0) === hex[3].charAt(1)) {
1551
1233
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
1552
1234
  }
1553
1235
  return hex.join('');
1554
1236
  }
1555
1237
 
1556
- /**
1557
- * Returns a colour object corresponding to a given number.
1558
- * @param {number} color input number
1559
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
1560
- */
1561
- function numberInputToObject(color) {
1562
- /* eslint-disable no-bitwise */
1563
- return {
1564
- r: color >> 16,
1565
- g: (color & 0xff00) >> 8,
1566
- b: color & 0xff,
1567
- };
1568
- /* eslint-enable no-bitwise */
1569
- }
1570
-
1571
1238
  /**
1572
1239
  * Permissive string parsing. Take in a number of formats, and output an object
1573
1240
  * based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
1574
1241
  * @param {string} input colour value in any format
1575
- * @returns {Record<string, (number | string)> | false} an object matching the RegExp
1242
+ * @returns {Record<string, (number | string | boolean)> | false} an object matching the RegExp
1576
1243
  */
1577
1244
  function stringInputToObject(input) {
1578
- let color = input.trim().toLowerCase();
1245
+ let color = toLowerCase(input.trim());
1579
1246
  if (color.length === 0) {
1580
1247
  return {
1581
- r: 0, g: 0, b: 0, a: 0,
1248
+ r: 0, g: 0, b: 0, a: 1,
1582
1249
  };
1583
1250
  }
1584
1251
  let named = false;
@@ -1586,11 +1253,9 @@
1586
1253
  color = getRGBFromName(color);
1587
1254
  named = true;
1588
1255
  } else if (nonColors.includes(color)) {
1589
- const isTransparent = color === 'transparent';
1590
- const rgb = isTransparent ? 0 : 255;
1591
- const a = isTransparent ? 0 : 1;
1256
+ const a = color === 'transparent' ? 0 : 1;
1592
1257
  return {
1593
- r: rgb, g: rgb, b: rgb, a, format: 'rgb',
1258
+ r: 0, g: 0, b: 0, a, format: 'rgb', ok: true,
1594
1259
  };
1595
1260
  }
1596
1261
 
@@ -1630,7 +1295,6 @@
1630
1295
  g: parseIntFromHex(m2),
1631
1296
  b: parseIntFromHex(m3),
1632
1297
  a: convertHexToDecimal(m4),
1633
- // format: named ? 'rgb' : 'hex8',
1634
1298
  format: named ? 'rgb' : 'hex',
1635
1299
  };
1636
1300
  }
@@ -1694,6 +1358,7 @@
1694
1358
  function inputToRGB(input) {
1695
1359
  let rgb = { r: 0, g: 0, b: 0 };
1696
1360
  let color = input;
1361
+ /** @type {string | number} */
1697
1362
  let a = 1;
1698
1363
  let s = null;
1699
1364
  let v = null;
@@ -1704,7 +1369,8 @@
1704
1369
  let r = null;
1705
1370
  let g = null;
1706
1371
  let ok = false;
1707
- let format = 'hex';
1372
+ const inputFormat = typeof color === 'object' && color.format;
1373
+ let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
1708
1374
 
1709
1375
  if (typeof input === 'string') {
1710
1376
  // @ts-ignore -- this now is converted to object
@@ -1745,14 +1411,17 @@
1745
1411
  format = 'hwb';
1746
1412
  }
1747
1413
  if (isValidCSSUnit(color.a)) {
1748
- a = color.a;
1749
- a = isPercentage(`${a}`) ? bound01(a, 100) : a;
1414
+ a = color.a; // @ts-ignore -- `parseFloat` works with numbers too
1415
+ a = isPercentage(`${a}`) || parseFloat(a) > 1 ? bound01(a, 100) : a;
1750
1416
  }
1751
1417
  }
1418
+ if (typeof color === 'undefined') {
1419
+ ok = true;
1420
+ }
1752
1421
 
1753
1422
  return {
1754
- ok, // @ts-ignore
1755
- format: color.format || format,
1423
+ ok,
1424
+ format,
1756
1425
  r: Math.min(255, Math.max(rgb.r, 0)),
1757
1426
  g: Math.min(255, Math.max(rgb.g, 0)),
1758
1427
  b: Math.min(255, Math.max(rgb.b, 0)),
@@ -1781,7 +1450,8 @@
1781
1450
  color = inputToRGB(color);
1782
1451
  }
1783
1452
  if (typeof color === 'number') {
1784
- color = numberInputToObject(color);
1453
+ const len = `${color}`.length;
1454
+ color = `#${(len === 2 ? '0' : '00')}${color}`;
1785
1455
  }
1786
1456
  const {
1787
1457
  r, g, b, a, ok, format,
@@ -1791,7 +1461,7 @@
1791
1461
  const self = this;
1792
1462
 
1793
1463
  /** @type {CP.ColorInput} */
1794
- self.originalInput = color;
1464
+ self.originalInput = input;
1795
1465
  /** @type {number} */
1796
1466
  self.r = r;
1797
1467
  /** @type {number} */
@@ -2181,6 +1851,7 @@
2181
1851
  isOnePointZero,
2182
1852
  isPercentage,
2183
1853
  isValidCSSUnit,
1854
+ isColorName,
2184
1855
  pad2,
2185
1856
  clamp01,
2186
1857
  bound01,
@@ -2198,10 +1869,11 @@
2198
1869
  hueToRgb,
2199
1870
  hwbToRgb,
2200
1871
  parseIntFromHex,
2201
- numberInputToObject,
2202
1872
  stringInputToObject,
2203
1873
  inputToRGB,
2204
1874
  roundPart,
1875
+ getElementStyle,
1876
+ setElementStyle,
2205
1877
  ObjectAssign,
2206
1878
  });
2207
1879
 
@@ -2210,7 +1882,7 @@
2210
1882
  * Returns a color palette with a given set of parameters.
2211
1883
  * @example
2212
1884
  * new ColorPalette(0, 12, 10);
2213
- * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
1885
+ * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: Array<Color> }
2214
1886
  */
2215
1887
  class ColorPalette {
2216
1888
  /**
@@ -2230,11 +1902,14 @@
2230
1902
  [hue, hueSteps, lightSteps] = args;
2231
1903
  } else if (args.length === 2) {
2232
1904
  [hueSteps, lightSteps] = args;
1905
+ if ([hueSteps, lightSteps].some((n) => n < 1)) {
1906
+ throw TypeError('ColorPalette: when 2 arguments used, both must be larger than 0.');
1907
+ }
2233
1908
  } else {
2234
1909
  throw TypeError('ColorPalette requires minimum 2 arguments');
2235
1910
  }
2236
1911
 
2237
- /** @type {string[]} */
1912
+ /** @type {Color[]} */
2238
1913
  const colors = [];
2239
1914
 
2240
1915
  const hueStep = 360 / hueSteps;
@@ -2263,7 +1938,7 @@
2263
1938
  for (let i = 0; i < hueSteps; i += 1) {
2264
1939
  const currentHue = ((hue + i * hueStep) % 360) / 360;
2265
1940
  lightnessArray.forEach((l) => {
2266
- colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
1941
+ colors.push(new Color({ h: currentHue, s: 1, l }));
2267
1942
  });
2268
1943
  }
2269
1944
 
@@ -2274,6 +1949,310 @@
2274
1949
  }
2275
1950
  }
2276
1951
 
1952
+ ObjectAssign(ColorPalette, { Color });
1953
+
1954
+ /** @type {Record<string, string>} */
1955
+ const colorPickerLabels = {
1956
+ pickerLabel: 'Colour Picker',
1957
+ appearanceLabel: 'Colour Appearance',
1958
+ valueLabel: 'Colour Value',
1959
+ toggleLabel: 'Select Colour',
1960
+ presetsLabel: 'Colour Presets',
1961
+ defaultsLabel: 'Colour Defaults',
1962
+ formatLabel: 'Format',
1963
+ alphaLabel: 'Alpha',
1964
+ hexLabel: 'Hexadecimal',
1965
+ hueLabel: 'Hue',
1966
+ whitenessLabel: 'Whiteness',
1967
+ blacknessLabel: 'Blackness',
1968
+ saturationLabel: 'Saturation',
1969
+ lightnessLabel: 'Lightness',
1970
+ redLabel: 'Red',
1971
+ greenLabel: 'Green',
1972
+ blueLabel: 'Blue',
1973
+ };
1974
+
1975
+ /**
1976
+ * A list of 17 color names used for WAI-ARIA compliance.
1977
+ * @type {string[]}
1978
+ */
1979
+ const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
1980
+
1981
+ const tabIndex = 'tabindex';
1982
+
1983
+ /**
1984
+ * Check if a string is valid JSON string.
1985
+ * @param {string} str the string input
1986
+ * @returns {boolean} the query result
1987
+ */
1988
+ function isValidJSON(str) {
1989
+ try {
1990
+ JSON.parse(str);
1991
+ } catch (e) {
1992
+ return false;
1993
+ }
1994
+ return true;
1995
+ }
1996
+
1997
+ /**
1998
+ * Shortcut for `String.toUpperCase()`.
1999
+ *
2000
+ * @param {string} source input string
2001
+ * @returns {string} uppercase output string
2002
+ */
2003
+ const toUpperCase = (source) => source.toUpperCase();
2004
+
2005
+ /**
2006
+ * A global namespace for aria-haspopup.
2007
+ * @type {string}
2008
+ */
2009
+ const ariaHasPopup = 'aria-haspopup';
2010
+
2011
+ /**
2012
+ * A global namespace for aria-hidden.
2013
+ * @type {string}
2014
+ */
2015
+ const ariaHidden = 'aria-hidden';
2016
+
2017
+ /**
2018
+ * A global namespace for aria-labelledby.
2019
+ * @type {string}
2020
+ */
2021
+ const ariaLabelledBy = 'aria-labelledby';
2022
+
2023
+ /**
2024
+ * This is a shortie for `document.createElement` method
2025
+ * which allows you to create a new `HTMLElement` for a given `tagName`
2026
+ * or based on an object with specific non-readonly attributes:
2027
+ * `id`, `className`, `textContent`, `style`, etc.
2028
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
2029
+ *
2030
+ * @param {Record<string, string> | string} param `tagName` or object
2031
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
2032
+ */
2033
+ function createElement(param) {
2034
+ if (typeof param === 'string') {
2035
+ return getDocument().createElement(param);
2036
+ }
2037
+
2038
+ const { tagName } = param;
2039
+ const attr = { ...param };
2040
+ const newElement = createElement(tagName);
2041
+ delete attr.tagName;
2042
+ ObjectAssign(newElement, attr);
2043
+ return newElement;
2044
+ }
2045
+
2046
+ /**
2047
+ * This is a shortie for `document.createElementNS` method
2048
+ * which allows you to create a new `HTMLElement` for a given `tagName`
2049
+ * or based on an object with specific non-readonly attributes:
2050
+ * `id`, `className`, `textContent`, `style`, etc.
2051
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
2052
+ *
2053
+ * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
2054
+ * @param {Record<string, string> | string} param `tagName` or object
2055
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
2056
+ */
2057
+ function createElementNS(namespace, param) {
2058
+ if (typeof param === 'string') {
2059
+ return getDocument().createElementNS(namespace, param);
2060
+ }
2061
+
2062
+ const { tagName } = param;
2063
+ const attr = { ...param };
2064
+ const newElement = createElementNS(namespace, tagName);
2065
+ delete attr.tagName;
2066
+ ObjectAssign(newElement, attr);
2067
+ return newElement;
2068
+ }
2069
+
2070
+ const vHidden = 'v-hidden';
2071
+
2072
+ /**
2073
+ * Returns the color form for `ColorPicker`.
2074
+ *
2075
+ * @param {CP.ColorPicker} self the `ColorPicker` instance
2076
+ * @returns {HTMLElement | Element} a new `<div>` element with color component `<input>`
2077
+ */
2078
+ function getColorForm(self) {
2079
+ const { format, id, componentLabels } = self;
2080
+ const colorForm = createElement({
2081
+ tagName: 'div',
2082
+ className: `color-form ${format}`,
2083
+ });
2084
+
2085
+ let components = ['hex'];
2086
+ if (format === 'rgb') components = ['red', 'green', 'blue', 'alpha'];
2087
+ else if (format === 'hsl') components = ['hue', 'saturation', 'lightness', 'alpha'];
2088
+ else if (format === 'hwb') components = ['hue', 'whiteness', 'blackness', 'alpha'];
2089
+
2090
+ components.forEach((c) => {
2091
+ const [C] = format === 'hex' ? ['#'] : toUpperCase(c).split('');
2092
+ const cID = `color_${format}_${c}_${id}`;
2093
+ const formatLabel = componentLabels[`${c}Label`];
2094
+ const cInputLabel = createElement({ tagName: 'label' });
2095
+ setAttribute(cInputLabel, 'for', cID);
2096
+ cInputLabel.append(
2097
+ createElement({ tagName: 'span', ariaHidden: 'true', innerText: `${C}:` }),
2098
+ createElement({ tagName: 'span', className: vHidden, innerText: formatLabel }),
2099
+ );
2100
+ const cInput = createElement({
2101
+ tagName: 'input',
2102
+ id: cID,
2103
+ // name: cID, - prevent saving the value to a form
2104
+ type: format === 'hex' ? 'text' : 'number',
2105
+ value: c === 'alpha' ? '100' : '0',
2106
+ className: `color-input ${c}`,
2107
+ });
2108
+ setAttribute(cInput, 'autocomplete', 'off');
2109
+ setAttribute(cInput, 'spellcheck', 'false');
2110
+
2111
+ // alpha
2112
+ let max = '100';
2113
+ let step = '1';
2114
+ if (c !== 'alpha') {
2115
+ if (format === 'rgb') {
2116
+ max = '255'; step = '1';
2117
+ } else if (c === 'hue') {
2118
+ max = '360'; step = '1';
2119
+ }
2120
+ }
2121
+ ObjectAssign(cInput, {
2122
+ min: '0',
2123
+ max,
2124
+ step,
2125
+ });
2126
+ colorForm.append(cInputLabel, cInput);
2127
+ });
2128
+ return colorForm;
2129
+ }
2130
+
2131
+ /**
2132
+ * A global namespace for aria-label.
2133
+ * @type {string}
2134
+ */
2135
+ const ariaLabel = 'aria-label';
2136
+
2137
+ /**
2138
+ * A global namespace for aria-valuemin.
2139
+ * @type {string}
2140
+ */
2141
+ const ariaValueMin = 'aria-valuemin';
2142
+
2143
+ /**
2144
+ * A global namespace for aria-valuemax.
2145
+ * @type {string}
2146
+ */
2147
+ const ariaValueMax = 'aria-valuemax';
2148
+
2149
+ /**
2150
+ * Returns all color controls for `ColorPicker`.
2151
+ *
2152
+ * @param {CP.ColorPicker} self the `ColorPicker` instance
2153
+ * @returns {HTMLElement | Element} color controls
2154
+ */
2155
+ function getColorControls(self) {
2156
+ const { format, componentLabels } = self;
2157
+ const {
2158
+ hueLabel, alphaLabel, lightnessLabel, saturationLabel,
2159
+ whitenessLabel, blacknessLabel,
2160
+ } = componentLabels;
2161
+
2162
+ const max1 = format === 'hsl' ? 360 : 100;
2163
+ const max2 = format === 'hsl' ? 100 : 360;
2164
+ const max3 = 100;
2165
+
2166
+ let ctrl1Label = format === 'hsl'
2167
+ ? `${hueLabel} & ${lightnessLabel}`
2168
+ : `${lightnessLabel} & ${saturationLabel}`;
2169
+
2170
+ ctrl1Label = format === 'hwb'
2171
+ ? `${whitenessLabel} & ${blacknessLabel}`
2172
+ : ctrl1Label;
2173
+
2174
+ const ctrl2Label = format === 'hsl'
2175
+ ? `${saturationLabel}`
2176
+ : `${hueLabel}`;
2177
+
2178
+ const colorControls = createElement({
2179
+ tagName: 'div',
2180
+ className: `color-controls ${format}`,
2181
+ });
2182
+
2183
+ const colorPointer = 'color-pointer';
2184
+ const colorSlider = 'color-slider';
2185
+
2186
+ const controls = [
2187
+ {
2188
+ i: 1,
2189
+ c: colorPointer,
2190
+ l: ctrl1Label,
2191
+ min: 0,
2192
+ max: max1,
2193
+ },
2194
+ {
2195
+ i: 2,
2196
+ c: colorSlider,
2197
+ l: ctrl2Label,
2198
+ min: 0,
2199
+ max: max2,
2200
+ },
2201
+ {
2202
+ i: 3,
2203
+ c: colorSlider,
2204
+ l: alphaLabel,
2205
+ min: 0,
2206
+ max: max3,
2207
+ },
2208
+ ];
2209
+
2210
+ controls.forEach((template) => {
2211
+ const {
2212
+ i, c, l, min, max,
2213
+ } = template;
2214
+ const control = createElement({
2215
+ tagName: 'div',
2216
+ className: 'color-control',
2217
+ });
2218
+ setAttribute(control, 'role', 'presentation');
2219
+
2220
+ control.append(
2221
+ createElement({
2222
+ tagName: 'div',
2223
+ className: `visual-control visual-control${i}`,
2224
+ }),
2225
+ );
2226
+
2227
+ const knob = createElement({
2228
+ tagName: 'div',
2229
+ className: `${c} knob`,
2230
+ ariaLive: 'polite',
2231
+ });
2232
+
2233
+ setAttribute(knob, ariaLabel, l);
2234
+ setAttribute(knob, 'role', 'slider');
2235
+ setAttribute(knob, tabIndex, '0');
2236
+ setAttribute(knob, ariaValueMin, `${min}`);
2237
+ setAttribute(knob, ariaValueMax, `${max}`);
2238
+ control.append(knob);
2239
+ colorControls.append(control);
2240
+ });
2241
+
2242
+ return colorControls;
2243
+ }
2244
+
2245
+ /**
2246
+ * Helps setting CSS variables to the color-menu.
2247
+ * @param {HTMLElement} element
2248
+ * @param {Record<string,any>} props
2249
+ */
2250
+ function setCSSProperties(element, props) {
2251
+ ObjectKeys(props).forEach((prop) => {
2252
+ element.style.setProperty(prop, props[prop]);
2253
+ });
2254
+ }
2255
+
2277
2256
  /**
2278
2257
  * Returns a color-defaults with given values and class.
2279
2258
  * @param {CP.ColorPicker} self
@@ -2307,7 +2286,8 @@
2307
2286
  optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2308
2287
  const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2309
2288
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2310
-
2289
+ /** @type {HTMLUListElement} */
2290
+ // @ts-ignore -- <UL> is an `HTMLElement`
2311
2291
  const menu = createElement({
2312
2292
  tagName: 'ul',
2313
2293
  className: finalClass,
@@ -2315,7 +2295,7 @@
2315
2295
  setAttribute(menu, 'role', 'listbox');
2316
2296
  setAttribute(menu, ariaLabel, menuLabel);
2317
2297
 
2318
- if (isScrollable) { // @ts-ignore
2298
+ if (isScrollable) {
2319
2299
  setCSSProperties(menu, {
2320
2300
  '--grid-item-size': `${optionSize}rem`,
2321
2301
  '--grid-fit': fit,
@@ -2326,15 +2306,19 @@
2326
2306
  }
2327
2307
 
2328
2308
  colorsArray.forEach((x) => {
2329
- const [value, label] = x.trim().split(':');
2330
- const xRealColor = new Color(value, format).toString();
2331
- const isActive = xRealColor === getAttribute(input, 'value');
2309
+ let [value, label] = typeof x === 'string' ? x.trim().split(':') : [];
2310
+ if (x instanceof Color) {
2311
+ value = x.toHexString();
2312
+ label = value;
2313
+ }
2314
+ const color = new Color(x instanceof Color ? x : value, format);
2315
+ const isActive = color.toString() === getAttribute(input, 'value');
2332
2316
  const active = isActive ? ' active' : '';
2333
2317
 
2334
2318
  const option = createElement({
2335
2319
  tagName: 'li',
2336
2320
  className: `color-option${active}`,
2337
- innerText: `${label || x}`,
2321
+ innerText: `${label || value}`,
2338
2322
  });
2339
2323
 
2340
2324
  setAttribute(option, tabIndex, '0');
@@ -2343,7 +2327,7 @@
2343
2327
  setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2344
2328
 
2345
2329
  if (isOptionsMenu) {
2346
- setElementStyle(option, { backgroundColor: x });
2330
+ setElementStyle(option, { backgroundColor: value });
2347
2331
  }
2348
2332
 
2349
2333
  menu.append(option);
@@ -2352,55 +2336,10 @@
2352
2336
  }
2353
2337
 
2354
2338
  /**
2355
- * Check if a string is valid JSON string.
2356
- * @param {string} str the string input
2357
- * @returns {boolean} the query result
2358
- */
2359
- function isValidJSON(str) {
2360
- try {
2361
- JSON.parse(str);
2362
- } catch (e) {
2363
- return false;
2364
- }
2365
- return true;
2366
- }
2367
-
2368
- var version = "0.0.1";
2369
-
2370
- // @ts-ignore
2371
-
2372
- const Version = version;
2373
-
2374
- // ColorPicker GC
2375
- // ==============
2376
- const colorPickerString = 'color-picker';
2377
- const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2378
- const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2379
- const colorPickerDefaults = {
2380
- componentLabels: colorPickerLabels,
2381
- colorLabels: colorNames,
2382
- format: 'rgb',
2383
- colorPresets: false,
2384
- colorKeywords: false,
2385
- };
2386
-
2387
- // ColorPicker Static Methods
2388
- // ==========================
2389
-
2390
- /** @type {CP.GetInstance<ColorPicker>} */
2391
- const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2392
-
2393
- /** @type {CP.InitCallback<ColorPicker>} */
2394
- const initColorPicker = (element) => new ColorPicker(element);
2395
-
2396
- // ColorPicker Private Methods
2397
- // ===========================
2398
-
2399
- /**
2400
- * Generate HTML markup and update instance properties.
2401
- * @param {ColorPicker} self
2402
- */
2403
- function initCallback(self) {
2339
+ * Generate HTML markup and update instance properties.
2340
+ * @param {CP.ColorPicker} self
2341
+ */
2342
+ function setMarkup(self) {
2404
2343
  const {
2405
2344
  input, parent, format, id, componentLabels, colorKeywords, colorPresets,
2406
2345
  } = self;
@@ -2415,9 +2354,7 @@
2415
2354
  self.color = new Color(color, format);
2416
2355
 
2417
2356
  // set initial controls dimensions
2418
- // make the controls smaller on mobile
2419
- const dropClass = isMobile ? ' mobile' : '';
2420
- const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
2357
+ const formatString = format === 'hex' ? hexLabel : toUpperCase(format);
2421
2358
 
2422
2359
  const pickerBtn = createElement({
2423
2360
  id: `picker-btn-${id}`,
@@ -2434,7 +2371,7 @@
2434
2371
 
2435
2372
  const pickerDropdown = createElement({
2436
2373
  tagName: 'div',
2437
- className: `color-dropdown picker${dropClass}`,
2374
+ className: 'color-dropdown picker',
2438
2375
  });
2439
2376
  setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
2440
2377
  setAttribute(pickerDropdown, 'role', 'group');
@@ -2450,7 +2387,7 @@
2450
2387
  if (colorKeywords || colorPresets) {
2451
2388
  const presetsDropdown = createElement({
2452
2389
  tagName: 'div',
2453
- className: `color-dropdown scrollable menu${dropClass}`,
2390
+ className: 'color-dropdown scrollable menu',
2454
2391
  });
2455
2392
 
2456
2393
  // color presets
@@ -2500,6 +2437,37 @@
2500
2437
  setAttribute(input, tabIndex, '-1');
2501
2438
  }
2502
2439
 
2440
+ var version = "0.0.2alpha1";
2441
+
2442
+ // @ts-ignore
2443
+
2444
+ const Version = version;
2445
+
2446
+ // ColorPicker GC
2447
+ // ==============
2448
+ const colorPickerString = 'color-picker';
2449
+ const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2450
+ const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2451
+ const colorPickerDefaults = {
2452
+ componentLabels: colorPickerLabels,
2453
+ colorLabels: colorNames,
2454
+ format: 'rgb',
2455
+ colorPresets: false,
2456
+ colorKeywords: false,
2457
+ };
2458
+
2459
+ // ColorPicker Static Methods
2460
+ // ==========================
2461
+
2462
+ /** @type {CP.GetInstance<ColorPicker>} */
2463
+ const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2464
+
2465
+ /** @type {CP.InitCallback<ColorPicker>} */
2466
+ const initColorPicker = (element) => new ColorPicker(element);
2467
+
2468
+ // ColorPicker Private Methods
2469
+ // ===========================
2470
+
2503
2471
  /**
2504
2472
  * Add / remove `ColorPicker` main event listeners.
2505
2473
  * @param {ColorPicker} self
@@ -2733,7 +2701,7 @@
2733
2701
  self.handleKnobs = self.handleKnobs.bind(self);
2734
2702
 
2735
2703
  // generate markup
2736
- initCallback(self);
2704
+ setMarkup(self);
2737
2705
 
2738
2706
  const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
2739
2707
  // set main elements
@@ -2929,7 +2897,7 @@
2929
2897
  const self = this;
2930
2898
  const { activeElement } = getDocument(self.input);
2931
2899
 
2932
- if ((isMobile && self.dragElement)
2900
+ if ((e.type === touchmoveEvent && self.dragElement)
2933
2901
  || (activeElement && self.controlKnobs.includes(activeElement))) {
2934
2902
  e.stopPropagation();
2935
2903
  e.preventDefault();
@@ -3747,4 +3715,4 @@
3747
3715
 
3748
3716
  return ColorPicker;
3749
3717
 
3750
- })));
3718
+ }));