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