@thednp/color-picker 0.0.1-alpha2 → 0.0.2-alpha1
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.
- package/README.md +32 -15
- package/dist/css/color-picker.css +38 -15
- package/dist/css/color-picker.min.css +2 -2
- package/dist/css/color-picker.rtl.css +38 -15
- package/dist/css/color-picker.rtl.min.css +2 -2
- package/dist/js/color-esm.js +1178 -0
- package/dist/js/color-esm.min.js +2 -0
- package/dist/js/color-palette-esm.js +1252 -0
- package/dist/js/color-palette-esm.min.js +2 -0
- package/dist/js/color-palette.js +1260 -0
- package/dist/js/color-palette.min.js +2 -0
- package/dist/js/color-picker-element-esm.js +433 -424
- package/dist/js/color-picker-element-esm.min.js +2 -2
- package/dist/js/color-picker-element.js +435 -426
- package/dist/js/color-picker-element.min.js +2 -2
- package/dist/js/color-picker-esm.js +745 -739
- package/dist/js/color-picker-esm.min.js +2 -2
- package/dist/js/color-picker.js +747 -741
- package/dist/js/color-picker.min.js +2 -2
- package/dist/js/color.js +1186 -0
- package/dist/js/color.min.js +2 -0
- package/package.json +19 -3
- package/src/js/color-palette.js +28 -12
- package/src/js/color-picker-element.js +8 -4
- package/src/js/color-picker.js +84 -172
- package/src/js/color.js +125 -131
- package/src/js/util/getColorControls.js +3 -3
- package/src/js/util/getColorForm.js +0 -1
- package/src/js/util/getColorMenu.js +31 -33
- package/src/js/util/roundPart.js +9 -0
- package/src/js/util/setCSSProperties.js +12 -0
- package/src/js/util/setMarkup.js +122 -0
- package/src/js/util/tabindex.js +3 -0
- package/src/js/util/version.js +6 -0
- package/src/scss/color-picker.scss +35 -16
- package/types/cp.d.ts +48 -20
- package/src/js/util/templates.js +0 -10
@@ -1,5 +1,5 @@
|
|
1
1
|
/*!
|
2
|
-
* ColorPicker v0.0.
|
2
|
+
* ColorPicker v0.0.2alpha1 (http://thednp.github.io/color-picker)
|
3
3
|
* Copyright 2022 © thednp
|
4
4
|
* Licensed under MIT (https://github.com/thednp/color-picker/blob/master/LICENSE)
|
5
5
|
*/
|
@@ -129,24 +129,6 @@ const ariaValueText = 'aria-valuetext';
|
|
129
129
|
*/
|
130
130
|
const ariaValueNow = 'aria-valuenow';
|
131
131
|
|
132
|
-
/**
|
133
|
-
* A global namespace for aria-haspopup.
|
134
|
-
* @type {string}
|
135
|
-
*/
|
136
|
-
const ariaHasPopup = 'aria-haspopup';
|
137
|
-
|
138
|
-
/**
|
139
|
-
* A global namespace for aria-hidden.
|
140
|
-
* @type {string}
|
141
|
-
*/
|
142
|
-
const ariaHidden = 'aria-hidden';
|
143
|
-
|
144
|
-
/**
|
145
|
-
* A global namespace for aria-labelledby.
|
146
|
-
* @type {string}
|
147
|
-
*/
|
148
|
-
const ariaLabelledBy = 'aria-labelledby';
|
149
|
-
|
150
132
|
/**
|
151
133
|
* A global namespace for `ArrowDown` key.
|
152
134
|
* @type {string} e.which = 40 equivalent
|
@@ -273,37 +255,6 @@ const resizeEvent = 'resize';
|
|
273
255
|
*/
|
274
256
|
const focusoutEvent = 'focusout';
|
275
257
|
|
276
|
-
// @ts-ignore
|
277
|
-
const { userAgentData: uaDATA } = navigator;
|
278
|
-
|
279
|
-
/**
|
280
|
-
* A global namespace for `userAgentData` object.
|
281
|
-
*/
|
282
|
-
const userAgentData = uaDATA;
|
283
|
-
|
284
|
-
const { userAgent: userAgentString } = navigator;
|
285
|
-
|
286
|
-
/**
|
287
|
-
* A global namespace for `navigator.userAgent` string.
|
288
|
-
*/
|
289
|
-
const userAgent = userAgentString;
|
290
|
-
|
291
|
-
const mobileBrands = /iPhone|iPad|iPod|Android/i;
|
292
|
-
let isMobileCheck = false;
|
293
|
-
|
294
|
-
if (userAgentData) {
|
295
|
-
isMobileCheck = userAgentData.brands
|
296
|
-
.some((/** @type {Record<String, any>} */x) => mobileBrands.test(x.brand));
|
297
|
-
} else {
|
298
|
-
isMobileCheck = mobileBrands.test(userAgent);
|
299
|
-
}
|
300
|
-
|
301
|
-
/**
|
302
|
-
* A global `boolean` for mobile detection.
|
303
|
-
* @type {boolean}
|
304
|
-
*/
|
305
|
-
const isMobile = isMobileCheck;
|
306
|
-
|
307
258
|
/**
|
308
259
|
* Returns the `document` or the `#document` element.
|
309
260
|
* @see https://github.com/floating-ui/floating-ui
|
@@ -524,60 +475,6 @@ function getElementsByClassName(selector, parent) {
|
|
524
475
|
return lookUp.getElementsByClassName(selector);
|
525
476
|
}
|
526
477
|
|
527
|
-
/**
|
528
|
-
* Shortcut for `Object.assign()` static method.
|
529
|
-
* @param {Record<string, any>} obj a target object
|
530
|
-
* @param {Record<string, any>} source a source object
|
531
|
-
*/
|
532
|
-
const ObjectAssign = (obj, source) => Object.assign(obj, source);
|
533
|
-
|
534
|
-
/**
|
535
|
-
* This is a shortie for `document.createElement` method
|
536
|
-
* which allows you to create a new `HTMLElement` for a given `tagName`
|
537
|
-
* or based on an object with specific non-readonly attributes:
|
538
|
-
* `id`, `className`, `textContent`, `style`, etc.
|
539
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
|
540
|
-
*
|
541
|
-
* @param {Record<string, string> | string} param `tagName` or object
|
542
|
-
* @return {HTMLElement | Element} a new `HTMLElement` or `Element`
|
543
|
-
*/
|
544
|
-
function createElement(param) {
|
545
|
-
if (typeof param === 'string') {
|
546
|
-
return getDocument().createElement(param);
|
547
|
-
}
|
548
|
-
|
549
|
-
const { tagName } = param;
|
550
|
-
const attr = { ...param };
|
551
|
-
const newElement = createElement(tagName);
|
552
|
-
delete attr.tagName;
|
553
|
-
ObjectAssign(newElement, attr);
|
554
|
-
return newElement;
|
555
|
-
}
|
556
|
-
|
557
|
-
/**
|
558
|
-
* This is a shortie for `document.createElementNS` method
|
559
|
-
* which allows you to create a new `HTMLElement` for a given `tagName`
|
560
|
-
* or based on an object with specific non-readonly attributes:
|
561
|
-
* `id`, `className`, `textContent`, `style`, etc.
|
562
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
|
563
|
-
*
|
564
|
-
* @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
|
565
|
-
* @param {Record<string, string> | string} param `tagName` or object
|
566
|
-
* @return {HTMLElement | Element} a new `HTMLElement` or `Element`
|
567
|
-
*/
|
568
|
-
function createElementNS(namespace, param) {
|
569
|
-
if (typeof param === 'string') {
|
570
|
-
return getDocument().createElementNS(namespace, param);
|
571
|
-
}
|
572
|
-
|
573
|
-
const { tagName } = param;
|
574
|
-
const attr = { ...param };
|
575
|
-
const newElement = createElementNS(namespace, tagName);
|
576
|
-
delete attr.tagName;
|
577
|
-
ObjectAssign(newElement, attr);
|
578
|
-
return newElement;
|
579
|
-
}
|
580
|
-
|
581
478
|
/**
|
582
479
|
* Shortcut for the `Element.dispatchEvent(Event)` method.
|
583
480
|
*
|
@@ -586,6 +483,13 @@ function createElementNS(namespace, param) {
|
|
586
483
|
*/
|
587
484
|
const dispatchEvent = (element, event) => element.dispatchEvent(event);
|
588
485
|
|
486
|
+
/**
|
487
|
+
* Shortcut for `Object.assign()` static method.
|
488
|
+
* @param {Record<string, any>} obj a target object
|
489
|
+
* @param {Record<string, any>} source a source object
|
490
|
+
*/
|
491
|
+
const ObjectAssign = (obj, source) => Object.assign(obj, source);
|
492
|
+
|
589
493
|
/** @type {Map<string, Map<HTMLElement | Element, Record<string, any>>>} */
|
590
494
|
const componentData = new Map();
|
591
495
|
/**
|
@@ -837,32 +741,10 @@ const setAttribute = (element, attribute, value) => element.setAttribute(attribu
|
|
837
741
|
*/
|
838
742
|
const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
|
839
743
|
|
840
|
-
/** @type {Record<string, string>} */
|
841
|
-
const colorPickerLabels = {
|
842
|
-
pickerLabel: 'Colour Picker',
|
843
|
-
appearanceLabel: 'Colour Appearance',
|
844
|
-
valueLabel: 'Colour Value',
|
845
|
-
toggleLabel: 'Select Colour',
|
846
|
-
presetsLabel: 'Colour Presets',
|
847
|
-
defaultsLabel: 'Colour Defaults',
|
848
|
-
formatLabel: 'Format',
|
849
|
-
alphaLabel: 'Alpha',
|
850
|
-
hexLabel: 'Hexadecimal',
|
851
|
-
hueLabel: 'Hue',
|
852
|
-
whitenessLabel: 'Whiteness',
|
853
|
-
blacknessLabel: 'Blackness',
|
854
|
-
saturationLabel: 'Saturation',
|
855
|
-
lightnessLabel: 'Lightness',
|
856
|
-
redLabel: 'Red',
|
857
|
-
greenLabel: 'Green',
|
858
|
-
blueLabel: 'Blue',
|
859
|
-
};
|
860
|
-
|
861
744
|
/**
|
862
|
-
* A
|
863
|
-
* @type {string[]}
|
745
|
+
* A global namespace for `document.head`.
|
864
746
|
*/
|
865
|
-
const
|
747
|
+
const { head: documentHead } = document;
|
866
748
|
|
867
749
|
/**
|
868
750
|
* A list of explicit default non-color values.
|
@@ -870,284 +752,104 @@ const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold',
|
|
870
752
|
const nonColors = ['transparent', 'currentColor', 'inherit', 'revert', 'initial'];
|
871
753
|
|
872
754
|
/**
|
873
|
-
*
|
874
|
-
*
|
875
|
-
* @
|
876
|
-
* @returns {string} uppercase output string
|
755
|
+
* Round colour components, for all formats except HEX.
|
756
|
+
* @param {number} v one of the colour components
|
757
|
+
* @returns {number} the rounded number
|
877
758
|
*/
|
878
|
-
|
759
|
+
function roundPart(v) {
|
760
|
+
const floor = Math.floor(v);
|
761
|
+
return v - floor < 0.5 ? floor : Math.round(v);
|
762
|
+
}
|
879
763
|
|
880
|
-
|
764
|
+
// Color supported formats
|
765
|
+
const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsv', 'hwb'];
|
881
766
|
|
882
|
-
|
883
|
-
|
884
|
-
*
|
885
|
-
* @param {CP.ColorPicker} self the `ColorPicker` instance
|
886
|
-
* @returns {HTMLElement | Element} a new `<div>` element with color component `<input>`
|
887
|
-
*/
|
888
|
-
function getColorForm(self) {
|
889
|
-
const { format, id, componentLabels } = self;
|
890
|
-
const colorForm = createElement({
|
891
|
-
tagName: 'div',
|
892
|
-
className: `color-form ${format}`,
|
893
|
-
});
|
767
|
+
// Hue angles
|
768
|
+
const ANGLES = 'deg|rad|grad|turn';
|
894
769
|
|
895
|
-
|
896
|
-
|
897
|
-
else if (format === 'hsl') components = ['hue', 'saturation', 'lightness', 'alpha'];
|
898
|
-
else if (format === 'hwb') components = ['hue', 'whiteness', 'blackness', 'alpha'];
|
770
|
+
// <http://www.w3.org/TR/css3-values/#integers>
|
771
|
+
const CSS_INTEGER = '[-\\+]?\\d+%?';
|
899
772
|
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
const formatLabel = componentLabels[`${c}Label`];
|
904
|
-
const cInputLabel = createElement({ tagName: 'label' });
|
905
|
-
setAttribute(cInputLabel, 'for', cID);
|
906
|
-
cInputLabel.append(
|
907
|
-
createElement({ tagName: 'span', ariaHidden: 'true', innerText: `${C}:` }),
|
908
|
-
createElement({ tagName: 'span', className: vHidden, innerText: formatLabel }),
|
909
|
-
);
|
910
|
-
const cInput = createElement({
|
911
|
-
tagName: 'input',
|
912
|
-
id: cID,
|
913
|
-
// name: cID, - prevent saving the value to a form
|
914
|
-
type: format === 'hex' ? 'text' : 'number',
|
915
|
-
value: c === 'alpha' ? '100' : '0',
|
916
|
-
className: `color-input ${c}`,
|
917
|
-
});
|
918
|
-
setAttribute(cInput, 'autocomplete', 'off');
|
919
|
-
setAttribute(cInput, 'spellcheck', 'false');
|
773
|
+
// Include CSS3 Module
|
774
|
+
// <http://www.w3.org/TR/css3-values/#number-value>
|
775
|
+
const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
|
920
776
|
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
777
|
+
// Include CSS4 Module Hue degrees unit
|
778
|
+
// <https://www.w3.org/TR/css3-values/#angle-value>
|
779
|
+
const CSS_ANGLE = `[-\\+]?\\d*\\.?\\d+(?:${ANGLES})?`;
|
780
|
+
|
781
|
+
// Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
|
782
|
+
const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
|
783
|
+
|
784
|
+
// Add angles to the mix
|
785
|
+
const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
|
786
|
+
|
787
|
+
// Start & end
|
788
|
+
const START_MATCH = '(?:[\\s|\\(\\s|\\s\\(\\s]+)?';
|
789
|
+
const END_MATCH = '(?:[\\s|\\)\\s]+)?';
|
790
|
+
// Components separation
|
791
|
+
const SEP = '(?:[,|\\s]+)';
|
792
|
+
const SEP2 = '(?:[,|\\/\\s]*)?';
|
793
|
+
|
794
|
+
// Actual matching.
|
795
|
+
// Parentheses and commas are optional, but not required.
|
796
|
+
// Whitespace can take the place of commas or opening paren
|
797
|
+
const PERMISSIVE_MATCH = `${START_MATCH}(${CSS_UNIT2})${SEP}(${CSS_UNIT})${SEP}(${CSS_UNIT})${SEP2}(${CSS_UNIT})?${END_MATCH}`;
|
798
|
+
|
799
|
+
const matchers = {
|
800
|
+
CSS_UNIT: new RegExp(CSS_UNIT2),
|
801
|
+
hwb: new RegExp(`hwb${PERMISSIVE_MATCH}`),
|
802
|
+
rgb: new RegExp(`rgb(?:a)?${PERMISSIVE_MATCH}`),
|
803
|
+
hsl: new RegExp(`hsl(?:a)?${PERMISSIVE_MATCH}`),
|
804
|
+
hsv: new RegExp(`hsv(?:a)?${PERMISSIVE_MATCH}`),
|
805
|
+
hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
806
|
+
hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
|
807
|
+
hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
808
|
+
hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
|
809
|
+
};
|
941
810
|
|
942
811
|
/**
|
943
|
-
*
|
944
|
-
*
|
812
|
+
* Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
|
813
|
+
* <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
|
814
|
+
* @param {string} n testing number
|
815
|
+
* @returns {boolean} the query result
|
945
816
|
*/
|
946
|
-
|
817
|
+
function isOnePointZero(n) {
|
818
|
+
return `${n}`.includes('.') && parseFloat(n) === 1;
|
819
|
+
}
|
947
820
|
|
948
821
|
/**
|
949
|
-
*
|
950
|
-
* @
|
822
|
+
* Check to see if string passed in is a percentage
|
823
|
+
* @param {string} n testing number
|
824
|
+
* @returns {boolean} the query result
|
951
825
|
*/
|
952
|
-
|
826
|
+
function isPercentage(n) {
|
827
|
+
return `${n}`.includes('%');
|
828
|
+
}
|
953
829
|
|
954
830
|
/**
|
955
|
-
*
|
956
|
-
* @
|
831
|
+
* Check to see if string passed is a web safe colour.
|
832
|
+
* @see https://stackoverflow.com/a/16994164
|
833
|
+
* @param {string} color a colour name, EG: *red*
|
834
|
+
* @returns {boolean} the query result
|
957
835
|
*/
|
958
|
-
|
836
|
+
function isColorName(color) {
|
837
|
+
if (nonColors.includes(color)
|
838
|
+
|| ['#', ...COLOR_FORMAT].some((f) => color.includes(f))) return false;
|
839
|
+
|
840
|
+
return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
|
841
|
+
setElementStyle(documentHead, { color });
|
842
|
+
const computedColor = getElementStyle(documentHead, 'color');
|
843
|
+
setElementStyle(documentHead, { color: '' });
|
844
|
+
return computedColor !== c;
|
845
|
+
});
|
846
|
+
}
|
959
847
|
|
960
848
|
/**
|
961
|
-
*
|
962
|
-
*
|
963
|
-
* @param {
|
964
|
-
* @returns {
|
965
|
-
*/
|
966
|
-
function getColorControls(self) {
|
967
|
-
const { format, componentLabels } = self;
|
968
|
-
const {
|
969
|
-
hueLabel, alphaLabel, lightnessLabel, saturationLabel,
|
970
|
-
whitenessLabel, blacknessLabel,
|
971
|
-
} = componentLabels;
|
972
|
-
|
973
|
-
const max1 = format === 'hsl' ? 360 : 100;
|
974
|
-
const max2 = format === 'hsl' ? 100 : 360;
|
975
|
-
const max3 = 100;
|
976
|
-
|
977
|
-
let ctrl1Label = format === 'hsl'
|
978
|
-
? `${hueLabel} & ${lightnessLabel}`
|
979
|
-
: `${lightnessLabel} & ${saturationLabel}`;
|
980
|
-
|
981
|
-
ctrl1Label = format === 'hwb'
|
982
|
-
? `${whitenessLabel} & ${blacknessLabel}`
|
983
|
-
: ctrl1Label;
|
984
|
-
|
985
|
-
const ctrl2Label = format === 'hsl'
|
986
|
-
? `${saturationLabel}`
|
987
|
-
: `${hueLabel}`;
|
988
|
-
|
989
|
-
const colorControls = createElement({
|
990
|
-
tagName: 'div',
|
991
|
-
className: `color-controls ${format}`,
|
992
|
-
});
|
993
|
-
|
994
|
-
const colorPointer = 'color-pointer';
|
995
|
-
const colorSlider = 'color-slider';
|
996
|
-
|
997
|
-
const controls = [
|
998
|
-
{
|
999
|
-
i: 1,
|
1000
|
-
c: colorPointer,
|
1001
|
-
l: ctrl1Label,
|
1002
|
-
min: 0,
|
1003
|
-
max: max1,
|
1004
|
-
},
|
1005
|
-
{
|
1006
|
-
i: 2,
|
1007
|
-
c: colorSlider,
|
1008
|
-
l: ctrl2Label,
|
1009
|
-
min: 0,
|
1010
|
-
max: max2,
|
1011
|
-
},
|
1012
|
-
{
|
1013
|
-
i: 3,
|
1014
|
-
c: colorSlider,
|
1015
|
-
l: alphaLabel,
|
1016
|
-
min: 0,
|
1017
|
-
max: max3,
|
1018
|
-
},
|
1019
|
-
];
|
1020
|
-
|
1021
|
-
controls.forEach((template) => {
|
1022
|
-
const {
|
1023
|
-
i, c, l, min, max,
|
1024
|
-
} = template;
|
1025
|
-
// const hidden = i === 2 && format === 'hwb' ? ' v-hidden' : '';
|
1026
|
-
const control = createElement({
|
1027
|
-
tagName: 'div',
|
1028
|
-
// className: `color-control${hidden}`,
|
1029
|
-
className: 'color-control',
|
1030
|
-
});
|
1031
|
-
setAttribute(control, 'role', 'presentation');
|
1032
|
-
|
1033
|
-
control.append(
|
1034
|
-
createElement({
|
1035
|
-
tagName: 'div',
|
1036
|
-
className: `visual-control visual-control${i}`,
|
1037
|
-
}),
|
1038
|
-
);
|
1039
|
-
|
1040
|
-
const knob = createElement({
|
1041
|
-
tagName: 'div',
|
1042
|
-
className: `${c} knob`,
|
1043
|
-
ariaLive: 'polite',
|
1044
|
-
});
|
1045
|
-
|
1046
|
-
setAttribute(knob, ariaLabel, l);
|
1047
|
-
setAttribute(knob, 'role', 'slider');
|
1048
|
-
setAttribute(knob, 'tabindex', '0');
|
1049
|
-
setAttribute(knob, ariaValueMin, `${min}`);
|
1050
|
-
setAttribute(knob, ariaValueMax, `${max}`);
|
1051
|
-
control.append(knob);
|
1052
|
-
colorControls.append(control);
|
1053
|
-
});
|
1054
|
-
|
1055
|
-
return colorControls;
|
1056
|
-
}
|
1057
|
-
|
1058
|
-
/**
|
1059
|
-
* Returns the `document.head` or the `<head>` element.
|
1060
|
-
*
|
1061
|
-
* @param {(Node | HTMLElement | Element | globalThis)=} node
|
1062
|
-
* @returns {HTMLElement | HTMLHeadElement}
|
1063
|
-
*/
|
1064
|
-
function getDocumentHead(node) {
|
1065
|
-
return getDocument(node).head;
|
1066
|
-
}
|
1067
|
-
|
1068
|
-
// Color supported formats
|
1069
|
-
const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
|
1070
|
-
|
1071
|
-
// Hue angles
|
1072
|
-
const ANGLES = 'deg|rad|grad|turn';
|
1073
|
-
|
1074
|
-
// <http://www.w3.org/TR/css3-values/#integers>
|
1075
|
-
const CSS_INTEGER = '[-\\+]?\\d+%?';
|
1076
|
-
|
1077
|
-
// Include CSS3 Module
|
1078
|
-
// <http://www.w3.org/TR/css3-values/#number-value>
|
1079
|
-
const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
|
1080
|
-
|
1081
|
-
// Include CSS4 Module Hue degrees unit
|
1082
|
-
// <https://www.w3.org/TR/css3-values/#angle-value>
|
1083
|
-
const CSS_ANGLE = `[-\\+]?\\d*\\.?\\d+(?:${ANGLES})?`;
|
1084
|
-
|
1085
|
-
// Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
|
1086
|
-
const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
|
1087
|
-
|
1088
|
-
// Add angles to the mix
|
1089
|
-
const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
|
1090
|
-
|
1091
|
-
// Actual matching.
|
1092
|
-
// Parentheses and commas are optional, but not required.
|
1093
|
-
// Whitespace can take the place of commas or opening paren
|
1094
|
-
const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
|
1095
|
-
|
1096
|
-
const matchers = {
|
1097
|
-
CSS_UNIT: new RegExp(CSS_UNIT2),
|
1098
|
-
hwb: new RegExp(`hwb${PERMISSIVE_MATCH}`),
|
1099
|
-
rgb: new RegExp(`rgb(?:a)?${PERMISSIVE_MATCH}`),
|
1100
|
-
hsl: new RegExp(`hsl(?:a)?${PERMISSIVE_MATCH}`),
|
1101
|
-
hsv: new RegExp(`hsv(?:a)?${PERMISSIVE_MATCH}`),
|
1102
|
-
hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
1103
|
-
hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
|
1104
|
-
hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
1105
|
-
hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
|
1106
|
-
};
|
1107
|
-
|
1108
|
-
/**
|
1109
|
-
* Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
|
1110
|
-
* <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
|
1111
|
-
* @param {string} n testing number
|
1112
|
-
* @returns {boolean} the query result
|
1113
|
-
*/
|
1114
|
-
function isOnePointZero(n) {
|
1115
|
-
return `${n}`.includes('.') && parseFloat(n) === 1;
|
1116
|
-
}
|
1117
|
-
|
1118
|
-
/**
|
1119
|
-
* Check to see if string passed in is a percentage
|
1120
|
-
* @param {string} n testing number
|
1121
|
-
* @returns {boolean} the query result
|
1122
|
-
*/
|
1123
|
-
function isPercentage(n) {
|
1124
|
-
return `${n}`.includes('%');
|
1125
|
-
}
|
1126
|
-
|
1127
|
-
/**
|
1128
|
-
* Check to see if string passed in is an angle
|
1129
|
-
* @param {string} n testing string
|
1130
|
-
* @returns {boolean} the query result
|
1131
|
-
*/
|
1132
|
-
function isAngle(n) {
|
1133
|
-
return ANGLES.split('|').some((a) => `${n}`.includes(a));
|
1134
|
-
}
|
1135
|
-
|
1136
|
-
/**
|
1137
|
-
* Check to see if string passed is a web safe colour.
|
1138
|
-
* @param {string} color a colour name, EG: *red*
|
1139
|
-
* @returns {boolean} the query result
|
1140
|
-
*/
|
1141
|
-
function isColorName(color) {
|
1142
|
-
return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
|
1143
|
-
&& !/[0-9]/.test(color);
|
1144
|
-
}
|
1145
|
-
|
1146
|
-
/**
|
1147
|
-
* Check to see if it looks like a CSS unit
|
1148
|
-
* (see `matchers` above for definition).
|
1149
|
-
* @param {string | number} color testing value
|
1150
|
-
* @returns {boolean} the query result
|
849
|
+
* Check to see if it looks like a CSS unit
|
850
|
+
* (see `matchers` above for definition).
|
851
|
+
* @param {string | number} color testing value
|
852
|
+
* @returns {boolean} the query result
|
1151
853
|
*/
|
1152
854
|
function isValidCSSUnit(color) {
|
1153
855
|
return Boolean(matchers.CSS_UNIT.exec(String(color)));
|
@@ -1161,15 +863,15 @@ function isValidCSSUnit(color) {
|
|
1161
863
|
*/
|
1162
864
|
function bound01(N, max) {
|
1163
865
|
let n = N;
|
1164
|
-
if (isOnePointZero(
|
1165
|
-
|
1166
|
-
n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
|
866
|
+
if (isOnePointZero(N)) n = '100%';
|
1167
867
|
|
1168
|
-
|
1169
|
-
|
868
|
+
const processPercent = isPercentage(n);
|
869
|
+
n = max === 360
|
870
|
+
? parseFloat(n)
|
871
|
+
: Math.min(max, Math.max(0, parseFloat(n)));
|
1170
872
|
|
1171
873
|
// Automatically convert percentage into number
|
1172
|
-
if (
|
874
|
+
if (processPercent) n = (n * max) / 100;
|
1173
875
|
|
1174
876
|
// Handle floating point rounding errors
|
1175
877
|
if (Math.abs(n - max) < 0.000001) {
|
@@ -1180,11 +882,11 @@ function bound01(N, max) {
|
|
1180
882
|
// If n is a hue given in degrees,
|
1181
883
|
// wrap around out-of-range values into [0, 360] range
|
1182
884
|
// then convert into [0, 1].
|
1183
|
-
n = (n < 0 ? (n % max) + max : n % max) /
|
885
|
+
n = (n < 0 ? (n % max) + max : n % max) / max;
|
1184
886
|
} else {
|
1185
887
|
// If n not a hue given in degrees
|
1186
888
|
// Convert into [0, 1] range if it isn't already.
|
1187
|
-
n = (n % max) /
|
889
|
+
n = (n % max) / max;
|
1188
890
|
}
|
1189
891
|
return n;
|
1190
892
|
}
|
@@ -1219,7 +921,6 @@ function clamp01(v) {
|
|
1219
921
|
* @returns {string}
|
1220
922
|
*/
|
1221
923
|
function getRGBFromName(name) {
|
1222
|
-
const documentHead = getDocumentHead();
|
1223
924
|
setElementStyle(documentHead, { color: name });
|
1224
925
|
const colorName = getElementStyle(documentHead, 'color');
|
1225
926
|
setElementStyle(documentHead, { color: '' });
|
@@ -1232,7 +933,7 @@ function getRGBFromName(name) {
|
|
1232
933
|
* @returns {string} - the hexadecimal value
|
1233
934
|
*/
|
1234
935
|
function convertDecimalToHex(d) {
|
1235
|
-
return
|
936
|
+
return roundPart(d * 255).toString(16);
|
1236
937
|
}
|
1237
938
|
|
1238
939
|
/**
|
@@ -1318,6 +1019,36 @@ function hueToRgb(p, q, t) {
|
|
1318
1019
|
return p;
|
1319
1020
|
}
|
1320
1021
|
|
1022
|
+
/**
|
1023
|
+
* Converts an HSL colour value to RGB.
|
1024
|
+
*
|
1025
|
+
* @param {number} h Hue Angle [0, 1]
|
1026
|
+
* @param {number} s Saturation [0, 1]
|
1027
|
+
* @param {number} l Lightness Angle [0, 1]
|
1028
|
+
* @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
1029
|
+
*/
|
1030
|
+
function hslToRgb(h, s, l) {
|
1031
|
+
let r = 0;
|
1032
|
+
let g = 0;
|
1033
|
+
let b = 0;
|
1034
|
+
|
1035
|
+
if (s === 0) {
|
1036
|
+
// achromatic
|
1037
|
+
g = l;
|
1038
|
+
b = l;
|
1039
|
+
r = l;
|
1040
|
+
} else {
|
1041
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
1042
|
+
const p = 2 * l - q;
|
1043
|
+
r = hueToRgb(p, q, h + 1 / 3);
|
1044
|
+
g = hueToRgb(p, q, h);
|
1045
|
+
b = hueToRgb(p, q, h - 1 / 3);
|
1046
|
+
}
|
1047
|
+
[r, g, b] = [r, g, b].map((x) => x * 255);
|
1048
|
+
|
1049
|
+
return { r, g, b };
|
1050
|
+
}
|
1051
|
+
|
1321
1052
|
/**
|
1322
1053
|
* Returns an HWB colour object from an RGB colour object.
|
1323
1054
|
* @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
|
@@ -1380,36 +1111,6 @@ function hwbToRgb(H, W, B) {
|
|
1380
1111
|
return { r, g, b };
|
1381
1112
|
}
|
1382
1113
|
|
1383
|
-
/**
|
1384
|
-
* Converts an HSL colour value to RGB.
|
1385
|
-
*
|
1386
|
-
* @param {number} h Hue Angle [0, 1]
|
1387
|
-
* @param {number} s Saturation [0, 1]
|
1388
|
-
* @param {number} l Lightness Angle [0, 1]
|
1389
|
-
* @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
1390
|
-
*/
|
1391
|
-
function hslToRgb(h, s, l) {
|
1392
|
-
let r = 0;
|
1393
|
-
let g = 0;
|
1394
|
-
let b = 0;
|
1395
|
-
|
1396
|
-
if (s === 0) {
|
1397
|
-
// achromatic
|
1398
|
-
g = l;
|
1399
|
-
b = l;
|
1400
|
-
r = l;
|
1401
|
-
} else {
|
1402
|
-
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
1403
|
-
const p = 2 * l - q;
|
1404
|
-
r = hueToRgb(p, q, h + 1 / 3);
|
1405
|
-
g = hueToRgb(p, q, h);
|
1406
|
-
b = hueToRgb(p, q, h - 1 / 3);
|
1407
|
-
}
|
1408
|
-
[r, g, b] = [r, g, b].map((x) => x * 255);
|
1409
|
-
|
1410
|
-
return { r, g, b };
|
1411
|
-
}
|
1412
|
-
|
1413
1114
|
/**
|
1414
1115
|
* Converts an RGB colour value to HSV.
|
1415
1116
|
*
|
@@ -1465,10 +1166,11 @@ function hsvToRgb(H, S, V) {
|
|
1465
1166
|
const q = v * (1 - f * s);
|
1466
1167
|
const t = v * (1 - (1 - f) * s);
|
1467
1168
|
const mod = i % 6;
|
1468
|
-
|
1469
|
-
|
1470
|
-
|
1471
|
-
|
1169
|
+
let r = [v, q, p, p, t, v][mod];
|
1170
|
+
let g = [t, v, v, q, p, p][mod];
|
1171
|
+
let b = [p, p, t, v, v, q][mod];
|
1172
|
+
[r, g, b] = [r, g, b].map((n) => n * 255);
|
1173
|
+
return { r, g, b };
|
1472
1174
|
}
|
1473
1175
|
|
1474
1176
|
/**
|
@@ -1484,15 +1186,15 @@ function hsvToRgb(H, S, V) {
|
|
1484
1186
|
*/
|
1485
1187
|
function rgbToHex(r, g, b, allow3Char) {
|
1486
1188
|
const hex = [
|
1487
|
-
pad2(
|
1488
|
-
pad2(
|
1489
|
-
pad2(
|
1189
|
+
pad2(roundPart(r).toString(16)),
|
1190
|
+
pad2(roundPart(g).toString(16)),
|
1191
|
+
pad2(roundPart(b).toString(16)),
|
1490
1192
|
];
|
1491
1193
|
|
1492
1194
|
// Return a 3 character hex if possible
|
1493
1195
|
if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
|
1494
1196
|
&& hex[1].charAt(0) === hex[1].charAt(1)
|
1495
|
-
|
1197
|
+
&& hex[2].charAt(0) === hex[2].charAt(1)) {
|
1496
1198
|
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
|
1497
1199
|
}
|
1498
1200
|
|
@@ -1511,48 +1213,33 @@ function rgbToHex(r, g, b, allow3Char) {
|
|
1511
1213
|
*/
|
1512
1214
|
function rgbaToHex(r, g, b, a, allow4Char) {
|
1513
1215
|
const hex = [
|
1514
|
-
pad2(
|
1515
|
-
pad2(
|
1516
|
-
pad2(
|
1216
|
+
pad2(roundPart(r).toString(16)),
|
1217
|
+
pad2(roundPart(g).toString(16)),
|
1218
|
+
pad2(roundPart(b).toString(16)),
|
1517
1219
|
pad2(convertDecimalToHex(a)),
|
1518
1220
|
];
|
1519
1221
|
|
1520
1222
|
// Return a 4 character hex if possible
|
1521
1223
|
if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
|
1522
1224
|
&& hex[1].charAt(0) === hex[1].charAt(1)
|
1523
|
-
|
1524
|
-
|
1225
|
+
&& hex[2].charAt(0) === hex[2].charAt(1)
|
1226
|
+
&& hex[3].charAt(0) === hex[3].charAt(1)) {
|
1525
1227
|
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
|
1526
1228
|
}
|
1527
1229
|
return hex.join('');
|
1528
1230
|
}
|
1529
1231
|
|
1530
|
-
/**
|
1531
|
-
* Returns a colour object corresponding to a given number.
|
1532
|
-
* @param {number} color input number
|
1533
|
-
* @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
1534
|
-
*/
|
1535
|
-
function numberInputToObject(color) {
|
1536
|
-
/* eslint-disable no-bitwise */
|
1537
|
-
return {
|
1538
|
-
r: color >> 16,
|
1539
|
-
g: (color & 0xff00) >> 8,
|
1540
|
-
b: color & 0xff,
|
1541
|
-
};
|
1542
|
-
/* eslint-enable no-bitwise */
|
1543
|
-
}
|
1544
|
-
|
1545
1232
|
/**
|
1546
1233
|
* Permissive string parsing. Take in a number of formats, and output an object
|
1547
1234
|
* based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
|
1548
1235
|
* @param {string} input colour value in any format
|
1549
|
-
* @returns {Record<string, (number | string)> | false} an object matching the RegExp
|
1236
|
+
* @returns {Record<string, (number | string | boolean)> | false} an object matching the RegExp
|
1550
1237
|
*/
|
1551
1238
|
function stringInputToObject(input) {
|
1552
|
-
let color = input.trim()
|
1239
|
+
let color = toLowerCase(input.trim());
|
1553
1240
|
if (color.length === 0) {
|
1554
1241
|
return {
|
1555
|
-
r: 0, g: 0, b: 0, a:
|
1242
|
+
r: 0, g: 0, b: 0, a: 1,
|
1556
1243
|
};
|
1557
1244
|
}
|
1558
1245
|
let named = false;
|
@@ -1560,11 +1247,9 @@ function stringInputToObject(input) {
|
|
1560
1247
|
color = getRGBFromName(color);
|
1561
1248
|
named = true;
|
1562
1249
|
} else if (nonColors.includes(color)) {
|
1563
|
-
const
|
1564
|
-
const rgb = isTransparent ? 0 : 255;
|
1565
|
-
const a = isTransparent ? 0 : 1;
|
1250
|
+
const a = color === 'transparent' ? 0 : 1;
|
1566
1251
|
return {
|
1567
|
-
r:
|
1252
|
+
r: 0, g: 0, b: 0, a, format: 'rgb', ok: true,
|
1568
1253
|
};
|
1569
1254
|
}
|
1570
1255
|
|
@@ -1604,7 +1289,6 @@ function stringInputToObject(input) {
|
|
1604
1289
|
g: parseIntFromHex(m2),
|
1605
1290
|
b: parseIntFromHex(m3),
|
1606
1291
|
a: convertHexToDecimal(m4),
|
1607
|
-
// format: named ? 'rgb' : 'hex8',
|
1608
1292
|
format: named ? 'rgb' : 'hex',
|
1609
1293
|
};
|
1610
1294
|
}
|
@@ -1668,6 +1352,7 @@ function stringInputToObject(input) {
|
|
1668
1352
|
function inputToRGB(input) {
|
1669
1353
|
let rgb = { r: 0, g: 0, b: 0 };
|
1670
1354
|
let color = input;
|
1355
|
+
/** @type {string | number} */
|
1671
1356
|
let a = 1;
|
1672
1357
|
let s = null;
|
1673
1358
|
let v = null;
|
@@ -1675,8 +1360,11 @@ function inputToRGB(input) {
|
|
1675
1360
|
let w = null;
|
1676
1361
|
let b = null;
|
1677
1362
|
let h = null;
|
1363
|
+
let r = null;
|
1364
|
+
let g = null;
|
1678
1365
|
let ok = false;
|
1679
|
-
|
1366
|
+
const inputFormat = typeof color === 'object' && color.format;
|
1367
|
+
let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
|
1680
1368
|
|
1681
1369
|
if (typeof input === 'string') {
|
1682
1370
|
// @ts-ignore -- this now is converted to object
|
@@ -1685,7 +1373,10 @@ function inputToRGB(input) {
|
|
1685
1373
|
}
|
1686
1374
|
if (typeof color === 'object') {
|
1687
1375
|
if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
|
1688
|
-
|
1376
|
+
({ r, g, b } = color);
|
1377
|
+
// RGB values now are all in [0, 255] range
|
1378
|
+
[r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255);
|
1379
|
+
rgb = { r, g, b };
|
1689
1380
|
ok = true;
|
1690
1381
|
format = 'rgb';
|
1691
1382
|
} else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
|
@@ -1714,14 +1405,17 @@ function inputToRGB(input) {
|
|
1714
1405
|
format = 'hwb';
|
1715
1406
|
}
|
1716
1407
|
if (isValidCSSUnit(color.a)) {
|
1717
|
-
a = color.a;
|
1718
|
-
a = isPercentage(`${a}`) ? bound01(a, 100) : a;
|
1408
|
+
a = color.a; // @ts-ignore -- `parseFloat` works with numbers too
|
1409
|
+
a = isPercentage(`${a}`) || parseFloat(a) > 1 ? bound01(a, 100) : a;
|
1719
1410
|
}
|
1720
1411
|
}
|
1412
|
+
if (typeof color === 'undefined') {
|
1413
|
+
ok = true;
|
1414
|
+
}
|
1721
1415
|
|
1722
1416
|
return {
|
1723
|
-
ok,
|
1724
|
-
format
|
1417
|
+
ok,
|
1418
|
+
format,
|
1725
1419
|
r: Math.min(255, Math.max(rgb.r, 0)),
|
1726
1420
|
g: Math.min(255, Math.max(rgb.g, 0)),
|
1727
1421
|
b: Math.min(255, Math.max(rgb.b, 0)),
|
@@ -1750,7 +1444,8 @@ class Color {
|
|
1750
1444
|
color = inputToRGB(color);
|
1751
1445
|
}
|
1752
1446
|
if (typeof color === 'number') {
|
1753
|
-
|
1447
|
+
const len = `${color}`.length;
|
1448
|
+
color = `#${(len === 2 ? '0' : '00')}${color}`;
|
1754
1449
|
}
|
1755
1450
|
const {
|
1756
1451
|
r, g, b, a, ok, format,
|
@@ -1760,7 +1455,7 @@ class Color {
|
|
1760
1455
|
const self = this;
|
1761
1456
|
|
1762
1457
|
/** @type {CP.ColorInput} */
|
1763
|
-
self.originalInput =
|
1458
|
+
self.originalInput = input;
|
1764
1459
|
/** @type {number} */
|
1765
1460
|
self.r = r;
|
1766
1461
|
/** @type {number} */
|
@@ -1773,14 +1468,6 @@ class Color {
|
|
1773
1468
|
self.ok = ok;
|
1774
1469
|
/** @type {CP.ColorFormats} */
|
1775
1470
|
self.format = configFormat || format;
|
1776
|
-
|
1777
|
-
// Don't let the range of [0,255] come back in [0,1].
|
1778
|
-
// Potentially lose a little bit of precision here, but will fix issues where
|
1779
|
-
// .5 gets interpreted as half of the total, instead of half of 1
|
1780
|
-
// If it was supposed to be 128, this was already taken care of by `inputToRgb`
|
1781
|
-
if (r < 1) self.r = Math.round(r);
|
1782
|
-
if (g < 1) self.g = Math.round(g);
|
1783
|
-
if (b < 1) self.b = Math.round(b);
|
1784
1471
|
}
|
1785
1472
|
|
1786
1473
|
/**
|
@@ -1796,7 +1483,7 @@ class Color {
|
|
1796
1483
|
* @returns {boolean} the query result
|
1797
1484
|
*/
|
1798
1485
|
get isDark() {
|
1799
|
-
return this.brightness <
|
1486
|
+
return this.brightness < 120;
|
1800
1487
|
}
|
1801
1488
|
|
1802
1489
|
/**
|
@@ -1848,13 +1535,9 @@ class Color {
|
|
1848
1535
|
const {
|
1849
1536
|
r, g, b, a,
|
1850
1537
|
} = this;
|
1851
|
-
const [R, G, B] = [r, g, b].map((x) => Math.round(x));
|
1852
1538
|
|
1853
1539
|
return {
|
1854
|
-
r:
|
1855
|
-
g: G,
|
1856
|
-
b: B,
|
1857
|
-
a: Math.round(a * 100) / 100,
|
1540
|
+
r, g, b, a: roundPart(a * 100) / 100,
|
1858
1541
|
};
|
1859
1542
|
}
|
1860
1543
|
|
@@ -1868,10 +1551,11 @@ class Color {
|
|
1868
1551
|
const {
|
1869
1552
|
r, g, b, a,
|
1870
1553
|
} = this.toRgb();
|
1554
|
+
const [R, G, B] = [r, g, b].map(roundPart);
|
1871
1555
|
|
1872
1556
|
return a === 1
|
1873
|
-
? `rgb(${
|
1874
|
-
: `rgba(${
|
1557
|
+
? `rgb(${R}, ${G}, ${B})`
|
1558
|
+
: `rgba(${R}, ${G}, ${B}, ${a})`;
|
1875
1559
|
}
|
1876
1560
|
|
1877
1561
|
/**
|
@@ -1884,9 +1568,10 @@ class Color {
|
|
1884
1568
|
const {
|
1885
1569
|
r, g, b, a,
|
1886
1570
|
} = this.toRgb();
|
1887
|
-
const
|
1571
|
+
const [R, G, B] = [r, g, b].map(roundPart);
|
1572
|
+
const A = a === 1 ? '' : ` / ${roundPart(a * 100)}%`;
|
1888
1573
|
|
1889
|
-
return `rgb(${
|
1574
|
+
return `rgb(${R} ${G} ${B}${A})`;
|
1890
1575
|
}
|
1891
1576
|
|
1892
1577
|
/**
|
@@ -1979,10 +1664,10 @@ class Color {
|
|
1979
1664
|
let {
|
1980
1665
|
h, s, l, a,
|
1981
1666
|
} = this.toHsl();
|
1982
|
-
h =
|
1983
|
-
s =
|
1984
|
-
l =
|
1985
|
-
a =
|
1667
|
+
h = roundPart(h * 360);
|
1668
|
+
s = roundPart(s * 100);
|
1669
|
+
l = roundPart(l * 100);
|
1670
|
+
a = roundPart(a * 100) / 100;
|
1986
1671
|
|
1987
1672
|
return a === 1
|
1988
1673
|
? `hsl(${h}, ${s}%, ${l}%)`
|
@@ -1999,11 +1684,11 @@ class Color {
|
|
1999
1684
|
let {
|
2000
1685
|
h, s, l, a,
|
2001
1686
|
} = this.toHsl();
|
2002
|
-
h =
|
2003
|
-
s =
|
2004
|
-
l =
|
2005
|
-
a =
|
2006
|
-
const A = a < 100 ? ` / ${
|
1687
|
+
h = roundPart(h * 360);
|
1688
|
+
s = roundPart(s * 100);
|
1689
|
+
l = roundPart(l * 100);
|
1690
|
+
a = roundPart(a * 100);
|
1691
|
+
const A = a < 100 ? ` / ${roundPart(a)}%` : '';
|
2007
1692
|
|
2008
1693
|
return `hsl(${h}deg ${s}% ${l}%${A})`;
|
2009
1694
|
}
|
@@ -2030,11 +1715,11 @@ class Color {
|
|
2030
1715
|
let {
|
2031
1716
|
h, w, b, a,
|
2032
1717
|
} = this.toHwb();
|
2033
|
-
h =
|
2034
|
-
w =
|
2035
|
-
b =
|
2036
|
-
a =
|
2037
|
-
const A = a < 100 ? ` / ${
|
1718
|
+
h = roundPart(h * 360);
|
1719
|
+
w = roundPart(w * 100);
|
1720
|
+
b = roundPart(b * 100);
|
1721
|
+
a = roundPart(a * 100);
|
1722
|
+
const A = a < 100 ? ` / ${roundPart(a)}%` : '';
|
2038
1723
|
|
2039
1724
|
return `hwb(${h}deg ${w}% ${b}%${A})`;
|
2040
1725
|
}
|
@@ -2140,108 +1825,426 @@ class Color {
|
|
2140
1825
|
const self = this;
|
2141
1826
|
const { format } = self;
|
2142
1827
|
|
2143
|
-
if (format === 'hex') return self.toHexString(allowShort);
|
2144
|
-
if (format === 'hsl') return self.toHslString();
|
2145
|
-
if (format === 'hwb') return self.toHwbString();
|
1828
|
+
if (format === 'hex') return self.toHexString(allowShort);
|
1829
|
+
if (format === 'hsl') return self.toHslString();
|
1830
|
+
if (format === 'hwb') return self.toHwbString();
|
1831
|
+
|
1832
|
+
return self.toRgbString();
|
1833
|
+
}
|
1834
|
+
}
|
1835
|
+
|
1836
|
+
ObjectAssign(Color, {
|
1837
|
+
ANGLES,
|
1838
|
+
CSS_ANGLE,
|
1839
|
+
CSS_INTEGER,
|
1840
|
+
CSS_NUMBER,
|
1841
|
+
CSS_UNIT,
|
1842
|
+
CSS_UNIT2,
|
1843
|
+
PERMISSIVE_MATCH,
|
1844
|
+
matchers,
|
1845
|
+
isOnePointZero,
|
1846
|
+
isPercentage,
|
1847
|
+
isValidCSSUnit,
|
1848
|
+
isColorName,
|
1849
|
+
pad2,
|
1850
|
+
clamp01,
|
1851
|
+
bound01,
|
1852
|
+
boundAlpha,
|
1853
|
+
getRGBFromName,
|
1854
|
+
convertHexToDecimal,
|
1855
|
+
convertDecimalToHex,
|
1856
|
+
rgbToHsl,
|
1857
|
+
rgbToHex,
|
1858
|
+
rgbToHsv,
|
1859
|
+
rgbToHwb,
|
1860
|
+
rgbaToHex,
|
1861
|
+
hslToRgb,
|
1862
|
+
hsvToRgb,
|
1863
|
+
hueToRgb,
|
1864
|
+
hwbToRgb,
|
1865
|
+
parseIntFromHex,
|
1866
|
+
stringInputToObject,
|
1867
|
+
inputToRGB,
|
1868
|
+
roundPart,
|
1869
|
+
getElementStyle,
|
1870
|
+
setElementStyle,
|
1871
|
+
ObjectAssign,
|
1872
|
+
});
|
1873
|
+
|
1874
|
+
/**
|
1875
|
+
* @class
|
1876
|
+
* Returns a color palette with a given set of parameters.
|
1877
|
+
* @example
|
1878
|
+
* new ColorPalette(0, 12, 10);
|
1879
|
+
* // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: Array<Color> }
|
1880
|
+
*/
|
1881
|
+
class ColorPalette {
|
1882
|
+
/**
|
1883
|
+
* The `hue` parameter is optional, which would be set to 0.
|
1884
|
+
* @param {number[]} args represeinting hue, hueSteps, lightSteps
|
1885
|
+
* * `args.hue` the starting Hue [0, 360]
|
1886
|
+
* * `args.hueSteps` Hue Steps Count [5, 24]
|
1887
|
+
* * `args.lightSteps` Lightness Steps Count [5, 12]
|
1888
|
+
*/
|
1889
|
+
constructor(...args) {
|
1890
|
+
let hue = 0;
|
1891
|
+
let hueSteps = 12;
|
1892
|
+
let lightSteps = 10;
|
1893
|
+
let lightnessArray = [0.5];
|
1894
|
+
|
1895
|
+
if (args.length === 3) {
|
1896
|
+
[hue, hueSteps, lightSteps] = args;
|
1897
|
+
} else if (args.length === 2) {
|
1898
|
+
[hueSteps, lightSteps] = args;
|
1899
|
+
if ([hueSteps, lightSteps].some((n) => n < 1)) {
|
1900
|
+
throw TypeError('ColorPalette: when 2 arguments used, both must be larger than 0.');
|
1901
|
+
}
|
1902
|
+
} else {
|
1903
|
+
throw TypeError('ColorPalette requires minimum 2 arguments');
|
1904
|
+
}
|
1905
|
+
|
1906
|
+
/** @type {Color[]} */
|
1907
|
+
const colors = [];
|
1908
|
+
|
1909
|
+
const hueStep = 360 / hueSteps;
|
1910
|
+
const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
|
1911
|
+
const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
|
1912
|
+
|
1913
|
+
let lightStep = 0.25;
|
1914
|
+
lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
|
1915
|
+
lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
|
1916
|
+
lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
|
1917
|
+
lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
|
1918
|
+
lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
|
1919
|
+
lightStep = lightSteps > 13 ? estimatedStep : lightStep;
|
1920
|
+
|
1921
|
+
// light tints
|
1922
|
+
for (let i = 1; i < half + 1; i += 1) {
|
1923
|
+
lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
|
1924
|
+
}
|
1925
|
+
|
1926
|
+
// dark tints
|
1927
|
+
for (let i = 1; i < lightSteps - half; i += 1) {
|
1928
|
+
lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
|
1929
|
+
}
|
1930
|
+
|
1931
|
+
// feed `colors` Array
|
1932
|
+
for (let i = 0; i < hueSteps; i += 1) {
|
1933
|
+
const currentHue = ((hue + i * hueStep) % 360) / 360;
|
1934
|
+
lightnessArray.forEach((l) => {
|
1935
|
+
colors.push(new Color({ h: currentHue, s: 1, l }));
|
1936
|
+
});
|
1937
|
+
}
|
1938
|
+
|
1939
|
+
this.hue = hue;
|
1940
|
+
this.hueSteps = hueSteps;
|
1941
|
+
this.lightSteps = lightSteps;
|
1942
|
+
this.colors = colors;
|
1943
|
+
}
|
1944
|
+
}
|
1945
|
+
|
1946
|
+
ObjectAssign(ColorPalette, { Color });
|
1947
|
+
|
1948
|
+
/** @type {Record<string, string>} */
|
1949
|
+
const colorPickerLabels = {
|
1950
|
+
pickerLabel: 'Colour Picker',
|
1951
|
+
appearanceLabel: 'Colour Appearance',
|
1952
|
+
valueLabel: 'Colour Value',
|
1953
|
+
toggleLabel: 'Select Colour',
|
1954
|
+
presetsLabel: 'Colour Presets',
|
1955
|
+
defaultsLabel: 'Colour Defaults',
|
1956
|
+
formatLabel: 'Format',
|
1957
|
+
alphaLabel: 'Alpha',
|
1958
|
+
hexLabel: 'Hexadecimal',
|
1959
|
+
hueLabel: 'Hue',
|
1960
|
+
whitenessLabel: 'Whiteness',
|
1961
|
+
blacknessLabel: 'Blackness',
|
1962
|
+
saturationLabel: 'Saturation',
|
1963
|
+
lightnessLabel: 'Lightness',
|
1964
|
+
redLabel: 'Red',
|
1965
|
+
greenLabel: 'Green',
|
1966
|
+
blueLabel: 'Blue',
|
1967
|
+
};
|
1968
|
+
|
1969
|
+
/**
|
1970
|
+
* A list of 17 color names used for WAI-ARIA compliance.
|
1971
|
+
* @type {string[]}
|
1972
|
+
*/
|
1973
|
+
const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
|
1974
|
+
|
1975
|
+
const tabIndex = 'tabindex';
|
1976
|
+
|
1977
|
+
/**
|
1978
|
+
* Check if a string is valid JSON string.
|
1979
|
+
* @param {string} str the string input
|
1980
|
+
* @returns {boolean} the query result
|
1981
|
+
*/
|
1982
|
+
function isValidJSON(str) {
|
1983
|
+
try {
|
1984
|
+
JSON.parse(str);
|
1985
|
+
} catch (e) {
|
1986
|
+
return false;
|
1987
|
+
}
|
1988
|
+
return true;
|
1989
|
+
}
|
1990
|
+
|
1991
|
+
/**
|
1992
|
+
* Shortcut for `String.toUpperCase()`.
|
1993
|
+
*
|
1994
|
+
* @param {string} source input string
|
1995
|
+
* @returns {string} uppercase output string
|
1996
|
+
*/
|
1997
|
+
const toUpperCase = (source) => source.toUpperCase();
|
1998
|
+
|
1999
|
+
/**
|
2000
|
+
* A global namespace for aria-haspopup.
|
2001
|
+
* @type {string}
|
2002
|
+
*/
|
2003
|
+
const ariaHasPopup = 'aria-haspopup';
|
2004
|
+
|
2005
|
+
/**
|
2006
|
+
* A global namespace for aria-hidden.
|
2007
|
+
* @type {string}
|
2008
|
+
*/
|
2009
|
+
const ariaHidden = 'aria-hidden';
|
2010
|
+
|
2011
|
+
/**
|
2012
|
+
* A global namespace for aria-labelledby.
|
2013
|
+
* @type {string}
|
2014
|
+
*/
|
2015
|
+
const ariaLabelledBy = 'aria-labelledby';
|
2016
|
+
|
2017
|
+
/**
|
2018
|
+
* This is a shortie for `document.createElement` method
|
2019
|
+
* which allows you to create a new `HTMLElement` for a given `tagName`
|
2020
|
+
* or based on an object with specific non-readonly attributes:
|
2021
|
+
* `id`, `className`, `textContent`, `style`, etc.
|
2022
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
|
2023
|
+
*
|
2024
|
+
* @param {Record<string, string> | string} param `tagName` or object
|
2025
|
+
* @return {HTMLElement | Element} a new `HTMLElement` or `Element`
|
2026
|
+
*/
|
2027
|
+
function createElement(param) {
|
2028
|
+
if (typeof param === 'string') {
|
2029
|
+
return getDocument().createElement(param);
|
2030
|
+
}
|
2031
|
+
|
2032
|
+
const { tagName } = param;
|
2033
|
+
const attr = { ...param };
|
2034
|
+
const newElement = createElement(tagName);
|
2035
|
+
delete attr.tagName;
|
2036
|
+
ObjectAssign(newElement, attr);
|
2037
|
+
return newElement;
|
2038
|
+
}
|
2039
|
+
|
2040
|
+
/**
|
2041
|
+
* This is a shortie for `document.createElementNS` method
|
2042
|
+
* which allows you to create a new `HTMLElement` for a given `tagName`
|
2043
|
+
* or based on an object with specific non-readonly attributes:
|
2044
|
+
* `id`, `className`, `textContent`, `style`, etc.
|
2045
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
|
2046
|
+
*
|
2047
|
+
* @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
|
2048
|
+
* @param {Record<string, string> | string} param `tagName` or object
|
2049
|
+
* @return {HTMLElement | Element} a new `HTMLElement` or `Element`
|
2050
|
+
*/
|
2051
|
+
function createElementNS(namespace, param) {
|
2052
|
+
if (typeof param === 'string') {
|
2053
|
+
return getDocument().createElementNS(namespace, param);
|
2054
|
+
}
|
2055
|
+
|
2056
|
+
const { tagName } = param;
|
2057
|
+
const attr = { ...param };
|
2058
|
+
const newElement = createElementNS(namespace, tagName);
|
2059
|
+
delete attr.tagName;
|
2060
|
+
ObjectAssign(newElement, attr);
|
2061
|
+
return newElement;
|
2062
|
+
}
|
2063
|
+
|
2064
|
+
const vHidden = 'v-hidden';
|
2065
|
+
|
2066
|
+
/**
|
2067
|
+
* Returns the color form for `ColorPicker`.
|
2068
|
+
*
|
2069
|
+
* @param {CP.ColorPicker} self the `ColorPicker` instance
|
2070
|
+
* @returns {HTMLElement | Element} a new `<div>` element with color component `<input>`
|
2071
|
+
*/
|
2072
|
+
function getColorForm(self) {
|
2073
|
+
const { format, id, componentLabels } = self;
|
2074
|
+
const colorForm = createElement({
|
2075
|
+
tagName: 'div',
|
2076
|
+
className: `color-form ${format}`,
|
2077
|
+
});
|
2078
|
+
|
2079
|
+
let components = ['hex'];
|
2080
|
+
if (format === 'rgb') components = ['red', 'green', 'blue', 'alpha'];
|
2081
|
+
else if (format === 'hsl') components = ['hue', 'saturation', 'lightness', 'alpha'];
|
2082
|
+
else if (format === 'hwb') components = ['hue', 'whiteness', 'blackness', 'alpha'];
|
2083
|
+
|
2084
|
+
components.forEach((c) => {
|
2085
|
+
const [C] = format === 'hex' ? ['#'] : toUpperCase(c).split('');
|
2086
|
+
const cID = `color_${format}_${c}_${id}`;
|
2087
|
+
const formatLabel = componentLabels[`${c}Label`];
|
2088
|
+
const cInputLabel = createElement({ tagName: 'label' });
|
2089
|
+
setAttribute(cInputLabel, 'for', cID);
|
2090
|
+
cInputLabel.append(
|
2091
|
+
createElement({ tagName: 'span', ariaHidden: 'true', innerText: `${C}:` }),
|
2092
|
+
createElement({ tagName: 'span', className: vHidden, innerText: formatLabel }),
|
2093
|
+
);
|
2094
|
+
const cInput = createElement({
|
2095
|
+
tagName: 'input',
|
2096
|
+
id: cID,
|
2097
|
+
// name: cID, - prevent saving the value to a form
|
2098
|
+
type: format === 'hex' ? 'text' : 'number',
|
2099
|
+
value: c === 'alpha' ? '100' : '0',
|
2100
|
+
className: `color-input ${c}`,
|
2101
|
+
});
|
2102
|
+
setAttribute(cInput, 'autocomplete', 'off');
|
2103
|
+
setAttribute(cInput, 'spellcheck', 'false');
|
2104
|
+
|
2105
|
+
// alpha
|
2106
|
+
let max = '100';
|
2107
|
+
let step = '1';
|
2108
|
+
if (c !== 'alpha') {
|
2109
|
+
if (format === 'rgb') {
|
2110
|
+
max = '255'; step = '1';
|
2111
|
+
} else if (c === 'hue') {
|
2112
|
+
max = '360'; step = '1';
|
2113
|
+
}
|
2114
|
+
}
|
2115
|
+
ObjectAssign(cInput, {
|
2116
|
+
min: '0',
|
2117
|
+
max,
|
2118
|
+
step,
|
2119
|
+
});
|
2120
|
+
colorForm.append(cInputLabel, cInput);
|
2121
|
+
});
|
2122
|
+
return colorForm;
|
2123
|
+
}
|
2124
|
+
|
2125
|
+
/**
|
2126
|
+
* A global namespace for aria-label.
|
2127
|
+
* @type {string}
|
2128
|
+
*/
|
2129
|
+
const ariaLabel = 'aria-label';
|
2130
|
+
|
2131
|
+
/**
|
2132
|
+
* A global namespace for aria-valuemin.
|
2133
|
+
* @type {string}
|
2134
|
+
*/
|
2135
|
+
const ariaValueMin = 'aria-valuemin';
|
2136
|
+
|
2137
|
+
/**
|
2138
|
+
* A global namespace for aria-valuemax.
|
2139
|
+
* @type {string}
|
2140
|
+
*/
|
2141
|
+
const ariaValueMax = 'aria-valuemax';
|
2142
|
+
|
2143
|
+
/**
|
2144
|
+
* Returns all color controls for `ColorPicker`.
|
2145
|
+
*
|
2146
|
+
* @param {CP.ColorPicker} self the `ColorPicker` instance
|
2147
|
+
* @returns {HTMLElement | Element} color controls
|
2148
|
+
*/
|
2149
|
+
function getColorControls(self) {
|
2150
|
+
const { format, componentLabels } = self;
|
2151
|
+
const {
|
2152
|
+
hueLabel, alphaLabel, lightnessLabel, saturationLabel,
|
2153
|
+
whitenessLabel, blacknessLabel,
|
2154
|
+
} = componentLabels;
|
2155
|
+
|
2156
|
+
const max1 = format === 'hsl' ? 360 : 100;
|
2157
|
+
const max2 = format === 'hsl' ? 100 : 360;
|
2158
|
+
const max3 = 100;
|
2159
|
+
|
2160
|
+
let ctrl1Label = format === 'hsl'
|
2161
|
+
? `${hueLabel} & ${lightnessLabel}`
|
2162
|
+
: `${lightnessLabel} & ${saturationLabel}`;
|
2163
|
+
|
2164
|
+
ctrl1Label = format === 'hwb'
|
2165
|
+
? `${whitenessLabel} & ${blacknessLabel}`
|
2166
|
+
: ctrl1Label;
|
2146
2167
|
|
2147
|
-
|
2148
|
-
|
2149
|
-
}
|
2168
|
+
const ctrl2Label = format === 'hsl'
|
2169
|
+
? `${saturationLabel}`
|
2170
|
+
: `${hueLabel}`;
|
2150
2171
|
|
2151
|
-
|
2152
|
-
|
2153
|
-
|
2154
|
-
|
2155
|
-
CSS_NUMBER,
|
2156
|
-
CSS_UNIT,
|
2157
|
-
CSS_UNIT2,
|
2158
|
-
PERMISSIVE_MATCH,
|
2159
|
-
matchers,
|
2160
|
-
isOnePointZero,
|
2161
|
-
isPercentage,
|
2162
|
-
isValidCSSUnit,
|
2163
|
-
pad2,
|
2164
|
-
clamp01,
|
2165
|
-
bound01,
|
2166
|
-
boundAlpha,
|
2167
|
-
getRGBFromName,
|
2168
|
-
convertHexToDecimal,
|
2169
|
-
convertDecimalToHex,
|
2170
|
-
rgbToHsl,
|
2171
|
-
rgbToHex,
|
2172
|
-
rgbToHsv,
|
2173
|
-
rgbToHwb,
|
2174
|
-
rgbaToHex,
|
2175
|
-
hslToRgb,
|
2176
|
-
hsvToRgb,
|
2177
|
-
hueToRgb,
|
2178
|
-
hwbToRgb,
|
2179
|
-
parseIntFromHex,
|
2180
|
-
numberInputToObject,
|
2181
|
-
stringInputToObject,
|
2182
|
-
inputToRGB,
|
2183
|
-
ObjectAssign,
|
2184
|
-
});
|
2172
|
+
const colorControls = createElement({
|
2173
|
+
tagName: 'div',
|
2174
|
+
className: `color-controls ${format}`,
|
2175
|
+
});
|
2185
2176
|
|
2186
|
-
|
2187
|
-
|
2188
|
-
* Returns a color palette with a given set of parameters.
|
2189
|
-
* @example
|
2190
|
-
* new ColorPalette(0, 12, 10);
|
2191
|
-
* // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
|
2192
|
-
*/
|
2193
|
-
class ColorPalette {
|
2194
|
-
/**
|
2195
|
-
* The `hue` parameter is optional, which would be set to 0.
|
2196
|
-
* @param {number[]} args represeinting hue, hueSteps, lightSteps
|
2197
|
-
* * `args.hue` the starting Hue [0, 360]
|
2198
|
-
* * `args.hueSteps` Hue Steps Count [5, 13]
|
2199
|
-
* * `args.lightSteps` Lightness Steps Count [8, 10]
|
2200
|
-
*/
|
2201
|
-
constructor(...args) {
|
2202
|
-
let hue = 0;
|
2203
|
-
let hueSteps = 12;
|
2204
|
-
let lightSteps = 10;
|
2205
|
-
let lightnessArray = [0.5];
|
2177
|
+
const colorPointer = 'color-pointer';
|
2178
|
+
const colorSlider = 'color-slider';
|
2206
2179
|
|
2207
|
-
|
2208
|
-
|
2209
|
-
|
2210
|
-
|
2211
|
-
|
2212
|
-
|
2213
|
-
|
2180
|
+
const controls = [
|
2181
|
+
{
|
2182
|
+
i: 1,
|
2183
|
+
c: colorPointer,
|
2184
|
+
l: ctrl1Label,
|
2185
|
+
min: 0,
|
2186
|
+
max: max1,
|
2187
|
+
},
|
2188
|
+
{
|
2189
|
+
i: 2,
|
2190
|
+
c: colorSlider,
|
2191
|
+
l: ctrl2Label,
|
2192
|
+
min: 0,
|
2193
|
+
max: max2,
|
2194
|
+
},
|
2195
|
+
{
|
2196
|
+
i: 3,
|
2197
|
+
c: colorSlider,
|
2198
|
+
l: alphaLabel,
|
2199
|
+
min: 0,
|
2200
|
+
max: max3,
|
2201
|
+
},
|
2202
|
+
];
|
2214
2203
|
|
2215
|
-
|
2216
|
-
const
|
2204
|
+
controls.forEach((template) => {
|
2205
|
+
const {
|
2206
|
+
i, c, l, min, max,
|
2207
|
+
} = template;
|
2208
|
+
const control = createElement({
|
2209
|
+
tagName: 'div',
|
2210
|
+
className: 'color-control',
|
2211
|
+
});
|
2212
|
+
setAttribute(control, 'role', 'presentation');
|
2217
2213
|
|
2218
|
-
|
2219
|
-
|
2220
|
-
|
2214
|
+
control.append(
|
2215
|
+
createElement({
|
2216
|
+
tagName: 'div',
|
2217
|
+
className: `visual-control visual-control${i}`,
|
2218
|
+
}),
|
2219
|
+
);
|
2221
2220
|
|
2222
|
-
|
2223
|
-
|
2224
|
-
|
2225
|
-
|
2221
|
+
const knob = createElement({
|
2222
|
+
tagName: 'div',
|
2223
|
+
className: `${c} knob`,
|
2224
|
+
ariaLive: 'polite',
|
2225
|
+
});
|
2226
2226
|
|
2227
|
-
|
2228
|
-
|
2229
|
-
|
2230
|
-
}
|
2227
|
+
setAttribute(knob, ariaLabel, l);
|
2228
|
+
setAttribute(knob, 'role', 'slider');
|
2229
|
+
setAttribute(knob, tabIndex, '0');
|
2230
|
+
setAttribute(knob, ariaValueMin, `${min}`);
|
2231
|
+
setAttribute(knob, ariaValueMax, `${max}`);
|
2232
|
+
control.append(knob);
|
2233
|
+
colorControls.append(control);
|
2234
|
+
});
|
2231
2235
|
|
2232
|
-
|
2233
|
-
|
2234
|
-
const currentHue = ((hue + i * hueStep) % 360) / 360;
|
2235
|
-
lightnessArray.forEach((l) => {
|
2236
|
-
colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
|
2237
|
-
});
|
2238
|
-
}
|
2236
|
+
return colorControls;
|
2237
|
+
}
|
2239
2238
|
|
2240
|
-
|
2241
|
-
|
2242
|
-
|
2243
|
-
|
2244
|
-
|
2239
|
+
/**
|
2240
|
+
* Helps setting CSS variables to the color-menu.
|
2241
|
+
* @param {HTMLElement} element
|
2242
|
+
* @param {Record<string,any>} props
|
2243
|
+
*/
|
2244
|
+
function setCSSProperties(element, props) {
|
2245
|
+
ObjectKeys(props).forEach((prop) => {
|
2246
|
+
element.style.setProperty(prop, props[prop]);
|
2247
|
+
});
|
2245
2248
|
}
|
2246
2249
|
|
2247
2250
|
/**
|
@@ -2261,68 +2264,64 @@ function getColorMenu(self, colorsSource, menuClass) {
|
|
2261
2264
|
colorsArray = colorsArray instanceof Array ? colorsArray : [];
|
2262
2265
|
const colorsCount = colorsArray.length;
|
2263
2266
|
const { lightSteps } = isPalette ? colorsSource : { lightSteps: null };
|
2264
|
-
|
2265
|
-
|| Math.max(...[5, 6, 7, 8, 9, 10].filter((x) => colorsCount > (x * 2) && !(colorsCount % x)));
|
2266
|
-
fit = Number.isFinite(fit) ? fit : 5;
|
2267
|
+
const fit = lightSteps || [9, 10].find((x) => colorsCount > x * 2 && !(colorsCount % x)) || 5;
|
2267
2268
|
const isMultiLine = isOptionsMenu && colorsCount > fit;
|
2268
|
-
let rowCountHover =
|
2269
|
-
rowCountHover = isMultiLine && colorsCount
|
2270
|
-
rowCountHover = colorsCount >=
|
2271
|
-
rowCountHover = colorsCount >=
|
2272
|
-
|
2273
|
-
const
|
2274
|
-
const isScrollable = isMultiLine && colorsCount > rowCountHover * fit;
|
2269
|
+
let rowCountHover = 2;
|
2270
|
+
rowCountHover = isMultiLine && colorsCount >= fit * 2 ? 3 : rowCountHover;
|
2271
|
+
rowCountHover = colorsCount >= fit * 3 ? 4 : rowCountHover;
|
2272
|
+
rowCountHover = colorsCount >= fit * 4 ? 5 : rowCountHover;
|
2273
|
+
const rowCount = rowCountHover - (colorsCount < fit * 3 ? 1 : 2);
|
2274
|
+
const isScrollable = isMultiLine && colorsCount > rowCount * fit;
|
2275
2275
|
let finalClass = menuClass;
|
2276
2276
|
finalClass += isScrollable ? ' scrollable' : '';
|
2277
2277
|
finalClass += isMultiLine ? ' multiline' : '';
|
2278
2278
|
const gap = isMultiLine ? '1px' : '0.25rem';
|
2279
2279
|
let optionSize = isMultiLine ? 1.75 : 2;
|
2280
|
-
optionSize =
|
2280
|
+
optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
|
2281
2281
|
const menuHeight = `${(rowCount || 1) * optionSize}rem`;
|
2282
2282
|
const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
|
2283
|
-
|
2284
|
-
|
2285
|
-
|
2283
|
+
/** @type {HTMLUListElement} */
|
2284
|
+
// @ts-ignore -- <UL> is an `HTMLElement`
|
2286
2285
|
const menu = createElement({
|
2287
2286
|
tagName: 'ul',
|
2288
2287
|
className: finalClass,
|
2289
2288
|
});
|
2290
2289
|
setAttribute(menu, 'role', 'listbox');
|
2291
|
-
setAttribute(menu, ariaLabel,
|
2292
|
-
|
2293
|
-
if (
|
2294
|
-
|
2295
|
-
|
2296
|
-
|
2297
|
-
|
2298
|
-
|
2299
|
-
|
2300
|
-
|
2301
|
-
};
|
2302
|
-
setElementStyle(menu, menuStyle);
|
2290
|
+
setAttribute(menu, ariaLabel, menuLabel);
|
2291
|
+
|
2292
|
+
if (isScrollable) {
|
2293
|
+
setCSSProperties(menu, {
|
2294
|
+
'--grid-item-size': `${optionSize}rem`,
|
2295
|
+
'--grid-fit': fit,
|
2296
|
+
'--grid-gap': gap,
|
2297
|
+
'--grid-height': menuHeight,
|
2298
|
+
'--grid-hover-height': menuHeightHover,
|
2299
|
+
});
|
2303
2300
|
}
|
2304
2301
|
|
2305
2302
|
colorsArray.forEach((x) => {
|
2306
|
-
|
2307
|
-
|
2308
|
-
|
2303
|
+
let [value, label] = typeof x === 'string' ? x.trim().split(':') : [];
|
2304
|
+
if (x instanceof Color) {
|
2305
|
+
value = x.toHexString();
|
2306
|
+
label = value;
|
2307
|
+
}
|
2308
|
+
const color = new Color(x instanceof Color ? x : value, format);
|
2309
|
+
const isActive = color.toString() === getAttribute(input, 'value');
|
2309
2310
|
const active = isActive ? ' active' : '';
|
2310
2311
|
|
2311
2312
|
const option = createElement({
|
2312
2313
|
tagName: 'li',
|
2313
2314
|
className: `color-option${active}`,
|
2314
|
-
innerText: `${label ||
|
2315
|
+
innerText: `${label || value}`,
|
2315
2316
|
});
|
2316
2317
|
|
2317
|
-
setAttribute(option,
|
2318
|
+
setAttribute(option, tabIndex, '0');
|
2318
2319
|
setAttribute(option, 'data-value', `${value}`);
|
2319
2320
|
setAttribute(option, 'role', 'option');
|
2320
2321
|
setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
|
2321
2322
|
|
2322
2323
|
if (isOptionsMenu) {
|
2323
|
-
setElementStyle(option, {
|
2324
|
-
width: `${optionSize}rem`, height: `${optionSize}rem`, backgroundColor: x,
|
2325
|
-
});
|
2324
|
+
setElementStyle(option, { backgroundColor: value });
|
2326
2325
|
}
|
2327
2326
|
|
2328
2327
|
menu.append(option);
|
@@ -2331,55 +2330,10 @@ function getColorMenu(self, colorsSource, menuClass) {
|
|
2331
2330
|
}
|
2332
2331
|
|
2333
2332
|
/**
|
2334
|
-
|
2335
|
-
|
2336
|
-
|
2337
|
-
|
2338
|
-
function isValidJSON(str) {
|
2339
|
-
try {
|
2340
|
-
JSON.parse(str);
|
2341
|
-
} catch (e) {
|
2342
|
-
return false;
|
2343
|
-
}
|
2344
|
-
return true;
|
2345
|
-
}
|
2346
|
-
|
2347
|
-
var version = "0.0.1alpha2";
|
2348
|
-
|
2349
|
-
// @ts-ignore
|
2350
|
-
|
2351
|
-
const Version = version;
|
2352
|
-
|
2353
|
-
// ColorPicker GC
|
2354
|
-
// ==============
|
2355
|
-
const colorPickerString = 'color-picker';
|
2356
|
-
const colorPickerSelector = `[data-function="${colorPickerString}"]`;
|
2357
|
-
const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
|
2358
|
-
const colorPickerDefaults = {
|
2359
|
-
componentLabels: colorPickerLabels,
|
2360
|
-
colorLabels: colorNames,
|
2361
|
-
format: 'rgb',
|
2362
|
-
colorPresets: undefined,
|
2363
|
-
colorKeywords: nonColors,
|
2364
|
-
};
|
2365
|
-
|
2366
|
-
// ColorPicker Static Methods
|
2367
|
-
// ==========================
|
2368
|
-
|
2369
|
-
/** @type {CP.GetInstance<ColorPicker>} */
|
2370
|
-
const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
|
2371
|
-
|
2372
|
-
/** @type {CP.InitCallback<ColorPicker>} */
|
2373
|
-
const initColorPicker = (element) => new ColorPicker(element);
|
2374
|
-
|
2375
|
-
// ColorPicker Private Methods
|
2376
|
-
// ===========================
|
2377
|
-
|
2378
|
-
/**
|
2379
|
-
* Generate HTML markup and update instance properties.
|
2380
|
-
* @param {ColorPicker} self
|
2381
|
-
*/
|
2382
|
-
function initCallback(self) {
|
2333
|
+
* Generate HTML markup and update instance properties.
|
2334
|
+
* @param {CP.ColorPicker} self
|
2335
|
+
*/
|
2336
|
+
function setMarkup(self) {
|
2383
2337
|
const {
|
2384
2338
|
input, parent, format, id, componentLabels, colorKeywords, colorPresets,
|
2385
2339
|
} = self;
|
@@ -2394,9 +2348,7 @@ function initCallback(self) {
|
|
2394
2348
|
self.color = new Color(color, format);
|
2395
2349
|
|
2396
2350
|
// set initial controls dimensions
|
2397
|
-
|
2398
|
-
const dropClass = isMobile ? ' mobile' : '';
|
2399
|
-
const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
|
2351
|
+
const formatString = format === 'hex' ? hexLabel : toUpperCase(format);
|
2400
2352
|
|
2401
2353
|
const pickerBtn = createElement({
|
2402
2354
|
id: `picker-btn-${id}`,
|
@@ -2413,7 +2365,7 @@ function initCallback(self) {
|
|
2413
2365
|
|
2414
2366
|
const pickerDropdown = createElement({
|
2415
2367
|
tagName: 'div',
|
2416
|
-
className:
|
2368
|
+
className: 'color-dropdown picker',
|
2417
2369
|
});
|
2418
2370
|
setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
|
2419
2371
|
setAttribute(pickerDropdown, 'role', 'group');
|
@@ -2429,7 +2381,7 @@ function initCallback(self) {
|
|
2429
2381
|
if (colorKeywords || colorPresets) {
|
2430
2382
|
const presetsDropdown = createElement({
|
2431
2383
|
tagName: 'div',
|
2432
|
-
className:
|
2384
|
+
className: 'color-dropdown scrollable menu',
|
2433
2385
|
});
|
2434
2386
|
|
2435
2387
|
// color presets
|
@@ -2449,7 +2401,7 @@ function initCallback(self) {
|
|
2449
2401
|
tagName: 'button',
|
2450
2402
|
className: 'menu-toggle btn-appearance',
|
2451
2403
|
});
|
2452
|
-
setAttribute(presetsBtn,
|
2404
|
+
setAttribute(presetsBtn, tabIndex, '-1');
|
2453
2405
|
setAttribute(presetsBtn, ariaExpanded, 'false');
|
2454
2406
|
setAttribute(presetsBtn, ariaHasPopup, 'true');
|
2455
2407
|
|
@@ -2476,9 +2428,40 @@ function initCallback(self) {
|
|
2476
2428
|
if (colorKeywords && nonColors.includes(colorValue)) {
|
2477
2429
|
self.value = colorValue;
|
2478
2430
|
}
|
2479
|
-
setAttribute(input,
|
2431
|
+
setAttribute(input, tabIndex, '-1');
|
2480
2432
|
}
|
2481
2433
|
|
2434
|
+
var version = "0.0.2alpha1";
|
2435
|
+
|
2436
|
+
// @ts-ignore
|
2437
|
+
|
2438
|
+
const Version = version;
|
2439
|
+
|
2440
|
+
// ColorPicker GC
|
2441
|
+
// ==============
|
2442
|
+
const colorPickerString = 'color-picker';
|
2443
|
+
const colorPickerSelector = `[data-function="${colorPickerString}"]`;
|
2444
|
+
const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
|
2445
|
+
const colorPickerDefaults = {
|
2446
|
+
componentLabels: colorPickerLabels,
|
2447
|
+
colorLabels: colorNames,
|
2448
|
+
format: 'rgb',
|
2449
|
+
colorPresets: false,
|
2450
|
+
colorKeywords: false,
|
2451
|
+
};
|
2452
|
+
|
2453
|
+
// ColorPicker Static Methods
|
2454
|
+
// ==========================
|
2455
|
+
|
2456
|
+
/** @type {CP.GetInstance<ColorPicker>} */
|
2457
|
+
const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
|
2458
|
+
|
2459
|
+
/** @type {CP.InitCallback<ColorPicker>} */
|
2460
|
+
const initColorPicker = (element) => new ColorPicker(element);
|
2461
|
+
|
2462
|
+
// ColorPicker Private Methods
|
2463
|
+
// ===========================
|
2464
|
+
|
2482
2465
|
/**
|
2483
2466
|
* Add / remove `ColorPicker` main event listeners.
|
2484
2467
|
* @param {ColorPicker} self
|
@@ -2577,8 +2560,19 @@ function showDropdown(self, dropdown) {
|
|
2577
2560
|
addClass(dropdown, 'bottom');
|
2578
2561
|
reflow(dropdown);
|
2579
2562
|
addClass(dropdown, 'show');
|
2563
|
+
|
2580
2564
|
if (isPicker) self.update();
|
2581
|
-
|
2565
|
+
|
2566
|
+
if (!self.isOpen) {
|
2567
|
+
toggleEventsOnShown(self, true);
|
2568
|
+
self.updateDropdownPosition();
|
2569
|
+
self.isOpen = true;
|
2570
|
+
setAttribute(self.input, tabIndex, '0');
|
2571
|
+
if (menuToggle) {
|
2572
|
+
setAttribute(menuToggle, tabIndex, '0');
|
2573
|
+
}
|
2574
|
+
}
|
2575
|
+
|
2582
2576
|
setAttribute(nextBtn, ariaExpanded, 'true');
|
2583
2577
|
if (activeBtn) {
|
2584
2578
|
setAttribute(activeBtn, ariaExpanded, 'false');
|
@@ -2701,7 +2695,7 @@ class ColorPicker {
|
|
2701
2695
|
self.handleKnobs = self.handleKnobs.bind(self);
|
2702
2696
|
|
2703
2697
|
// generate markup
|
2704
|
-
|
2698
|
+
setMarkup(self);
|
2705
2699
|
|
2706
2700
|
const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
|
2707
2701
|
// set main elements
|
@@ -2748,7 +2742,7 @@ class ColorPicker {
|
|
2748
2742
|
set value(v) { this.input.value = v; }
|
2749
2743
|
|
2750
2744
|
/** Check if the colour presets include any non-colour. */
|
2751
|
-
get
|
2745
|
+
get hasNonColor() {
|
2752
2746
|
return this.colorKeywords instanceof Array
|
2753
2747
|
&& this.colorKeywords.some((x) => nonColors.includes(x));
|
2754
2748
|
}
|
@@ -2804,7 +2798,7 @@ class ColorPicker {
|
|
2804
2798
|
const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
|
2805
2799
|
const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
|
2806
2800
|
const alpha = 1 - controlPositions.c3y / offsetHeight;
|
2807
|
-
const roundA =
|
2801
|
+
const roundA = roundPart((alpha * 100)) / 100;
|
2808
2802
|
|
2809
2803
|
if (format !== 'hsl') {
|
2810
2804
|
const fill = new Color({
|
@@ -2822,7 +2816,7 @@ class ColorPicker {
|
|
2822
2816
|
});
|
2823
2817
|
setElementStyle(v2, { background: hueGradient });
|
2824
2818
|
} else {
|
2825
|
-
const saturation =
|
2819
|
+
const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
|
2826
2820
|
const fill0 = new Color({
|
2827
2821
|
r: 255, g: 0, b: 0, a: alpha,
|
2828
2822
|
}).saturate(-saturation).toRgbString();
|
@@ -2897,7 +2891,7 @@ class ColorPicker {
|
|
2897
2891
|
const self = this;
|
2898
2892
|
const { activeElement } = getDocument(self.input);
|
2899
2893
|
|
2900
|
-
if ((
|
2894
|
+
if ((e.type === touchmoveEvent && self.dragElement)
|
2901
2895
|
|| (activeElement && self.controlKnobs.includes(activeElement))) {
|
2902
2896
|
e.stopPropagation();
|
2903
2897
|
e.preventDefault();
|
@@ -2977,12 +2971,12 @@ class ColorPicker {
|
|
2977
2971
|
|
2978
2972
|
self.update();
|
2979
2973
|
|
2980
|
-
if (currentActive) {
|
2981
|
-
removeClass(currentActive, 'active');
|
2982
|
-
removeAttribute(currentActive, ariaSelected);
|
2983
|
-
}
|
2984
|
-
|
2985
2974
|
if (currentActive !== target) {
|
2975
|
+
if (currentActive) {
|
2976
|
+
removeClass(currentActive, 'active');
|
2977
|
+
removeAttribute(currentActive, ariaSelected);
|
2978
|
+
}
|
2979
|
+
|
2986
2980
|
addClass(target, 'active');
|
2987
2981
|
setAttribute(target, ariaSelected, 'true');
|
2988
2982
|
|
@@ -3104,30 +3098,41 @@ class ColorPicker {
|
|
3104
3098
|
if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
|
3105
3099
|
e.preventDefault();
|
3106
3100
|
|
3107
|
-
const { controlKnobs } = self;
|
3101
|
+
const { format, controlKnobs, visuals } = self;
|
3102
|
+
const { offsetWidth, offsetHeight } = visuals[0];
|
3108
3103
|
const [c1, c2, c3] = controlKnobs;
|
3109
3104
|
const { activeElement } = getDocument(c1);
|
3110
3105
|
const currentKnob = controlKnobs.find((x) => x === activeElement);
|
3106
|
+
const yRatio = offsetHeight / (format === 'hsl' ? 100 : 360);
|
3111
3107
|
|
3112
3108
|
if (currentKnob) {
|
3113
3109
|
let offsetX = 0;
|
3114
3110
|
let offsetY = 0;
|
3111
|
+
|
3115
3112
|
if (target === c1) {
|
3113
|
+
const xRatio = offsetWidth / (format === 'hsl' ? 360 : 100);
|
3114
|
+
|
3116
3115
|
if ([keyArrowLeft, keyArrowRight].includes(code)) {
|
3117
|
-
self.controlPositions.c1x += code === keyArrowRight ?
|
3116
|
+
self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
|
3118
3117
|
} else if ([keyArrowUp, keyArrowDown].includes(code)) {
|
3119
|
-
self.controlPositions.c1y += code === keyArrowDown ?
|
3118
|
+
self.controlPositions.c1y += code === keyArrowDown ? yRatio : -yRatio;
|
3120
3119
|
}
|
3121
3120
|
|
3122
3121
|
offsetX = self.controlPositions.c1x;
|
3123
3122
|
offsetY = self.controlPositions.c1y;
|
3124
3123
|
self.changeControl1(offsetX, offsetY);
|
3125
3124
|
} else if (target === c2) {
|
3126
|
-
self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code)
|
3125
|
+
self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code)
|
3126
|
+
? yRatio
|
3127
|
+
: -yRatio;
|
3128
|
+
|
3127
3129
|
offsetY = self.controlPositions.c2y;
|
3128
3130
|
self.changeControl2(offsetY);
|
3129
3131
|
} else if (target === c3) {
|
3130
|
-
self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code)
|
3132
|
+
self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code)
|
3133
|
+
? yRatio
|
3134
|
+
: -yRatio;
|
3135
|
+
|
3131
3136
|
offsetY = self.controlPositions.c3y;
|
3132
3137
|
self.changeAlpha(offsetY);
|
3133
3138
|
}
|
@@ -3149,7 +3154,7 @@ class ColorPicker {
|
|
3149
3154
|
const [v1, v2, v3, v4] = format === 'rgb'
|
3150
3155
|
? inputs.map((i) => parseFloat(i.value) / (i === i4 ? 100 : 1))
|
3151
3156
|
: inputs.map((i) => parseFloat(i.value) / (i !== i1 ? 100 : 360));
|
3152
|
-
const isNonColorValue = self.
|
3157
|
+
const isNonColorValue = self.hasNonColor && nonColors.includes(currentValue);
|
3153
3158
|
const alpha = i4 ? v4 : (1 - controlPositions.c3y / offsetHeight);
|
3154
3159
|
|
3155
3160
|
if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
|
@@ -3408,11 +3413,11 @@ class ColorPicker {
|
|
3408
3413
|
} = componentLabels;
|
3409
3414
|
const { r, g, b } = color.toRgb();
|
3410
3415
|
const [knob1, knob2, knob3] = controlKnobs;
|
3411
|
-
const hue =
|
3416
|
+
const hue = roundPart(hsl.h * 360);
|
3412
3417
|
const alpha = color.a;
|
3413
3418
|
const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
|
3414
|
-
const saturation =
|
3415
|
-
const lightness =
|
3419
|
+
const saturation = roundPart(saturationSource * 100);
|
3420
|
+
const lightness = roundPart(hsl.l * 100);
|
3416
3421
|
const hsvl = hsv.v * 100;
|
3417
3422
|
let colorName;
|
3418
3423
|
|
@@ -3459,8 +3464,8 @@ class ColorPicker {
|
|
3459
3464
|
setAttribute(knob2, ariaValueNow, `${saturation}`);
|
3460
3465
|
} else if (format === 'hwb') {
|
3461
3466
|
const { hwb } = self;
|
3462
|
-
const whiteness =
|
3463
|
-
const blackness =
|
3467
|
+
const whiteness = roundPart(hwb.w * 100);
|
3468
|
+
const blackness = roundPart(hwb.b * 100);
|
3464
3469
|
colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
|
3465
3470
|
setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
|
3466
3471
|
setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
|
@@ -3476,7 +3481,7 @@ class ColorPicker {
|
|
3476
3481
|
setAttribute(knob2, ariaValueNow, `${hue}`);
|
3477
3482
|
}
|
3478
3483
|
|
3479
|
-
const alphaValue =
|
3484
|
+
const alphaValue = roundPart(alpha * 100);
|
3480
3485
|
setAttribute(knob3, ariaValueText, `${alphaValue}%`);
|
3481
3486
|
setAttribute(knob3, ariaValueNow, `${alphaValue}`);
|
3482
3487
|
|
@@ -3499,10 +3504,16 @@ class ColorPicker {
|
|
3499
3504
|
/** Updates the control knobs actual positions. */
|
3500
3505
|
updateControls() {
|
3501
3506
|
const { controlKnobs, controlPositions } = this;
|
3507
|
+
let {
|
3508
|
+
c1x, c1y, c2y, c3y,
|
3509
|
+
} = controlPositions;
|
3502
3510
|
const [control1, control2, control3] = controlKnobs;
|
3503
|
-
|
3504
|
-
|
3505
|
-
|
3511
|
+
// round control positions
|
3512
|
+
[c1x, c1y, c2y, c3y] = [c1x, c1y, c2y, c3y].map(roundPart);
|
3513
|
+
|
3514
|
+
setElementStyle(control1, { transform: `translate3d(${c1x - 4}px,${c1y - 4}px,0)` });
|
3515
|
+
setElementStyle(control2, { transform: `translate3d(0,${c2y - 4}px,0)` });
|
3516
|
+
setElementStyle(control3, { transform: `translate3d(0,${c3y - 4}px,0)` });
|
3506
3517
|
}
|
3507
3518
|
|
3508
3519
|
/**
|
@@ -3515,16 +3526,16 @@ class ColorPicker {
|
|
3515
3526
|
value: oldColor, format, inputs, color, hsl,
|
3516
3527
|
} = self;
|
3517
3528
|
const [i1, i2, i3, i4] = inputs;
|
3518
|
-
const alpha =
|
3519
|
-
const hue =
|
3529
|
+
const alpha = roundPart(color.a * 100);
|
3530
|
+
const hue = roundPart(hsl.h * 360);
|
3520
3531
|
let newColor;
|
3521
3532
|
|
3522
3533
|
if (format === 'hex') {
|
3523
3534
|
newColor = self.color.toHexString(true);
|
3524
3535
|
i1.value = self.hex;
|
3525
3536
|
} else if (format === 'hsl') {
|
3526
|
-
const lightness =
|
3527
|
-
const saturation =
|
3537
|
+
const lightness = roundPart(hsl.l * 100);
|
3538
|
+
const saturation = roundPart(hsl.s * 100);
|
3528
3539
|
newColor = self.color.toHslString();
|
3529
3540
|
i1.value = `${hue}`;
|
3530
3541
|
i2.value = `${saturation}`;
|
@@ -3532,8 +3543,8 @@ class ColorPicker {
|
|
3532
3543
|
i4.value = `${alpha}`;
|
3533
3544
|
} else if (format === 'hwb') {
|
3534
3545
|
const { w, b } = self.hwb;
|
3535
|
-
const whiteness =
|
3536
|
-
const blackness =
|
3546
|
+
const whiteness = roundPart(w * 100);
|
3547
|
+
const blackness = roundPart(b * 100);
|
3537
3548
|
|
3538
3549
|
newColor = self.color.toHwbString();
|
3539
3550
|
i1.value = `${hue}`;
|
@@ -3541,7 +3552,8 @@ class ColorPicker {
|
|
3541
3552
|
i3.value = `${blackness}`;
|
3542
3553
|
i4.value = `${alpha}`;
|
3543
3554
|
} else if (format === 'rgb') {
|
3544
|
-
|
3555
|
+
let { r, g, b } = self.rgb;
|
3556
|
+
[r, g, b] = [r, g, b].map(roundPart);
|
3545
3557
|
|
3546
3558
|
newColor = self.color.toRgbString();
|
3547
3559
|
i1.value = `${r}`;
|
@@ -3605,7 +3617,7 @@ class ColorPicker {
|
|
3605
3617
|
const self = this;
|
3606
3618
|
const { colorPicker } = self;
|
3607
3619
|
|
3608
|
-
if (!hasClass(colorPicker,
|
3620
|
+
if (!['top', 'bottom'].some((c) => hasClass(colorPicker, c))) {
|
3609
3621
|
showDropdown(self, colorPicker);
|
3610
3622
|
}
|
3611
3623
|
}
|
@@ -3622,21 +3634,6 @@ class ColorPicker {
|
|
3622
3634
|
}
|
3623
3635
|
}
|
3624
3636
|
|
3625
|
-
/** Shows the `ColorPicker` dropdown or the presets menu. */
|
3626
|
-
show() {
|
3627
|
-
const self = this;
|
3628
|
-
const { menuToggle } = self;
|
3629
|
-
if (!self.isOpen) {
|
3630
|
-
toggleEventsOnShown(self, true);
|
3631
|
-
self.updateDropdownPosition();
|
3632
|
-
self.isOpen = true;
|
3633
|
-
setAttribute(self.input, 'tabindex', '0');
|
3634
|
-
if (menuToggle) {
|
3635
|
-
setAttribute(menuToggle, 'tabindex', '0');
|
3636
|
-
}
|
3637
|
-
}
|
3638
|
-
}
|
3639
|
-
|
3640
3637
|
/**
|
3641
3638
|
* Hides the currently open `ColorPicker` dropdown.
|
3642
3639
|
* @param {boolean=} focusPrevented
|
@@ -3671,9 +3668,9 @@ class ColorPicker {
|
|
3671
3668
|
if (!focusPrevented) {
|
3672
3669
|
focus(pickerToggle);
|
3673
3670
|
}
|
3674
|
-
setAttribute(input,
|
3671
|
+
setAttribute(input, tabIndex, '-1');
|
3675
3672
|
if (menuToggle) {
|
3676
|
-
setAttribute(menuToggle,
|
3673
|
+
setAttribute(menuToggle, tabIndex, '-1');
|
3677
3674
|
}
|
3678
3675
|
}
|
3679
3676
|
}
|
@@ -3687,7 +3684,10 @@ class ColorPicker {
|
|
3687
3684
|
[...parent.children].forEach((el) => {
|
3688
3685
|
if (el !== input) el.remove();
|
3689
3686
|
});
|
3687
|
+
|
3688
|
+
removeAttribute(input, tabIndex);
|
3690
3689
|
setElementStyle(input, { backgroundColor: '' });
|
3690
|
+
|
3691
3691
|
['txt-light', 'txt-dark'].forEach((c) => removeClass(parent, c));
|
3692
3692
|
Data.remove(input, colorPickerString);
|
3693
3693
|
}
|
@@ -3695,10 +3695,16 @@ class ColorPicker {
|
|
3695
3695
|
|
3696
3696
|
ObjectAssign(ColorPicker, {
|
3697
3697
|
Color,
|
3698
|
+
ColorPalette,
|
3698
3699
|
Version,
|
3699
3700
|
getInstance: getColorPickerInstance,
|
3700
3701
|
init: initColorPicker,
|
3701
3702
|
selector: colorPickerSelector,
|
3703
|
+
// utils important for render
|
3704
|
+
roundPart,
|
3705
|
+
setElementStyle,
|
3706
|
+
setAttribute,
|
3707
|
+
getBoundingClientRect,
|
3702
3708
|
});
|
3703
3709
|
|
3704
|
-
export default
|
3710
|
+
export { ColorPicker as default };
|