@thednp/color-picker 0.0.1 → 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 +1 -0
- 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 +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 +287 -319
- package/dist/js/color-picker-element-esm.min.js +2 -2
- package/dist/js/color-picker-element.js +289 -321
- package/dist/js/color-picker-element.min.js +2 -2
- package/dist/js/color-picker-esm.js +520 -552
- package/dist/js/color-picker-esm.min.js +2 -2
- package/dist/js/color-picker.js +522 -554
- 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 +10 -3
- package/src/js/color-picker-element.js +1 -1
- package/src/js/color-picker.js +7 -120
- package/src/js/color.js +88 -91
- 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 +47 -17
- package/src/js/util/templates.js +0 -10
@@ -1,5 +1,5 @@
|
|
1
1
|
/*!
|
2
|
-
* ColorPicker v0.0.
|
2
|
+
* ColorPicker v0.0.2alpha1 (http://thednp.github.io/color-picker)
|
3
3
|
* Copyright 2022 © thednp
|
4
4
|
* Licensed under MIT (https://github.com/thednp/color-picker/blob/master/LICENSE)
|
5
5
|
*/
|
@@ -129,24 +129,6 @@ const ariaValueText = 'aria-valuetext';
|
|
129
129
|
*/
|
130
130
|
const ariaValueNow = 'aria-valuenow';
|
131
131
|
|
132
|
-
/**
|
133
|
-
* A global namespace for aria-haspopup.
|
134
|
-
* @type {string}
|
135
|
-
*/
|
136
|
-
const ariaHasPopup = 'aria-haspopup';
|
137
|
-
|
138
|
-
/**
|
139
|
-
* A global namespace for aria-hidden.
|
140
|
-
* @type {string}
|
141
|
-
*/
|
142
|
-
const ariaHidden = 'aria-hidden';
|
143
|
-
|
144
|
-
/**
|
145
|
-
* A global namespace for aria-labelledby.
|
146
|
-
* @type {string}
|
147
|
-
*/
|
148
|
-
const ariaLabelledBy = 'aria-labelledby';
|
149
|
-
|
150
132
|
/**
|
151
133
|
* A global namespace for `ArrowDown` key.
|
152
134
|
* @type {string} e.which = 40 equivalent
|
@@ -273,37 +255,6 @@ const resizeEvent = 'resize';
|
|
273
255
|
*/
|
274
256
|
const focusoutEvent = 'focusout';
|
275
257
|
|
276
|
-
// @ts-ignore
|
277
|
-
const { userAgentData: uaDATA } = navigator;
|
278
|
-
|
279
|
-
/**
|
280
|
-
* A global namespace for `userAgentData` object.
|
281
|
-
*/
|
282
|
-
const userAgentData = uaDATA;
|
283
|
-
|
284
|
-
const { userAgent: userAgentString } = navigator;
|
285
|
-
|
286
|
-
/**
|
287
|
-
* A global namespace for `navigator.userAgent` string.
|
288
|
-
*/
|
289
|
-
const userAgent = userAgentString;
|
290
|
-
|
291
|
-
const mobileBrands = /iPhone|iPad|iPod|Android/i;
|
292
|
-
let isMobileCheck = false;
|
293
|
-
|
294
|
-
if (userAgentData) {
|
295
|
-
isMobileCheck = userAgentData.brands
|
296
|
-
.some((/** @type {Record<String, any>} */x) => mobileBrands.test(x.brand));
|
297
|
-
} else {
|
298
|
-
isMobileCheck = mobileBrands.test(userAgent);
|
299
|
-
}
|
300
|
-
|
301
|
-
/**
|
302
|
-
* A global `boolean` for mobile detection.
|
303
|
-
* @type {boolean}
|
304
|
-
*/
|
305
|
-
const isMobile = isMobileCheck;
|
306
|
-
|
307
258
|
/**
|
308
259
|
* Returns the `document` or the `#document` element.
|
309
260
|
* @see https://github.com/floating-ui/floating-ui
|
@@ -524,60 +475,6 @@ function getElementsByClassName(selector, parent) {
|
|
524
475
|
return lookUp.getElementsByClassName(selector);
|
525
476
|
}
|
526
477
|
|
527
|
-
/**
|
528
|
-
* Shortcut for `Object.assign()` static method.
|
529
|
-
* @param {Record<string, any>} obj a target object
|
530
|
-
* @param {Record<string, any>} source a source object
|
531
|
-
*/
|
532
|
-
const ObjectAssign = (obj, source) => Object.assign(obj, source);
|
533
|
-
|
534
|
-
/**
|
535
|
-
* This is a shortie for `document.createElement` method
|
536
|
-
* which allows you to create a new `HTMLElement` for a given `tagName`
|
537
|
-
* or based on an object with specific non-readonly attributes:
|
538
|
-
* `id`, `className`, `textContent`, `style`, etc.
|
539
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
|
540
|
-
*
|
541
|
-
* @param {Record<string, string> | string} param `tagName` or object
|
542
|
-
* @return {HTMLElement | Element} a new `HTMLElement` or `Element`
|
543
|
-
*/
|
544
|
-
function createElement(param) {
|
545
|
-
if (typeof param === 'string') {
|
546
|
-
return getDocument().createElement(param);
|
547
|
-
}
|
548
|
-
|
549
|
-
const { tagName } = param;
|
550
|
-
const attr = { ...param };
|
551
|
-
const newElement = createElement(tagName);
|
552
|
-
delete attr.tagName;
|
553
|
-
ObjectAssign(newElement, attr);
|
554
|
-
return newElement;
|
555
|
-
}
|
556
|
-
|
557
|
-
/**
|
558
|
-
* This is a shortie for `document.createElementNS` method
|
559
|
-
* which allows you to create a new `HTMLElement` for a given `tagName`
|
560
|
-
* or based on an object with specific non-readonly attributes:
|
561
|
-
* `id`, `className`, `textContent`, `style`, etc.
|
562
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
|
563
|
-
*
|
564
|
-
* @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
|
565
|
-
* @param {Record<string, string> | string} param `tagName` or object
|
566
|
-
* @return {HTMLElement | Element} a new `HTMLElement` or `Element`
|
567
|
-
*/
|
568
|
-
function createElementNS(namespace, param) {
|
569
|
-
if (typeof param === 'string') {
|
570
|
-
return getDocument().createElementNS(namespace, param);
|
571
|
-
}
|
572
|
-
|
573
|
-
const { tagName } = param;
|
574
|
-
const attr = { ...param };
|
575
|
-
const newElement = createElementNS(namespace, tagName);
|
576
|
-
delete attr.tagName;
|
577
|
-
ObjectAssign(newElement, attr);
|
578
|
-
return newElement;
|
579
|
-
}
|
580
|
-
|
581
478
|
/**
|
582
479
|
* Shortcut for the `Element.dispatchEvent(Event)` method.
|
583
480
|
*
|
@@ -586,6 +483,13 @@ function createElementNS(namespace, param) {
|
|
586
483
|
*/
|
587
484
|
const dispatchEvent = (element, event) => element.dispatchEvent(event);
|
588
485
|
|
486
|
+
/**
|
487
|
+
* Shortcut for `Object.assign()` static method.
|
488
|
+
* @param {Record<string, any>} obj a target object
|
489
|
+
* @param {Record<string, any>} source a source object
|
490
|
+
*/
|
491
|
+
const ObjectAssign = (obj, source) => Object.assign(obj, source);
|
492
|
+
|
589
493
|
/** @type {Map<string, Map<HTMLElement | Element, Record<string, any>>>} */
|
590
494
|
const componentData = new Map();
|
591
495
|
/**
|
@@ -661,20 +565,13 @@ const Data = {
|
|
661
565
|
*/
|
662
566
|
const getInstance = (target, component) => Data.get(target, component);
|
663
567
|
|
664
|
-
/**
|
665
|
-
* Shortcut for `Object.keys()` static method.
|
666
|
-
* @param {Record<string, any>} obj a target object
|
667
|
-
* @returns {string[]}
|
668
|
-
*/
|
669
|
-
const ObjectKeys = (obj) => Object.keys(obj);
|
670
|
-
|
671
568
|
/**
|
672
569
|
* Shortcut for multiple uses of `HTMLElement.style.propertyName` method.
|
673
570
|
* @param {HTMLElement | Element} element target element
|
674
571
|
* @param {Partial<CSSStyleDeclaration>} styles attribute value
|
675
572
|
*/
|
676
573
|
// @ts-ignore
|
677
|
-
const setElementStyle = (element, styles) => ObjectAssign(element.style, styles);
|
574
|
+
const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
|
678
575
|
|
679
576
|
/**
|
680
577
|
* Shortcut for `HTMLElement.getAttribute()` method.
|
@@ -717,6 +614,13 @@ function normalizeValue(value) {
|
|
717
614
|
return value;
|
718
615
|
}
|
719
616
|
|
617
|
+
/**
|
618
|
+
* Shortcut for `Object.keys()` static method.
|
619
|
+
* @param {Record<string, any>} obj a target object
|
620
|
+
* @returns {string[]}
|
621
|
+
*/
|
622
|
+
const ObjectKeys = (obj) => Object.keys(obj);
|
623
|
+
|
720
624
|
/**
|
721
625
|
* Shortcut for `String.toLowerCase()`.
|
722
626
|
*
|
@@ -837,32 +741,10 @@ const setAttribute = (element, attribute, value) => element.setAttribute(attribu
|
|
837
741
|
*/
|
838
742
|
const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
|
839
743
|
|
840
|
-
/** @type {Record<string, string>} */
|
841
|
-
const colorPickerLabels = {
|
842
|
-
pickerLabel: 'Colour Picker',
|
843
|
-
appearanceLabel: 'Colour Appearance',
|
844
|
-
valueLabel: 'Colour Value',
|
845
|
-
toggleLabel: 'Select Colour',
|
846
|
-
presetsLabel: 'Colour Presets',
|
847
|
-
defaultsLabel: 'Colour Defaults',
|
848
|
-
formatLabel: 'Format',
|
849
|
-
alphaLabel: 'Alpha',
|
850
|
-
hexLabel: 'Hexadecimal',
|
851
|
-
hueLabel: 'Hue',
|
852
|
-
whitenessLabel: 'Whiteness',
|
853
|
-
blacknessLabel: 'Blackness',
|
854
|
-
saturationLabel: 'Saturation',
|
855
|
-
lightnessLabel: 'Lightness',
|
856
|
-
redLabel: 'Red',
|
857
|
-
greenLabel: 'Green',
|
858
|
-
blueLabel: 'Blue',
|
859
|
-
};
|
860
|
-
|
861
744
|
/**
|
862
|
-
* A
|
863
|
-
* @type {string[]}
|
745
|
+
* A global namespace for `document.head`.
|
864
746
|
*/
|
865
|
-
const
|
747
|
+
const { head: documentHead } = document;
|
866
748
|
|
867
749
|
/**
|
868
750
|
* A list of explicit default non-color values.
|
@@ -870,297 +752,97 @@ const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold',
|
|
870
752
|
const nonColors = ['transparent', 'currentColor', 'inherit', 'revert', 'initial'];
|
871
753
|
|
872
754
|
/**
|
873
|
-
*
|
874
|
-
*
|
875
|
-
* @
|
876
|
-
* @returns {string} uppercase output string
|
755
|
+
* Round colour components, for all formats except HEX.
|
756
|
+
* @param {number} v one of the colour components
|
757
|
+
* @returns {number} the rounded number
|
877
758
|
*/
|
878
|
-
|
759
|
+
function roundPart(v) {
|
760
|
+
const floor = Math.floor(v);
|
761
|
+
return v - floor < 0.5 ? floor : Math.round(v);
|
762
|
+
}
|
879
763
|
|
880
|
-
|
764
|
+
// Color supported formats
|
765
|
+
const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsv', 'hwb'];
|
881
766
|
|
882
|
-
|
883
|
-
|
884
|
-
*
|
885
|
-
* @param {CP.ColorPicker} self the `ColorPicker` instance
|
886
|
-
* @returns {HTMLElement | Element} a new `<div>` element with color component `<input>`
|
887
|
-
*/
|
888
|
-
function getColorForm(self) {
|
889
|
-
const { format, id, componentLabels } = self;
|
890
|
-
const colorForm = createElement({
|
891
|
-
tagName: 'div',
|
892
|
-
className: `color-form ${format}`,
|
893
|
-
});
|
767
|
+
// Hue angles
|
768
|
+
const ANGLES = 'deg|rad|grad|turn';
|
894
769
|
|
895
|
-
|
896
|
-
|
897
|
-
else if (format === 'hsl') components = ['hue', 'saturation', 'lightness', 'alpha'];
|
898
|
-
else if (format === 'hwb') components = ['hue', 'whiteness', 'blackness', 'alpha'];
|
770
|
+
// <http://www.w3.org/TR/css3-values/#integers>
|
771
|
+
const CSS_INTEGER = '[-\\+]?\\d+%?';
|
899
772
|
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
const formatLabel = componentLabels[`${c}Label`];
|
904
|
-
const cInputLabel = createElement({ tagName: 'label' });
|
905
|
-
setAttribute(cInputLabel, 'for', cID);
|
906
|
-
cInputLabel.append(
|
907
|
-
createElement({ tagName: 'span', ariaHidden: 'true', innerText: `${C}:` }),
|
908
|
-
createElement({ tagName: 'span', className: vHidden, innerText: formatLabel }),
|
909
|
-
);
|
910
|
-
const cInput = createElement({
|
911
|
-
tagName: 'input',
|
912
|
-
id: cID,
|
913
|
-
// name: cID, - prevent saving the value to a form
|
914
|
-
type: format === 'hex' ? 'text' : 'number',
|
915
|
-
value: c === 'alpha' ? '100' : '0',
|
916
|
-
className: `color-input ${c}`,
|
917
|
-
});
|
918
|
-
setAttribute(cInput, 'autocomplete', 'off');
|
919
|
-
setAttribute(cInput, 'spellcheck', 'false');
|
773
|
+
// Include CSS3 Module
|
774
|
+
// <http://www.w3.org/TR/css3-values/#number-value>
|
775
|
+
const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
|
920
776
|
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
777
|
+
// Include CSS4 Module Hue degrees unit
|
778
|
+
// <https://www.w3.org/TR/css3-values/#angle-value>
|
779
|
+
const CSS_ANGLE = `[-\\+]?\\d*\\.?\\d+(?:${ANGLES})?`;
|
780
|
+
|
781
|
+
// Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
|
782
|
+
const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
|
783
|
+
|
784
|
+
// Add angles to the mix
|
785
|
+
const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
|
786
|
+
|
787
|
+
// Start & end
|
788
|
+
const START_MATCH = '(?:[\\s|\\(\\s|\\s\\(\\s]+)?';
|
789
|
+
const END_MATCH = '(?:[\\s|\\)\\s]+)?';
|
790
|
+
// Components separation
|
791
|
+
const SEP = '(?:[,|\\s]+)';
|
792
|
+
const SEP2 = '(?:[,|\\/\\s]*)?';
|
793
|
+
|
794
|
+
// Actual matching.
|
795
|
+
// Parentheses and commas are optional, but not required.
|
796
|
+
// Whitespace can take the place of commas or opening paren
|
797
|
+
const PERMISSIVE_MATCH = `${START_MATCH}(${CSS_UNIT2})${SEP}(${CSS_UNIT})${SEP}(${CSS_UNIT})${SEP2}(${CSS_UNIT})?${END_MATCH}`;
|
798
|
+
|
799
|
+
const matchers = {
|
800
|
+
CSS_UNIT: new RegExp(CSS_UNIT2),
|
801
|
+
hwb: new RegExp(`hwb${PERMISSIVE_MATCH}`),
|
802
|
+
rgb: new RegExp(`rgb(?:a)?${PERMISSIVE_MATCH}`),
|
803
|
+
hsl: new RegExp(`hsl(?:a)?${PERMISSIVE_MATCH}`),
|
804
|
+
hsv: new RegExp(`hsv(?:a)?${PERMISSIVE_MATCH}`),
|
805
|
+
hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
806
|
+
hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
|
807
|
+
hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
808
|
+
hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
|
809
|
+
};
|
940
810
|
|
941
811
|
/**
|
942
|
-
*
|
943
|
-
*
|
812
|
+
* Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
|
813
|
+
* <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
|
814
|
+
* @param {string} n testing number
|
815
|
+
* @returns {boolean} the query result
|
944
816
|
*/
|
945
|
-
|
817
|
+
function isOnePointZero(n) {
|
818
|
+
return `${n}`.includes('.') && parseFloat(n) === 1;
|
819
|
+
}
|
946
820
|
|
947
821
|
/**
|
948
|
-
*
|
949
|
-
* @
|
822
|
+
* Check to see if string passed in is a percentage
|
823
|
+
* @param {string} n testing number
|
824
|
+
* @returns {boolean} the query result
|
950
825
|
*/
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
* A global namespace for aria-valuemax.
|
955
|
-
* @type {string}
|
956
|
-
*/
|
957
|
-
const ariaValueMax = 'aria-valuemax';
|
958
|
-
|
959
|
-
const tabIndex = 'tabindex';
|
960
|
-
|
961
|
-
/**
|
962
|
-
* Returns all color controls for `ColorPicker`.
|
963
|
-
*
|
964
|
-
* @param {CP.ColorPicker} self the `ColorPicker` instance
|
965
|
-
* @returns {HTMLElement | Element} color controls
|
966
|
-
*/
|
967
|
-
function getColorControls(self) {
|
968
|
-
const { format, componentLabels } = self;
|
969
|
-
const {
|
970
|
-
hueLabel, alphaLabel, lightnessLabel, saturationLabel,
|
971
|
-
whitenessLabel, blacknessLabel,
|
972
|
-
} = componentLabels;
|
973
|
-
|
974
|
-
const max1 = format === 'hsl' ? 360 : 100;
|
975
|
-
const max2 = format === 'hsl' ? 100 : 360;
|
976
|
-
const max3 = 100;
|
977
|
-
|
978
|
-
let ctrl1Label = format === 'hsl'
|
979
|
-
? `${hueLabel} & ${lightnessLabel}`
|
980
|
-
: `${lightnessLabel} & ${saturationLabel}`;
|
981
|
-
|
982
|
-
ctrl1Label = format === 'hwb'
|
983
|
-
? `${whitenessLabel} & ${blacknessLabel}`
|
984
|
-
: ctrl1Label;
|
985
|
-
|
986
|
-
const ctrl2Label = format === 'hsl'
|
987
|
-
? `${saturationLabel}`
|
988
|
-
: `${hueLabel}`;
|
989
|
-
|
990
|
-
const colorControls = createElement({
|
991
|
-
tagName: 'div',
|
992
|
-
className: `color-controls ${format}`,
|
993
|
-
});
|
994
|
-
|
995
|
-
const colorPointer = 'color-pointer';
|
996
|
-
const colorSlider = 'color-slider';
|
997
|
-
|
998
|
-
const controls = [
|
999
|
-
{
|
1000
|
-
i: 1,
|
1001
|
-
c: colorPointer,
|
1002
|
-
l: ctrl1Label,
|
1003
|
-
min: 0,
|
1004
|
-
max: max1,
|
1005
|
-
},
|
1006
|
-
{
|
1007
|
-
i: 2,
|
1008
|
-
c: colorSlider,
|
1009
|
-
l: ctrl2Label,
|
1010
|
-
min: 0,
|
1011
|
-
max: max2,
|
1012
|
-
},
|
1013
|
-
{
|
1014
|
-
i: 3,
|
1015
|
-
c: colorSlider,
|
1016
|
-
l: alphaLabel,
|
1017
|
-
min: 0,
|
1018
|
-
max: max3,
|
1019
|
-
},
|
1020
|
-
];
|
1021
|
-
|
1022
|
-
controls.forEach((template) => {
|
1023
|
-
const {
|
1024
|
-
i, c, l, min, max,
|
1025
|
-
} = template;
|
1026
|
-
const control = createElement({
|
1027
|
-
tagName: 'div',
|
1028
|
-
className: 'color-control',
|
1029
|
-
});
|
1030
|
-
setAttribute(control, 'role', 'presentation');
|
1031
|
-
|
1032
|
-
control.append(
|
1033
|
-
createElement({
|
1034
|
-
tagName: 'div',
|
1035
|
-
className: `visual-control visual-control${i}`,
|
1036
|
-
}),
|
1037
|
-
);
|
1038
|
-
|
1039
|
-
const knob = createElement({
|
1040
|
-
tagName: 'div',
|
1041
|
-
className: `${c} knob`,
|
1042
|
-
ariaLive: 'polite',
|
1043
|
-
});
|
1044
|
-
|
1045
|
-
setAttribute(knob, ariaLabel, l);
|
1046
|
-
setAttribute(knob, 'role', 'slider');
|
1047
|
-
setAttribute(knob, tabIndex, '0');
|
1048
|
-
setAttribute(knob, ariaValueMin, `${min}`);
|
1049
|
-
setAttribute(knob, ariaValueMax, `${max}`);
|
1050
|
-
control.append(knob);
|
1051
|
-
colorControls.append(control);
|
1052
|
-
});
|
1053
|
-
|
1054
|
-
return colorControls;
|
1055
|
-
}
|
1056
|
-
|
1057
|
-
/**
|
1058
|
-
* Helps setting CSS variables to the color-menu.
|
1059
|
-
* @param {HTMLElement} element
|
1060
|
-
* @param {Record<string,any>} props
|
1061
|
-
*/
|
1062
|
-
function setCSSProperties(element, props) {
|
1063
|
-
ObjectKeys(props).forEach((prop) => {
|
1064
|
-
element.style.setProperty(prop, props[prop]);
|
1065
|
-
});
|
1066
|
-
}
|
1067
|
-
|
1068
|
-
/**
|
1069
|
-
* Returns the `document.head` or the `<head>` element.
|
1070
|
-
*
|
1071
|
-
* @param {(Node | HTMLElement | Element | globalThis)=} node
|
1072
|
-
* @returns {HTMLElement | HTMLHeadElement}
|
1073
|
-
*/
|
1074
|
-
function getDocumentHead(node) {
|
1075
|
-
return getDocument(node).head;
|
1076
|
-
}
|
1077
|
-
|
1078
|
-
/**
|
1079
|
-
* Round colour components, for all formats except HEX.
|
1080
|
-
* @param {number} v one of the colour components
|
1081
|
-
* @returns {number} the rounded number
|
1082
|
-
*/
|
1083
|
-
function roundPart(v) {
|
1084
|
-
const floor = Math.floor(v);
|
1085
|
-
return v - floor < 0.5 ? floor : Math.round(v);
|
1086
|
-
}
|
1087
|
-
|
1088
|
-
// Color supported formats
|
1089
|
-
const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
|
1090
|
-
|
1091
|
-
// Hue angles
|
1092
|
-
const ANGLES = 'deg|rad|grad|turn';
|
1093
|
-
|
1094
|
-
// <http://www.w3.org/TR/css3-values/#integers>
|
1095
|
-
const CSS_INTEGER = '[-\\+]?\\d+%?';
|
1096
|
-
|
1097
|
-
// Include CSS3 Module
|
1098
|
-
// <http://www.w3.org/TR/css3-values/#number-value>
|
1099
|
-
const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
|
1100
|
-
|
1101
|
-
// Include CSS4 Module Hue degrees unit
|
1102
|
-
// <https://www.w3.org/TR/css3-values/#angle-value>
|
1103
|
-
const CSS_ANGLE = `[-\\+]?\\d*\\.?\\d+(?:${ANGLES})?`;
|
1104
|
-
|
1105
|
-
// Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
|
1106
|
-
const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
|
1107
|
-
|
1108
|
-
// Add angles to the mix
|
1109
|
-
const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
|
1110
|
-
|
1111
|
-
// Actual matching.
|
1112
|
-
// Parentheses and commas are optional, but not required.
|
1113
|
-
// Whitespace can take the place of commas or opening paren
|
1114
|
-
const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
|
1115
|
-
|
1116
|
-
const matchers = {
|
1117
|
-
CSS_UNIT: new RegExp(CSS_UNIT2),
|
1118
|
-
hwb: new RegExp(`hwb${PERMISSIVE_MATCH}`),
|
1119
|
-
rgb: new RegExp(`rgb(?:a)?${PERMISSIVE_MATCH}`),
|
1120
|
-
hsl: new RegExp(`hsl(?:a)?${PERMISSIVE_MATCH}`),
|
1121
|
-
hsv: new RegExp(`hsv(?:a)?${PERMISSIVE_MATCH}`),
|
1122
|
-
hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
1123
|
-
hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
|
1124
|
-
hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
1125
|
-
hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
|
1126
|
-
};
|
1127
|
-
|
1128
|
-
/**
|
1129
|
-
* Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
|
1130
|
-
* <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
|
1131
|
-
* @param {string} n testing number
|
1132
|
-
* @returns {boolean} the query result
|
1133
|
-
*/
|
1134
|
-
function isOnePointZero(n) {
|
1135
|
-
return `${n}`.includes('.') && parseFloat(n) === 1;
|
1136
|
-
}
|
1137
|
-
|
1138
|
-
/**
|
1139
|
-
* Check to see if string passed in is a percentage
|
1140
|
-
* @param {string} n testing number
|
1141
|
-
* @returns {boolean} the query result
|
1142
|
-
*/
|
1143
|
-
function isPercentage(n) {
|
1144
|
-
return `${n}`.includes('%');
|
1145
|
-
}
|
1146
|
-
|
1147
|
-
/**
|
1148
|
-
* Check to see if string passed in is an angle
|
1149
|
-
* @param {string} n testing string
|
1150
|
-
* @returns {boolean} the query result
|
1151
|
-
*/
|
1152
|
-
function isAngle(n) {
|
1153
|
-
return ANGLES.split('|').some((a) => `${n}`.includes(a));
|
1154
|
-
}
|
826
|
+
function isPercentage(n) {
|
827
|
+
return `${n}`.includes('%');
|
828
|
+
}
|
1155
829
|
|
1156
830
|
/**
|
1157
831
|
* Check to see if string passed is a web safe colour.
|
832
|
+
* @see https://stackoverflow.com/a/16994164
|
1158
833
|
* @param {string} color a colour name, EG: *red*
|
1159
834
|
* @returns {boolean} the query result
|
1160
835
|
*/
|
1161
836
|
function isColorName(color) {
|
1162
|
-
|
1163
|
-
|
837
|
+
if (nonColors.includes(color)
|
838
|
+
|| ['#', ...COLOR_FORMAT].some((f) => color.includes(f))) return false;
|
839
|
+
|
840
|
+
return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
|
841
|
+
setElementStyle(documentHead, { color });
|
842
|
+
const computedColor = getElementStyle(documentHead, 'color');
|
843
|
+
setElementStyle(documentHead, { color: '' });
|
844
|
+
return computedColor !== c;
|
845
|
+
});
|
1164
846
|
}
|
1165
847
|
|
1166
848
|
/**
|
@@ -1181,15 +863,15 @@ function isValidCSSUnit(color) {
|
|
1181
863
|
*/
|
1182
864
|
function bound01(N, max) {
|
1183
865
|
let n = N;
|
1184
|
-
if (isOnePointZero(
|
1185
|
-
|
1186
|
-
n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
|
866
|
+
if (isOnePointZero(N)) n = '100%';
|
1187
867
|
|
1188
|
-
|
1189
|
-
|
868
|
+
const processPercent = isPercentage(n);
|
869
|
+
n = max === 360
|
870
|
+
? parseFloat(n)
|
871
|
+
: Math.min(max, Math.max(0, parseFloat(n)));
|
1190
872
|
|
1191
873
|
// Automatically convert percentage into number
|
1192
|
-
if (
|
874
|
+
if (processPercent) n = (n * max) / 100;
|
1193
875
|
|
1194
876
|
// Handle floating point rounding errors
|
1195
877
|
if (Math.abs(n - max) < 0.000001) {
|
@@ -1200,11 +882,11 @@ function bound01(N, max) {
|
|
1200
882
|
// If n is a hue given in degrees,
|
1201
883
|
// wrap around out-of-range values into [0, 360] range
|
1202
884
|
// then convert into [0, 1].
|
1203
|
-
n = (n < 0 ? (n % max) + max : n % max) /
|
885
|
+
n = (n < 0 ? (n % max) + max : n % max) / max;
|
1204
886
|
} else {
|
1205
887
|
// If n not a hue given in degrees
|
1206
888
|
// Convert into [0, 1] range if it isn't already.
|
1207
|
-
n = (n % max) /
|
889
|
+
n = (n % max) / max;
|
1208
890
|
}
|
1209
891
|
return n;
|
1210
892
|
}
|
@@ -1239,7 +921,6 @@ function clamp01(v) {
|
|
1239
921
|
* @returns {string}
|
1240
922
|
*/
|
1241
923
|
function getRGBFromName(name) {
|
1242
|
-
const documentHead = getDocumentHead();
|
1243
924
|
setElementStyle(documentHead, { color: name });
|
1244
925
|
const colorName = getElementStyle(documentHead, 'color');
|
1245
926
|
setElementStyle(documentHead, { color: '' });
|
@@ -1338,6 +1019,36 @@ function hueToRgb(p, q, t) {
|
|
1338
1019
|
return p;
|
1339
1020
|
}
|
1340
1021
|
|
1022
|
+
/**
|
1023
|
+
* Converts an HSL colour value to RGB.
|
1024
|
+
*
|
1025
|
+
* @param {number} h Hue Angle [0, 1]
|
1026
|
+
* @param {number} s Saturation [0, 1]
|
1027
|
+
* @param {number} l Lightness Angle [0, 1]
|
1028
|
+
* @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
1029
|
+
*/
|
1030
|
+
function hslToRgb(h, s, l) {
|
1031
|
+
let r = 0;
|
1032
|
+
let g = 0;
|
1033
|
+
let b = 0;
|
1034
|
+
|
1035
|
+
if (s === 0) {
|
1036
|
+
// achromatic
|
1037
|
+
g = l;
|
1038
|
+
b = l;
|
1039
|
+
r = l;
|
1040
|
+
} else {
|
1041
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
1042
|
+
const p = 2 * l - q;
|
1043
|
+
r = hueToRgb(p, q, h + 1 / 3);
|
1044
|
+
g = hueToRgb(p, q, h);
|
1045
|
+
b = hueToRgb(p, q, h - 1 / 3);
|
1046
|
+
}
|
1047
|
+
[r, g, b] = [r, g, b].map((x) => x * 255);
|
1048
|
+
|
1049
|
+
return { r, g, b };
|
1050
|
+
}
|
1051
|
+
|
1341
1052
|
/**
|
1342
1053
|
* Returns an HWB colour object from an RGB colour object.
|
1343
1054
|
* @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
|
@@ -1400,36 +1111,6 @@ function hwbToRgb(H, W, B) {
|
|
1400
1111
|
return { r, g, b };
|
1401
1112
|
}
|
1402
1113
|
|
1403
|
-
/**
|
1404
|
-
* Converts an HSL colour value to RGB.
|
1405
|
-
*
|
1406
|
-
* @param {number} h Hue Angle [0, 1]
|
1407
|
-
* @param {number} s Saturation [0, 1]
|
1408
|
-
* @param {number} l Lightness Angle [0, 1]
|
1409
|
-
* @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
1410
|
-
*/
|
1411
|
-
function hslToRgb(h, s, l) {
|
1412
|
-
let r = 0;
|
1413
|
-
let g = 0;
|
1414
|
-
let b = 0;
|
1415
|
-
|
1416
|
-
if (s === 0) {
|
1417
|
-
// achromatic
|
1418
|
-
g = l;
|
1419
|
-
b = l;
|
1420
|
-
r = l;
|
1421
|
-
} else {
|
1422
|
-
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
1423
|
-
const p = 2 * l - q;
|
1424
|
-
r = hueToRgb(p, q, h + 1 / 3);
|
1425
|
-
g = hueToRgb(p, q, h);
|
1426
|
-
b = hueToRgb(p, q, h - 1 / 3);
|
1427
|
-
}
|
1428
|
-
[r, g, b] = [r, g, b].map((x) => x * 255);
|
1429
|
-
|
1430
|
-
return { r, g, b };
|
1431
|
-
}
|
1432
|
-
|
1433
1114
|
/**
|
1434
1115
|
* Converts an RGB colour value to HSV.
|
1435
1116
|
*
|
@@ -1485,10 +1166,11 @@ function hsvToRgb(H, S, V) {
|
|
1485
1166
|
const q = v * (1 - f * s);
|
1486
1167
|
const t = v * (1 - (1 - f) * s);
|
1487
1168
|
const mod = i % 6;
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1169
|
+
let r = [v, q, p, p, t, v][mod];
|
1170
|
+
let g = [t, v, v, q, p, p][mod];
|
1171
|
+
let b = [p, p, t, v, v, q][mod];
|
1172
|
+
[r, g, b] = [r, g, b].map((n) => n * 255);
|
1173
|
+
return { r, g, b };
|
1492
1174
|
}
|
1493
1175
|
|
1494
1176
|
/**
|
@@ -1512,7 +1194,7 @@ function rgbToHex(r, g, b, allow3Char) {
|
|
1512
1194
|
// Return a 3 character hex if possible
|
1513
1195
|
if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
|
1514
1196
|
&& hex[1].charAt(0) === hex[1].charAt(1)
|
1515
|
-
|
1197
|
+
&& hex[2].charAt(0) === hex[2].charAt(1)) {
|
1516
1198
|
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
|
1517
1199
|
}
|
1518
1200
|
|
@@ -1540,39 +1222,24 @@ function rgbaToHex(r, g, b, a, allow4Char) {
|
|
1540
1222
|
// Return a 4 character hex if possible
|
1541
1223
|
if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
|
1542
1224
|
&& hex[1].charAt(0) === hex[1].charAt(1)
|
1543
|
-
|
1544
|
-
|
1225
|
+
&& hex[2].charAt(0) === hex[2].charAt(1)
|
1226
|
+
&& hex[3].charAt(0) === hex[3].charAt(1)) {
|
1545
1227
|
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
|
1546
1228
|
}
|
1547
1229
|
return hex.join('');
|
1548
1230
|
}
|
1549
1231
|
|
1550
|
-
/**
|
1551
|
-
* Returns a colour object corresponding to a given number.
|
1552
|
-
* @param {number} color input number
|
1553
|
-
* @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
1554
|
-
*/
|
1555
|
-
function numberInputToObject(color) {
|
1556
|
-
/* eslint-disable no-bitwise */
|
1557
|
-
return {
|
1558
|
-
r: color >> 16,
|
1559
|
-
g: (color & 0xff00) >> 8,
|
1560
|
-
b: color & 0xff,
|
1561
|
-
};
|
1562
|
-
/* eslint-enable no-bitwise */
|
1563
|
-
}
|
1564
|
-
|
1565
1232
|
/**
|
1566
1233
|
* Permissive string parsing. Take in a number of formats, and output an object
|
1567
1234
|
* based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
|
1568
1235
|
* @param {string} input colour value in any format
|
1569
|
-
* @returns {Record<string, (number | string)> | false} an object matching the RegExp
|
1236
|
+
* @returns {Record<string, (number | string | boolean)> | false} an object matching the RegExp
|
1570
1237
|
*/
|
1571
1238
|
function stringInputToObject(input) {
|
1572
|
-
let color = input.trim()
|
1239
|
+
let color = toLowerCase(input.trim());
|
1573
1240
|
if (color.length === 0) {
|
1574
1241
|
return {
|
1575
|
-
r: 0, g: 0, b: 0, a:
|
1242
|
+
r: 0, g: 0, b: 0, a: 1,
|
1576
1243
|
};
|
1577
1244
|
}
|
1578
1245
|
let named = false;
|
@@ -1580,11 +1247,9 @@ function stringInputToObject(input) {
|
|
1580
1247
|
color = getRGBFromName(color);
|
1581
1248
|
named = true;
|
1582
1249
|
} else if (nonColors.includes(color)) {
|
1583
|
-
const
|
1584
|
-
const rgb = isTransparent ? 0 : 255;
|
1585
|
-
const a = isTransparent ? 0 : 1;
|
1250
|
+
const a = color === 'transparent' ? 0 : 1;
|
1586
1251
|
return {
|
1587
|
-
r:
|
1252
|
+
r: 0, g: 0, b: 0, a, format: 'rgb', ok: true,
|
1588
1253
|
};
|
1589
1254
|
}
|
1590
1255
|
|
@@ -1624,7 +1289,6 @@ function stringInputToObject(input) {
|
|
1624
1289
|
g: parseIntFromHex(m2),
|
1625
1290
|
b: parseIntFromHex(m3),
|
1626
1291
|
a: convertHexToDecimal(m4),
|
1627
|
-
// format: named ? 'rgb' : 'hex8',
|
1628
1292
|
format: named ? 'rgb' : 'hex',
|
1629
1293
|
};
|
1630
1294
|
}
|
@@ -1688,6 +1352,7 @@ function stringInputToObject(input) {
|
|
1688
1352
|
function inputToRGB(input) {
|
1689
1353
|
let rgb = { r: 0, g: 0, b: 0 };
|
1690
1354
|
let color = input;
|
1355
|
+
/** @type {string | number} */
|
1691
1356
|
let a = 1;
|
1692
1357
|
let s = null;
|
1693
1358
|
let v = null;
|
@@ -1698,7 +1363,8 @@ function inputToRGB(input) {
|
|
1698
1363
|
let r = null;
|
1699
1364
|
let g = null;
|
1700
1365
|
let ok = false;
|
1701
|
-
|
1366
|
+
const inputFormat = typeof color === 'object' && color.format;
|
1367
|
+
let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
|
1702
1368
|
|
1703
1369
|
if (typeof input === 'string') {
|
1704
1370
|
// @ts-ignore -- this now is converted to object
|
@@ -1739,14 +1405,17 @@ function inputToRGB(input) {
|
|
1739
1405
|
format = 'hwb';
|
1740
1406
|
}
|
1741
1407
|
if (isValidCSSUnit(color.a)) {
|
1742
|
-
a = color.a;
|
1743
|
-
a = isPercentage(`${a}`) ? bound01(a, 100) : a;
|
1408
|
+
a = color.a; // @ts-ignore -- `parseFloat` works with numbers too
|
1409
|
+
a = isPercentage(`${a}`) || parseFloat(a) > 1 ? bound01(a, 100) : a;
|
1744
1410
|
}
|
1745
1411
|
}
|
1412
|
+
if (typeof color === 'undefined') {
|
1413
|
+
ok = true;
|
1414
|
+
}
|
1746
1415
|
|
1747
1416
|
return {
|
1748
|
-
ok,
|
1749
|
-
format
|
1417
|
+
ok,
|
1418
|
+
format,
|
1750
1419
|
r: Math.min(255, Math.max(rgb.r, 0)),
|
1751
1420
|
g: Math.min(255, Math.max(rgb.g, 0)),
|
1752
1421
|
b: Math.min(255, Math.max(rgb.b, 0)),
|
@@ -1775,7 +1444,8 @@ class Color {
|
|
1775
1444
|
color = inputToRGB(color);
|
1776
1445
|
}
|
1777
1446
|
if (typeof color === 'number') {
|
1778
|
-
|
1447
|
+
const len = `${color}`.length;
|
1448
|
+
color = `#${(len === 2 ? '0' : '00')}${color}`;
|
1779
1449
|
}
|
1780
1450
|
const {
|
1781
1451
|
r, g, b, a, ok, format,
|
@@ -1785,7 +1455,7 @@ class Color {
|
|
1785
1455
|
const self = this;
|
1786
1456
|
|
1787
1457
|
/** @type {CP.ColorInput} */
|
1788
|
-
self.originalInput =
|
1458
|
+
self.originalInput = input;
|
1789
1459
|
/** @type {number} */
|
1790
1460
|
self.r = r;
|
1791
1461
|
/** @type {number} */
|
@@ -2175,6 +1845,7 @@ ObjectAssign(Color, {
|
|
2175
1845
|
isOnePointZero,
|
2176
1846
|
isPercentage,
|
2177
1847
|
isValidCSSUnit,
|
1848
|
+
isColorName,
|
2178
1849
|
pad2,
|
2179
1850
|
clamp01,
|
2180
1851
|
bound01,
|
@@ -2192,10 +1863,11 @@ ObjectAssign(Color, {
|
|
2192
1863
|
hueToRgb,
|
2193
1864
|
hwbToRgb,
|
2194
1865
|
parseIntFromHex,
|
2195
|
-
numberInputToObject,
|
2196
1866
|
stringInputToObject,
|
2197
1867
|
inputToRGB,
|
2198
1868
|
roundPart,
|
1869
|
+
getElementStyle,
|
1870
|
+
setElementStyle,
|
2199
1871
|
ObjectAssign,
|
2200
1872
|
});
|
2201
1873
|
|
@@ -2204,7 +1876,7 @@ ObjectAssign(Color, {
|
|
2204
1876
|
* Returns a color palette with a given set of parameters.
|
2205
1877
|
* @example
|
2206
1878
|
* new ColorPalette(0, 12, 10);
|
2207
|
-
* // => { hue: 0, hueSteps: 12, lightSteps: 10, colors:
|
1879
|
+
* // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: Array<Color> }
|
2208
1880
|
*/
|
2209
1881
|
class ColorPalette {
|
2210
1882
|
/**
|
@@ -2224,11 +1896,14 @@ class ColorPalette {
|
|
2224
1896
|
[hue, hueSteps, lightSteps] = args;
|
2225
1897
|
} else if (args.length === 2) {
|
2226
1898
|
[hueSteps, lightSteps] = args;
|
1899
|
+
if ([hueSteps, lightSteps].some((n) => n < 1)) {
|
1900
|
+
throw TypeError('ColorPalette: when 2 arguments used, both must be larger than 0.');
|
1901
|
+
}
|
2227
1902
|
} else {
|
2228
1903
|
throw TypeError('ColorPalette requires minimum 2 arguments');
|
2229
1904
|
}
|
2230
1905
|
|
2231
|
-
/** @type {
|
1906
|
+
/** @type {Color[]} */
|
2232
1907
|
const colors = [];
|
2233
1908
|
|
2234
1909
|
const hueStep = 360 / hueSteps;
|
@@ -2257,7 +1932,7 @@ class ColorPalette {
|
|
2257
1932
|
for (let i = 0; i < hueSteps; i += 1) {
|
2258
1933
|
const currentHue = ((hue + i * hueStep) % 360) / 360;
|
2259
1934
|
lightnessArray.forEach((l) => {
|
2260
|
-
colors.push(new Color({ h: currentHue, s: 1, l })
|
1935
|
+
colors.push(new Color({ h: currentHue, s: 1, l }));
|
2261
1936
|
});
|
2262
1937
|
}
|
2263
1938
|
|
@@ -2268,6 +1943,310 @@ class ColorPalette {
|
|
2268
1943
|
}
|
2269
1944
|
}
|
2270
1945
|
|
1946
|
+
ObjectAssign(ColorPalette, { Color });
|
1947
|
+
|
1948
|
+
/** @type {Record<string, string>} */
|
1949
|
+
const colorPickerLabels = {
|
1950
|
+
pickerLabel: 'Colour Picker',
|
1951
|
+
appearanceLabel: 'Colour Appearance',
|
1952
|
+
valueLabel: 'Colour Value',
|
1953
|
+
toggleLabel: 'Select Colour',
|
1954
|
+
presetsLabel: 'Colour Presets',
|
1955
|
+
defaultsLabel: 'Colour Defaults',
|
1956
|
+
formatLabel: 'Format',
|
1957
|
+
alphaLabel: 'Alpha',
|
1958
|
+
hexLabel: 'Hexadecimal',
|
1959
|
+
hueLabel: 'Hue',
|
1960
|
+
whitenessLabel: 'Whiteness',
|
1961
|
+
blacknessLabel: 'Blackness',
|
1962
|
+
saturationLabel: 'Saturation',
|
1963
|
+
lightnessLabel: 'Lightness',
|
1964
|
+
redLabel: 'Red',
|
1965
|
+
greenLabel: 'Green',
|
1966
|
+
blueLabel: 'Blue',
|
1967
|
+
};
|
1968
|
+
|
1969
|
+
/**
|
1970
|
+
* A list of 17 color names used for WAI-ARIA compliance.
|
1971
|
+
* @type {string[]}
|
1972
|
+
*/
|
1973
|
+
const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
|
1974
|
+
|
1975
|
+
const tabIndex = 'tabindex';
|
1976
|
+
|
1977
|
+
/**
|
1978
|
+
* Check if a string is valid JSON string.
|
1979
|
+
* @param {string} str the string input
|
1980
|
+
* @returns {boolean} the query result
|
1981
|
+
*/
|
1982
|
+
function isValidJSON(str) {
|
1983
|
+
try {
|
1984
|
+
JSON.parse(str);
|
1985
|
+
} catch (e) {
|
1986
|
+
return false;
|
1987
|
+
}
|
1988
|
+
return true;
|
1989
|
+
}
|
1990
|
+
|
1991
|
+
/**
|
1992
|
+
* Shortcut for `String.toUpperCase()`.
|
1993
|
+
*
|
1994
|
+
* @param {string} source input string
|
1995
|
+
* @returns {string} uppercase output string
|
1996
|
+
*/
|
1997
|
+
const toUpperCase = (source) => source.toUpperCase();
|
1998
|
+
|
1999
|
+
/**
|
2000
|
+
* A global namespace for aria-haspopup.
|
2001
|
+
* @type {string}
|
2002
|
+
*/
|
2003
|
+
const ariaHasPopup = 'aria-haspopup';
|
2004
|
+
|
2005
|
+
/**
|
2006
|
+
* A global namespace for aria-hidden.
|
2007
|
+
* @type {string}
|
2008
|
+
*/
|
2009
|
+
const ariaHidden = 'aria-hidden';
|
2010
|
+
|
2011
|
+
/**
|
2012
|
+
* A global namespace for aria-labelledby.
|
2013
|
+
* @type {string}
|
2014
|
+
*/
|
2015
|
+
const ariaLabelledBy = 'aria-labelledby';
|
2016
|
+
|
2017
|
+
/**
|
2018
|
+
* This is a shortie for `document.createElement` method
|
2019
|
+
* which allows you to create a new `HTMLElement` for a given `tagName`
|
2020
|
+
* or based on an object with specific non-readonly attributes:
|
2021
|
+
* `id`, `className`, `textContent`, `style`, etc.
|
2022
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
|
2023
|
+
*
|
2024
|
+
* @param {Record<string, string> | string} param `tagName` or object
|
2025
|
+
* @return {HTMLElement | Element} a new `HTMLElement` or `Element`
|
2026
|
+
*/
|
2027
|
+
function createElement(param) {
|
2028
|
+
if (typeof param === 'string') {
|
2029
|
+
return getDocument().createElement(param);
|
2030
|
+
}
|
2031
|
+
|
2032
|
+
const { tagName } = param;
|
2033
|
+
const attr = { ...param };
|
2034
|
+
const newElement = createElement(tagName);
|
2035
|
+
delete attr.tagName;
|
2036
|
+
ObjectAssign(newElement, attr);
|
2037
|
+
return newElement;
|
2038
|
+
}
|
2039
|
+
|
2040
|
+
/**
|
2041
|
+
* This is a shortie for `document.createElementNS` method
|
2042
|
+
* which allows you to create a new `HTMLElement` for a given `tagName`
|
2043
|
+
* or based on an object with specific non-readonly attributes:
|
2044
|
+
* `id`, `className`, `textContent`, `style`, etc.
|
2045
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
|
2046
|
+
*
|
2047
|
+
* @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
|
2048
|
+
* @param {Record<string, string> | string} param `tagName` or object
|
2049
|
+
* @return {HTMLElement | Element} a new `HTMLElement` or `Element`
|
2050
|
+
*/
|
2051
|
+
function createElementNS(namespace, param) {
|
2052
|
+
if (typeof param === 'string') {
|
2053
|
+
return getDocument().createElementNS(namespace, param);
|
2054
|
+
}
|
2055
|
+
|
2056
|
+
const { tagName } = param;
|
2057
|
+
const attr = { ...param };
|
2058
|
+
const newElement = createElementNS(namespace, tagName);
|
2059
|
+
delete attr.tagName;
|
2060
|
+
ObjectAssign(newElement, attr);
|
2061
|
+
return newElement;
|
2062
|
+
}
|
2063
|
+
|
2064
|
+
const vHidden = 'v-hidden';
|
2065
|
+
|
2066
|
+
/**
|
2067
|
+
* Returns the color form for `ColorPicker`.
|
2068
|
+
*
|
2069
|
+
* @param {CP.ColorPicker} self the `ColorPicker` instance
|
2070
|
+
* @returns {HTMLElement | Element} a new `<div>` element with color component `<input>`
|
2071
|
+
*/
|
2072
|
+
function getColorForm(self) {
|
2073
|
+
const { format, id, componentLabels } = self;
|
2074
|
+
const colorForm = createElement({
|
2075
|
+
tagName: 'div',
|
2076
|
+
className: `color-form ${format}`,
|
2077
|
+
});
|
2078
|
+
|
2079
|
+
let components = ['hex'];
|
2080
|
+
if (format === 'rgb') components = ['red', 'green', 'blue', 'alpha'];
|
2081
|
+
else if (format === 'hsl') components = ['hue', 'saturation', 'lightness', 'alpha'];
|
2082
|
+
else if (format === 'hwb') components = ['hue', 'whiteness', 'blackness', 'alpha'];
|
2083
|
+
|
2084
|
+
components.forEach((c) => {
|
2085
|
+
const [C] = format === 'hex' ? ['#'] : toUpperCase(c).split('');
|
2086
|
+
const cID = `color_${format}_${c}_${id}`;
|
2087
|
+
const formatLabel = componentLabels[`${c}Label`];
|
2088
|
+
const cInputLabel = createElement({ tagName: 'label' });
|
2089
|
+
setAttribute(cInputLabel, 'for', cID);
|
2090
|
+
cInputLabel.append(
|
2091
|
+
createElement({ tagName: 'span', ariaHidden: 'true', innerText: `${C}:` }),
|
2092
|
+
createElement({ tagName: 'span', className: vHidden, innerText: formatLabel }),
|
2093
|
+
);
|
2094
|
+
const cInput = createElement({
|
2095
|
+
tagName: 'input',
|
2096
|
+
id: cID,
|
2097
|
+
// name: cID, - prevent saving the value to a form
|
2098
|
+
type: format === 'hex' ? 'text' : 'number',
|
2099
|
+
value: c === 'alpha' ? '100' : '0',
|
2100
|
+
className: `color-input ${c}`,
|
2101
|
+
});
|
2102
|
+
setAttribute(cInput, 'autocomplete', 'off');
|
2103
|
+
setAttribute(cInput, 'spellcheck', 'false');
|
2104
|
+
|
2105
|
+
// alpha
|
2106
|
+
let max = '100';
|
2107
|
+
let step = '1';
|
2108
|
+
if (c !== 'alpha') {
|
2109
|
+
if (format === 'rgb') {
|
2110
|
+
max = '255'; step = '1';
|
2111
|
+
} else if (c === 'hue') {
|
2112
|
+
max = '360'; step = '1';
|
2113
|
+
}
|
2114
|
+
}
|
2115
|
+
ObjectAssign(cInput, {
|
2116
|
+
min: '0',
|
2117
|
+
max,
|
2118
|
+
step,
|
2119
|
+
});
|
2120
|
+
colorForm.append(cInputLabel, cInput);
|
2121
|
+
});
|
2122
|
+
return colorForm;
|
2123
|
+
}
|
2124
|
+
|
2125
|
+
/**
|
2126
|
+
* A global namespace for aria-label.
|
2127
|
+
* @type {string}
|
2128
|
+
*/
|
2129
|
+
const ariaLabel = 'aria-label';
|
2130
|
+
|
2131
|
+
/**
|
2132
|
+
* A global namespace for aria-valuemin.
|
2133
|
+
* @type {string}
|
2134
|
+
*/
|
2135
|
+
const ariaValueMin = 'aria-valuemin';
|
2136
|
+
|
2137
|
+
/**
|
2138
|
+
* A global namespace for aria-valuemax.
|
2139
|
+
* @type {string}
|
2140
|
+
*/
|
2141
|
+
const ariaValueMax = 'aria-valuemax';
|
2142
|
+
|
2143
|
+
/**
|
2144
|
+
* Returns all color controls for `ColorPicker`.
|
2145
|
+
*
|
2146
|
+
* @param {CP.ColorPicker} self the `ColorPicker` instance
|
2147
|
+
* @returns {HTMLElement | Element} color controls
|
2148
|
+
*/
|
2149
|
+
function getColorControls(self) {
|
2150
|
+
const { format, componentLabels } = self;
|
2151
|
+
const {
|
2152
|
+
hueLabel, alphaLabel, lightnessLabel, saturationLabel,
|
2153
|
+
whitenessLabel, blacknessLabel,
|
2154
|
+
} = componentLabels;
|
2155
|
+
|
2156
|
+
const max1 = format === 'hsl' ? 360 : 100;
|
2157
|
+
const max2 = format === 'hsl' ? 100 : 360;
|
2158
|
+
const max3 = 100;
|
2159
|
+
|
2160
|
+
let ctrl1Label = format === 'hsl'
|
2161
|
+
? `${hueLabel} & ${lightnessLabel}`
|
2162
|
+
: `${lightnessLabel} & ${saturationLabel}`;
|
2163
|
+
|
2164
|
+
ctrl1Label = format === 'hwb'
|
2165
|
+
? `${whitenessLabel} & ${blacknessLabel}`
|
2166
|
+
: ctrl1Label;
|
2167
|
+
|
2168
|
+
const ctrl2Label = format === 'hsl'
|
2169
|
+
? `${saturationLabel}`
|
2170
|
+
: `${hueLabel}`;
|
2171
|
+
|
2172
|
+
const colorControls = createElement({
|
2173
|
+
tagName: 'div',
|
2174
|
+
className: `color-controls ${format}`,
|
2175
|
+
});
|
2176
|
+
|
2177
|
+
const colorPointer = 'color-pointer';
|
2178
|
+
const colorSlider = 'color-slider';
|
2179
|
+
|
2180
|
+
const controls = [
|
2181
|
+
{
|
2182
|
+
i: 1,
|
2183
|
+
c: colorPointer,
|
2184
|
+
l: ctrl1Label,
|
2185
|
+
min: 0,
|
2186
|
+
max: max1,
|
2187
|
+
},
|
2188
|
+
{
|
2189
|
+
i: 2,
|
2190
|
+
c: colorSlider,
|
2191
|
+
l: ctrl2Label,
|
2192
|
+
min: 0,
|
2193
|
+
max: max2,
|
2194
|
+
},
|
2195
|
+
{
|
2196
|
+
i: 3,
|
2197
|
+
c: colorSlider,
|
2198
|
+
l: alphaLabel,
|
2199
|
+
min: 0,
|
2200
|
+
max: max3,
|
2201
|
+
},
|
2202
|
+
];
|
2203
|
+
|
2204
|
+
controls.forEach((template) => {
|
2205
|
+
const {
|
2206
|
+
i, c, l, min, max,
|
2207
|
+
} = template;
|
2208
|
+
const control = createElement({
|
2209
|
+
tagName: 'div',
|
2210
|
+
className: 'color-control',
|
2211
|
+
});
|
2212
|
+
setAttribute(control, 'role', 'presentation');
|
2213
|
+
|
2214
|
+
control.append(
|
2215
|
+
createElement({
|
2216
|
+
tagName: 'div',
|
2217
|
+
className: `visual-control visual-control${i}`,
|
2218
|
+
}),
|
2219
|
+
);
|
2220
|
+
|
2221
|
+
const knob = createElement({
|
2222
|
+
tagName: 'div',
|
2223
|
+
className: `${c} knob`,
|
2224
|
+
ariaLive: 'polite',
|
2225
|
+
});
|
2226
|
+
|
2227
|
+
setAttribute(knob, ariaLabel, l);
|
2228
|
+
setAttribute(knob, 'role', 'slider');
|
2229
|
+
setAttribute(knob, tabIndex, '0');
|
2230
|
+
setAttribute(knob, ariaValueMin, `${min}`);
|
2231
|
+
setAttribute(knob, ariaValueMax, `${max}`);
|
2232
|
+
control.append(knob);
|
2233
|
+
colorControls.append(control);
|
2234
|
+
});
|
2235
|
+
|
2236
|
+
return colorControls;
|
2237
|
+
}
|
2238
|
+
|
2239
|
+
/**
|
2240
|
+
* Helps setting CSS variables to the color-menu.
|
2241
|
+
* @param {HTMLElement} element
|
2242
|
+
* @param {Record<string,any>} props
|
2243
|
+
*/
|
2244
|
+
function setCSSProperties(element, props) {
|
2245
|
+
ObjectKeys(props).forEach((prop) => {
|
2246
|
+
element.style.setProperty(prop, props[prop]);
|
2247
|
+
});
|
2248
|
+
}
|
2249
|
+
|
2271
2250
|
/**
|
2272
2251
|
* Returns a color-defaults with given values and class.
|
2273
2252
|
* @param {CP.ColorPicker} self
|
@@ -2301,7 +2280,8 @@ function getColorMenu(self, colorsSource, menuClass) {
|
|
2301
2280
|
optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
|
2302
2281
|
const menuHeight = `${(rowCount || 1) * optionSize}rem`;
|
2303
2282
|
const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
|
2304
|
-
|
2283
|
+
/** @type {HTMLUListElement} */
|
2284
|
+
// @ts-ignore -- <UL> is an `HTMLElement`
|
2305
2285
|
const menu = createElement({
|
2306
2286
|
tagName: 'ul',
|
2307
2287
|
className: finalClass,
|
@@ -2309,7 +2289,7 @@ function getColorMenu(self, colorsSource, menuClass) {
|
|
2309
2289
|
setAttribute(menu, 'role', 'listbox');
|
2310
2290
|
setAttribute(menu, ariaLabel, menuLabel);
|
2311
2291
|
|
2312
|
-
if (isScrollable) {
|
2292
|
+
if (isScrollable) {
|
2313
2293
|
setCSSProperties(menu, {
|
2314
2294
|
'--grid-item-size': `${optionSize}rem`,
|
2315
2295
|
'--grid-fit': fit,
|
@@ -2320,15 +2300,19 @@ function getColorMenu(self, colorsSource, menuClass) {
|
|
2320
2300
|
}
|
2321
2301
|
|
2322
2302
|
colorsArray.forEach((x) => {
|
2323
|
-
|
2324
|
-
|
2325
|
-
|
2303
|
+
let [value, label] = typeof x === 'string' ? x.trim().split(':') : [];
|
2304
|
+
if (x instanceof Color) {
|
2305
|
+
value = x.toHexString();
|
2306
|
+
label = value;
|
2307
|
+
}
|
2308
|
+
const color = new Color(x instanceof Color ? x : value, format);
|
2309
|
+
const isActive = color.toString() === getAttribute(input, 'value');
|
2326
2310
|
const active = isActive ? ' active' : '';
|
2327
2311
|
|
2328
2312
|
const option = createElement({
|
2329
2313
|
tagName: 'li',
|
2330
2314
|
className: `color-option${active}`,
|
2331
|
-
innerText: `${label ||
|
2315
|
+
innerText: `${label || value}`,
|
2332
2316
|
});
|
2333
2317
|
|
2334
2318
|
setAttribute(option, tabIndex, '0');
|
@@ -2337,7 +2321,7 @@ function getColorMenu(self, colorsSource, menuClass) {
|
|
2337
2321
|
setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
|
2338
2322
|
|
2339
2323
|
if (isOptionsMenu) {
|
2340
|
-
setElementStyle(option, { backgroundColor:
|
2324
|
+
setElementStyle(option, { backgroundColor: value });
|
2341
2325
|
}
|
2342
2326
|
|
2343
2327
|
menu.append(option);
|
@@ -2346,55 +2330,10 @@ function getColorMenu(self, colorsSource, menuClass) {
|
|
2346
2330
|
}
|
2347
2331
|
|
2348
2332
|
/**
|
2349
|
-
|
2350
|
-
|
2351
|
-
|
2352
|
-
|
2353
|
-
function isValidJSON(str) {
|
2354
|
-
try {
|
2355
|
-
JSON.parse(str);
|
2356
|
-
} catch (e) {
|
2357
|
-
return false;
|
2358
|
-
}
|
2359
|
-
return true;
|
2360
|
-
}
|
2361
|
-
|
2362
|
-
var version = "0.0.1";
|
2363
|
-
|
2364
|
-
// @ts-ignore
|
2365
|
-
|
2366
|
-
const Version = version;
|
2367
|
-
|
2368
|
-
// ColorPicker GC
|
2369
|
-
// ==============
|
2370
|
-
const colorPickerString = 'color-picker';
|
2371
|
-
const colorPickerSelector = `[data-function="${colorPickerString}"]`;
|
2372
|
-
const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
|
2373
|
-
const colorPickerDefaults = {
|
2374
|
-
componentLabels: colorPickerLabels,
|
2375
|
-
colorLabels: colorNames,
|
2376
|
-
format: 'rgb',
|
2377
|
-
colorPresets: false,
|
2378
|
-
colorKeywords: false,
|
2379
|
-
};
|
2380
|
-
|
2381
|
-
// ColorPicker Static Methods
|
2382
|
-
// ==========================
|
2383
|
-
|
2384
|
-
/** @type {CP.GetInstance<ColorPicker>} */
|
2385
|
-
const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
|
2386
|
-
|
2387
|
-
/** @type {CP.InitCallback<ColorPicker>} */
|
2388
|
-
const initColorPicker = (element) => new ColorPicker(element);
|
2389
|
-
|
2390
|
-
// ColorPicker Private Methods
|
2391
|
-
// ===========================
|
2392
|
-
|
2393
|
-
/**
|
2394
|
-
* Generate HTML markup and update instance properties.
|
2395
|
-
* @param {ColorPicker} self
|
2396
|
-
*/
|
2397
|
-
function initCallback(self) {
|
2333
|
+
* Generate HTML markup and update instance properties.
|
2334
|
+
* @param {CP.ColorPicker} self
|
2335
|
+
*/
|
2336
|
+
function setMarkup(self) {
|
2398
2337
|
const {
|
2399
2338
|
input, parent, format, id, componentLabels, colorKeywords, colorPresets,
|
2400
2339
|
} = self;
|
@@ -2409,9 +2348,7 @@ function initCallback(self) {
|
|
2409
2348
|
self.color = new Color(color, format);
|
2410
2349
|
|
2411
2350
|
// set initial controls dimensions
|
2412
|
-
|
2413
|
-
const dropClass = isMobile ? ' mobile' : '';
|
2414
|
-
const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
|
2351
|
+
const formatString = format === 'hex' ? hexLabel : toUpperCase(format);
|
2415
2352
|
|
2416
2353
|
const pickerBtn = createElement({
|
2417
2354
|
id: `picker-btn-${id}`,
|
@@ -2428,7 +2365,7 @@ function initCallback(self) {
|
|
2428
2365
|
|
2429
2366
|
const pickerDropdown = createElement({
|
2430
2367
|
tagName: 'div',
|
2431
|
-
className:
|
2368
|
+
className: 'color-dropdown picker',
|
2432
2369
|
});
|
2433
2370
|
setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
|
2434
2371
|
setAttribute(pickerDropdown, 'role', 'group');
|
@@ -2444,7 +2381,7 @@ function initCallback(self) {
|
|
2444
2381
|
if (colorKeywords || colorPresets) {
|
2445
2382
|
const presetsDropdown = createElement({
|
2446
2383
|
tagName: 'div',
|
2447
|
-
className:
|
2384
|
+
className: 'color-dropdown scrollable menu',
|
2448
2385
|
});
|
2449
2386
|
|
2450
2387
|
// color presets
|
@@ -2494,6 +2431,37 @@ function initCallback(self) {
|
|
2494
2431
|
setAttribute(input, tabIndex, '-1');
|
2495
2432
|
}
|
2496
2433
|
|
2434
|
+
var version = "0.0.2alpha1";
|
2435
|
+
|
2436
|
+
// @ts-ignore
|
2437
|
+
|
2438
|
+
const Version = version;
|
2439
|
+
|
2440
|
+
// ColorPicker GC
|
2441
|
+
// ==============
|
2442
|
+
const colorPickerString = 'color-picker';
|
2443
|
+
const colorPickerSelector = `[data-function="${colorPickerString}"]`;
|
2444
|
+
const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
|
2445
|
+
const colorPickerDefaults = {
|
2446
|
+
componentLabels: colorPickerLabels,
|
2447
|
+
colorLabels: colorNames,
|
2448
|
+
format: 'rgb',
|
2449
|
+
colorPresets: false,
|
2450
|
+
colorKeywords: false,
|
2451
|
+
};
|
2452
|
+
|
2453
|
+
// ColorPicker Static Methods
|
2454
|
+
// ==========================
|
2455
|
+
|
2456
|
+
/** @type {CP.GetInstance<ColorPicker>} */
|
2457
|
+
const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
|
2458
|
+
|
2459
|
+
/** @type {CP.InitCallback<ColorPicker>} */
|
2460
|
+
const initColorPicker = (element) => new ColorPicker(element);
|
2461
|
+
|
2462
|
+
// ColorPicker Private Methods
|
2463
|
+
// ===========================
|
2464
|
+
|
2497
2465
|
/**
|
2498
2466
|
* Add / remove `ColorPicker` main event listeners.
|
2499
2467
|
* @param {ColorPicker} self
|
@@ -2727,7 +2695,7 @@ class ColorPicker {
|
|
2727
2695
|
self.handleKnobs = self.handleKnobs.bind(self);
|
2728
2696
|
|
2729
2697
|
// generate markup
|
2730
|
-
|
2698
|
+
setMarkup(self);
|
2731
2699
|
|
2732
2700
|
const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
|
2733
2701
|
// set main elements
|
@@ -2923,7 +2891,7 @@ class ColorPicker {
|
|
2923
2891
|
const self = this;
|
2924
2892
|
const { activeElement } = getDocument(self.input);
|
2925
2893
|
|
2926
|
-
if ((
|
2894
|
+
if ((e.type === touchmoveEvent && self.dragElement)
|
2927
2895
|
|| (activeElement && self.controlKnobs.includes(activeElement))) {
|
2928
2896
|
e.stopPropagation();
|
2929
2897
|
e.preventDefault();
|
@@ -3739,4 +3707,4 @@ ObjectAssign(ColorPicker, {
|
|
3739
3707
|
getBoundingClientRect,
|
3740
3708
|
});
|
3741
3709
|
|
3742
|
-
export default
|
3710
|
+
export { ColorPicker as default };
|