@thednp/color-picker 0.0.1 → 0.0.2-alpha3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,13 @@
1
1
  /*!
2
- * ColorPicker v0.0.1 (http://thednp.github.io/color-picker)
2
+ * ColorPicker v0.0.2alpha3 (http://thednp.github.io/color-picker)
3
3
  * Copyright 2022 © thednp
4
4
  * Licensed under MIT (https://github.com/thednp/color-picker/blob/master/LICENSE)
5
5
  */
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,99 @@
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
+ if (['black', 'white'].includes(color)) return true;
847
+
848
+ return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
849
+ setElementStyle(documentHead, { color });
850
+ const computedColor = getElementStyle(documentHead, 'color');
851
+ setElementStyle(documentHead, { color: '' });
852
+ return computedColor !== c;
853
+ });
1170
854
  }
1171
855
 
1172
856
  /**
@@ -1187,15 +871,20 @@
1187
871
  */
1188
872
  function bound01(N, max) {
1189
873
  let n = N;
1190
- if (isOnePointZero(n)) n = '100%';
1191
874
 
1192
- n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
875
+ if (typeof N === 'number'
876
+ && Math.min(N, 0) === 0 // round values to 6 decimals Math.round(N * (10 ** 6)) / 10 ** 6
877
+ && Math.max(N, 1) === 1) return N;
878
+
879
+ if (isOnePointZero(N)) n = '100%';
1193
880
 
1194
- // Handle hue angles
1195
- if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
881
+ const processPercent = isPercentage(n);
882
+ n = max === 360
883
+ ? parseFloat(n)
884
+ : Math.min(max, Math.max(0, parseFloat(n)));
1196
885
 
1197
886
  // Automatically convert percentage into number
1198
- if (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
887
+ if (processPercent) n = (n * max) / 100;
1199
888
 
1200
889
  // Handle floating point rounding errors
1201
890
  if (Math.abs(n - max) < 0.000001) {
@@ -1206,11 +895,11 @@
1206
895
  // If n is a hue given in degrees,
1207
896
  // wrap around out-of-range values into [0, 360] range
1208
897
  // then convert into [0, 1].
1209
- n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max));
898
+ n = (n < 0 ? (n % max) + max : n % max) / max;
1210
899
  } else {
1211
900
  // If n not a hue given in degrees
1212
901
  // Convert into [0, 1] range if it isn't already.
1213
- n = (n % max) / parseFloat(String(max));
902
+ n = (n % max) / max;
1214
903
  }
1215
904
  return n;
1216
905
  }
@@ -1245,7 +934,6 @@
1245
934
  * @returns {string}
1246
935
  */
1247
936
  function getRGBFromName(name) {
1248
- const documentHead = getDocumentHead();
1249
937
  setElementStyle(documentHead, { color: name });
1250
938
  const colorName = getElementStyle(documentHead, 'color');
1251
939
  setElementStyle(documentHead, { color: '' });
@@ -1291,15 +979,12 @@
1291
979
  /**
1292
980
  * Converts an RGB colour value to HSL.
1293
981
  *
1294
- * @param {number} R Red component [0, 255]
1295
- * @param {number} G Green component [0, 255]
1296
- * @param {number} B Blue component [0, 255]
982
+ * @param {number} r Red component [0, 1]
983
+ * @param {number} g Green component [0, 1]
984
+ * @param {number} b Blue component [0, 1]
1297
985
  * @returns {CP.HSL} {h,s,l} object with [0, 1] ranged values
1298
986
  */
1299
- function rgbToHsl(R, G, B) {
1300
- const r = R / 255;
1301
- const g = G / 255;
1302
- const b = B / 255;
987
+ function rgbToHsl(r, g, b) {
1303
988
  const max = Math.max(r, g, b);
1304
989
  const min = Math.min(r, g, b);
1305
990
  let h = 0;
@@ -1311,17 +996,10 @@
1311
996
  } else {
1312
997
  const d = max - min;
1313
998
  s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
1314
- switch (max) {
1315
- case r:
1316
- h = (g - b) / d + (g < b ? 6 : 0);
1317
- break;
1318
- case g:
1319
- h = (b - r) / d + 2;
1320
- break;
1321
- case b:
1322
- h = (r - g) / d + 4;
1323
- break;
1324
- }
999
+ if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
1000
+ if (max === g) h = (b - r) / d + 2;
1001
+ if (max === b) h = (r - g) / d + 4;
1002
+
1325
1003
  h /= 6;
1326
1004
  }
1327
1005
  return { h, s, l };
@@ -1344,21 +1022,46 @@
1344
1022
  return p;
1345
1023
  }
1346
1024
 
1025
+ /**
1026
+ * Converts an HSL colour value to RGB.
1027
+ *
1028
+ * @param {number} h Hue Angle [0, 1]
1029
+ * @param {number} s Saturation [0, 1]
1030
+ * @param {number} l Lightness Angle [0, 1]
1031
+ * @returns {CP.RGB} {r,g,b} object with [0, 1] ranged values
1032
+ */
1033
+ function hslToRgb(h, s, l) {
1034
+ let r = 0;
1035
+ let g = 0;
1036
+ let b = 0;
1037
+
1038
+ if (s === 0) {
1039
+ // achromatic
1040
+ g = l;
1041
+ b = l;
1042
+ r = l;
1043
+ } else {
1044
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
1045
+ const p = 2 * l - q;
1046
+ r = hueToRgb(p, q, h + 1 / 3);
1047
+ g = hueToRgb(p, q, h);
1048
+ b = hueToRgb(p, q, h - 1 / 3);
1049
+ }
1050
+
1051
+ return { r, g, b };
1052
+ }
1053
+
1347
1054
  /**
1348
1055
  * Returns an HWB colour object from an RGB colour object.
1349
1056
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
1350
1057
  * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
1351
1058
  *
1352
- * @param {number} R Red component [0, 255]
1353
- * @param {number} G Green [0, 255]
1354
- * @param {number} B Blue [0, 255]
1059
+ * @param {number} r Red component [0, 1]
1060
+ * @param {number} g Green [0, 1]
1061
+ * @param {number} b Blue [0, 1]
1355
1062
  * @return {CP.HWB} {h,w,b} object with [0, 1] ranged values
1356
1063
  */
1357
- function rgbToHwb(R, G, B) {
1358
- const r = R / 255;
1359
- const g = G / 255;
1360
- const b = B / 255;
1361
-
1064
+ function rgbToHwb(r, g, b) {
1362
1065
  let f = 0;
1363
1066
  let i = 0;
1364
1067
  const whiteness = Math.min(r, g, b);
@@ -1388,50 +1091,18 @@
1388
1091
  * @param {number} H Hue Angle [0, 1]
1389
1092
  * @param {number} W Whiteness [0, 1]
1390
1093
  * @param {number} B Blackness [0, 1]
1391
- * @return {CP.RGB} {r,g,b} object with [0, 255] ranged values
1094
+ * @return {CP.RGB} {r,g,b} object with [0, 1] ranged values
1392
1095
  *
1393
1096
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
1394
1097
  * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
1395
1098
  */
1396
1099
  function hwbToRgb(H, W, B) {
1397
1100
  if (W + B >= 1) {
1398
- const gray = (W / (W + B)) * 255;
1101
+ const gray = W / (W + B);
1399
1102
  return { r: gray, g: gray, b: gray };
1400
1103
  }
1401
1104
  let { r, g, b } = hslToRgb(H, 1, 0.5);
1402
- [r, g, b] = [r, g, b]
1403
- .map((v) => (v / 255) * (1 - W - B) + W)
1404
- .map((v) => v * 255);
1405
-
1406
- return { r, g, b };
1407
- }
1408
-
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);
1105
+ [r, g, b] = [r, g, b].map((v) => v * (1 - W - B) + W);
1435
1106
 
1436
1107
  return { r, g, b };
1437
1108
  }
@@ -1439,15 +1110,12 @@
1439
1110
  /**
1440
1111
  * Converts an RGB colour value to HSV.
1441
1112
  *
1442
- * @param {number} R Red component [0, 255]
1443
- * @param {number} G Green [0, 255]
1444
- * @param {number} B Blue [0, 255]
1113
+ * @param {number} r Red component [0, 1]
1114
+ * @param {number} g Green [0, 1]
1115
+ * @param {number} b Blue [0, 1]
1445
1116
  * @returns {CP.HSV} {h,s,v} object with [0, 1] ranged values
1446
1117
  */
1447
- function rgbToHsv(R, G, B) {
1448
- const r = R / 255;
1449
- const g = G / 255;
1450
- const b = B / 255;
1118
+ function rgbToHsv(r, g, b) {
1451
1119
  const max = Math.max(r, g, b);
1452
1120
  const min = Math.min(r, g, b);
1453
1121
  let h = 0;
@@ -1457,17 +1125,10 @@
1457
1125
  if (max === min) {
1458
1126
  h = 0; // achromatic
1459
1127
  } else {
1460
- switch (max) {
1461
- case r:
1462
- h = (g - b) / d + (g < b ? 6 : 0);
1463
- break;
1464
- case g:
1465
- h = (b - r) / d + 2;
1466
- break;
1467
- case b:
1468
- h = (r - g) / d + 4;
1469
- break;
1470
- }
1128
+ if (r === max) h = (g - b) / d + (g < b ? 6 : 0);
1129
+ if (g === max) h = (b - r) / d + 2;
1130
+ if (b === max) h = (r - g) / d + 4;
1131
+
1471
1132
  h /= 6;
1472
1133
  }
1473
1134
  return { h, s, v };
@@ -1494,7 +1155,7 @@
1494
1155
  const r = [v, q, p, p, t, v][mod];
1495
1156
  const g = [t, v, v, q, p, p][mod];
1496
1157
  const b = [p, p, t, v, v, q][mod];
1497
- return { r: r * 255, g: g * 255, b: b * 255 };
1158
+ return { r, g, b };
1498
1159
  }
1499
1160
 
1500
1161
  /**
@@ -1518,7 +1179,7 @@
1518
1179
  // Return a 3 character hex if possible
1519
1180
  if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
1520
1181
  && hex[1].charAt(0) === hex[1].charAt(1)
1521
- && hex[2].charAt(0) === hex[2].charAt(1)) {
1182
+ && hex[2].charAt(0) === hex[2].charAt(1)) {
1522
1183
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
1523
1184
  }
1524
1185
 
@@ -1546,51 +1207,34 @@
1546
1207
  // Return a 4 character hex if possible
1547
1208
  if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
1548
1209
  && 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)) {
1210
+ && hex[2].charAt(0) === hex[2].charAt(1)
1211
+ && hex[3].charAt(0) === hex[3].charAt(1)) {
1551
1212
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
1552
1213
  }
1553
1214
  return hex.join('');
1554
1215
  }
1555
1216
 
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
1217
  /**
1572
1218
  * Permissive string parsing. Take in a number of formats, and output an object
1573
1219
  * based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
1574
1220
  * @param {string} input colour value in any format
1575
- * @returns {Record<string, (number | string)> | false} an object matching the RegExp
1221
+ * @returns {Record<string, (number | string | boolean)> | false} an object matching the RegExp
1576
1222
  */
1577
1223
  function stringInputToObject(input) {
1578
- let color = input.trim().toLowerCase();
1224
+ let color = toLowerCase(input.trim());
1225
+
1579
1226
  if (color.length === 0) {
1580
1227
  return {
1581
- r: 0, g: 0, b: 0, a: 0,
1228
+ r: 0, g: 0, b: 0, a: 1,
1582
1229
  };
1583
1230
  }
1584
- let named = false;
1231
+
1585
1232
  if (isColorName(color)) {
1586
1233
  color = getRGBFromName(color);
1587
- named = true;
1588
1234
  } else if (nonColors.includes(color)) {
1589
- const isTransparent = color === 'transparent';
1590
- const rgb = isTransparent ? 0 : 255;
1591
- const a = isTransparent ? 0 : 1;
1235
+ const a = color === 'transparent' ? 0 : 1;
1592
1236
  return {
1593
- r: rgb, g: rgb, b: rgb, a, format: 'rgb',
1237
+ r: 0, g: 0, b: 0, a, format: 'rgb', ok: true,
1594
1238
  };
1595
1239
  }
1596
1240
 
@@ -1605,24 +1249,28 @@
1605
1249
  r: m1, g: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'rgb',
1606
1250
  };
1607
1251
  }
1252
+
1608
1253
  [, m1, m2, m3, m4] = matchers.hsl.exec(color) || [];
1609
1254
  if (m1 && m2 && m3/* && m4 */) {
1610
1255
  return {
1611
1256
  h: m1, s: m2, l: m3, a: m4 !== undefined ? m4 : 1, format: 'hsl',
1612
1257
  };
1613
1258
  }
1259
+
1614
1260
  [, m1, m2, m3, m4] = matchers.hsv.exec(color) || [];
1615
1261
  if (m1 && m2 && m3/* && m4 */) {
1616
1262
  return {
1617
1263
  h: m1, s: m2, v: m3, a: m4 !== undefined ? m4 : 1, format: 'hsv',
1618
1264
  };
1619
1265
  }
1266
+
1620
1267
  [, m1, m2, m3, m4] = matchers.hwb.exec(color) || [];
1621
1268
  if (m1 && m2 && m3) {
1622
1269
  return {
1623
1270
  h: m1, w: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'hwb',
1624
1271
  };
1625
1272
  }
1273
+
1626
1274
  [, m1, m2, m3, m4] = matchers.hex8.exec(color) || [];
1627
1275
  if (m1 && m2 && m3 && m4) {
1628
1276
  return {
@@ -1630,19 +1278,20 @@
1630
1278
  g: parseIntFromHex(m2),
1631
1279
  b: parseIntFromHex(m3),
1632
1280
  a: convertHexToDecimal(m4),
1633
- // format: named ? 'rgb' : 'hex8',
1634
- format: named ? 'rgb' : 'hex',
1281
+ format: 'hex',
1635
1282
  };
1636
1283
  }
1284
+
1637
1285
  [, m1, m2, m3] = matchers.hex6.exec(color) || [];
1638
1286
  if (m1 && m2 && m3) {
1639
1287
  return {
1640
1288
  r: parseIntFromHex(m1),
1641
1289
  g: parseIntFromHex(m2),
1642
1290
  b: parseIntFromHex(m3),
1643
- format: named ? 'rgb' : 'hex',
1291
+ format: 'hex',
1644
1292
  };
1645
1293
  }
1294
+
1646
1295
  [, m1, m2, m3, m4] = matchers.hex4.exec(color) || [];
1647
1296
  if (m1 && m2 && m3 && m4) {
1648
1297
  return {
@@ -1650,19 +1299,20 @@
1650
1299
  g: parseIntFromHex(m2 + m2),
1651
1300
  b: parseIntFromHex(m3 + m3),
1652
1301
  a: convertHexToDecimal(m4 + m4),
1653
- // format: named ? 'rgb' : 'hex8',
1654
- format: named ? 'rgb' : 'hex',
1302
+ format: 'hex',
1655
1303
  };
1656
1304
  }
1305
+
1657
1306
  [, m1, m2, m3] = matchers.hex3.exec(color) || [];
1658
1307
  if (m1 && m2 && m3) {
1659
1308
  return {
1660
1309
  r: parseIntFromHex(m1 + m1),
1661
1310
  g: parseIntFromHex(m2 + m2),
1662
1311
  b: parseIntFromHex(m3 + m3),
1663
- format: named ? 'rgb' : 'hex',
1312
+ format: 'hex',
1664
1313
  };
1665
1314
  }
1315
+
1666
1316
  return false;
1667
1317
  }
1668
1318
 
@@ -1693,7 +1343,9 @@
1693
1343
  */
1694
1344
  function inputToRGB(input) {
1695
1345
  let rgb = { r: 0, g: 0, b: 0 };
1346
+ /** @type {*} */
1696
1347
  let color = input;
1348
+ /** @type {string | number} */
1697
1349
  let a = 1;
1698
1350
  let s = null;
1699
1351
  let v = null;
@@ -1704,58 +1356,67 @@
1704
1356
  let r = null;
1705
1357
  let g = null;
1706
1358
  let ok = false;
1707
- let format = 'hex';
1359
+ const inputFormat = typeof color === 'object' && color.format;
1360
+ let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
1708
1361
 
1709
1362
  if (typeof input === 'string') {
1710
- // @ts-ignore -- this now is converted to object
1711
1363
  color = stringInputToObject(input);
1712
1364
  if (color) ok = true;
1713
1365
  }
1714
1366
  if (typeof color === 'object') {
1715
1367
  if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
1716
1368
  ({ r, g, b } = color);
1717
- // RGB values now are all in [0, 255] range
1718
- [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255);
1369
+ // RGB values now are all in [0, 1] range
1370
+ [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255));
1719
1371
  rgb = { r, g, b };
1720
1372
  ok = true;
1721
- format = 'rgb';
1722
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
1373
+ format = color.format || 'rgb';
1374
+ }
1375
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
1723
1376
  ({ h, s, v } = color);
1724
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
1725
- s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
1726
- v = typeof v === 'number' ? v : bound01(v, 100); // brightness can be `5%` or a [0, 1] value
1377
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
1378
+ s = bound01(s, 100); // saturation can be `5%` or a [0, 1] value
1379
+ v = bound01(v, 100); // brightness can be `5%` or a [0, 1] value
1727
1380
  rgb = hsvToRgb(h, s, v);
1728
1381
  ok = true;
1729
1382
  format = 'hsv';
1730
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
1383
+ }
1384
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
1731
1385
  ({ h, s, l } = color);
1732
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
1733
- s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
1734
- l = typeof l === 'number' ? l : bound01(l, 100); // lightness can be `5%` or a [0, 1] value
1386
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
1387
+ s = bound01(s, 100); // saturation can be `5%` or a [0, 1] value
1388
+ l = bound01(l, 100); // lightness can be `5%` or a [0, 1] value
1735
1389
  rgb = hslToRgb(h, s, l);
1736
1390
  ok = true;
1737
1391
  format = 'hsl';
1738
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
1392
+ }
1393
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
1739
1394
  ({ h, w, b } = color);
1740
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
1741
- w = typeof w === 'number' ? w : bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
1742
- b = typeof b === 'number' ? b : bound01(b, 100); // blackness can be `5%` or a [0, 1] value
1395
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
1396
+ w = bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
1397
+ b = bound01(b, 100); // blackness can be `5%` or a [0, 1] value
1743
1398
  rgb = hwbToRgb(h, w, b);
1744
1399
  ok = true;
1745
1400
  format = 'hwb';
1746
1401
  }
1747
1402
  if (isValidCSSUnit(color.a)) {
1748
- a = color.a;
1749
- a = isPercentage(`${a}`) ? bound01(a, 100) : a;
1403
+ a = color.a; // @ts-ignore -- `parseFloat` works with numbers too
1404
+ a = isPercentage(`${a}`) || parseFloat(a) > 1 ? bound01(a, 100) : a;
1750
1405
  }
1751
1406
  }
1407
+ if (typeof color === 'undefined') {
1408
+ ok = true;
1409
+ }
1752
1410
 
1753
1411
  return {
1754
- ok, // @ts-ignore
1755
- format: color.format || format,
1756
- r: Math.min(255, Math.max(rgb.r, 0)),
1757
- g: Math.min(255, Math.max(rgb.g, 0)),
1758
- b: Math.min(255, Math.max(rgb.b, 0)),
1412
+ ok,
1413
+ format,
1414
+ // r: Math.min(255, Math.max(rgb.r, 0)),
1415
+ // g: Math.min(255, Math.max(rgb.g, 0)),
1416
+ // b: Math.min(255, Math.max(rgb.b, 0)),
1417
+ r: rgb.r,
1418
+ g: rgb.g,
1419
+ b: rgb.b,
1759
1420
  a: boundAlpha(a),
1760
1421
  };
1761
1422
  }
@@ -1774,15 +1435,13 @@
1774
1435
  constructor(input, config) {
1775
1436
  let color = input;
1776
1437
  const configFormat = config && COLOR_FORMAT.includes(config)
1777
- ? config : 'rgb';
1438
+ ? config : '';
1778
1439
 
1779
- // If input is already a `Color`, return itself
1440
+ // If input is already a `Color`, clone its values
1780
1441
  if (color instanceof Color) {
1781
1442
  color = inputToRGB(color);
1782
1443
  }
1783
- if (typeof color === 'number') {
1784
- color = numberInputToObject(color);
1785
- }
1444
+
1786
1445
  const {
1787
1446
  r, g, b, a, ok, format,
1788
1447
  } = inputToRGB(color);
@@ -1791,7 +1450,7 @@
1791
1450
  const self = this;
1792
1451
 
1793
1452
  /** @type {CP.ColorInput} */
1794
- self.originalInput = color;
1453
+ self.originalInput = input;
1795
1454
  /** @type {number} */
1796
1455
  self.r = r;
1797
1456
  /** @type {number} */
@@ -1832,24 +1491,21 @@
1832
1491
  let R = 0;
1833
1492
  let G = 0;
1834
1493
  let B = 0;
1835
- const rp = r / 255;
1836
- const rg = g / 255;
1837
- const rb = b / 255;
1838
1494
 
1839
- if (rp <= 0.03928) {
1840
- R = rp / 12.92;
1495
+ if (r <= 0.03928) {
1496
+ R = r / 12.92;
1841
1497
  } else {
1842
- R = ((rp + 0.055) / 1.055) ** 2.4;
1498
+ R = ((r + 0.055) / 1.055) ** 2.4;
1843
1499
  }
1844
- if (rg <= 0.03928) {
1845
- G = rg / 12.92;
1500
+ if (g <= 0.03928) {
1501
+ G = g / 12.92;
1846
1502
  } else {
1847
- G = ((rg + 0.055) / 1.055) ** 2.4;
1503
+ G = ((g + 0.055) / 1.055) ** 2.4;
1848
1504
  }
1849
- if (rb <= 0.03928) {
1850
- B = rb / 12.92;
1505
+ if (b <= 0.03928) {
1506
+ B = b / 12.92;
1851
1507
  } else {
1852
- B = ((rb + 0.055) / 1.055) ** 2.4;
1508
+ B = ((b + 0.055) / 1.055) ** 2.4;
1853
1509
  }
1854
1510
  return 0.2126 * R + 0.7152 * G + 0.0722 * B;
1855
1511
  }
@@ -1859,7 +1515,7 @@
1859
1515
  * @returns {number} a number in the [0, 255] range
1860
1516
  */
1861
1517
  get brightness() {
1862
- const { r, g, b } = this;
1518
+ const { r, g, b } = this.toRgb();
1863
1519
  return (r * 299 + g * 587 + b * 114) / 1000;
1864
1520
  }
1865
1521
 
@@ -1868,12 +1524,14 @@
1868
1524
  * @returns {CP.RGBA} an {r,g,b,a} object with [0, 255] ranged values
1869
1525
  */
1870
1526
  toRgb() {
1871
- const {
1527
+ let {
1872
1528
  r, g, b, a,
1873
1529
  } = this;
1874
1530
 
1531
+ [r, g, b] = [r, g, b].map((n) => roundPart(n * 255 * 100) / 100);
1532
+ a = roundPart(a * 100) / 100;
1875
1533
  return {
1876
- r, g, b, a: roundPart(a * 100) / 100,
1534
+ r, g, b, a,
1877
1535
  };
1878
1536
  }
1879
1537
 
@@ -1967,7 +1625,7 @@
1967
1625
  toHsv() {
1968
1626
  const {
1969
1627
  r, g, b, a,
1970
- } = this.toRgb();
1628
+ } = this;
1971
1629
  const { h, s, v } = rgbToHsv(r, g, b);
1972
1630
 
1973
1631
  return {
@@ -1982,7 +1640,7 @@
1982
1640
  toHsl() {
1983
1641
  const {
1984
1642
  r, g, b, a,
1985
- } = this.toRgb();
1643
+ } = this;
1986
1644
  const { h, s, l } = rgbToHsl(r, g, b);
1987
1645
 
1988
1646
  return {
@@ -2067,6 +1725,7 @@
2067
1725
  */
2068
1726
  setAlpha(alpha) {
2069
1727
  const self = this;
1728
+ if (typeof alpha !== 'number') return self;
2070
1729
  self.a = boundAlpha(alpha);
2071
1730
  return self;
2072
1731
  }
@@ -2181,6 +1840,7 @@
2181
1840
  isOnePointZero,
2182
1841
  isPercentage,
2183
1842
  isValidCSSUnit,
1843
+ isColorName,
2184
1844
  pad2,
2185
1845
  clamp01,
2186
1846
  bound01,
@@ -2198,10 +1858,11 @@
2198
1858
  hueToRgb,
2199
1859
  hwbToRgb,
2200
1860
  parseIntFromHex,
2201
- numberInputToObject,
2202
1861
  stringInputToObject,
2203
1862
  inputToRGB,
2204
1863
  roundPart,
1864
+ getElementStyle,
1865
+ setElementStyle,
2205
1866
  ObjectAssign,
2206
1867
  });
2207
1868
 
@@ -2210,7 +1871,7 @@
2210
1871
  * Returns a color palette with a given set of parameters.
2211
1872
  * @example
2212
1873
  * new ColorPalette(0, 12, 10);
2213
- * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
1874
+ * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: Array<Color> }
2214
1875
  */
2215
1876
  class ColorPalette {
2216
1877
  /**
@@ -2230,48 +1891,352 @@
2230
1891
  [hue, hueSteps, lightSteps] = args;
2231
1892
  } else if (args.length === 2) {
2232
1893
  [hueSteps, lightSteps] = args;
2233
- } else {
2234
- throw TypeError('ColorPalette requires minimum 2 arguments');
1894
+ if ([hueSteps, lightSteps].some((n) => n < 1)) {
1895
+ throw TypeError('ColorPalette: both arguments must be higher than 0.');
1896
+ }
2235
1897
  }
2236
1898
 
2237
- /** @type {string[]} */
1899
+ /** @type {*} */
2238
1900
  const colors = [];
2239
-
2240
1901
  const hueStep = 360 / hueSteps;
2241
1902
  const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
2242
- const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
1903
+ const steps1To13 = [0.25, 0.2, 0.15, 0.11, 0.09, 0.075];
1904
+ const lightSets = [[1, 2, 3], [4, 5], [6, 7], [8, 9], [10, 11], [12, 13]];
1905
+ const closestSet = lightSets.find((set) => set.includes(lightSteps));
1906
+
1907
+ // find a lightStep that won't go beyond black and white
1908
+ // something within the [10-90] range of lightness
1909
+ const lightStep = closestSet
1910
+ ? steps1To13[lightSets.indexOf(closestSet)]
1911
+ : (100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100);
1912
+
1913
+ // light tints
1914
+ for (let i = 1; i < half + 1; i += 1) {
1915
+ lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
1916
+ }
1917
+
1918
+ // dark tints
1919
+ for (let i = 1; i < lightSteps - half; i += 1) {
1920
+ lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
1921
+ }
1922
+
1923
+ // feed `colors` Array
1924
+ for (let i = 0; i < hueSteps; i += 1) {
1925
+ const currentHue = ((hue + i * hueStep) % 360) / 360;
1926
+ lightnessArray.forEach((l) => {
1927
+ colors.push(new Color({ h: currentHue, s: 1, l }));
1928
+ });
1929
+ }
1930
+
1931
+ this.hue = hue;
1932
+ this.hueSteps = hueSteps;
1933
+ this.lightSteps = lightSteps;
1934
+ this.colors = colors;
1935
+ }
1936
+ }
1937
+
1938
+ ObjectAssign(ColorPalette, { Color });
1939
+
1940
+ /** @type {Record<string, string>} */
1941
+ const colorPickerLabels = {
1942
+ pickerLabel: 'Colour Picker',
1943
+ appearanceLabel: 'Colour Appearance',
1944
+ valueLabel: 'Colour Value',
1945
+ toggleLabel: 'Select Colour',
1946
+ presetsLabel: 'Colour Presets',
1947
+ defaultsLabel: 'Colour Defaults',
1948
+ formatLabel: 'Format',
1949
+ alphaLabel: 'Alpha',
1950
+ hexLabel: 'Hexadecimal',
1951
+ hueLabel: 'Hue',
1952
+ whitenessLabel: 'Whiteness',
1953
+ blacknessLabel: 'Blackness',
1954
+ saturationLabel: 'Saturation',
1955
+ lightnessLabel: 'Lightness',
1956
+ redLabel: 'Red',
1957
+ greenLabel: 'Green',
1958
+ blueLabel: 'Blue',
1959
+ };
1960
+
1961
+ /**
1962
+ * A list of 17 color names used for WAI-ARIA compliance.
1963
+ * @type {string[]}
1964
+ */
1965
+ const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
1966
+
1967
+ const tabIndex = 'tabindex';
1968
+
1969
+ /**
1970
+ * Check if a string is valid JSON string.
1971
+ * @param {string} str the string input
1972
+ * @returns {boolean} the query result
1973
+ */
1974
+ function isValidJSON(str) {
1975
+ try {
1976
+ JSON.parse(str);
1977
+ } catch (e) {
1978
+ return false;
1979
+ }
1980
+ return true;
1981
+ }
1982
+
1983
+ /**
1984
+ * Shortcut for `String.toUpperCase()`.
1985
+ *
1986
+ * @param {string} source input string
1987
+ * @returns {string} uppercase output string
1988
+ */
1989
+ const toUpperCase = (source) => source.toUpperCase();
1990
+
1991
+ /**
1992
+ * A global namespace for aria-haspopup.
1993
+ * @type {string}
1994
+ */
1995
+ const ariaHasPopup = 'aria-haspopup';
1996
+
1997
+ /**
1998
+ * A global namespace for aria-hidden.
1999
+ * @type {string}
2000
+ */
2001
+ const ariaHidden = 'aria-hidden';
2002
+
2003
+ /**
2004
+ * A global namespace for aria-labelledby.
2005
+ * @type {string}
2006
+ */
2007
+ const ariaLabelledBy = 'aria-labelledby';
2008
+
2009
+ /**
2010
+ * This is a shortie for `document.createElement` method
2011
+ * which allows you to create a new `HTMLElement` for a given `tagName`
2012
+ * or based on an object with specific non-readonly attributes:
2013
+ * `id`, `className`, `textContent`, `style`, etc.
2014
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
2015
+ *
2016
+ * @param {Record<string, string> | string} param `tagName` or object
2017
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
2018
+ */
2019
+ function createElement(param) {
2020
+ if (typeof param === 'string') {
2021
+ return getDocument().createElement(param);
2022
+ }
2023
+
2024
+ const { tagName } = param;
2025
+ const attr = { ...param };
2026
+ const newElement = createElement(tagName);
2027
+ delete attr.tagName;
2028
+ ObjectAssign(newElement, attr);
2029
+ return newElement;
2030
+ }
2031
+
2032
+ /**
2033
+ * This is a shortie for `document.createElementNS` method
2034
+ * which allows you to create a new `HTMLElement` for a given `tagName`
2035
+ * or based on an object with specific non-readonly attributes:
2036
+ * `id`, `className`, `textContent`, `style`, etc.
2037
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
2038
+ *
2039
+ * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
2040
+ * @param {Record<string, string> | string} param `tagName` or object
2041
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
2042
+ */
2043
+ function createElementNS(namespace, param) {
2044
+ if (typeof param === 'string') {
2045
+ return getDocument().createElementNS(namespace, param);
2046
+ }
2047
+
2048
+ const { tagName } = param;
2049
+ const attr = { ...param };
2050
+ const newElement = createElementNS(namespace, tagName);
2051
+ delete attr.tagName;
2052
+ ObjectAssign(newElement, attr);
2053
+ return newElement;
2054
+ }
2055
+
2056
+ const vHidden = 'v-hidden';
2057
+
2058
+ /**
2059
+ * Returns the color form for `ColorPicker`.
2060
+ *
2061
+ * @param {CP.ColorPicker} self the `ColorPicker` instance
2062
+ * @returns {HTMLElement | Element} a new `<div>` element with color component `<input>`
2063
+ */
2064
+ function getColorForm(self) {
2065
+ const { format, id, componentLabels } = self;
2066
+ const colorForm = createElement({
2067
+ tagName: 'div',
2068
+ className: `color-form ${format}`,
2069
+ });
2070
+
2071
+ let components = ['hex'];
2072
+ if (format === 'rgb') components = ['red', 'green', 'blue', 'alpha'];
2073
+ else if (format === 'hsl') components = ['hue', 'saturation', 'lightness', 'alpha'];
2074
+ else if (format === 'hwb') components = ['hue', 'whiteness', 'blackness', 'alpha'];
2075
+
2076
+ components.forEach((c) => {
2077
+ const [C] = format === 'hex' ? ['#'] : toUpperCase(c).split('');
2078
+ const cID = `color_${format}_${c}_${id}`;
2079
+ const formatLabel = componentLabels[`${c}Label`];
2080
+ const cInputLabel = createElement({ tagName: 'label' });
2081
+ setAttribute(cInputLabel, 'for', cID);
2082
+ cInputLabel.append(
2083
+ createElement({ tagName: 'span', ariaHidden: 'true', innerText: `${C}:` }),
2084
+ createElement({ tagName: 'span', className: vHidden, innerText: formatLabel }),
2085
+ );
2086
+ const cInput = createElement({
2087
+ tagName: 'input',
2088
+ id: cID,
2089
+ // name: cID, - prevent saving the value to a form
2090
+ type: format === 'hex' ? 'text' : 'number',
2091
+ value: c === 'alpha' ? '100' : '0',
2092
+ className: `color-input ${c}`,
2093
+ });
2094
+ setAttribute(cInput, 'autocomplete', 'off');
2095
+ setAttribute(cInput, 'spellcheck', 'false');
2096
+
2097
+ // alpha
2098
+ let max = '100';
2099
+ let step = '1';
2100
+ if (c !== 'alpha') {
2101
+ if (format === 'rgb') {
2102
+ max = '255'; step = '1';
2103
+ } else if (c === 'hue') {
2104
+ max = '360'; step = '1';
2105
+ }
2106
+ }
2107
+ ObjectAssign(cInput, {
2108
+ min: '0',
2109
+ max,
2110
+ step,
2111
+ });
2112
+ colorForm.append(cInputLabel, cInput);
2113
+ });
2114
+ return colorForm;
2115
+ }
2116
+
2117
+ /**
2118
+ * A global namespace for aria-label.
2119
+ * @type {string}
2120
+ */
2121
+ const ariaLabel = 'aria-label';
2122
+
2123
+ /**
2124
+ * A global namespace for aria-valuemin.
2125
+ * @type {string}
2126
+ */
2127
+ const ariaValueMin = 'aria-valuemin';
2128
+
2129
+ /**
2130
+ * A global namespace for aria-valuemax.
2131
+ * @type {string}
2132
+ */
2133
+ const ariaValueMax = 'aria-valuemax';
2134
+
2135
+ /**
2136
+ * Returns all color controls for `ColorPicker`.
2137
+ *
2138
+ * @param {CP.ColorPicker} self the `ColorPicker` instance
2139
+ * @returns {HTMLElement | Element} color controls
2140
+ */
2141
+ function getColorControls(self) {
2142
+ const { format, componentLabels } = self;
2143
+ const {
2144
+ hueLabel, alphaLabel, lightnessLabel, saturationLabel,
2145
+ whitenessLabel, blacknessLabel,
2146
+ } = componentLabels;
2147
+
2148
+ const max1 = format === 'hsl' ? 360 : 100;
2149
+ const max2 = format === 'hsl' ? 100 : 360;
2150
+ const max3 = 100;
2151
+
2152
+ let ctrl1Label = format === 'hsl'
2153
+ ? `${hueLabel} & ${lightnessLabel}`
2154
+ : `${lightnessLabel} & ${saturationLabel}`;
2155
+
2156
+ ctrl1Label = format === 'hwb'
2157
+ ? `${whitenessLabel} & ${blacknessLabel}`
2158
+ : ctrl1Label;
2159
+
2160
+ const ctrl2Label = format === 'hsl'
2161
+ ? `${saturationLabel}`
2162
+ : `${hueLabel}`;
2163
+
2164
+ const colorControls = createElement({
2165
+ tagName: 'div',
2166
+ className: `color-controls ${format}`,
2167
+ });
2168
+
2169
+ const colorPointer = 'color-pointer';
2170
+ const colorSlider = 'color-slider';
2171
+
2172
+ const controls = [
2173
+ {
2174
+ i: 1,
2175
+ c: colorPointer,
2176
+ l: ctrl1Label,
2177
+ min: 0,
2178
+ max: max1,
2179
+ },
2180
+ {
2181
+ i: 2,
2182
+ c: colorSlider,
2183
+ l: ctrl2Label,
2184
+ min: 0,
2185
+ max: max2,
2186
+ },
2187
+ {
2188
+ i: 3,
2189
+ c: colorSlider,
2190
+ l: alphaLabel,
2191
+ min: 0,
2192
+ max: max3,
2193
+ },
2194
+ ];
2195
+
2196
+ controls.forEach((template) => {
2197
+ const {
2198
+ i, c, l, min, max,
2199
+ } = template;
2200
+ const control = createElement({
2201
+ tagName: 'div',
2202
+ className: 'color-control',
2203
+ });
2204
+ setAttribute(control, 'role', 'presentation');
2243
2205
 
2244
- let lightStep = 0.25;
2245
- lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
2246
- lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
2247
- lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
2248
- lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
2249
- lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
2250
- lightStep = lightSteps > 13 ? estimatedStep : lightStep;
2206
+ control.append(
2207
+ createElement({
2208
+ tagName: 'div',
2209
+ className: `visual-control visual-control${i}`,
2210
+ }),
2211
+ );
2251
2212
 
2252
- // light tints
2253
- for (let i = 1; i < half + 1; i += 1) {
2254
- lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
2255
- }
2213
+ const knob = createElement({
2214
+ tagName: 'div',
2215
+ className: `${c} knob`,
2216
+ ariaLive: 'polite',
2217
+ });
2256
2218
 
2257
- // dark tints
2258
- for (let i = 1; i < lightSteps - half; i += 1) {
2259
- lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
2260
- }
2219
+ setAttribute(knob, ariaLabel, l);
2220
+ setAttribute(knob, 'role', 'slider');
2221
+ setAttribute(knob, tabIndex, '0');
2222
+ setAttribute(knob, ariaValueMin, `${min}`);
2223
+ setAttribute(knob, ariaValueMax, `${max}`);
2224
+ control.append(knob);
2225
+ colorControls.append(control);
2226
+ });
2261
2227
 
2262
- // feed `colors` Array
2263
- for (let i = 0; i < hueSteps; i += 1) {
2264
- const currentHue = ((hue + i * hueStep) % 360) / 360;
2265
- lightnessArray.forEach((l) => {
2266
- colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
2267
- });
2268
- }
2228
+ return colorControls;
2229
+ }
2269
2230
 
2270
- this.hue = hue;
2271
- this.hueSteps = hueSteps;
2272
- this.lightSteps = lightSteps;
2273
- this.colors = colors;
2274
- }
2231
+ /**
2232
+ * Helps setting CSS variables to the color-menu.
2233
+ * @param {HTMLElement} element
2234
+ * @param {Record<string,any>} props
2235
+ */
2236
+ function setCSSProperties(element, props) {
2237
+ ObjectKeys(props).forEach((prop) => {
2238
+ element.style.setProperty(prop, props[prop]);
2239
+ });
2275
2240
  }
2276
2241
 
2277
2242
  /**
@@ -2307,7 +2272,8 @@
2307
2272
  optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2308
2273
  const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2309
2274
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2310
-
2275
+ /** @type {HTMLUListElement} */
2276
+ // @ts-ignore -- <UL> is an `HTMLElement`
2311
2277
  const menu = createElement({
2312
2278
  tagName: 'ul',
2313
2279
  className: finalClass,
@@ -2315,7 +2281,7 @@
2315
2281
  setAttribute(menu, 'role', 'listbox');
2316
2282
  setAttribute(menu, ariaLabel, menuLabel);
2317
2283
 
2318
- if (isScrollable) { // @ts-ignore
2284
+ if (isScrollable) {
2319
2285
  setCSSProperties(menu, {
2320
2286
  '--grid-item-size': `${optionSize}rem`,
2321
2287
  '--grid-fit': fit,
@@ -2326,15 +2292,19 @@
2326
2292
  }
2327
2293
 
2328
2294
  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');
2295
+ let [value, label] = typeof x === 'string' ? x.trim().split(':') : [];
2296
+ if (x instanceof Color) {
2297
+ value = x.toHexString();
2298
+ label = value;
2299
+ }
2300
+ const color = new Color(x instanceof Color ? x : value, format);
2301
+ const isActive = color.toString() === getAttribute(input, 'value');
2332
2302
  const active = isActive ? ' active' : '';
2333
2303
 
2334
2304
  const option = createElement({
2335
2305
  tagName: 'li',
2336
2306
  className: `color-option${active}`,
2337
- innerText: `${label || x}`,
2307
+ innerText: `${label || value}`,
2338
2308
  });
2339
2309
 
2340
2310
  setAttribute(option, tabIndex, '0');
@@ -2343,7 +2313,7 @@
2343
2313
  setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2344
2314
 
2345
2315
  if (isOptionsMenu) {
2346
- setElementStyle(option, { backgroundColor: x });
2316
+ setElementStyle(option, { backgroundColor: value });
2347
2317
  }
2348
2318
 
2349
2319
  menu.append(option);
@@ -2352,55 +2322,10 @@
2352
2322
  }
2353
2323
 
2354
2324
  /**
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) {
2325
+ * Generate HTML markup and update instance properties.
2326
+ * @param {CP.ColorPicker} self
2327
+ */
2328
+ function setMarkup(self) {
2404
2329
  const {
2405
2330
  input, parent, format, id, componentLabels, colorKeywords, colorPresets,
2406
2331
  } = self;
@@ -2415,9 +2340,7 @@
2415
2340
  self.color = new Color(color, format);
2416
2341
 
2417
2342
  // set initial controls dimensions
2418
- // make the controls smaller on mobile
2419
- const dropClass = isMobile ? ' mobile' : '';
2420
- const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
2343
+ const formatString = format === 'hex' ? hexLabel : toUpperCase(format);
2421
2344
 
2422
2345
  const pickerBtn = createElement({
2423
2346
  id: `picker-btn-${id}`,
@@ -2434,7 +2357,7 @@
2434
2357
 
2435
2358
  const pickerDropdown = createElement({
2436
2359
  tagName: 'div',
2437
- className: `color-dropdown picker${dropClass}`,
2360
+ className: 'color-dropdown picker',
2438
2361
  });
2439
2362
  setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
2440
2363
  setAttribute(pickerDropdown, 'role', 'group');
@@ -2450,7 +2373,7 @@
2450
2373
  if (colorKeywords || colorPresets) {
2451
2374
  const presetsDropdown = createElement({
2452
2375
  tagName: 'div',
2453
- className: `color-dropdown scrollable menu${dropClass}`,
2376
+ className: 'color-dropdown scrollable menu',
2454
2377
  });
2455
2378
 
2456
2379
  // color presets
@@ -2500,6 +2423,37 @@
2500
2423
  setAttribute(input, tabIndex, '-1');
2501
2424
  }
2502
2425
 
2426
+ var version = "0.0.2alpha3";
2427
+
2428
+ // @ts-ignore
2429
+
2430
+ const Version = version;
2431
+
2432
+ // ColorPicker GC
2433
+ // ==============
2434
+ const colorPickerString = 'color-picker';
2435
+ const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2436
+ const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2437
+ const colorPickerDefaults = {
2438
+ componentLabels: colorPickerLabels,
2439
+ colorLabels: colorNames,
2440
+ format: 'rgb',
2441
+ colorPresets: false,
2442
+ colorKeywords: false,
2443
+ };
2444
+
2445
+ // ColorPicker Static Methods
2446
+ // ==========================
2447
+
2448
+ /** @type {CP.GetInstance<ColorPicker>} */
2449
+ const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2450
+
2451
+ /** @type {CP.InitCallback<ColorPicker>} */
2452
+ const initColorPicker = (element) => new ColorPicker(element);
2453
+
2454
+ // ColorPicker Private Methods
2455
+ // ===========================
2456
+
2503
2457
  /**
2504
2458
  * Add / remove `ColorPicker` main event listeners.
2505
2459
  * @param {ColorPicker} self
@@ -2512,8 +2466,6 @@
2512
2466
  fn(input, focusinEvent, self.showPicker);
2513
2467
  fn(pickerToggle, mouseclickEvent, self.togglePicker);
2514
2468
 
2515
- fn(input, keydownEvent, self.keyToggle);
2516
-
2517
2469
  if (menuToggle) {
2518
2470
  fn(menuToggle, mouseclickEvent, self.toggleMenu);
2519
2471
  }
@@ -2551,8 +2503,7 @@
2551
2503
  fn(doc, pointerEvents.move, self.pointerMove);
2552
2504
  fn(doc, pointerEvents.up, self.pointerUp);
2553
2505
  fn(parent, focusoutEvent, self.handleFocusOut);
2554
- // @ts-ignore -- this is `Window`
2555
- fn(win, keyupEvent, self.handleDismiss);
2506
+ fn(doc, keyupEvent, self.handleDismiss);
2556
2507
  }
2557
2508
 
2558
2509
  /**
@@ -2636,7 +2587,7 @@
2636
2587
  const input = querySelector(target);
2637
2588
 
2638
2589
  // invalidate
2639
- if (!input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
2590
+ if (!input) throw new TypeError(`ColorPicker target "${target}" cannot be found.`);
2640
2591
  self.input = input;
2641
2592
 
2642
2593
  const parent = closest(input, colorPickerParentSelector);
@@ -2683,15 +2634,14 @@
2683
2634
  });
2684
2635
 
2685
2636
  // update and expose component labels
2686
- const tempLabels = ObjectAssign({}, colorPickerLabels);
2687
- const jsonLabels = componentLabels && isValidJSON(componentLabels)
2688
- ? JSON.parse(componentLabels) : componentLabels || {};
2637
+ const tempComponentLabels = componentLabels && isValidJSON(componentLabels)
2638
+ ? JSON.parse(componentLabels) : componentLabels;
2689
2639
 
2690
2640
  /** @type {Record<string, string>} */
2691
- self.componentLabels = ObjectAssign(tempLabels, jsonLabels);
2641
+ self.componentLabels = ObjectAssign(colorPickerLabels, tempComponentLabels);
2692
2642
 
2693
2643
  /** @type {Color} */
2694
- self.color = new Color('white', format);
2644
+ self.color = new Color(input.value || '#fff', format);
2695
2645
 
2696
2646
  /** @type {CP.ColorFormats} */
2697
2647
  self.format = format;
@@ -2700,7 +2650,7 @@
2700
2650
  if (colorKeywords instanceof Array) {
2701
2651
  self.colorKeywords = colorKeywords;
2702
2652
  } else if (typeof colorKeywords === 'string' && colorKeywords.length) {
2703
- self.colorKeywords = colorKeywords.split(',');
2653
+ self.colorKeywords = colorKeywords.split(',').map((x) => x.trim());
2704
2654
  }
2705
2655
 
2706
2656
  // set colour presets
@@ -2729,11 +2679,10 @@
2729
2679
  self.handleFocusOut = self.handleFocusOut.bind(self);
2730
2680
  self.changeHandler = self.changeHandler.bind(self);
2731
2681
  self.handleDismiss = self.handleDismiss.bind(self);
2732
- self.keyToggle = self.keyToggle.bind(self);
2733
2682
  self.handleKnobs = self.handleKnobs.bind(self);
2734
2683
 
2735
2684
  // generate markup
2736
- initCallback(self);
2685
+ setMarkup(self);
2737
2686
 
2738
2687
  const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
2739
2688
  // set main elements
@@ -2821,76 +2770,83 @@
2821
2770
  return inputValue !== '' && new Color(inputValue).isValid;
2822
2771
  }
2823
2772
 
2773
+ /** Returns the colour appearance, usually the closest colour name for the current value. */
2774
+ get appearance() {
2775
+ const {
2776
+ colorLabels, hsl, hsv, format,
2777
+ } = this;
2778
+
2779
+ const hue = roundPart(hsl.h * 360);
2780
+ const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
2781
+ const saturation = roundPart(saturationSource * 100);
2782
+ const lightness = roundPart(hsl.l * 100);
2783
+ const hsvl = hsv.v * 100;
2784
+
2785
+ let colorName;
2786
+
2787
+ // determine color appearance
2788
+ if (lightness === 100 && saturation === 0) {
2789
+ colorName = colorLabels.white;
2790
+ } else if (lightness === 0) {
2791
+ colorName = colorLabels.black;
2792
+ } else if (saturation === 0) {
2793
+ colorName = colorLabels.grey;
2794
+ } else if (hue < 15 || hue >= 345) {
2795
+ colorName = colorLabels.red;
2796
+ } else if (hue >= 15 && hue < 45) {
2797
+ colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
2798
+ } else if (hue >= 45 && hue < 75) {
2799
+ const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
2800
+ const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
2801
+ colorName = isGold ? colorLabels.gold : colorLabels.yellow;
2802
+ colorName = isOlive ? colorLabels.olive : colorName;
2803
+ } else if (hue >= 75 && hue < 155) {
2804
+ colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
2805
+ } else if (hue >= 155 && hue < 175) {
2806
+ colorName = colorLabels.teal;
2807
+ } else if (hue >= 175 && hue < 195) {
2808
+ colorName = colorLabels.cyan;
2809
+ } else if (hue >= 195 && hue < 255) {
2810
+ colorName = colorLabels.blue;
2811
+ } else if (hue >= 255 && hue < 270) {
2812
+ colorName = colorLabels.violet;
2813
+ } else if (hue >= 270 && hue < 295) {
2814
+ colorName = colorLabels.magenta;
2815
+ } else if (hue >= 295 && hue < 345) {
2816
+ colorName = colorLabels.pink;
2817
+ }
2818
+ return colorName;
2819
+ }
2820
+
2824
2821
  /** Updates `ColorPicker` visuals. */
2825
2822
  updateVisuals() {
2826
2823
  const self = this;
2827
2824
  const {
2828
- format, controlPositions, visuals,
2825
+ controlPositions, visuals,
2829
2826
  } = self;
2830
2827
  const [v1, v2, v3] = visuals;
2831
- const { offsetWidth, offsetHeight } = v1;
2832
- const hue = format === 'hsl'
2833
- ? controlPositions.c1x / offsetWidth
2834
- : controlPositions.c2y / offsetHeight;
2835
- // @ts-ignore - `hslToRgb` is assigned to `Color` as static method
2836
- const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
2828
+ const { offsetHeight } = v1;
2829
+ const hue = controlPositions.c2y / offsetHeight;
2830
+ const { r, g, b } = new Color({ h: hue, s: 1, l: 0.5 }).toRgb();
2837
2831
  const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
2838
2832
  const alpha = 1 - controlPositions.c3y / offsetHeight;
2839
2833
  const roundA = roundPart((alpha * 100)) / 100;
2840
2834
 
2841
- if (format !== 'hsl') {
2842
- const fill = new Color({
2843
- h: hue, s: 1, l: 0.5, a: alpha,
2844
- }).toRgbString();
2845
- const hueGradient = `linear-gradient(
2846
- rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2847
- rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2848
- rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2849
- rgb(255,0,0) 100%)`;
2850
- setElementStyle(v1, {
2851
- background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2852
- linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2853
- ${whiteGrad}`,
2854
- });
2855
- setElementStyle(v2, { background: hueGradient });
2856
- } else {
2857
- const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
2858
- const fill0 = new Color({
2859
- r: 255, g: 0, b: 0, a: alpha,
2860
- }).saturate(-saturation).toRgbString();
2861
- const fill1 = new Color({
2862
- r: 255, g: 255, b: 0, a: alpha,
2863
- }).saturate(-saturation).toRgbString();
2864
- const fill2 = new Color({
2865
- r: 0, g: 255, b: 0, a: alpha,
2866
- }).saturate(-saturation).toRgbString();
2867
- const fill3 = new Color({
2868
- r: 0, g: 255, b: 255, a: alpha,
2869
- }).saturate(-saturation).toRgbString();
2870
- const fill4 = new Color({
2871
- r: 0, g: 0, b: 255, a: alpha,
2872
- }).saturate(-saturation).toRgbString();
2873
- const fill5 = new Color({
2874
- r: 255, g: 0, b: 255, a: alpha,
2875
- }).saturate(-saturation).toRgbString();
2876
- const fill6 = new Color({
2877
- r: 255, g: 0, b: 0, a: alpha,
2878
- }).saturate(-saturation).toRgbString();
2879
- const fillGradient = `linear-gradient(to right,
2880
- ${fill0} 0%, ${fill1} 16.67%, ${fill2} 33.33%, ${fill3} 50%,
2881
- ${fill4} 66.67%, ${fill5} 83.33%, ${fill6} 100%)`;
2882
- const lightGrad = `linear-gradient(rgba(255,255,255,${roundA}) 0%, rgba(255,255,255,0) 50%),
2883
- linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,${roundA}) 100%)`;
2884
-
2885
- setElementStyle(v1, { background: `${lightGrad},${fillGradient},${whiteGrad}` });
2886
- const {
2887
- r: gr, g: gg, b: gb,
2888
- } = new Color({ r, g, b }).greyscale().toRgb();
2835
+ const fill = new Color({
2836
+ h: hue, s: 1, l: 0.5, a: alpha,
2837
+ }).toRgbString();
2838
+ const hueGradient = `linear-gradient(
2839
+ rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2840
+ rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2841
+ rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2842
+ rgb(255,0,0) 100%)`;
2843
+ setElementStyle(v1, {
2844
+ background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2845
+ linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2846
+ ${whiteGrad}`,
2847
+ });
2848
+ setElementStyle(v2, { background: hueGradient });
2889
2849
 
2890
- setElementStyle(v2, {
2891
- background: `linear-gradient(rgb(${r},${g},${b}) 0%, rgb(${gr},${gg},${gb}) 100%)`,
2892
- });
2893
- }
2894
2850
  setElementStyle(v3, {
2895
2851
  background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
2896
2852
  });
@@ -2929,7 +2885,7 @@
2929
2885
  const self = this;
2930
2886
  const { activeElement } = getDocument(self.input);
2931
2887
 
2932
- if ((isMobile && self.dragElement)
2888
+ if ((e.type === touchmoveEvent && self.dragElement)
2933
2889
  || (activeElement && self.controlKnobs.includes(activeElement))) {
2934
2890
  e.stopPropagation();
2935
2891
  e.preventDefault();
@@ -3040,13 +2996,13 @@
3040
2996
  const [v1, v2, v3] = visuals;
3041
2997
  const [c1, c2, c3] = controlKnobs;
3042
2998
  /** @type {HTMLElement} */
3043
- const visual = hasClass(target, 'visual-control')
3044
- ? target : querySelector('.visual-control', target.parentElement);
2999
+ const visual = controlKnobs.includes(target) ? target.previousElementSibling : target;
3045
3000
  const visualRect = getBoundingClientRect(visual);
3001
+ const html = getDocumentElement(v1);
3046
3002
  const X = type === 'touchstart' ? touches[0].pageX : pageX;
3047
3003
  const Y = type === 'touchstart' ? touches[0].pageY : pageY;
3048
- const offsetX = X - window.pageXOffset - visualRect.left;
3049
- const offsetY = Y - window.pageYOffset - visualRect.top;
3004
+ const offsetX = X - html.scrollLeft - visualRect.left;
3005
+ const offsetY = Y - html.scrollTop - visualRect.top;
3050
3006
 
3051
3007
  if (target === v1 || target === c1) {
3052
3008
  self.dragElement = visual;
@@ -3106,10 +3062,11 @@
3106
3062
  if (!dragElement) return;
3107
3063
 
3108
3064
  const controlRect = getBoundingClientRect(dragElement);
3109
- const X = type === 'touchmove' ? touches[0].pageX : pageX;
3110
- const Y = type === 'touchmove' ? touches[0].pageY : pageY;
3111
- const offsetX = X - window.pageXOffset - controlRect.left;
3112
- const offsetY = Y - window.pageYOffset - controlRect.top;
3065
+ const win = getDocumentElement(v1);
3066
+ const X = type === touchmoveEvent ? touches[0].pageX : pageX;
3067
+ const Y = type === touchmoveEvent ? touches[0].pageY : pageY;
3068
+ const offsetX = X - win.scrollLeft - controlRect.left;
3069
+ const offsetY = Y - win.scrollTop - controlRect.top;
3113
3070
 
3114
3071
  if (dragElement === v1) {
3115
3072
  self.changeControl1(offsetX, offsetY);
@@ -3136,19 +3093,19 @@
3136
3093
  if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
3137
3094
  e.preventDefault();
3138
3095
 
3139
- const { format, controlKnobs, visuals } = self;
3096
+ const { controlKnobs, visuals } = self;
3140
3097
  const { offsetWidth, offsetHeight } = visuals[0];
3141
3098
  const [c1, c2, c3] = controlKnobs;
3142
3099
  const { activeElement } = getDocument(c1);
3143
3100
  const currentKnob = controlKnobs.find((x) => x === activeElement);
3144
- const yRatio = offsetHeight / (format === 'hsl' ? 100 : 360);
3101
+ const yRatio = offsetHeight / 360;
3145
3102
 
3146
3103
  if (currentKnob) {
3147
3104
  let offsetX = 0;
3148
3105
  let offsetY = 0;
3149
3106
 
3150
3107
  if (target === c1) {
3151
- const xRatio = offsetWidth / (format === 'hsl' ? 360 : 100);
3108
+ const xRatio = offsetWidth / 100;
3152
3109
 
3153
3110
  if ([keyArrowLeft, keyArrowRight].includes(code)) {
3154
3111
  self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
@@ -3198,7 +3155,7 @@
3198
3155
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
3199
3156
  if (activeElement === input) {
3200
3157
  if (isNonColorValue) {
3201
- colorSource = 'white';
3158
+ colorSource = currentValue === 'transparent' ? 'rgba(0,0,0,0)' : 'rgb(0,0,0)';
3202
3159
  } else {
3203
3160
  colorSource = currentValue;
3204
3161
  }
@@ -3249,9 +3206,7 @@
3249
3206
  changeControl1(X, Y) {
3250
3207
  const self = this;
3251
3208
  let [offsetX, offsetY] = [0, 0];
3252
- const {
3253
- format, controlPositions, visuals,
3254
- } = self;
3209
+ const { controlPositions, visuals } = self;
3255
3210
  const { offsetHeight, offsetWidth } = visuals[0];
3256
3211
 
3257
3212
  if (X > offsetWidth) offsetX = offsetWidth;
@@ -3260,29 +3215,19 @@
3260
3215
  if (Y > offsetHeight) offsetY = offsetHeight;
3261
3216
  else if (Y >= 0) offsetY = Y;
3262
3217
 
3263
- const hue = format === 'hsl'
3264
- ? offsetX / offsetWidth
3265
- : controlPositions.c2y / offsetHeight;
3218
+ const hue = controlPositions.c2y / offsetHeight;
3266
3219
 
3267
- const saturation = format === 'hsl'
3268
- ? 1 - controlPositions.c2y / offsetHeight
3269
- : offsetX / offsetWidth;
3220
+ const saturation = offsetX / offsetWidth;
3270
3221
 
3271
3222
  const lightness = 1 - offsetY / offsetHeight;
3272
3223
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3273
3224
 
3274
- const colorObject = format === 'hsl'
3275
- ? {
3276
- h: hue, s: saturation, l: lightness, a: alpha,
3277
- }
3278
- : {
3279
- h: hue, s: saturation, v: lightness, a: alpha,
3280
- };
3281
-
3282
3225
  // new color
3283
3226
  const {
3284
3227
  r, g, b, a,
3285
- } = new Color(colorObject);
3228
+ } = new Color({
3229
+ h: hue, s: saturation, v: lightness, a: alpha,
3230
+ });
3286
3231
 
3287
3232
  ObjectAssign(self.color, {
3288
3233
  r, g, b, a,
@@ -3309,7 +3254,7 @@
3309
3254
  changeControl2(Y) {
3310
3255
  const self = this;
3311
3256
  const {
3312
- format, controlPositions, visuals,
3257
+ controlPositions, visuals,
3313
3258
  } = self;
3314
3259
  const { offsetHeight, offsetWidth } = visuals[0];
3315
3260
 
@@ -3318,26 +3263,17 @@
3318
3263
  if (Y > offsetHeight) offsetY = offsetHeight;
3319
3264
  else if (Y >= 0) offsetY = Y;
3320
3265
 
3321
- const hue = format === 'hsl'
3322
- ? controlPositions.c1x / offsetWidth
3323
- : offsetY / offsetHeight;
3324
- const saturation = format === 'hsl'
3325
- ? 1 - offsetY / offsetHeight
3326
- : controlPositions.c1x / offsetWidth;
3266
+ const hue = offsetY / offsetHeight;
3267
+ const saturation = controlPositions.c1x / offsetWidth;
3327
3268
  const lightness = 1 - controlPositions.c1y / offsetHeight;
3328
3269
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3329
- const colorObject = format === 'hsl'
3330
- ? {
3331
- h: hue, s: saturation, l: lightness, a: alpha,
3332
- }
3333
- : {
3334
- h: hue, s: saturation, v: lightness, a: alpha,
3335
- };
3336
3270
 
3337
3271
  // new color
3338
3272
  const {
3339
3273
  r, g, b, a,
3340
- } = new Color(colorObject);
3274
+ } = new Color({
3275
+ h: hue, s: saturation, v: lightness, a: alpha,
3276
+ });
3341
3277
 
3342
3278
  ObjectAssign(self.color, {
3343
3279
  r, g, b, a,
@@ -3424,18 +3360,18 @@
3424
3360
  setControlPositions() {
3425
3361
  const self = this;
3426
3362
  const {
3427
- format, visuals, color, hsl, hsv,
3363
+ visuals, color, hsv,
3428
3364
  } = self;
3429
3365
  const { offsetHeight, offsetWidth } = visuals[0];
3430
3366
  const alpha = color.a;
3431
- const hue = hsl.h;
3367
+ const hue = hsv.h;
3432
3368
 
3433
- const saturation = format !== 'hsl' ? hsv.s : hsl.s;
3434
- const lightness = format !== 'hsl' ? hsv.v : hsl.l;
3369
+ const saturation = hsv.s;
3370
+ const lightness = hsv.v;
3435
3371
 
3436
- self.controlPositions.c1x = format !== 'hsl' ? saturation * offsetWidth : hue * offsetWidth;
3372
+ self.controlPositions.c1x = saturation * offsetWidth;
3437
3373
  self.controlPositions.c1y = (1 - lightness) * offsetHeight;
3438
- self.controlPositions.c2y = format !== 'hsl' ? hue * offsetHeight : (1 - saturation) * offsetHeight;
3374
+ self.controlPositions.c2y = hue * offsetHeight;
3439
3375
  self.controlPositions.c3y = (1 - alpha) * offsetHeight;
3440
3376
  }
3441
3377
 
@@ -3443,78 +3379,40 @@
3443
3379
  updateAppearance() {
3444
3380
  const self = this;
3445
3381
  const {
3446
- componentLabels, colorLabels, color, parent,
3447
- hsl, hsv, hex, format, controlKnobs,
3382
+ componentLabels, color, parent,
3383
+ hsv, hex, format, controlKnobs,
3448
3384
  } = self;
3449
3385
  const {
3450
3386
  appearanceLabel, hexLabel, valueLabel,
3451
3387
  } = componentLabels;
3452
- const { r, g, b } = color.toRgb();
3388
+ let { r, g, b } = color.toRgb();
3453
3389
  const [knob1, knob2, knob3] = controlKnobs;
3454
- const hue = roundPart(hsl.h * 360);
3390
+ const hue = roundPart(hsv.h * 360);
3455
3391
  const alpha = color.a;
3456
- const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
3457
- const saturation = roundPart(saturationSource * 100);
3458
- const lightness = roundPart(hsl.l * 100);
3459
- const hsvl = hsv.v * 100;
3460
- let colorName;
3461
-
3462
- // determine color appearance
3463
- if (lightness === 100 && saturation === 0) {
3464
- colorName = colorLabels.white;
3465
- } else if (lightness === 0) {
3466
- colorName = colorLabels.black;
3467
- } else if (saturation === 0) {
3468
- colorName = colorLabels.grey;
3469
- } else if (hue < 15 || hue >= 345) {
3470
- colorName = colorLabels.red;
3471
- } else if (hue >= 15 && hue < 45) {
3472
- colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
3473
- } else if (hue >= 45 && hue < 75) {
3474
- const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
3475
- const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
3476
- colorName = isGold ? colorLabels.gold : colorLabels.yellow;
3477
- colorName = isOlive ? colorLabels.olive : colorName;
3478
- } else if (hue >= 75 && hue < 155) {
3479
- colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
3480
- } else if (hue >= 155 && hue < 175) {
3481
- colorName = colorLabels.teal;
3482
- } else if (hue >= 175 && hue < 195) {
3483
- colorName = colorLabels.cyan;
3484
- } else if (hue >= 195 && hue < 255) {
3485
- colorName = colorLabels.blue;
3486
- } else if (hue >= 255 && hue < 270) {
3487
- colorName = colorLabels.violet;
3488
- } else if (hue >= 270 && hue < 295) {
3489
- colorName = colorLabels.magenta;
3490
- } else if (hue >= 295 && hue < 345) {
3491
- colorName = colorLabels.pink;
3492
- }
3392
+ const saturation = roundPart(hsv.s * 100);
3393
+ const lightness = roundPart(hsv.v * 100);
3394
+ const colorName = self.appearance;
3493
3395
 
3494
3396
  let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
3495
3397
 
3496
- if (format === 'hsl') {
3497
- colorLabel = `HSL: ${hue}°, ${saturation}%, ${lightness}%`;
3498
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3499
- setAttribute(knob1, ariaValueText, `${hue}° & ${lightness}%`);
3500
- setAttribute(knob1, ariaValueNow, `${hue}`);
3501
- setAttribute(knob2, ariaValueText, `${saturation}%`);
3502
- setAttribute(knob2, ariaValueNow, `${saturation}`);
3503
- } else if (format === 'hwb') {
3398
+ if (format === 'hwb') {
3504
3399
  const { hwb } = self;
3505
3400
  const whiteness = roundPart(hwb.w * 100);
3506
3401
  const blackness = roundPart(hwb.b * 100);
3507
3402
  colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
3508
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3509
3403
  setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
3510
3404
  setAttribute(knob1, ariaValueNow, `${whiteness}`);
3405
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3511
3406
  setAttribute(knob2, ariaValueText, `${hue}%`);
3512
3407
  setAttribute(knob2, ariaValueNow, `${hue}`);
3513
3408
  } else {
3409
+ [r, g, b] = [r, g, b].map(roundPart);
3410
+ colorLabel = format === 'hsl' ? `HSL: ${hue}°, ${saturation}%, ${lightness}%` : colorLabel;
3514
3411
  colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
3515
- setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3412
+
3516
3413
  setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
3517
3414
  setAttribute(knob1, ariaValueNow, `${lightness}`);
3415
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3518
3416
  setAttribute(knob2, ariaValueText, `${hue}°`);
3519
3417
  setAttribute(knob2, ariaValueNow, `${hue}`);
3520
3418
  }
@@ -3609,37 +3507,13 @@
3609
3507
  }
3610
3508
  }
3611
3509
 
3612
- /**
3613
- * The `Space` & `Enter` keys specific event listener.
3614
- * Toggle visibility of the `ColorPicker` / the presets menu, showing one will hide the other.
3615
- * @param {KeyboardEvent} e
3616
- * @this {ColorPicker}
3617
- */
3618
- keyToggle(e) {
3619
- const self = this;
3620
- const { menuToggle } = self;
3621
- const { activeElement } = getDocument(menuToggle);
3622
- const { code } = e;
3623
-
3624
- if ([keyEnter, keySpace].includes(code)) {
3625
- if ((menuToggle && activeElement === menuToggle) || !activeElement) {
3626
- e.preventDefault();
3627
- if (!activeElement) {
3628
- self.togglePicker(e);
3629
- } else {
3630
- self.toggleMenu();
3631
- }
3632
- }
3633
- }
3634
- }
3635
-
3636
3510
  /**
3637
3511
  * Toggle the `ColorPicker` dropdown visibility.
3638
- * @param {Event} e
3512
+ * @param {Event=} e
3639
3513
  * @this {ColorPicker}
3640
3514
  */
3641
3515
  togglePicker(e) {
3642
- e.preventDefault();
3516
+ if (e) e.preventDefault();
3643
3517
  const self = this;
3644
3518
  const { colorPicker } = self;
3645
3519
 
@@ -3660,8 +3534,13 @@
3660
3534
  }
3661
3535
  }
3662
3536
 
3663
- /** Toggles the visibility of the `ColorPicker` presets menu. */
3664
- toggleMenu() {
3537
+ /**
3538
+ * Toggles the visibility of the `ColorPicker` presets menu.
3539
+ * @param {Event=} e
3540
+ * @this {ColorPicker}
3541
+ */
3542
+ toggleMenu(e) {
3543
+ if (e) e.preventDefault();
3665
3544
  const self = this;
3666
3545
  const { colorMenu } = self;
3667
3546
 
@@ -3687,6 +3566,10 @@
3687
3566
  const relatedBtn = openPicker ? pickerToggle : menuToggle;
3688
3567
  const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
3689
3568
 
3569
+ // if (!self.isValid) {
3570
+ self.value = self.color.toString(true);
3571
+ // }
3572
+
3690
3573
  if (openDropdown) {
3691
3574
  removeClass(openDropdown, 'show');
3692
3575
  setAttribute(relatedBtn, ariaExpanded, 'false');
@@ -3700,9 +3583,6 @@
3700
3583
  }, animationDuration);
3701
3584
  }
3702
3585
 
3703
- if (!self.isValid) {
3704
- self.value = self.color.toString();
3705
- }
3706
3586
  if (!focusPrevented) {
3707
3587
  focus(pickerToggle);
3708
3588
  }
@@ -3747,4 +3627,4 @@
3747
3627
 
3748
3628
  return ColorPicker;
3749
3629
 
3750
- })));
3630
+ }));