@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
package/dist/js/color-picker.js
CHANGED
@@ -1,13 +1,13 @@
|
|
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
|
*/
|
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
|
/**
|
@@ -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
|
869
|
-
* @type {string[]}
|
751
|
+
* A global namespace for `document.head`.
|
870
752
|
*/
|
871
|
-
const
|
753
|
+
const { head: documentHead } = document;
|
872
754
|
|
873
755
|
/**
|
874
756
|
* A list of explicit default non-color values.
|
@@ -876,284 +758,104 @@
|
|
876
758
|
const nonColors = ['transparent', 'currentColor', 'inherit', 'revert', 'initial'];
|
877
759
|
|
878
760
|
/**
|
879
|
-
*
|
880
|
-
*
|
881
|
-
* @
|
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
|
-
|
765
|
+
function roundPart(v) {
|
766
|
+
const floor = Math.floor(v);
|
767
|
+
return v - floor < 0.5 ? floor : Math.round(v);
|
768
|
+
}
|
885
769
|
|
886
|
-
|
770
|
+
// Color supported formats
|
771
|
+
const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsv', 'hwb'];
|
887
772
|
|
888
|
-
|
889
|
-
|
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
|
-
|
902
|
-
|
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
|
-
|
907
|
-
|
908
|
-
|
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
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
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
|
+
};
|
947
816
|
|
948
817
|
/**
|
949
|
-
*
|
950
|
-
*
|
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
|
951
822
|
*/
|
952
|
-
|
823
|
+
function isOnePointZero(n) {
|
824
|
+
return `${n}`.includes('.') && parseFloat(n) === 1;
|
825
|
+
}
|
953
826
|
|
954
827
|
/**
|
955
|
-
*
|
956
|
-
* @
|
828
|
+
* Check to see if string passed in is a percentage
|
829
|
+
* @param {string} n testing number
|
830
|
+
* @returns {boolean} the query result
|
957
831
|
*/
|
958
|
-
|
832
|
+
function isPercentage(n) {
|
833
|
+
return `${n}`.includes('%');
|
834
|
+
}
|
959
835
|
|
960
836
|
/**
|
961
|
-
*
|
962
|
-
* @
|
837
|
+
* Check to see if string passed is a web safe colour.
|
838
|
+
* @see https://stackoverflow.com/a/16994164
|
839
|
+
* @param {string} color a colour name, EG: *red*
|
840
|
+
* @returns {boolean} the query result
|
963
841
|
*/
|
964
|
-
|
842
|
+
function isColorName(color) {
|
843
|
+
if (nonColors.includes(color)
|
844
|
+
|| ['#', ...COLOR_FORMAT].some((f) => color.includes(f))) return false;
|
845
|
+
|
846
|
+
return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
|
847
|
+
setElementStyle(documentHead, { color });
|
848
|
+
const computedColor = getElementStyle(documentHead, 'color');
|
849
|
+
setElementStyle(documentHead, { color: '' });
|
850
|
+
return computedColor !== c;
|
851
|
+
});
|
852
|
+
}
|
965
853
|
|
966
854
|
/**
|
967
|
-
*
|
968
|
-
*
|
969
|
-
* @param {
|
970
|
-
* @returns {
|
971
|
-
*/
|
972
|
-
function getColorControls(self) {
|
973
|
-
const { format, componentLabels } = self;
|
974
|
-
const {
|
975
|
-
hueLabel, alphaLabel, lightnessLabel, saturationLabel,
|
976
|
-
whitenessLabel, blacknessLabel,
|
977
|
-
} = componentLabels;
|
978
|
-
|
979
|
-
const max1 = format === 'hsl' ? 360 : 100;
|
980
|
-
const max2 = format === 'hsl' ? 100 : 360;
|
981
|
-
const max3 = 100;
|
982
|
-
|
983
|
-
let ctrl1Label = format === 'hsl'
|
984
|
-
? `${hueLabel} & ${lightnessLabel}`
|
985
|
-
: `${lightnessLabel} & ${saturationLabel}`;
|
986
|
-
|
987
|
-
ctrl1Label = format === 'hwb'
|
988
|
-
? `${whitenessLabel} & ${blacknessLabel}`
|
989
|
-
: ctrl1Label;
|
990
|
-
|
991
|
-
const ctrl2Label = format === 'hsl'
|
992
|
-
? `${saturationLabel}`
|
993
|
-
: `${hueLabel}`;
|
994
|
-
|
995
|
-
const colorControls = createElement({
|
996
|
-
tagName: 'div',
|
997
|
-
className: `color-controls ${format}`,
|
998
|
-
});
|
999
|
-
|
1000
|
-
const colorPointer = 'color-pointer';
|
1001
|
-
const colorSlider = 'color-slider';
|
1002
|
-
|
1003
|
-
const controls = [
|
1004
|
-
{
|
1005
|
-
i: 1,
|
1006
|
-
c: colorPointer,
|
1007
|
-
l: ctrl1Label,
|
1008
|
-
min: 0,
|
1009
|
-
max: max1,
|
1010
|
-
},
|
1011
|
-
{
|
1012
|
-
i: 2,
|
1013
|
-
c: colorSlider,
|
1014
|
-
l: ctrl2Label,
|
1015
|
-
min: 0,
|
1016
|
-
max: max2,
|
1017
|
-
},
|
1018
|
-
{
|
1019
|
-
i: 3,
|
1020
|
-
c: colorSlider,
|
1021
|
-
l: alphaLabel,
|
1022
|
-
min: 0,
|
1023
|
-
max: max3,
|
1024
|
-
},
|
1025
|
-
];
|
1026
|
-
|
1027
|
-
controls.forEach((template) => {
|
1028
|
-
const {
|
1029
|
-
i, c, l, min, max,
|
1030
|
-
} = template;
|
1031
|
-
// const hidden = i === 2 && format === 'hwb' ? ' v-hidden' : '';
|
1032
|
-
const control = createElement({
|
1033
|
-
tagName: 'div',
|
1034
|
-
// className: `color-control${hidden}`,
|
1035
|
-
className: 'color-control',
|
1036
|
-
});
|
1037
|
-
setAttribute(control, 'role', 'presentation');
|
1038
|
-
|
1039
|
-
control.append(
|
1040
|
-
createElement({
|
1041
|
-
tagName: 'div',
|
1042
|
-
className: `visual-control visual-control${i}`,
|
1043
|
-
}),
|
1044
|
-
);
|
1045
|
-
|
1046
|
-
const knob = createElement({
|
1047
|
-
tagName: 'div',
|
1048
|
-
className: `${c} knob`,
|
1049
|
-
ariaLive: 'polite',
|
1050
|
-
});
|
1051
|
-
|
1052
|
-
setAttribute(knob, ariaLabel, l);
|
1053
|
-
setAttribute(knob, 'role', 'slider');
|
1054
|
-
setAttribute(knob, 'tabindex', '0');
|
1055
|
-
setAttribute(knob, ariaValueMin, `${min}`);
|
1056
|
-
setAttribute(knob, ariaValueMax, `${max}`);
|
1057
|
-
control.append(knob);
|
1058
|
-
colorControls.append(control);
|
1059
|
-
});
|
1060
|
-
|
1061
|
-
return colorControls;
|
1062
|
-
}
|
1063
|
-
|
1064
|
-
/**
|
1065
|
-
* Returns the `document.head` or the `<head>` element.
|
1066
|
-
*
|
1067
|
-
* @param {(Node | HTMLElement | Element | globalThis)=} node
|
1068
|
-
* @returns {HTMLElement | HTMLHeadElement}
|
1069
|
-
*/
|
1070
|
-
function getDocumentHead(node) {
|
1071
|
-
return getDocument(node).head;
|
1072
|
-
}
|
1073
|
-
|
1074
|
-
// Color supported formats
|
1075
|
-
const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
|
1076
|
-
|
1077
|
-
// Hue angles
|
1078
|
-
const ANGLES = 'deg|rad|grad|turn';
|
1079
|
-
|
1080
|
-
// <http://www.w3.org/TR/css3-values/#integers>
|
1081
|
-
const CSS_INTEGER = '[-\\+]?\\d+%?';
|
1082
|
-
|
1083
|
-
// Include CSS3 Module
|
1084
|
-
// <http://www.w3.org/TR/css3-values/#number-value>
|
1085
|
-
const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
|
1086
|
-
|
1087
|
-
// Include CSS4 Module Hue degrees unit
|
1088
|
-
// <https://www.w3.org/TR/css3-values/#angle-value>
|
1089
|
-
const CSS_ANGLE = `[-\\+]?\\d*\\.?\\d+(?:${ANGLES})?`;
|
1090
|
-
|
1091
|
-
// Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
|
1092
|
-
const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
|
1093
|
-
|
1094
|
-
// Add angles to the mix
|
1095
|
-
const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
|
1096
|
-
|
1097
|
-
// Actual matching.
|
1098
|
-
// Parentheses and commas are optional, but not required.
|
1099
|
-
// Whitespace can take the place of commas or opening paren
|
1100
|
-
const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
|
1101
|
-
|
1102
|
-
const matchers = {
|
1103
|
-
CSS_UNIT: new RegExp(CSS_UNIT2),
|
1104
|
-
hwb: new RegExp(`hwb${PERMISSIVE_MATCH}`),
|
1105
|
-
rgb: new RegExp(`rgb(?:a)?${PERMISSIVE_MATCH}`),
|
1106
|
-
hsl: new RegExp(`hsl(?:a)?${PERMISSIVE_MATCH}`),
|
1107
|
-
hsv: new RegExp(`hsv(?:a)?${PERMISSIVE_MATCH}`),
|
1108
|
-
hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
1109
|
-
hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
|
1110
|
-
hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
1111
|
-
hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
|
1112
|
-
};
|
1113
|
-
|
1114
|
-
/**
|
1115
|
-
* Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
|
1116
|
-
* <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
|
1117
|
-
* @param {string} n testing number
|
1118
|
-
* @returns {boolean} the query result
|
1119
|
-
*/
|
1120
|
-
function isOnePointZero(n) {
|
1121
|
-
return `${n}`.includes('.') && parseFloat(n) === 1;
|
1122
|
-
}
|
1123
|
-
|
1124
|
-
/**
|
1125
|
-
* Check to see if string passed in is a percentage
|
1126
|
-
* @param {string} n testing number
|
1127
|
-
* @returns {boolean} the query result
|
1128
|
-
*/
|
1129
|
-
function isPercentage(n) {
|
1130
|
-
return `${n}`.includes('%');
|
1131
|
-
}
|
1132
|
-
|
1133
|
-
/**
|
1134
|
-
* Check to see if string passed in is an angle
|
1135
|
-
* @param {string} n testing string
|
1136
|
-
* @returns {boolean} the query result
|
1137
|
-
*/
|
1138
|
-
function isAngle(n) {
|
1139
|
-
return ANGLES.split('|').some((a) => `${n}`.includes(a));
|
1140
|
-
}
|
1141
|
-
|
1142
|
-
/**
|
1143
|
-
* Check to see if string passed is a web safe colour.
|
1144
|
-
* @param {string} color a colour name, EG: *red*
|
1145
|
-
* @returns {boolean} the query result
|
1146
|
-
*/
|
1147
|
-
function isColorName(color) {
|
1148
|
-
return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
|
1149
|
-
&& !/[0-9]/.test(color);
|
1150
|
-
}
|
1151
|
-
|
1152
|
-
/**
|
1153
|
-
* Check to see if it looks like a CSS unit
|
1154
|
-
* (see `matchers` above for definition).
|
1155
|
-
* @param {string | number} color testing value
|
1156
|
-
* @returns {boolean} the query result
|
855
|
+
* Check to see if it looks like a CSS unit
|
856
|
+
* (see `matchers` above for definition).
|
857
|
+
* @param {string | number} color testing value
|
858
|
+
* @returns {boolean} the query result
|
1157
859
|
*/
|
1158
860
|
function isValidCSSUnit(color) {
|
1159
861
|
return Boolean(matchers.CSS_UNIT.exec(String(color)));
|
@@ -1167,15 +869,15 @@
|
|
1167
869
|
*/
|
1168
870
|
function bound01(N, max) {
|
1169
871
|
let n = N;
|
1170
|
-
if (isOnePointZero(
|
1171
|
-
|
1172
|
-
n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
|
872
|
+
if (isOnePointZero(N)) n = '100%';
|
1173
873
|
|
1174
|
-
|
1175
|
-
|
874
|
+
const processPercent = isPercentage(n);
|
875
|
+
n = max === 360
|
876
|
+
? parseFloat(n)
|
877
|
+
: Math.min(max, Math.max(0, parseFloat(n)));
|
1176
878
|
|
1177
879
|
// Automatically convert percentage into number
|
1178
|
-
if (
|
880
|
+
if (processPercent) n = (n * max) / 100;
|
1179
881
|
|
1180
882
|
// Handle floating point rounding errors
|
1181
883
|
if (Math.abs(n - max) < 0.000001) {
|
@@ -1186,11 +888,11 @@
|
|
1186
888
|
// If n is a hue given in degrees,
|
1187
889
|
// wrap around out-of-range values into [0, 360] range
|
1188
890
|
// then convert into [0, 1].
|
1189
|
-
n = (n < 0 ? (n % max) + max : n % max) /
|
891
|
+
n = (n < 0 ? (n % max) + max : n % max) / max;
|
1190
892
|
} else {
|
1191
893
|
// If n not a hue given in degrees
|
1192
894
|
// Convert into [0, 1] range if it isn't already.
|
1193
|
-
n = (n % max) /
|
895
|
+
n = (n % max) / max;
|
1194
896
|
}
|
1195
897
|
return n;
|
1196
898
|
}
|
@@ -1225,7 +927,6 @@
|
|
1225
927
|
* @returns {string}
|
1226
928
|
*/
|
1227
929
|
function getRGBFromName(name) {
|
1228
|
-
const documentHead = getDocumentHead();
|
1229
930
|
setElementStyle(documentHead, { color: name });
|
1230
931
|
const colorName = getElementStyle(documentHead, 'color');
|
1231
932
|
setElementStyle(documentHead, { color: '' });
|
@@ -1238,7 +939,7 @@
|
|
1238
939
|
* @returns {string} - the hexadecimal value
|
1239
940
|
*/
|
1240
941
|
function convertDecimalToHex(d) {
|
1241
|
-
return
|
942
|
+
return roundPart(d * 255).toString(16);
|
1242
943
|
}
|
1243
944
|
|
1244
945
|
/**
|
@@ -1324,6 +1025,36 @@
|
|
1324
1025
|
return p;
|
1325
1026
|
}
|
1326
1027
|
|
1028
|
+
/**
|
1029
|
+
* Converts an HSL colour value to RGB.
|
1030
|
+
*
|
1031
|
+
* @param {number} h Hue Angle [0, 1]
|
1032
|
+
* @param {number} s Saturation [0, 1]
|
1033
|
+
* @param {number} l Lightness Angle [0, 1]
|
1034
|
+
* @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
1035
|
+
*/
|
1036
|
+
function hslToRgb(h, s, l) {
|
1037
|
+
let r = 0;
|
1038
|
+
let g = 0;
|
1039
|
+
let b = 0;
|
1040
|
+
|
1041
|
+
if (s === 0) {
|
1042
|
+
// achromatic
|
1043
|
+
g = l;
|
1044
|
+
b = l;
|
1045
|
+
r = l;
|
1046
|
+
} else {
|
1047
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
1048
|
+
const p = 2 * l - q;
|
1049
|
+
r = hueToRgb(p, q, h + 1 / 3);
|
1050
|
+
g = hueToRgb(p, q, h);
|
1051
|
+
b = hueToRgb(p, q, h - 1 / 3);
|
1052
|
+
}
|
1053
|
+
[r, g, b] = [r, g, b].map((x) => x * 255);
|
1054
|
+
|
1055
|
+
return { r, g, b };
|
1056
|
+
}
|
1057
|
+
|
1327
1058
|
/**
|
1328
1059
|
* Returns an HWB colour object from an RGB colour object.
|
1329
1060
|
* @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
|
@@ -1386,36 +1117,6 @@
|
|
1386
1117
|
return { r, g, b };
|
1387
1118
|
}
|
1388
1119
|
|
1389
|
-
/**
|
1390
|
-
* Converts an HSL colour value to RGB.
|
1391
|
-
*
|
1392
|
-
* @param {number} h Hue Angle [0, 1]
|
1393
|
-
* @param {number} s Saturation [0, 1]
|
1394
|
-
* @param {number} l Lightness Angle [0, 1]
|
1395
|
-
* @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
1396
|
-
*/
|
1397
|
-
function hslToRgb(h, s, l) {
|
1398
|
-
let r = 0;
|
1399
|
-
let g = 0;
|
1400
|
-
let b = 0;
|
1401
|
-
|
1402
|
-
if (s === 0) {
|
1403
|
-
// achromatic
|
1404
|
-
g = l;
|
1405
|
-
b = l;
|
1406
|
-
r = l;
|
1407
|
-
} else {
|
1408
|
-
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
1409
|
-
const p = 2 * l - q;
|
1410
|
-
r = hueToRgb(p, q, h + 1 / 3);
|
1411
|
-
g = hueToRgb(p, q, h);
|
1412
|
-
b = hueToRgb(p, q, h - 1 / 3);
|
1413
|
-
}
|
1414
|
-
[r, g, b] = [r, g, b].map((x) => x * 255);
|
1415
|
-
|
1416
|
-
return { r, g, b };
|
1417
|
-
}
|
1418
|
-
|
1419
1120
|
/**
|
1420
1121
|
* Converts an RGB colour value to HSV.
|
1421
1122
|
*
|
@@ -1471,10 +1172,11 @@
|
|
1471
1172
|
const q = v * (1 - f * s);
|
1472
1173
|
const t = v * (1 - (1 - f) * s);
|
1473
1174
|
const mod = i % 6;
|
1474
|
-
|
1475
|
-
|
1476
|
-
|
1477
|
-
|
1175
|
+
let r = [v, q, p, p, t, v][mod];
|
1176
|
+
let g = [t, v, v, q, p, p][mod];
|
1177
|
+
let b = [p, p, t, v, v, q][mod];
|
1178
|
+
[r, g, b] = [r, g, b].map((n) => n * 255);
|
1179
|
+
return { r, g, b };
|
1478
1180
|
}
|
1479
1181
|
|
1480
1182
|
/**
|
@@ -1490,15 +1192,15 @@
|
|
1490
1192
|
*/
|
1491
1193
|
function rgbToHex(r, g, b, allow3Char) {
|
1492
1194
|
const hex = [
|
1493
|
-
pad2(
|
1494
|
-
pad2(
|
1495
|
-
pad2(
|
1195
|
+
pad2(roundPart(r).toString(16)),
|
1196
|
+
pad2(roundPart(g).toString(16)),
|
1197
|
+
pad2(roundPart(b).toString(16)),
|
1496
1198
|
];
|
1497
1199
|
|
1498
1200
|
// Return a 3 character hex if possible
|
1499
1201
|
if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
|
1500
1202
|
&& hex[1].charAt(0) === hex[1].charAt(1)
|
1501
|
-
|
1203
|
+
&& hex[2].charAt(0) === hex[2].charAt(1)) {
|
1502
1204
|
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
|
1503
1205
|
}
|
1504
1206
|
|
@@ -1517,48 +1219,33 @@
|
|
1517
1219
|
*/
|
1518
1220
|
function rgbaToHex(r, g, b, a, allow4Char) {
|
1519
1221
|
const hex = [
|
1520
|
-
pad2(
|
1521
|
-
pad2(
|
1522
|
-
pad2(
|
1222
|
+
pad2(roundPart(r).toString(16)),
|
1223
|
+
pad2(roundPart(g).toString(16)),
|
1224
|
+
pad2(roundPart(b).toString(16)),
|
1523
1225
|
pad2(convertDecimalToHex(a)),
|
1524
1226
|
];
|
1525
1227
|
|
1526
1228
|
// Return a 4 character hex if possible
|
1527
1229
|
if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
|
1528
1230
|
&& hex[1].charAt(0) === hex[1].charAt(1)
|
1529
|
-
|
1530
|
-
|
1231
|
+
&& hex[2].charAt(0) === hex[2].charAt(1)
|
1232
|
+
&& hex[3].charAt(0) === hex[3].charAt(1)) {
|
1531
1233
|
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
|
1532
1234
|
}
|
1533
1235
|
return hex.join('');
|
1534
1236
|
}
|
1535
1237
|
|
1536
|
-
/**
|
1537
|
-
* Returns a colour object corresponding to a given number.
|
1538
|
-
* @param {number} color input number
|
1539
|
-
* @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
1540
|
-
*/
|
1541
|
-
function numberInputToObject(color) {
|
1542
|
-
/* eslint-disable no-bitwise */
|
1543
|
-
return {
|
1544
|
-
r: color >> 16,
|
1545
|
-
g: (color & 0xff00) >> 8,
|
1546
|
-
b: color & 0xff,
|
1547
|
-
};
|
1548
|
-
/* eslint-enable no-bitwise */
|
1549
|
-
}
|
1550
|
-
|
1551
1238
|
/**
|
1552
1239
|
* Permissive string parsing. Take in a number of formats, and output an object
|
1553
1240
|
* based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
|
1554
1241
|
* @param {string} input colour value in any format
|
1555
|
-
* @returns {Record<string, (number | string)> | false} an object matching the RegExp
|
1242
|
+
* @returns {Record<string, (number | string | boolean)> | false} an object matching the RegExp
|
1556
1243
|
*/
|
1557
1244
|
function stringInputToObject(input) {
|
1558
|
-
let color = input.trim()
|
1245
|
+
let color = toLowerCase(input.trim());
|
1559
1246
|
if (color.length === 0) {
|
1560
1247
|
return {
|
1561
|
-
r: 0, g: 0, b: 0, a:
|
1248
|
+
r: 0, g: 0, b: 0, a: 1,
|
1562
1249
|
};
|
1563
1250
|
}
|
1564
1251
|
let named = false;
|
@@ -1566,11 +1253,9 @@
|
|
1566
1253
|
color = getRGBFromName(color);
|
1567
1254
|
named = true;
|
1568
1255
|
} else if (nonColors.includes(color)) {
|
1569
|
-
const
|
1570
|
-
const rgb = isTransparent ? 0 : 255;
|
1571
|
-
const a = isTransparent ? 0 : 1;
|
1256
|
+
const a = color === 'transparent' ? 0 : 1;
|
1572
1257
|
return {
|
1573
|
-
r:
|
1258
|
+
r: 0, g: 0, b: 0, a, format: 'rgb', ok: true,
|
1574
1259
|
};
|
1575
1260
|
}
|
1576
1261
|
|
@@ -1610,7 +1295,6 @@
|
|
1610
1295
|
g: parseIntFromHex(m2),
|
1611
1296
|
b: parseIntFromHex(m3),
|
1612
1297
|
a: convertHexToDecimal(m4),
|
1613
|
-
// format: named ? 'rgb' : 'hex8',
|
1614
1298
|
format: named ? 'rgb' : 'hex',
|
1615
1299
|
};
|
1616
1300
|
}
|
@@ -1674,6 +1358,7 @@
|
|
1674
1358
|
function inputToRGB(input) {
|
1675
1359
|
let rgb = { r: 0, g: 0, b: 0 };
|
1676
1360
|
let color = input;
|
1361
|
+
/** @type {string | number} */
|
1677
1362
|
let a = 1;
|
1678
1363
|
let s = null;
|
1679
1364
|
let v = null;
|
@@ -1681,8 +1366,11 @@
|
|
1681
1366
|
let w = null;
|
1682
1367
|
let b = null;
|
1683
1368
|
let h = null;
|
1369
|
+
let r = null;
|
1370
|
+
let g = null;
|
1684
1371
|
let ok = false;
|
1685
|
-
|
1372
|
+
const inputFormat = typeof color === 'object' && color.format;
|
1373
|
+
let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
|
1686
1374
|
|
1687
1375
|
if (typeof input === 'string') {
|
1688
1376
|
// @ts-ignore -- this now is converted to object
|
@@ -1691,7 +1379,10 @@
|
|
1691
1379
|
}
|
1692
1380
|
if (typeof color === 'object') {
|
1693
1381
|
if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
|
1694
|
-
|
1382
|
+
({ r, g, b } = color);
|
1383
|
+
// RGB values now are all in [0, 255] range
|
1384
|
+
[r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255);
|
1385
|
+
rgb = { r, g, b };
|
1695
1386
|
ok = true;
|
1696
1387
|
format = 'rgb';
|
1697
1388
|
} else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
|
@@ -1720,14 +1411,17 @@
|
|
1720
1411
|
format = 'hwb';
|
1721
1412
|
}
|
1722
1413
|
if (isValidCSSUnit(color.a)) {
|
1723
|
-
a = color.a;
|
1724
|
-
a = isPercentage(`${a}`) ? bound01(a, 100) : a;
|
1414
|
+
a = color.a; // @ts-ignore -- `parseFloat` works with numbers too
|
1415
|
+
a = isPercentage(`${a}`) || parseFloat(a) > 1 ? bound01(a, 100) : a;
|
1725
1416
|
}
|
1726
1417
|
}
|
1418
|
+
if (typeof color === 'undefined') {
|
1419
|
+
ok = true;
|
1420
|
+
}
|
1727
1421
|
|
1728
1422
|
return {
|
1729
|
-
ok,
|
1730
|
-
format
|
1423
|
+
ok,
|
1424
|
+
format,
|
1731
1425
|
r: Math.min(255, Math.max(rgb.r, 0)),
|
1732
1426
|
g: Math.min(255, Math.max(rgb.g, 0)),
|
1733
1427
|
b: Math.min(255, Math.max(rgb.b, 0)),
|
@@ -1756,7 +1450,8 @@
|
|
1756
1450
|
color = inputToRGB(color);
|
1757
1451
|
}
|
1758
1452
|
if (typeof color === 'number') {
|
1759
|
-
|
1453
|
+
const len = `${color}`.length;
|
1454
|
+
color = `#${(len === 2 ? '0' : '00')}${color}`;
|
1760
1455
|
}
|
1761
1456
|
const {
|
1762
1457
|
r, g, b, a, ok, format,
|
@@ -1766,7 +1461,7 @@
|
|
1766
1461
|
const self = this;
|
1767
1462
|
|
1768
1463
|
/** @type {CP.ColorInput} */
|
1769
|
-
self.originalInput =
|
1464
|
+
self.originalInput = input;
|
1770
1465
|
/** @type {number} */
|
1771
1466
|
self.r = r;
|
1772
1467
|
/** @type {number} */
|
@@ -1779,14 +1474,6 @@
|
|
1779
1474
|
self.ok = ok;
|
1780
1475
|
/** @type {CP.ColorFormats} */
|
1781
1476
|
self.format = configFormat || format;
|
1782
|
-
|
1783
|
-
// Don't let the range of [0,255] come back in [0,1].
|
1784
|
-
// Potentially lose a little bit of precision here, but will fix issues where
|
1785
|
-
// .5 gets interpreted as half of the total, instead of half of 1
|
1786
|
-
// If it was supposed to be 128, this was already taken care of by `inputToRgb`
|
1787
|
-
if (r < 1) self.r = Math.round(r);
|
1788
|
-
if (g < 1) self.g = Math.round(g);
|
1789
|
-
if (b < 1) self.b = Math.round(b);
|
1790
1477
|
}
|
1791
1478
|
|
1792
1479
|
/**
|
@@ -1802,7 +1489,7 @@
|
|
1802
1489
|
* @returns {boolean} the query result
|
1803
1490
|
*/
|
1804
1491
|
get isDark() {
|
1805
|
-
return this.brightness <
|
1492
|
+
return this.brightness < 120;
|
1806
1493
|
}
|
1807
1494
|
|
1808
1495
|
/**
|
@@ -1854,13 +1541,9 @@
|
|
1854
1541
|
const {
|
1855
1542
|
r, g, b, a,
|
1856
1543
|
} = this;
|
1857
|
-
const [R, G, B] = [r, g, b].map((x) => Math.round(x));
|
1858
1544
|
|
1859
1545
|
return {
|
1860
|
-
r:
|
1861
|
-
g: G,
|
1862
|
-
b: B,
|
1863
|
-
a: Math.round(a * 100) / 100,
|
1546
|
+
r, g, b, a: roundPart(a * 100) / 100,
|
1864
1547
|
};
|
1865
1548
|
}
|
1866
1549
|
|
@@ -1874,10 +1557,11 @@
|
|
1874
1557
|
const {
|
1875
1558
|
r, g, b, a,
|
1876
1559
|
} = this.toRgb();
|
1560
|
+
const [R, G, B] = [r, g, b].map(roundPart);
|
1877
1561
|
|
1878
1562
|
return a === 1
|
1879
|
-
? `rgb(${
|
1880
|
-
: `rgba(${
|
1563
|
+
? `rgb(${R}, ${G}, ${B})`
|
1564
|
+
: `rgba(${R}, ${G}, ${B}, ${a})`;
|
1881
1565
|
}
|
1882
1566
|
|
1883
1567
|
/**
|
@@ -1890,9 +1574,10 @@
|
|
1890
1574
|
const {
|
1891
1575
|
r, g, b, a,
|
1892
1576
|
} = this.toRgb();
|
1893
|
-
const
|
1577
|
+
const [R, G, B] = [r, g, b].map(roundPart);
|
1578
|
+
const A = a === 1 ? '' : ` / ${roundPart(a * 100)}%`;
|
1894
1579
|
|
1895
|
-
return `rgb(${
|
1580
|
+
return `rgb(${R} ${G} ${B}${A})`;
|
1896
1581
|
}
|
1897
1582
|
|
1898
1583
|
/**
|
@@ -1985,10 +1670,10 @@
|
|
1985
1670
|
let {
|
1986
1671
|
h, s, l, a,
|
1987
1672
|
} = this.toHsl();
|
1988
|
-
h =
|
1989
|
-
s =
|
1990
|
-
l =
|
1991
|
-
a =
|
1673
|
+
h = roundPart(h * 360);
|
1674
|
+
s = roundPart(s * 100);
|
1675
|
+
l = roundPart(l * 100);
|
1676
|
+
a = roundPart(a * 100) / 100;
|
1992
1677
|
|
1993
1678
|
return a === 1
|
1994
1679
|
? `hsl(${h}, ${s}%, ${l}%)`
|
@@ -2005,11 +1690,11 @@
|
|
2005
1690
|
let {
|
2006
1691
|
h, s, l, a,
|
2007
1692
|
} = this.toHsl();
|
2008
|
-
h =
|
2009
|
-
s =
|
2010
|
-
l =
|
2011
|
-
a =
|
2012
|
-
const A = a < 100 ? ` / ${
|
1693
|
+
h = roundPart(h * 360);
|
1694
|
+
s = roundPart(s * 100);
|
1695
|
+
l = roundPart(l * 100);
|
1696
|
+
a = roundPart(a * 100);
|
1697
|
+
const A = a < 100 ? ` / ${roundPart(a)}%` : '';
|
2013
1698
|
|
2014
1699
|
return `hsl(${h}deg ${s}% ${l}%${A})`;
|
2015
1700
|
}
|
@@ -2036,11 +1721,11 @@
|
|
2036
1721
|
let {
|
2037
1722
|
h, w, b, a,
|
2038
1723
|
} = this.toHwb();
|
2039
|
-
h =
|
2040
|
-
w =
|
2041
|
-
b =
|
2042
|
-
a =
|
2043
|
-
const A = a < 100 ? ` / ${
|
1724
|
+
h = roundPart(h * 360);
|
1725
|
+
w = roundPart(w * 100);
|
1726
|
+
b = roundPart(b * 100);
|
1727
|
+
a = roundPart(a * 100);
|
1728
|
+
const A = a < 100 ? ` / ${roundPart(a)}%` : '';
|
2044
1729
|
|
2045
1730
|
return `hwb(${h}deg ${w}% ${b}%${A})`;
|
2046
1731
|
}
|
@@ -2146,108 +1831,426 @@
|
|
2146
1831
|
const self = this;
|
2147
1832
|
const { format } = self;
|
2148
1833
|
|
2149
|
-
if (format === 'hex') return self.toHexString(allowShort);
|
2150
|
-
if (format === 'hsl') return self.toHslString();
|
2151
|
-
if (format === 'hwb') return self.toHwbString();
|
1834
|
+
if (format === 'hex') return self.toHexString(allowShort);
|
1835
|
+
if (format === 'hsl') return self.toHslString();
|
1836
|
+
if (format === 'hwb') return self.toHwbString();
|
1837
|
+
|
1838
|
+
return self.toRgbString();
|
1839
|
+
}
|
1840
|
+
}
|
1841
|
+
|
1842
|
+
ObjectAssign(Color, {
|
1843
|
+
ANGLES,
|
1844
|
+
CSS_ANGLE,
|
1845
|
+
CSS_INTEGER,
|
1846
|
+
CSS_NUMBER,
|
1847
|
+
CSS_UNIT,
|
1848
|
+
CSS_UNIT2,
|
1849
|
+
PERMISSIVE_MATCH,
|
1850
|
+
matchers,
|
1851
|
+
isOnePointZero,
|
1852
|
+
isPercentage,
|
1853
|
+
isValidCSSUnit,
|
1854
|
+
isColorName,
|
1855
|
+
pad2,
|
1856
|
+
clamp01,
|
1857
|
+
bound01,
|
1858
|
+
boundAlpha,
|
1859
|
+
getRGBFromName,
|
1860
|
+
convertHexToDecimal,
|
1861
|
+
convertDecimalToHex,
|
1862
|
+
rgbToHsl,
|
1863
|
+
rgbToHex,
|
1864
|
+
rgbToHsv,
|
1865
|
+
rgbToHwb,
|
1866
|
+
rgbaToHex,
|
1867
|
+
hslToRgb,
|
1868
|
+
hsvToRgb,
|
1869
|
+
hueToRgb,
|
1870
|
+
hwbToRgb,
|
1871
|
+
parseIntFromHex,
|
1872
|
+
stringInputToObject,
|
1873
|
+
inputToRGB,
|
1874
|
+
roundPart,
|
1875
|
+
getElementStyle,
|
1876
|
+
setElementStyle,
|
1877
|
+
ObjectAssign,
|
1878
|
+
});
|
1879
|
+
|
1880
|
+
/**
|
1881
|
+
* @class
|
1882
|
+
* Returns a color palette with a given set of parameters.
|
1883
|
+
* @example
|
1884
|
+
* new ColorPalette(0, 12, 10);
|
1885
|
+
* // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: Array<Color> }
|
1886
|
+
*/
|
1887
|
+
class ColorPalette {
|
1888
|
+
/**
|
1889
|
+
* The `hue` parameter is optional, which would be set to 0.
|
1890
|
+
* @param {number[]} args represeinting hue, hueSteps, lightSteps
|
1891
|
+
* * `args.hue` the starting Hue [0, 360]
|
1892
|
+
* * `args.hueSteps` Hue Steps Count [5, 24]
|
1893
|
+
* * `args.lightSteps` Lightness Steps Count [5, 12]
|
1894
|
+
*/
|
1895
|
+
constructor(...args) {
|
1896
|
+
let hue = 0;
|
1897
|
+
let hueSteps = 12;
|
1898
|
+
let lightSteps = 10;
|
1899
|
+
let lightnessArray = [0.5];
|
1900
|
+
|
1901
|
+
if (args.length === 3) {
|
1902
|
+
[hue, hueSteps, lightSteps] = args;
|
1903
|
+
} else if (args.length === 2) {
|
1904
|
+
[hueSteps, lightSteps] = args;
|
1905
|
+
if ([hueSteps, lightSteps].some((n) => n < 1)) {
|
1906
|
+
throw TypeError('ColorPalette: when 2 arguments used, both must be larger than 0.');
|
1907
|
+
}
|
1908
|
+
} else {
|
1909
|
+
throw TypeError('ColorPalette requires minimum 2 arguments');
|
1910
|
+
}
|
1911
|
+
|
1912
|
+
/** @type {Color[]} */
|
1913
|
+
const colors = [];
|
1914
|
+
|
1915
|
+
const hueStep = 360 / hueSteps;
|
1916
|
+
const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
|
1917
|
+
const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
|
1918
|
+
|
1919
|
+
let lightStep = 0.25;
|
1920
|
+
lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
|
1921
|
+
lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
|
1922
|
+
lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
|
1923
|
+
lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
|
1924
|
+
lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
|
1925
|
+
lightStep = lightSteps > 13 ? estimatedStep : lightStep;
|
1926
|
+
|
1927
|
+
// light tints
|
1928
|
+
for (let i = 1; i < half + 1; i += 1) {
|
1929
|
+
lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
|
1930
|
+
}
|
1931
|
+
|
1932
|
+
// dark tints
|
1933
|
+
for (let i = 1; i < lightSteps - half; i += 1) {
|
1934
|
+
lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
|
1935
|
+
}
|
1936
|
+
|
1937
|
+
// feed `colors` Array
|
1938
|
+
for (let i = 0; i < hueSteps; i += 1) {
|
1939
|
+
const currentHue = ((hue + i * hueStep) % 360) / 360;
|
1940
|
+
lightnessArray.forEach((l) => {
|
1941
|
+
colors.push(new Color({ h: currentHue, s: 1, l }));
|
1942
|
+
});
|
1943
|
+
}
|
1944
|
+
|
1945
|
+
this.hue = hue;
|
1946
|
+
this.hueSteps = hueSteps;
|
1947
|
+
this.lightSteps = lightSteps;
|
1948
|
+
this.colors = colors;
|
1949
|
+
}
|
1950
|
+
}
|
1951
|
+
|
1952
|
+
ObjectAssign(ColorPalette, { Color });
|
1953
|
+
|
1954
|
+
/** @type {Record<string, string>} */
|
1955
|
+
const colorPickerLabels = {
|
1956
|
+
pickerLabel: 'Colour Picker',
|
1957
|
+
appearanceLabel: 'Colour Appearance',
|
1958
|
+
valueLabel: 'Colour Value',
|
1959
|
+
toggleLabel: 'Select Colour',
|
1960
|
+
presetsLabel: 'Colour Presets',
|
1961
|
+
defaultsLabel: 'Colour Defaults',
|
1962
|
+
formatLabel: 'Format',
|
1963
|
+
alphaLabel: 'Alpha',
|
1964
|
+
hexLabel: 'Hexadecimal',
|
1965
|
+
hueLabel: 'Hue',
|
1966
|
+
whitenessLabel: 'Whiteness',
|
1967
|
+
blacknessLabel: 'Blackness',
|
1968
|
+
saturationLabel: 'Saturation',
|
1969
|
+
lightnessLabel: 'Lightness',
|
1970
|
+
redLabel: 'Red',
|
1971
|
+
greenLabel: 'Green',
|
1972
|
+
blueLabel: 'Blue',
|
1973
|
+
};
|
1974
|
+
|
1975
|
+
/**
|
1976
|
+
* A list of 17 color names used for WAI-ARIA compliance.
|
1977
|
+
* @type {string[]}
|
1978
|
+
*/
|
1979
|
+
const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
|
1980
|
+
|
1981
|
+
const tabIndex = 'tabindex';
|
1982
|
+
|
1983
|
+
/**
|
1984
|
+
* Check if a string is valid JSON string.
|
1985
|
+
* @param {string} str the string input
|
1986
|
+
* @returns {boolean} the query result
|
1987
|
+
*/
|
1988
|
+
function isValidJSON(str) {
|
1989
|
+
try {
|
1990
|
+
JSON.parse(str);
|
1991
|
+
} catch (e) {
|
1992
|
+
return false;
|
1993
|
+
}
|
1994
|
+
return true;
|
1995
|
+
}
|
1996
|
+
|
1997
|
+
/**
|
1998
|
+
* Shortcut for `String.toUpperCase()`.
|
1999
|
+
*
|
2000
|
+
* @param {string} source input string
|
2001
|
+
* @returns {string} uppercase output string
|
2002
|
+
*/
|
2003
|
+
const toUpperCase = (source) => source.toUpperCase();
|
2004
|
+
|
2005
|
+
/**
|
2006
|
+
* A global namespace for aria-haspopup.
|
2007
|
+
* @type {string}
|
2008
|
+
*/
|
2009
|
+
const ariaHasPopup = 'aria-haspopup';
|
2010
|
+
|
2011
|
+
/**
|
2012
|
+
* A global namespace for aria-hidden.
|
2013
|
+
* @type {string}
|
2014
|
+
*/
|
2015
|
+
const ariaHidden = 'aria-hidden';
|
2016
|
+
|
2017
|
+
/**
|
2018
|
+
* A global namespace for aria-labelledby.
|
2019
|
+
* @type {string}
|
2020
|
+
*/
|
2021
|
+
const ariaLabelledBy = 'aria-labelledby';
|
2022
|
+
|
2023
|
+
/**
|
2024
|
+
* This is a shortie for `document.createElement` method
|
2025
|
+
* which allows you to create a new `HTMLElement` for a given `tagName`
|
2026
|
+
* or based on an object with specific non-readonly attributes:
|
2027
|
+
* `id`, `className`, `textContent`, `style`, etc.
|
2028
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
|
2029
|
+
*
|
2030
|
+
* @param {Record<string, string> | string} param `tagName` or object
|
2031
|
+
* @return {HTMLElement | Element} a new `HTMLElement` or `Element`
|
2032
|
+
*/
|
2033
|
+
function createElement(param) {
|
2034
|
+
if (typeof param === 'string') {
|
2035
|
+
return getDocument().createElement(param);
|
2036
|
+
}
|
2037
|
+
|
2038
|
+
const { tagName } = param;
|
2039
|
+
const attr = { ...param };
|
2040
|
+
const newElement = createElement(tagName);
|
2041
|
+
delete attr.tagName;
|
2042
|
+
ObjectAssign(newElement, attr);
|
2043
|
+
return newElement;
|
2044
|
+
}
|
2045
|
+
|
2046
|
+
/**
|
2047
|
+
* This is a shortie for `document.createElementNS` method
|
2048
|
+
* which allows you to create a new `HTMLElement` for a given `tagName`
|
2049
|
+
* or based on an object with specific non-readonly attributes:
|
2050
|
+
* `id`, `className`, `textContent`, `style`, etc.
|
2051
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
|
2052
|
+
*
|
2053
|
+
* @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
|
2054
|
+
* @param {Record<string, string> | string} param `tagName` or object
|
2055
|
+
* @return {HTMLElement | Element} a new `HTMLElement` or `Element`
|
2056
|
+
*/
|
2057
|
+
function createElementNS(namespace, param) {
|
2058
|
+
if (typeof param === 'string') {
|
2059
|
+
return getDocument().createElementNS(namespace, param);
|
2060
|
+
}
|
2061
|
+
|
2062
|
+
const { tagName } = param;
|
2063
|
+
const attr = { ...param };
|
2064
|
+
const newElement = createElementNS(namespace, tagName);
|
2065
|
+
delete attr.tagName;
|
2066
|
+
ObjectAssign(newElement, attr);
|
2067
|
+
return newElement;
|
2068
|
+
}
|
2069
|
+
|
2070
|
+
const vHidden = 'v-hidden';
|
2071
|
+
|
2072
|
+
/**
|
2073
|
+
* Returns the color form for `ColorPicker`.
|
2074
|
+
*
|
2075
|
+
* @param {CP.ColorPicker} self the `ColorPicker` instance
|
2076
|
+
* @returns {HTMLElement | Element} a new `<div>` element with color component `<input>`
|
2077
|
+
*/
|
2078
|
+
function getColorForm(self) {
|
2079
|
+
const { format, id, componentLabels } = self;
|
2080
|
+
const colorForm = createElement({
|
2081
|
+
tagName: 'div',
|
2082
|
+
className: `color-form ${format}`,
|
2083
|
+
});
|
2084
|
+
|
2085
|
+
let components = ['hex'];
|
2086
|
+
if (format === 'rgb') components = ['red', 'green', 'blue', 'alpha'];
|
2087
|
+
else if (format === 'hsl') components = ['hue', 'saturation', 'lightness', 'alpha'];
|
2088
|
+
else if (format === 'hwb') components = ['hue', 'whiteness', 'blackness', 'alpha'];
|
2089
|
+
|
2090
|
+
components.forEach((c) => {
|
2091
|
+
const [C] = format === 'hex' ? ['#'] : toUpperCase(c).split('');
|
2092
|
+
const cID = `color_${format}_${c}_${id}`;
|
2093
|
+
const formatLabel = componentLabels[`${c}Label`];
|
2094
|
+
const cInputLabel = createElement({ tagName: 'label' });
|
2095
|
+
setAttribute(cInputLabel, 'for', cID);
|
2096
|
+
cInputLabel.append(
|
2097
|
+
createElement({ tagName: 'span', ariaHidden: 'true', innerText: `${C}:` }),
|
2098
|
+
createElement({ tagName: 'span', className: vHidden, innerText: formatLabel }),
|
2099
|
+
);
|
2100
|
+
const cInput = createElement({
|
2101
|
+
tagName: 'input',
|
2102
|
+
id: cID,
|
2103
|
+
// name: cID, - prevent saving the value to a form
|
2104
|
+
type: format === 'hex' ? 'text' : 'number',
|
2105
|
+
value: c === 'alpha' ? '100' : '0',
|
2106
|
+
className: `color-input ${c}`,
|
2107
|
+
});
|
2108
|
+
setAttribute(cInput, 'autocomplete', 'off');
|
2109
|
+
setAttribute(cInput, 'spellcheck', 'false');
|
2110
|
+
|
2111
|
+
// alpha
|
2112
|
+
let max = '100';
|
2113
|
+
let step = '1';
|
2114
|
+
if (c !== 'alpha') {
|
2115
|
+
if (format === 'rgb') {
|
2116
|
+
max = '255'; step = '1';
|
2117
|
+
} else if (c === 'hue') {
|
2118
|
+
max = '360'; step = '1';
|
2119
|
+
}
|
2120
|
+
}
|
2121
|
+
ObjectAssign(cInput, {
|
2122
|
+
min: '0',
|
2123
|
+
max,
|
2124
|
+
step,
|
2125
|
+
});
|
2126
|
+
colorForm.append(cInputLabel, cInput);
|
2127
|
+
});
|
2128
|
+
return colorForm;
|
2129
|
+
}
|
2130
|
+
|
2131
|
+
/**
|
2132
|
+
* A global namespace for aria-label.
|
2133
|
+
* @type {string}
|
2134
|
+
*/
|
2135
|
+
const ariaLabel = 'aria-label';
|
2136
|
+
|
2137
|
+
/**
|
2138
|
+
* A global namespace for aria-valuemin.
|
2139
|
+
* @type {string}
|
2140
|
+
*/
|
2141
|
+
const ariaValueMin = 'aria-valuemin';
|
2142
|
+
|
2143
|
+
/**
|
2144
|
+
* A global namespace for aria-valuemax.
|
2145
|
+
* @type {string}
|
2146
|
+
*/
|
2147
|
+
const ariaValueMax = 'aria-valuemax';
|
2148
|
+
|
2149
|
+
/**
|
2150
|
+
* Returns all color controls for `ColorPicker`.
|
2151
|
+
*
|
2152
|
+
* @param {CP.ColorPicker} self the `ColorPicker` instance
|
2153
|
+
* @returns {HTMLElement | Element} color controls
|
2154
|
+
*/
|
2155
|
+
function getColorControls(self) {
|
2156
|
+
const { format, componentLabels } = self;
|
2157
|
+
const {
|
2158
|
+
hueLabel, alphaLabel, lightnessLabel, saturationLabel,
|
2159
|
+
whitenessLabel, blacknessLabel,
|
2160
|
+
} = componentLabels;
|
2161
|
+
|
2162
|
+
const max1 = format === 'hsl' ? 360 : 100;
|
2163
|
+
const max2 = format === 'hsl' ? 100 : 360;
|
2164
|
+
const max3 = 100;
|
2165
|
+
|
2166
|
+
let ctrl1Label = format === 'hsl'
|
2167
|
+
? `${hueLabel} & ${lightnessLabel}`
|
2168
|
+
: `${lightnessLabel} & ${saturationLabel}`;
|
2169
|
+
|
2170
|
+
ctrl1Label = format === 'hwb'
|
2171
|
+
? `${whitenessLabel} & ${blacknessLabel}`
|
2172
|
+
: ctrl1Label;
|
2152
2173
|
|
2153
|
-
|
2154
|
-
|
2155
|
-
|
2174
|
+
const ctrl2Label = format === 'hsl'
|
2175
|
+
? `${saturationLabel}`
|
2176
|
+
: `${hueLabel}`;
|
2156
2177
|
|
2157
|
-
|
2158
|
-
|
2159
|
-
|
2160
|
-
|
2161
|
-
CSS_NUMBER,
|
2162
|
-
CSS_UNIT,
|
2163
|
-
CSS_UNIT2,
|
2164
|
-
PERMISSIVE_MATCH,
|
2165
|
-
matchers,
|
2166
|
-
isOnePointZero,
|
2167
|
-
isPercentage,
|
2168
|
-
isValidCSSUnit,
|
2169
|
-
pad2,
|
2170
|
-
clamp01,
|
2171
|
-
bound01,
|
2172
|
-
boundAlpha,
|
2173
|
-
getRGBFromName,
|
2174
|
-
convertHexToDecimal,
|
2175
|
-
convertDecimalToHex,
|
2176
|
-
rgbToHsl,
|
2177
|
-
rgbToHex,
|
2178
|
-
rgbToHsv,
|
2179
|
-
rgbToHwb,
|
2180
|
-
rgbaToHex,
|
2181
|
-
hslToRgb,
|
2182
|
-
hsvToRgb,
|
2183
|
-
hueToRgb,
|
2184
|
-
hwbToRgb,
|
2185
|
-
parseIntFromHex,
|
2186
|
-
numberInputToObject,
|
2187
|
-
stringInputToObject,
|
2188
|
-
inputToRGB,
|
2189
|
-
ObjectAssign,
|
2190
|
-
});
|
2178
|
+
const colorControls = createElement({
|
2179
|
+
tagName: 'div',
|
2180
|
+
className: `color-controls ${format}`,
|
2181
|
+
});
|
2191
2182
|
|
2192
|
-
|
2193
|
-
|
2194
|
-
* Returns a color palette with a given set of parameters.
|
2195
|
-
* @example
|
2196
|
-
* new ColorPalette(0, 12, 10);
|
2197
|
-
* // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
|
2198
|
-
*/
|
2199
|
-
class ColorPalette {
|
2200
|
-
/**
|
2201
|
-
* The `hue` parameter is optional, which would be set to 0.
|
2202
|
-
* @param {number[]} args represeinting hue, hueSteps, lightSteps
|
2203
|
-
* * `args.hue` the starting Hue [0, 360]
|
2204
|
-
* * `args.hueSteps` Hue Steps Count [5, 13]
|
2205
|
-
* * `args.lightSteps` Lightness Steps Count [8, 10]
|
2206
|
-
*/
|
2207
|
-
constructor(...args) {
|
2208
|
-
let hue = 0;
|
2209
|
-
let hueSteps = 12;
|
2210
|
-
let lightSteps = 10;
|
2211
|
-
let lightnessArray = [0.5];
|
2183
|
+
const colorPointer = 'color-pointer';
|
2184
|
+
const colorSlider = 'color-slider';
|
2212
2185
|
|
2213
|
-
|
2214
|
-
|
2215
|
-
|
2216
|
-
|
2217
|
-
|
2218
|
-
|
2219
|
-
|
2186
|
+
const controls = [
|
2187
|
+
{
|
2188
|
+
i: 1,
|
2189
|
+
c: colorPointer,
|
2190
|
+
l: ctrl1Label,
|
2191
|
+
min: 0,
|
2192
|
+
max: max1,
|
2193
|
+
},
|
2194
|
+
{
|
2195
|
+
i: 2,
|
2196
|
+
c: colorSlider,
|
2197
|
+
l: ctrl2Label,
|
2198
|
+
min: 0,
|
2199
|
+
max: max2,
|
2200
|
+
},
|
2201
|
+
{
|
2202
|
+
i: 3,
|
2203
|
+
c: colorSlider,
|
2204
|
+
l: alphaLabel,
|
2205
|
+
min: 0,
|
2206
|
+
max: max3,
|
2207
|
+
},
|
2208
|
+
];
|
2220
2209
|
|
2221
|
-
|
2222
|
-
const
|
2210
|
+
controls.forEach((template) => {
|
2211
|
+
const {
|
2212
|
+
i, c, l, min, max,
|
2213
|
+
} = template;
|
2214
|
+
const control = createElement({
|
2215
|
+
tagName: 'div',
|
2216
|
+
className: 'color-control',
|
2217
|
+
});
|
2218
|
+
setAttribute(control, 'role', 'presentation');
|
2223
2219
|
|
2224
|
-
|
2225
|
-
|
2226
|
-
|
2220
|
+
control.append(
|
2221
|
+
createElement({
|
2222
|
+
tagName: 'div',
|
2223
|
+
className: `visual-control visual-control${i}`,
|
2224
|
+
}),
|
2225
|
+
);
|
2227
2226
|
|
2228
|
-
|
2229
|
-
|
2230
|
-
|
2231
|
-
|
2227
|
+
const knob = createElement({
|
2228
|
+
tagName: 'div',
|
2229
|
+
className: `${c} knob`,
|
2230
|
+
ariaLive: 'polite',
|
2231
|
+
});
|
2232
2232
|
|
2233
|
-
|
2234
|
-
|
2235
|
-
|
2236
|
-
}
|
2233
|
+
setAttribute(knob, ariaLabel, l);
|
2234
|
+
setAttribute(knob, 'role', 'slider');
|
2235
|
+
setAttribute(knob, tabIndex, '0');
|
2236
|
+
setAttribute(knob, ariaValueMin, `${min}`);
|
2237
|
+
setAttribute(knob, ariaValueMax, `${max}`);
|
2238
|
+
control.append(knob);
|
2239
|
+
colorControls.append(control);
|
2240
|
+
});
|
2237
2241
|
|
2238
|
-
|
2239
|
-
|
2240
|
-
const currentHue = ((hue + i * hueStep) % 360) / 360;
|
2241
|
-
lightnessArray.forEach((l) => {
|
2242
|
-
colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
|
2243
|
-
});
|
2244
|
-
}
|
2242
|
+
return colorControls;
|
2243
|
+
}
|
2245
2244
|
|
2246
|
-
|
2247
|
-
|
2248
|
-
|
2249
|
-
|
2250
|
-
|
2245
|
+
/**
|
2246
|
+
* Helps setting CSS variables to the color-menu.
|
2247
|
+
* @param {HTMLElement} element
|
2248
|
+
* @param {Record<string,any>} props
|
2249
|
+
*/
|
2250
|
+
function setCSSProperties(element, props) {
|
2251
|
+
ObjectKeys(props).forEach((prop) => {
|
2252
|
+
element.style.setProperty(prop, props[prop]);
|
2253
|
+
});
|
2251
2254
|
}
|
2252
2255
|
|
2253
2256
|
/**
|
@@ -2267,68 +2270,64 @@
|
|
2267
2270
|
colorsArray = colorsArray instanceof Array ? colorsArray : [];
|
2268
2271
|
const colorsCount = colorsArray.length;
|
2269
2272
|
const { lightSteps } = isPalette ? colorsSource : { lightSteps: null };
|
2270
|
-
|
2271
|
-
|| Math.max(...[5, 6, 7, 8, 9, 10].filter((x) => colorsCount > (x * 2) && !(colorsCount % x)));
|
2272
|
-
fit = Number.isFinite(fit) ? fit : 5;
|
2273
|
+
const fit = lightSteps || [9, 10].find((x) => colorsCount > x * 2 && !(colorsCount % x)) || 5;
|
2273
2274
|
const isMultiLine = isOptionsMenu && colorsCount > fit;
|
2274
|
-
let rowCountHover =
|
2275
|
-
rowCountHover = isMultiLine && colorsCount
|
2276
|
-
rowCountHover = colorsCount >=
|
2277
|
-
rowCountHover = colorsCount >=
|
2278
|
-
|
2279
|
-
const
|
2280
|
-
const isScrollable = isMultiLine && colorsCount > rowCountHover * fit;
|
2275
|
+
let rowCountHover = 2;
|
2276
|
+
rowCountHover = isMultiLine && colorsCount >= fit * 2 ? 3 : rowCountHover;
|
2277
|
+
rowCountHover = colorsCount >= fit * 3 ? 4 : rowCountHover;
|
2278
|
+
rowCountHover = colorsCount >= fit * 4 ? 5 : rowCountHover;
|
2279
|
+
const rowCount = rowCountHover - (colorsCount < fit * 3 ? 1 : 2);
|
2280
|
+
const isScrollable = isMultiLine && colorsCount > rowCount * fit;
|
2281
2281
|
let finalClass = menuClass;
|
2282
2282
|
finalClass += isScrollable ? ' scrollable' : '';
|
2283
2283
|
finalClass += isMultiLine ? ' multiline' : '';
|
2284
2284
|
const gap = isMultiLine ? '1px' : '0.25rem';
|
2285
2285
|
let optionSize = isMultiLine ? 1.75 : 2;
|
2286
|
-
optionSize =
|
2286
|
+
optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
|
2287
2287
|
const menuHeight = `${(rowCount || 1) * optionSize}rem`;
|
2288
2288
|
const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
|
2289
|
-
|
2290
|
-
|
2291
|
-
|
2289
|
+
/** @type {HTMLUListElement} */
|
2290
|
+
// @ts-ignore -- <UL> is an `HTMLElement`
|
2292
2291
|
const menu = createElement({
|
2293
2292
|
tagName: 'ul',
|
2294
2293
|
className: finalClass,
|
2295
2294
|
});
|
2296
2295
|
setAttribute(menu, 'role', 'listbox');
|
2297
|
-
setAttribute(menu, ariaLabel,
|
2298
|
-
|
2299
|
-
if (
|
2300
|
-
|
2301
|
-
|
2302
|
-
|
2303
|
-
|
2304
|
-
|
2305
|
-
|
2306
|
-
|
2307
|
-
};
|
2308
|
-
setElementStyle(menu, menuStyle);
|
2296
|
+
setAttribute(menu, ariaLabel, menuLabel);
|
2297
|
+
|
2298
|
+
if (isScrollable) {
|
2299
|
+
setCSSProperties(menu, {
|
2300
|
+
'--grid-item-size': `${optionSize}rem`,
|
2301
|
+
'--grid-fit': fit,
|
2302
|
+
'--grid-gap': gap,
|
2303
|
+
'--grid-height': menuHeight,
|
2304
|
+
'--grid-hover-height': menuHeightHover,
|
2305
|
+
});
|
2309
2306
|
}
|
2310
2307
|
|
2311
2308
|
colorsArray.forEach((x) => {
|
2312
|
-
|
2313
|
-
|
2314
|
-
|
2309
|
+
let [value, label] = typeof x === 'string' ? x.trim().split(':') : [];
|
2310
|
+
if (x instanceof Color) {
|
2311
|
+
value = x.toHexString();
|
2312
|
+
label = value;
|
2313
|
+
}
|
2314
|
+
const color = new Color(x instanceof Color ? x : value, format);
|
2315
|
+
const isActive = color.toString() === getAttribute(input, 'value');
|
2315
2316
|
const active = isActive ? ' active' : '';
|
2316
2317
|
|
2317
2318
|
const option = createElement({
|
2318
2319
|
tagName: 'li',
|
2319
2320
|
className: `color-option${active}`,
|
2320
|
-
innerText: `${label ||
|
2321
|
+
innerText: `${label || value}`,
|
2321
2322
|
});
|
2322
2323
|
|
2323
|
-
setAttribute(option,
|
2324
|
+
setAttribute(option, tabIndex, '0');
|
2324
2325
|
setAttribute(option, 'data-value', `${value}`);
|
2325
2326
|
setAttribute(option, 'role', 'option');
|
2326
2327
|
setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
|
2327
2328
|
|
2328
2329
|
if (isOptionsMenu) {
|
2329
|
-
setElementStyle(option, {
|
2330
|
-
width: `${optionSize}rem`, height: `${optionSize}rem`, backgroundColor: x,
|
2331
|
-
});
|
2330
|
+
setElementStyle(option, { backgroundColor: value });
|
2332
2331
|
}
|
2333
2332
|
|
2334
2333
|
menu.append(option);
|
@@ -2337,55 +2336,10 @@
|
|
2337
2336
|
}
|
2338
2337
|
|
2339
2338
|
/**
|
2340
|
-
|
2341
|
-
|
2342
|
-
|
2343
|
-
|
2344
|
-
function isValidJSON(str) {
|
2345
|
-
try {
|
2346
|
-
JSON.parse(str);
|
2347
|
-
} catch (e) {
|
2348
|
-
return false;
|
2349
|
-
}
|
2350
|
-
return true;
|
2351
|
-
}
|
2352
|
-
|
2353
|
-
var version = "0.0.1alpha2";
|
2354
|
-
|
2355
|
-
// @ts-ignore
|
2356
|
-
|
2357
|
-
const Version = version;
|
2358
|
-
|
2359
|
-
// ColorPicker GC
|
2360
|
-
// ==============
|
2361
|
-
const colorPickerString = 'color-picker';
|
2362
|
-
const colorPickerSelector = `[data-function="${colorPickerString}"]`;
|
2363
|
-
const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
|
2364
|
-
const colorPickerDefaults = {
|
2365
|
-
componentLabels: colorPickerLabels,
|
2366
|
-
colorLabels: colorNames,
|
2367
|
-
format: 'rgb',
|
2368
|
-
colorPresets: undefined,
|
2369
|
-
colorKeywords: nonColors,
|
2370
|
-
};
|
2371
|
-
|
2372
|
-
// ColorPicker Static Methods
|
2373
|
-
// ==========================
|
2374
|
-
|
2375
|
-
/** @type {CP.GetInstance<ColorPicker>} */
|
2376
|
-
const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
|
2377
|
-
|
2378
|
-
/** @type {CP.InitCallback<ColorPicker>} */
|
2379
|
-
const initColorPicker = (element) => new ColorPicker(element);
|
2380
|
-
|
2381
|
-
// ColorPicker Private Methods
|
2382
|
-
// ===========================
|
2383
|
-
|
2384
|
-
/**
|
2385
|
-
* Generate HTML markup and update instance properties.
|
2386
|
-
* @param {ColorPicker} self
|
2387
|
-
*/
|
2388
|
-
function initCallback(self) {
|
2339
|
+
* Generate HTML markup and update instance properties.
|
2340
|
+
* @param {CP.ColorPicker} self
|
2341
|
+
*/
|
2342
|
+
function setMarkup(self) {
|
2389
2343
|
const {
|
2390
2344
|
input, parent, format, id, componentLabels, colorKeywords, colorPresets,
|
2391
2345
|
} = self;
|
@@ -2400,9 +2354,7 @@
|
|
2400
2354
|
self.color = new Color(color, format);
|
2401
2355
|
|
2402
2356
|
// set initial controls dimensions
|
2403
|
-
|
2404
|
-
const dropClass = isMobile ? ' mobile' : '';
|
2405
|
-
const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
|
2357
|
+
const formatString = format === 'hex' ? hexLabel : toUpperCase(format);
|
2406
2358
|
|
2407
2359
|
const pickerBtn = createElement({
|
2408
2360
|
id: `picker-btn-${id}`,
|
@@ -2419,7 +2371,7 @@
|
|
2419
2371
|
|
2420
2372
|
const pickerDropdown = createElement({
|
2421
2373
|
tagName: 'div',
|
2422
|
-
className:
|
2374
|
+
className: 'color-dropdown picker',
|
2423
2375
|
});
|
2424
2376
|
setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
|
2425
2377
|
setAttribute(pickerDropdown, 'role', 'group');
|
@@ -2435,7 +2387,7 @@
|
|
2435
2387
|
if (colorKeywords || colorPresets) {
|
2436
2388
|
const presetsDropdown = createElement({
|
2437
2389
|
tagName: 'div',
|
2438
|
-
className:
|
2390
|
+
className: 'color-dropdown scrollable menu',
|
2439
2391
|
});
|
2440
2392
|
|
2441
2393
|
// color presets
|
@@ -2455,7 +2407,7 @@
|
|
2455
2407
|
tagName: 'button',
|
2456
2408
|
className: 'menu-toggle btn-appearance',
|
2457
2409
|
});
|
2458
|
-
setAttribute(presetsBtn,
|
2410
|
+
setAttribute(presetsBtn, tabIndex, '-1');
|
2459
2411
|
setAttribute(presetsBtn, ariaExpanded, 'false');
|
2460
2412
|
setAttribute(presetsBtn, ariaHasPopup, 'true');
|
2461
2413
|
|
@@ -2482,9 +2434,40 @@
|
|
2482
2434
|
if (colorKeywords && nonColors.includes(colorValue)) {
|
2483
2435
|
self.value = colorValue;
|
2484
2436
|
}
|
2485
|
-
setAttribute(input,
|
2437
|
+
setAttribute(input, tabIndex, '-1');
|
2486
2438
|
}
|
2487
2439
|
|
2440
|
+
var version = "0.0.2alpha1";
|
2441
|
+
|
2442
|
+
// @ts-ignore
|
2443
|
+
|
2444
|
+
const Version = version;
|
2445
|
+
|
2446
|
+
// ColorPicker GC
|
2447
|
+
// ==============
|
2448
|
+
const colorPickerString = 'color-picker';
|
2449
|
+
const colorPickerSelector = `[data-function="${colorPickerString}"]`;
|
2450
|
+
const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
|
2451
|
+
const colorPickerDefaults = {
|
2452
|
+
componentLabels: colorPickerLabels,
|
2453
|
+
colorLabels: colorNames,
|
2454
|
+
format: 'rgb',
|
2455
|
+
colorPresets: false,
|
2456
|
+
colorKeywords: false,
|
2457
|
+
};
|
2458
|
+
|
2459
|
+
// ColorPicker Static Methods
|
2460
|
+
// ==========================
|
2461
|
+
|
2462
|
+
/** @type {CP.GetInstance<ColorPicker>} */
|
2463
|
+
const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
|
2464
|
+
|
2465
|
+
/** @type {CP.InitCallback<ColorPicker>} */
|
2466
|
+
const initColorPicker = (element) => new ColorPicker(element);
|
2467
|
+
|
2468
|
+
// ColorPicker Private Methods
|
2469
|
+
// ===========================
|
2470
|
+
|
2488
2471
|
/**
|
2489
2472
|
* Add / remove `ColorPicker` main event listeners.
|
2490
2473
|
* @param {ColorPicker} self
|
@@ -2583,8 +2566,19 @@
|
|
2583
2566
|
addClass(dropdown, 'bottom');
|
2584
2567
|
reflow(dropdown);
|
2585
2568
|
addClass(dropdown, 'show');
|
2569
|
+
|
2586
2570
|
if (isPicker) self.update();
|
2587
|
-
|
2571
|
+
|
2572
|
+
if (!self.isOpen) {
|
2573
|
+
toggleEventsOnShown(self, true);
|
2574
|
+
self.updateDropdownPosition();
|
2575
|
+
self.isOpen = true;
|
2576
|
+
setAttribute(self.input, tabIndex, '0');
|
2577
|
+
if (menuToggle) {
|
2578
|
+
setAttribute(menuToggle, tabIndex, '0');
|
2579
|
+
}
|
2580
|
+
}
|
2581
|
+
|
2588
2582
|
setAttribute(nextBtn, ariaExpanded, 'true');
|
2589
2583
|
if (activeBtn) {
|
2590
2584
|
setAttribute(activeBtn, ariaExpanded, 'false');
|
@@ -2707,7 +2701,7 @@
|
|
2707
2701
|
self.handleKnobs = self.handleKnobs.bind(self);
|
2708
2702
|
|
2709
2703
|
// generate markup
|
2710
|
-
|
2704
|
+
setMarkup(self);
|
2711
2705
|
|
2712
2706
|
const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
|
2713
2707
|
// set main elements
|
@@ -2754,7 +2748,7 @@
|
|
2754
2748
|
set value(v) { this.input.value = v; }
|
2755
2749
|
|
2756
2750
|
/** Check if the colour presets include any non-colour. */
|
2757
|
-
get
|
2751
|
+
get hasNonColor() {
|
2758
2752
|
return this.colorKeywords instanceof Array
|
2759
2753
|
&& this.colorKeywords.some((x) => nonColors.includes(x));
|
2760
2754
|
}
|
@@ -2810,7 +2804,7 @@
|
|
2810
2804
|
const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
|
2811
2805
|
const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
|
2812
2806
|
const alpha = 1 - controlPositions.c3y / offsetHeight;
|
2813
|
-
const roundA =
|
2807
|
+
const roundA = roundPart((alpha * 100)) / 100;
|
2814
2808
|
|
2815
2809
|
if (format !== 'hsl') {
|
2816
2810
|
const fill = new Color({
|
@@ -2828,7 +2822,7 @@
|
|
2828
2822
|
});
|
2829
2823
|
setElementStyle(v2, { background: hueGradient });
|
2830
2824
|
} else {
|
2831
|
-
const saturation =
|
2825
|
+
const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
|
2832
2826
|
const fill0 = new Color({
|
2833
2827
|
r: 255, g: 0, b: 0, a: alpha,
|
2834
2828
|
}).saturate(-saturation).toRgbString();
|
@@ -2903,7 +2897,7 @@
|
|
2903
2897
|
const self = this;
|
2904
2898
|
const { activeElement } = getDocument(self.input);
|
2905
2899
|
|
2906
|
-
if ((
|
2900
|
+
if ((e.type === touchmoveEvent && self.dragElement)
|
2907
2901
|
|| (activeElement && self.controlKnobs.includes(activeElement))) {
|
2908
2902
|
e.stopPropagation();
|
2909
2903
|
e.preventDefault();
|
@@ -2983,12 +2977,12 @@
|
|
2983
2977
|
|
2984
2978
|
self.update();
|
2985
2979
|
|
2986
|
-
if (currentActive) {
|
2987
|
-
removeClass(currentActive, 'active');
|
2988
|
-
removeAttribute(currentActive, ariaSelected);
|
2989
|
-
}
|
2990
|
-
|
2991
2980
|
if (currentActive !== target) {
|
2981
|
+
if (currentActive) {
|
2982
|
+
removeClass(currentActive, 'active');
|
2983
|
+
removeAttribute(currentActive, ariaSelected);
|
2984
|
+
}
|
2985
|
+
|
2992
2986
|
addClass(target, 'active');
|
2993
2987
|
setAttribute(target, ariaSelected, 'true');
|
2994
2988
|
|
@@ -3110,30 +3104,41 @@
|
|
3110
3104
|
if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
|
3111
3105
|
e.preventDefault();
|
3112
3106
|
|
3113
|
-
const { controlKnobs } = self;
|
3107
|
+
const { format, controlKnobs, visuals } = self;
|
3108
|
+
const { offsetWidth, offsetHeight } = visuals[0];
|
3114
3109
|
const [c1, c2, c3] = controlKnobs;
|
3115
3110
|
const { activeElement } = getDocument(c1);
|
3116
3111
|
const currentKnob = controlKnobs.find((x) => x === activeElement);
|
3112
|
+
const yRatio = offsetHeight / (format === 'hsl' ? 100 : 360);
|
3117
3113
|
|
3118
3114
|
if (currentKnob) {
|
3119
3115
|
let offsetX = 0;
|
3120
3116
|
let offsetY = 0;
|
3117
|
+
|
3121
3118
|
if (target === c1) {
|
3119
|
+
const xRatio = offsetWidth / (format === 'hsl' ? 360 : 100);
|
3120
|
+
|
3122
3121
|
if ([keyArrowLeft, keyArrowRight].includes(code)) {
|
3123
|
-
self.controlPositions.c1x += code === keyArrowRight ?
|
3122
|
+
self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
|
3124
3123
|
} else if ([keyArrowUp, keyArrowDown].includes(code)) {
|
3125
|
-
self.controlPositions.c1y += code === keyArrowDown ?
|
3124
|
+
self.controlPositions.c1y += code === keyArrowDown ? yRatio : -yRatio;
|
3126
3125
|
}
|
3127
3126
|
|
3128
3127
|
offsetX = self.controlPositions.c1x;
|
3129
3128
|
offsetY = self.controlPositions.c1y;
|
3130
3129
|
self.changeControl1(offsetX, offsetY);
|
3131
3130
|
} else if (target === c2) {
|
3132
|
-
self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code)
|
3131
|
+
self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code)
|
3132
|
+
? yRatio
|
3133
|
+
: -yRatio;
|
3134
|
+
|
3133
3135
|
offsetY = self.controlPositions.c2y;
|
3134
3136
|
self.changeControl2(offsetY);
|
3135
3137
|
} else if (target === c3) {
|
3136
|
-
self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code)
|
3138
|
+
self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code)
|
3139
|
+
? yRatio
|
3140
|
+
: -yRatio;
|
3141
|
+
|
3137
3142
|
offsetY = self.controlPositions.c3y;
|
3138
3143
|
self.changeAlpha(offsetY);
|
3139
3144
|
}
|
@@ -3155,7 +3160,7 @@
|
|
3155
3160
|
const [v1, v2, v3, v4] = format === 'rgb'
|
3156
3161
|
? inputs.map((i) => parseFloat(i.value) / (i === i4 ? 100 : 1))
|
3157
3162
|
: inputs.map((i) => parseFloat(i.value) / (i !== i1 ? 100 : 360));
|
3158
|
-
const isNonColorValue = self.
|
3163
|
+
const isNonColorValue = self.hasNonColor && nonColors.includes(currentValue);
|
3159
3164
|
const alpha = i4 ? v4 : (1 - controlPositions.c3y / offsetHeight);
|
3160
3165
|
|
3161
3166
|
if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
|
@@ -3414,11 +3419,11 @@
|
|
3414
3419
|
} = componentLabels;
|
3415
3420
|
const { r, g, b } = color.toRgb();
|
3416
3421
|
const [knob1, knob2, knob3] = controlKnobs;
|
3417
|
-
const hue =
|
3422
|
+
const hue = roundPart(hsl.h * 360);
|
3418
3423
|
const alpha = color.a;
|
3419
3424
|
const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
|
3420
|
-
const saturation =
|
3421
|
-
const lightness =
|
3425
|
+
const saturation = roundPart(saturationSource * 100);
|
3426
|
+
const lightness = roundPart(hsl.l * 100);
|
3422
3427
|
const hsvl = hsv.v * 100;
|
3423
3428
|
let colorName;
|
3424
3429
|
|
@@ -3465,8 +3470,8 @@
|
|
3465
3470
|
setAttribute(knob2, ariaValueNow, `${saturation}`);
|
3466
3471
|
} else if (format === 'hwb') {
|
3467
3472
|
const { hwb } = self;
|
3468
|
-
const whiteness =
|
3469
|
-
const blackness =
|
3473
|
+
const whiteness = roundPart(hwb.w * 100);
|
3474
|
+
const blackness = roundPart(hwb.b * 100);
|
3470
3475
|
colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
|
3471
3476
|
setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
|
3472
3477
|
setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
|
@@ -3482,7 +3487,7 @@
|
|
3482
3487
|
setAttribute(knob2, ariaValueNow, `${hue}`);
|
3483
3488
|
}
|
3484
3489
|
|
3485
|
-
const alphaValue =
|
3490
|
+
const alphaValue = roundPart(alpha * 100);
|
3486
3491
|
setAttribute(knob3, ariaValueText, `${alphaValue}%`);
|
3487
3492
|
setAttribute(knob3, ariaValueNow, `${alphaValue}`);
|
3488
3493
|
|
@@ -3505,10 +3510,16 @@
|
|
3505
3510
|
/** Updates the control knobs actual positions. */
|
3506
3511
|
updateControls() {
|
3507
3512
|
const { controlKnobs, controlPositions } = this;
|
3513
|
+
let {
|
3514
|
+
c1x, c1y, c2y, c3y,
|
3515
|
+
} = controlPositions;
|
3508
3516
|
const [control1, control2, control3] = controlKnobs;
|
3509
|
-
|
3510
|
-
|
3511
|
-
|
3517
|
+
// round control positions
|
3518
|
+
[c1x, c1y, c2y, c3y] = [c1x, c1y, c2y, c3y].map(roundPart);
|
3519
|
+
|
3520
|
+
setElementStyle(control1, { transform: `translate3d(${c1x - 4}px,${c1y - 4}px,0)` });
|
3521
|
+
setElementStyle(control2, { transform: `translate3d(0,${c2y - 4}px,0)` });
|
3522
|
+
setElementStyle(control3, { transform: `translate3d(0,${c3y - 4}px,0)` });
|
3512
3523
|
}
|
3513
3524
|
|
3514
3525
|
/**
|
@@ -3521,16 +3532,16 @@
|
|
3521
3532
|
value: oldColor, format, inputs, color, hsl,
|
3522
3533
|
} = self;
|
3523
3534
|
const [i1, i2, i3, i4] = inputs;
|
3524
|
-
const alpha =
|
3525
|
-
const hue =
|
3535
|
+
const alpha = roundPart(color.a * 100);
|
3536
|
+
const hue = roundPart(hsl.h * 360);
|
3526
3537
|
let newColor;
|
3527
3538
|
|
3528
3539
|
if (format === 'hex') {
|
3529
3540
|
newColor = self.color.toHexString(true);
|
3530
3541
|
i1.value = self.hex;
|
3531
3542
|
} else if (format === 'hsl') {
|
3532
|
-
const lightness =
|
3533
|
-
const saturation =
|
3543
|
+
const lightness = roundPart(hsl.l * 100);
|
3544
|
+
const saturation = roundPart(hsl.s * 100);
|
3534
3545
|
newColor = self.color.toHslString();
|
3535
3546
|
i1.value = `${hue}`;
|
3536
3547
|
i2.value = `${saturation}`;
|
@@ -3538,8 +3549,8 @@
|
|
3538
3549
|
i4.value = `${alpha}`;
|
3539
3550
|
} else if (format === 'hwb') {
|
3540
3551
|
const { w, b } = self.hwb;
|
3541
|
-
const whiteness =
|
3542
|
-
const blackness =
|
3552
|
+
const whiteness = roundPart(w * 100);
|
3553
|
+
const blackness = roundPart(b * 100);
|
3543
3554
|
|
3544
3555
|
newColor = self.color.toHwbString();
|
3545
3556
|
i1.value = `${hue}`;
|
@@ -3547,7 +3558,8 @@
|
|
3547
3558
|
i3.value = `${blackness}`;
|
3548
3559
|
i4.value = `${alpha}`;
|
3549
3560
|
} else if (format === 'rgb') {
|
3550
|
-
|
3561
|
+
let { r, g, b } = self.rgb;
|
3562
|
+
[r, g, b] = [r, g, b].map(roundPart);
|
3551
3563
|
|
3552
3564
|
newColor = self.color.toRgbString();
|
3553
3565
|
i1.value = `${r}`;
|
@@ -3611,7 +3623,7 @@
|
|
3611
3623
|
const self = this;
|
3612
3624
|
const { colorPicker } = self;
|
3613
3625
|
|
3614
|
-
if (!hasClass(colorPicker,
|
3626
|
+
if (!['top', 'bottom'].some((c) => hasClass(colorPicker, c))) {
|
3615
3627
|
showDropdown(self, colorPicker);
|
3616
3628
|
}
|
3617
3629
|
}
|
@@ -3628,21 +3640,6 @@
|
|
3628
3640
|
}
|
3629
3641
|
}
|
3630
3642
|
|
3631
|
-
/** Shows the `ColorPicker` dropdown or the presets menu. */
|
3632
|
-
show() {
|
3633
|
-
const self = this;
|
3634
|
-
const { menuToggle } = self;
|
3635
|
-
if (!self.isOpen) {
|
3636
|
-
toggleEventsOnShown(self, true);
|
3637
|
-
self.updateDropdownPosition();
|
3638
|
-
self.isOpen = true;
|
3639
|
-
setAttribute(self.input, 'tabindex', '0');
|
3640
|
-
if (menuToggle) {
|
3641
|
-
setAttribute(menuToggle, 'tabindex', '0');
|
3642
|
-
}
|
3643
|
-
}
|
3644
|
-
}
|
3645
|
-
|
3646
3643
|
/**
|
3647
3644
|
* Hides the currently open `ColorPicker` dropdown.
|
3648
3645
|
* @param {boolean=} focusPrevented
|
@@ -3677,9 +3674,9 @@
|
|
3677
3674
|
if (!focusPrevented) {
|
3678
3675
|
focus(pickerToggle);
|
3679
3676
|
}
|
3680
|
-
setAttribute(input,
|
3677
|
+
setAttribute(input, tabIndex, '-1');
|
3681
3678
|
if (menuToggle) {
|
3682
|
-
setAttribute(menuToggle,
|
3679
|
+
setAttribute(menuToggle, tabIndex, '-1');
|
3683
3680
|
}
|
3684
3681
|
}
|
3685
3682
|
}
|
@@ -3693,7 +3690,10 @@
|
|
3693
3690
|
[...parent.children].forEach((el) => {
|
3694
3691
|
if (el !== input) el.remove();
|
3695
3692
|
});
|
3693
|
+
|
3694
|
+
removeAttribute(input, tabIndex);
|
3696
3695
|
setElementStyle(input, { backgroundColor: '' });
|
3696
|
+
|
3697
3697
|
['txt-light', 'txt-dark'].forEach((c) => removeClass(parent, c));
|
3698
3698
|
Data.remove(input, colorPickerString);
|
3699
3699
|
}
|
@@ -3701,12 +3701,18 @@
|
|
3701
3701
|
|
3702
3702
|
ObjectAssign(ColorPicker, {
|
3703
3703
|
Color,
|
3704
|
+
ColorPalette,
|
3704
3705
|
Version,
|
3705
3706
|
getInstance: getColorPickerInstance,
|
3706
3707
|
init: initColorPicker,
|
3707
3708
|
selector: colorPickerSelector,
|
3709
|
+
// utils important for render
|
3710
|
+
roundPart,
|
3711
|
+
setElementStyle,
|
3712
|
+
setAttribute,
|
3713
|
+
getBoundingClientRect,
|
3708
3714
|
});
|
3709
3715
|
|
3710
3716
|
return ColorPicker;
|
3711
3717
|
|
3712
|
-
}))
|
3718
|
+
}));
|