@thednp/color-picker 0.0.1-alpha1 → 0.0.1
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/LICENSE +1 -1
- package/README.md +63 -26
- package/dist/css/color-picker.css +504 -337
- package/dist/css/color-picker.min.css +2 -0
- package/dist/css/color-picker.rtl.css +529 -0
- package/dist/css/color-picker.rtl.min.css +2 -0
- package/dist/js/color-picker-element-esm.js +3851 -2
- package/dist/js/color-picker-element-esm.min.js +2 -0
- package/dist/js/color-picker-element.js +2086 -1278
- package/dist/js/color-picker-element.min.js +2 -2
- package/dist/js/color-picker-esm.js +3742 -0
- package/dist/js/color-picker-esm.min.js +2 -0
- package/dist/js/color-picker.js +2030 -1286
- package/dist/js/color-picker.min.js +2 -2
- package/package.json +18 -9
- package/src/js/color-palette.js +71 -0
- package/src/js/color-picker-element.js +62 -16
- package/src/js/color-picker.js +734 -618
- package/src/js/color.js +621 -358
- package/src/js/index.js +0 -9
- package/src/js/util/colorNames.js +2 -152
- package/src/js/util/colorPickerLabels.js +22 -0
- package/src/js/util/getColorControls.js +103 -0
- package/src/js/util/getColorForm.js +26 -19
- package/src/js/util/getColorMenu.js +88 -0
- package/src/js/util/isValidJSON.js +13 -0
- package/src/js/util/nonColors.js +5 -0
- package/src/js/util/roundPart.js +9 -0
- package/src/js/util/setCSSProperties.js +12 -0
- package/src/js/util/tabindex.js +3 -0
- package/src/js/util/templates.js +1 -0
- package/src/scss/color-picker.rtl.scss +23 -0
- package/src/scss/color-picker.scss +449 -0
- package/types/cp.d.ts +263 -162
- package/types/index.d.ts +9 -2
- package/types/source/source.ts +2 -1
- package/types/source/types.d.ts +28 -5
- package/dist/js/color-picker.esm.js +0 -2998
- package/dist/js/color-picker.esm.min.js +0 -2
- package/src/js/util/getColorControl.js +0 -49
- package/src/js/util/init.js +0 -14
package/dist/js/color-picker.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
/*!
|
2
|
-
* ColorPicker v0.0.
|
2
|
+
* ColorPicker v0.0.1 (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
|
*/
|
@@ -9,37 +9,6 @@
|
|
9
9
|
(global = global || self, global.ColorPicker = factory());
|
10
10
|
}(this, (function () { 'use strict';
|
11
11
|
|
12
|
-
/**
|
13
|
-
* Returns the `document` or the `#document` element.
|
14
|
-
* @see https://github.com/floating-ui/floating-ui
|
15
|
-
* @param {(Node | HTMLElement | Element | globalThis)=} node
|
16
|
-
* @returns {Document}
|
17
|
-
*/
|
18
|
-
function getDocument(node) {
|
19
|
-
if (node instanceof HTMLElement) return node.ownerDocument;
|
20
|
-
if (node instanceof Window) return node.document;
|
21
|
-
return window.document;
|
22
|
-
}
|
23
|
-
|
24
|
-
/**
|
25
|
-
* A global array of possible `ParentNode`.
|
26
|
-
*/
|
27
|
-
const parentNodes = [Document, Element, HTMLElement];
|
28
|
-
|
29
|
-
/**
|
30
|
-
* A shortcut for `(document|Element).querySelectorAll`.
|
31
|
-
*
|
32
|
-
* @param {string} selector the input selector
|
33
|
-
* @param {(HTMLElement | Element | Document | Node)=} parent optional node to look into
|
34
|
-
* @return {NodeListOf<HTMLElement | Element>} the query result
|
35
|
-
*/
|
36
|
-
function querySelectorAll(selector, parent) {
|
37
|
-
const lookUp = parent && parentNodes
|
38
|
-
.some((x) => parent instanceof x) ? parent : getDocument();
|
39
|
-
// @ts-ignore -- `ShadowRoot` is also a node
|
40
|
-
return lookUp.querySelectorAll(selector);
|
41
|
-
}
|
42
|
-
|
43
12
|
/** @type {Record<string, any>} */
|
44
13
|
const EventRegistry = {};
|
45
14
|
|
@@ -136,6 +105,12 @@
|
|
136
105
|
}
|
137
106
|
};
|
138
107
|
|
108
|
+
/**
|
109
|
+
* A global namespace for aria-description.
|
110
|
+
* @type {string}
|
111
|
+
*/
|
112
|
+
const ariaDescription = 'aria-description';
|
113
|
+
|
139
114
|
/**
|
140
115
|
* A global namespace for aria-selected.
|
141
116
|
* @type {string}
|
@@ -148,6 +123,24 @@
|
|
148
123
|
*/
|
149
124
|
const ariaExpanded = 'aria-expanded';
|
150
125
|
|
126
|
+
/**
|
127
|
+
* A global namespace for aria-valuetext.
|
128
|
+
* @type {string}
|
129
|
+
*/
|
130
|
+
const ariaValueText = 'aria-valuetext';
|
131
|
+
|
132
|
+
/**
|
133
|
+
* A global namespace for aria-valuenow.
|
134
|
+
* @type {string}
|
135
|
+
*/
|
136
|
+
const ariaValueNow = 'aria-valuenow';
|
137
|
+
|
138
|
+
/**
|
139
|
+
* A global namespace for aria-haspopup.
|
140
|
+
* @type {string}
|
141
|
+
*/
|
142
|
+
const ariaHasPopup = 'aria-haspopup';
|
143
|
+
|
151
144
|
/**
|
152
145
|
* A global namespace for aria-hidden.
|
153
146
|
* @type {string}
|
@@ -202,6 +195,90 @@
|
|
202
195
|
*/
|
203
196
|
const keyEscape = 'Escape';
|
204
197
|
|
198
|
+
/**
|
199
|
+
* A global namespace for `focusin` event.
|
200
|
+
* @type {string}
|
201
|
+
*/
|
202
|
+
const focusinEvent = 'focusin';
|
203
|
+
|
204
|
+
/**
|
205
|
+
* A global namespace for `click` event.
|
206
|
+
* @type {string}
|
207
|
+
*/
|
208
|
+
const mouseclickEvent = 'click';
|
209
|
+
|
210
|
+
/**
|
211
|
+
* A global namespace for `keydown` event.
|
212
|
+
* @type {string}
|
213
|
+
*/
|
214
|
+
const keydownEvent = 'keydown';
|
215
|
+
|
216
|
+
/**
|
217
|
+
* A global namespace for `change` event.
|
218
|
+
* @type {string}
|
219
|
+
*/
|
220
|
+
const changeEvent = 'change';
|
221
|
+
|
222
|
+
/**
|
223
|
+
* A global namespace for `touchstart` event.
|
224
|
+
* @type {string}
|
225
|
+
*/
|
226
|
+
const touchstartEvent = 'touchstart';
|
227
|
+
|
228
|
+
/**
|
229
|
+
* A global namespace for `touchmove` event.
|
230
|
+
* @type {string}
|
231
|
+
*/
|
232
|
+
const touchmoveEvent = 'touchmove';
|
233
|
+
|
234
|
+
/**
|
235
|
+
* A global namespace for `touchend` event.
|
236
|
+
* @type {string}
|
237
|
+
*/
|
238
|
+
const touchendEvent = 'touchend';
|
239
|
+
|
240
|
+
/**
|
241
|
+
* A global namespace for `mousedown` event.
|
242
|
+
* @type {string}
|
243
|
+
*/
|
244
|
+
const mousedownEvent = 'mousedown';
|
245
|
+
|
246
|
+
/**
|
247
|
+
* A global namespace for `mousemove` event.
|
248
|
+
* @type {string}
|
249
|
+
*/
|
250
|
+
const mousemoveEvent = 'mousemove';
|
251
|
+
|
252
|
+
/**
|
253
|
+
* A global namespace for `mouseup` event.
|
254
|
+
* @type {string}
|
255
|
+
*/
|
256
|
+
const mouseupEvent = 'mouseup';
|
257
|
+
|
258
|
+
/**
|
259
|
+
* A global namespace for `scroll` event.
|
260
|
+
* @type {string}
|
261
|
+
*/
|
262
|
+
const scrollEvent = 'scroll';
|
263
|
+
|
264
|
+
/**
|
265
|
+
* A global namespace for `keyup` event.
|
266
|
+
* @type {string}
|
267
|
+
*/
|
268
|
+
const keyupEvent = 'keyup';
|
269
|
+
|
270
|
+
/**
|
271
|
+
* A global namespace for `resize` event.
|
272
|
+
* @type {string}
|
273
|
+
*/
|
274
|
+
const resizeEvent = 'resize';
|
275
|
+
|
276
|
+
/**
|
277
|
+
* A global namespace for `focusout` event.
|
278
|
+
* @type {string}
|
279
|
+
*/
|
280
|
+
const focusoutEvent = 'focusout';
|
281
|
+
|
205
282
|
// @ts-ignore
|
206
283
|
const { userAgentData: uaDATA } = navigator;
|
207
284
|
|
@@ -233,7 +310,70 @@
|
|
233
310
|
*/
|
234
311
|
const isMobile = isMobileCheck;
|
235
312
|
|
236
|
-
|
313
|
+
/**
|
314
|
+
* Returns the `document` or the `#document` element.
|
315
|
+
* @see https://github.com/floating-ui/floating-ui
|
316
|
+
* @param {(Node | HTMLElement | Element | globalThis)=} node
|
317
|
+
* @returns {Document}
|
318
|
+
*/
|
319
|
+
function getDocument(node) {
|
320
|
+
if (node instanceof HTMLElement) return node.ownerDocument;
|
321
|
+
if (node instanceof Window) return node.document;
|
322
|
+
return window.document;
|
323
|
+
}
|
324
|
+
|
325
|
+
/**
|
326
|
+
* Returns the `document.documentElement` or the `<html>` element.
|
327
|
+
*
|
328
|
+
* @param {(Node | HTMLElement | Element | globalThis)=} node
|
329
|
+
* @returns {HTMLElement | HTMLHtmlElement}
|
330
|
+
*/
|
331
|
+
function getDocumentElement(node) {
|
332
|
+
return getDocument(node).documentElement;
|
333
|
+
}
|
334
|
+
|
335
|
+
/**
|
336
|
+
* Returns the `Window` object of a target node.
|
337
|
+
* @see https://github.com/floating-ui/floating-ui
|
338
|
+
*
|
339
|
+
* @param {(Node | HTMLElement | Element | Window)=} node target node
|
340
|
+
* @returns {globalThis}
|
341
|
+
*/
|
342
|
+
function getWindow(node) {
|
343
|
+
if (node == null) {
|
344
|
+
return window;
|
345
|
+
}
|
346
|
+
|
347
|
+
if (!(node instanceof Window)) {
|
348
|
+
const { ownerDocument } = node;
|
349
|
+
return ownerDocument ? ownerDocument.defaultView || window : window;
|
350
|
+
}
|
351
|
+
|
352
|
+
// @ts-ignore
|
353
|
+
return node;
|
354
|
+
}
|
355
|
+
|
356
|
+
/**
|
357
|
+
* Shortcut for `window.getComputedStyle(element).propertyName`
|
358
|
+
* static method.
|
359
|
+
*
|
360
|
+
* * If `element` parameter is not an `HTMLElement`, `getComputedStyle`
|
361
|
+
* throws a `ReferenceError`.
|
362
|
+
*
|
363
|
+
* @param {HTMLElement | Element} element target
|
364
|
+
* @param {string} property the css property
|
365
|
+
* @return {string} the css property value
|
366
|
+
*/
|
367
|
+
function getElementStyle(element, property) {
|
368
|
+
const computedStyle = getComputedStyle(element);
|
369
|
+
|
370
|
+
// @ts-ignore -- must use camelcase strings,
|
371
|
+
// or non-camelcase strings with `getPropertyValue`
|
372
|
+
return property in computedStyle ? computedStyle[property] : '';
|
373
|
+
}
|
374
|
+
|
375
|
+
let elementUID = 0;
|
376
|
+
let elementMapUID = 0;
|
237
377
|
const elementIDMap = new Map();
|
238
378
|
|
239
379
|
/**
|
@@ -244,27 +384,25 @@
|
|
244
384
|
* @returns {number} an existing or new unique ID
|
245
385
|
*/
|
246
386
|
function getUID(element, key) {
|
247
|
-
elementUID
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
if (
|
253
|
-
|
254
|
-
if (!Number.isNaN(elMapId)) {
|
255
|
-
result = elMapId;
|
256
|
-
} else {
|
257
|
-
elMap.set(key, result);
|
258
|
-
}
|
259
|
-
} else {
|
260
|
-
elementIDMap.set(element, new Map());
|
261
|
-
elMap = elementIDMap.get(element);
|
262
|
-
elMap.set(key, result);
|
387
|
+
let result = key ? elementUID : elementMapUID;
|
388
|
+
|
389
|
+
if (key) {
|
390
|
+
const elID = getUID(element);
|
391
|
+
const elMap = elementIDMap.get(elID) || new Map();
|
392
|
+
if (!elementIDMap.has(elID)) {
|
393
|
+
elementIDMap.set(elID, elMap);
|
263
394
|
}
|
264
|
-
|
265
|
-
|
395
|
+
if (!elMap.has(key)) {
|
396
|
+
elMap.set(key, result);
|
397
|
+
elementUID += 1;
|
398
|
+
} else result = elMap.get(key);
|
266
399
|
} else {
|
267
|
-
|
400
|
+
const elkey = element.id || element;
|
401
|
+
|
402
|
+
if (!elementIDMap.has(elkey)) {
|
403
|
+
elementIDMap.set(elkey, result);
|
404
|
+
elementMapUID += 1;
|
405
|
+
} else result = elementIDMap.get(elkey);
|
268
406
|
}
|
269
407
|
return result;
|
270
408
|
}
|
@@ -303,6 +441,41 @@
|
|
303
441
|
};
|
304
442
|
}
|
305
443
|
|
444
|
+
/**
|
445
|
+
* A global namespace for 'transitionDuration' string.
|
446
|
+
* @type {string}
|
447
|
+
*/
|
448
|
+
const transitionDuration = 'transitionDuration';
|
449
|
+
|
450
|
+
/**
|
451
|
+
* A global namespace for `transitionProperty` string for modern browsers.
|
452
|
+
*
|
453
|
+
* @type {string}
|
454
|
+
*/
|
455
|
+
const transitionProperty = 'transitionProperty';
|
456
|
+
|
457
|
+
/**
|
458
|
+
* Utility to get the computed `transitionDuration`
|
459
|
+
* from Element in miliseconds.
|
460
|
+
*
|
461
|
+
* @param {HTMLElement | Element} element target
|
462
|
+
* @return {number} the value in miliseconds
|
463
|
+
*/
|
464
|
+
function getElementTransitionDuration(element) {
|
465
|
+
const propertyValue = getElementStyle(element, transitionProperty);
|
466
|
+
const durationValue = getElementStyle(element, transitionDuration);
|
467
|
+
const durationScale = durationValue.includes('ms') ? 1 : 1000;
|
468
|
+
const duration = propertyValue && propertyValue !== 'none'
|
469
|
+
? parseFloat(durationValue) * durationScale : 0;
|
470
|
+
|
471
|
+
return !Number.isNaN(duration) ? duration : 0;
|
472
|
+
}
|
473
|
+
|
474
|
+
/**
|
475
|
+
* A global array of possible `ParentNode`.
|
476
|
+
*/
|
477
|
+
const parentNodes = [Document, Element, HTMLElement];
|
478
|
+
|
306
479
|
/**
|
307
480
|
* A global array with `Element` | `HTMLElement`.
|
308
481
|
*/
|
@@ -343,6 +516,20 @@
|
|
343
516
|
|| closest(element.getRootNode().host, selector)) : null;
|
344
517
|
}
|
345
518
|
|
519
|
+
/**
|
520
|
+
* Shortcut for `HTMLElement.getElementsByClassName` method. Some `Node` elements
|
521
|
+
* like `ShadowRoot` do not support `getElementsByClassName`.
|
522
|
+
*
|
523
|
+
* @param {string} selector the class name
|
524
|
+
* @param {(HTMLElement | Element | Document)=} parent optional Element to look into
|
525
|
+
* @return {HTMLCollectionOf<HTMLElement | Element>} the 'HTMLCollection'
|
526
|
+
*/
|
527
|
+
function getElementsByClassName(selector, parent) {
|
528
|
+
const lookUp = parent && parentNodes.some((x) => parent instanceof x)
|
529
|
+
? parent : getDocument();
|
530
|
+
return lookUp.getElementsByClassName(selector);
|
531
|
+
}
|
532
|
+
|
346
533
|
/**
|
347
534
|
* Shortcut for `Object.assign()` static method.
|
348
535
|
* @param {Record<string, any>} obj a target object
|
@@ -480,6 +667,132 @@
|
|
480
667
|
*/
|
481
668
|
const getInstance = (target, component) => Data.get(target, component);
|
482
669
|
|
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
|
+
/**
|
678
|
+
* Shortcut for multiple uses of `HTMLElement.style.propertyName` method.
|
679
|
+
* @param {HTMLElement | Element} element target element
|
680
|
+
* @param {Partial<CSSStyleDeclaration>} styles attribute value
|
681
|
+
*/
|
682
|
+
// @ts-ignore
|
683
|
+
const setElementStyle = (element, styles) => ObjectAssign(element.style, styles);
|
684
|
+
|
685
|
+
/**
|
686
|
+
* Shortcut for `HTMLElement.getAttribute()` method.
|
687
|
+
* @param {HTMLElement | Element} element target element
|
688
|
+
* @param {string} attribute attribute name
|
689
|
+
* @returns {string?} attribute value
|
690
|
+
*/
|
691
|
+
const getAttribute = (element, attribute) => element.getAttribute(attribute);
|
692
|
+
|
693
|
+
/**
|
694
|
+
* The raw value or a given component option.
|
695
|
+
*
|
696
|
+
* @typedef {string | HTMLElement | Function | number | boolean | null} niceValue
|
697
|
+
*/
|
698
|
+
|
699
|
+
/**
|
700
|
+
* Utility to normalize component options
|
701
|
+
*
|
702
|
+
* @param {any} value the input value
|
703
|
+
* @return {niceValue} the normalized value
|
704
|
+
*/
|
705
|
+
function normalizeValue(value) {
|
706
|
+
if (value === 'true') { // boolean
|
707
|
+
return true;
|
708
|
+
}
|
709
|
+
|
710
|
+
if (value === 'false') { // boolean
|
711
|
+
return false;
|
712
|
+
}
|
713
|
+
|
714
|
+
if (!Number.isNaN(+value)) { // number
|
715
|
+
return +value;
|
716
|
+
}
|
717
|
+
|
718
|
+
if (value === '' || value === 'null') { // null
|
719
|
+
return null;
|
720
|
+
}
|
721
|
+
|
722
|
+
// string / function / HTMLElement / object
|
723
|
+
return value;
|
724
|
+
}
|
725
|
+
|
726
|
+
/**
|
727
|
+
* Shortcut for `String.toLowerCase()`.
|
728
|
+
*
|
729
|
+
* @param {string} source input string
|
730
|
+
* @returns {string} lowercase output string
|
731
|
+
*/
|
732
|
+
const toLowerCase = (source) => source.toLowerCase();
|
733
|
+
|
734
|
+
/**
|
735
|
+
* Utility to normalize component options.
|
736
|
+
*
|
737
|
+
* @param {HTMLElement | Element} element target
|
738
|
+
* @param {Record<string, any>} defaultOps component default options
|
739
|
+
* @param {Record<string, any>} inputOps component instance options
|
740
|
+
* @param {string=} ns component namespace
|
741
|
+
* @return {Record<string, any>} normalized component options object
|
742
|
+
*/
|
743
|
+
function normalizeOptions(element, defaultOps, inputOps, ns) {
|
744
|
+
// @ts-ignore -- our targets are always `HTMLElement`
|
745
|
+
const data = { ...element.dataset };
|
746
|
+
/** @type {Record<string, any>} */
|
747
|
+
const normalOps = {};
|
748
|
+
/** @type {Record<string, any>} */
|
749
|
+
const dataOps = {};
|
750
|
+
const title = 'title';
|
751
|
+
|
752
|
+
ObjectKeys(data).forEach((k) => {
|
753
|
+
const key = ns && k.includes(ns)
|
754
|
+
? k.replace(ns, '').replace(/[A-Z]/, (match) => toLowerCase(match))
|
755
|
+
: k;
|
756
|
+
|
757
|
+
dataOps[key] = normalizeValue(data[k]);
|
758
|
+
});
|
759
|
+
|
760
|
+
ObjectKeys(inputOps).forEach((k) => {
|
761
|
+
inputOps[k] = normalizeValue(inputOps[k]);
|
762
|
+
});
|
763
|
+
|
764
|
+
ObjectKeys(defaultOps).forEach((k) => {
|
765
|
+
if (k in inputOps) {
|
766
|
+
normalOps[k] = inputOps[k];
|
767
|
+
} else if (k in dataOps) {
|
768
|
+
normalOps[k] = dataOps[k];
|
769
|
+
} else {
|
770
|
+
normalOps[k] = k === title
|
771
|
+
? getAttribute(element, title)
|
772
|
+
: defaultOps[k];
|
773
|
+
}
|
774
|
+
});
|
775
|
+
|
776
|
+
return normalOps;
|
777
|
+
}
|
778
|
+
|
779
|
+
/**
|
780
|
+
* Utility to force re-paint of an `HTMLElement` target.
|
781
|
+
*
|
782
|
+
* @param {HTMLElement | Element} element is the target
|
783
|
+
* @return {number} the `Element.offsetHeight` value
|
784
|
+
*/
|
785
|
+
// @ts-ignore
|
786
|
+
const reflow = (element) => element.offsetHeight;
|
787
|
+
|
788
|
+
/**
|
789
|
+
* Utility to focus an `HTMLElement` target.
|
790
|
+
*
|
791
|
+
* @param {HTMLElement | Element} element is the target
|
792
|
+
*/
|
793
|
+
// @ts-ignore -- `Element`s resulted from querySelector can focus too
|
794
|
+
const focus = (element) => element.focus();
|
795
|
+
|
483
796
|
/**
|
484
797
|
* Check class in `HTMLElement.classList`.
|
485
798
|
*
|
@@ -513,14 +826,6 @@
|
|
513
826
|
element.classList.remove(classNAME);
|
514
827
|
}
|
515
828
|
|
516
|
-
/**
|
517
|
-
* Shortcut for `HTMLElement.hasAttribute()` method.
|
518
|
-
* @param {HTMLElement | Element} element target element
|
519
|
-
* @param {string} attribute attribute name
|
520
|
-
* @returns {boolean} the query result
|
521
|
-
*/
|
522
|
-
const hasAttribute = (element, attribute) => element.hasAttribute(attribute);
|
523
|
-
|
524
829
|
/**
|
525
830
|
* Shortcut for `HTMLElement.setAttribute()` method.
|
526
831
|
* @param {HTMLElement | Element} element target element
|
@@ -530,14 +835,6 @@
|
|
530
835
|
*/
|
531
836
|
const setAttribute = (element, attribute, value) => element.setAttribute(attribute, value);
|
532
837
|
|
533
|
-
/**
|
534
|
-
* Shortcut for `HTMLElement.getAttribute()` method.
|
535
|
-
* @param {HTMLElement | Element} element target element
|
536
|
-
* @param {string} attribute attribute name
|
537
|
-
* @returns {string?} attribute value
|
538
|
-
*/
|
539
|
-
const getAttribute = (element, attribute) => element.getAttribute(attribute);
|
540
|
-
|
541
838
|
/**
|
542
839
|
* Shortcut for `HTMLElement.removeAttribute()` method.
|
543
840
|
* @param {HTMLElement | Element} element target element
|
@@ -546,6 +843,38 @@
|
|
546
843
|
*/
|
547
844
|
const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
|
548
845
|
|
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
|
+
/**
|
868
|
+
* A list of 17 color names used for WAI-ARIA compliance.
|
869
|
+
* @type {string[]}
|
870
|
+
*/
|
871
|
+
const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
|
872
|
+
|
873
|
+
/**
|
874
|
+
* A list of explicit default non-color values.
|
875
|
+
*/
|
876
|
+
const nonColors = ['transparent', 'currentColor', 'inherit', 'revert', 'initial'];
|
877
|
+
|
549
878
|
/**
|
550
879
|
* Shortcut for `String.toUpperCase()`.
|
551
880
|
*
|
@@ -557,12 +886,13 @@
|
|
557
886
|
const vHidden = 'v-hidden';
|
558
887
|
|
559
888
|
/**
|
560
|
-
* Returns the color form
|
889
|
+
* Returns the color form for `ColorPicker`.
|
890
|
+
*
|
561
891
|
* @param {CP.ColorPicker} self the `ColorPicker` instance
|
562
|
-
* @returns {HTMLElement | Element}
|
892
|
+
* @returns {HTMLElement | Element} a new `<div>` element with color component `<input>`
|
563
893
|
*/
|
564
894
|
function getColorForm(self) {
|
565
|
-
const { format, id } = self;
|
895
|
+
const { format, id, componentLabels } = self;
|
566
896
|
const colorForm = createElement({
|
567
897
|
tagName: 'div',
|
568
898
|
className: `color-form ${format}`,
|
@@ -571,85 +901,174 @@
|
|
571
901
|
let components = ['hex'];
|
572
902
|
if (format === 'rgb') components = ['red', 'green', 'blue', 'alpha'];
|
573
903
|
else if (format === 'hsl') components = ['hue', 'saturation', 'lightness', 'alpha'];
|
904
|
+
else if (format === 'hwb') components = ['hue', 'whiteness', 'blackness', 'alpha'];
|
574
905
|
|
575
906
|
components.forEach((c) => {
|
576
907
|
const [C] = format === 'hex' ? ['#'] : toUpperCase(c).split('');
|
577
908
|
const cID = `color_${format}_${c}_${id}`;
|
909
|
+
const formatLabel = componentLabels[`${c}Label`];
|
578
910
|
const cInputLabel = createElement({ tagName: 'label' });
|
579
911
|
setAttribute(cInputLabel, 'for', cID);
|
580
912
|
cInputLabel.append(
|
581
913
|
createElement({ tagName: 'span', ariaHidden: 'true', innerText: `${C}:` }),
|
582
|
-
createElement({ tagName: 'span', className: vHidden, innerText:
|
914
|
+
createElement({ tagName: 'span', className: vHidden, innerText: formatLabel }),
|
583
915
|
);
|
584
916
|
const cInput = createElement({
|
585
917
|
tagName: 'input',
|
586
|
-
id: cID,
|
918
|
+
id: cID,
|
919
|
+
// name: cID, - prevent saving the value to a form
|
587
920
|
type: format === 'hex' ? 'text' : 'number',
|
588
|
-
value: c === 'alpha' ? '
|
921
|
+
value: c === 'alpha' ? '100' : '0',
|
589
922
|
className: `color-input ${c}`,
|
590
|
-
autocomplete: 'off',
|
591
|
-
spellcheck: 'false',
|
592
923
|
});
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
924
|
+
setAttribute(cInput, 'autocomplete', 'off');
|
925
|
+
setAttribute(cInput, 'spellcheck', 'false');
|
926
|
+
|
927
|
+
// alpha
|
928
|
+
let max = '100';
|
929
|
+
let step = '1';
|
930
|
+
if (c !== 'alpha') {
|
931
|
+
if (format === 'rgb') {
|
932
|
+
max = '255'; step = '1';
|
933
|
+
} else if (c === 'hue') {
|
934
|
+
max = '360'; step = '1';
|
599
935
|
}
|
600
|
-
ObjectAssign(cInput, {
|
601
|
-
min: '0',
|
602
|
-
max,
|
603
|
-
step,
|
604
|
-
});
|
605
936
|
}
|
937
|
+
ObjectAssign(cInput, {
|
938
|
+
min: '0',
|
939
|
+
max,
|
940
|
+
step,
|
941
|
+
});
|
606
942
|
colorForm.append(cInputLabel, cInput);
|
607
943
|
});
|
608
|
-
return colorForm;
|
944
|
+
return colorForm;
|
945
|
+
}
|
946
|
+
|
947
|
+
/**
|
948
|
+
* A global namespace for aria-label.
|
949
|
+
* @type {string}
|
950
|
+
*/
|
951
|
+
const ariaLabel = 'aria-label';
|
952
|
+
|
953
|
+
/**
|
954
|
+
* A global namespace for aria-valuemin.
|
955
|
+
* @type {string}
|
956
|
+
*/
|
957
|
+
const ariaValueMin = 'aria-valuemin';
|
958
|
+
|
959
|
+
/**
|
960
|
+
* A global namespace for aria-valuemax.
|
961
|
+
* @type {string}
|
962
|
+
*/
|
963
|
+
const ariaValueMax = 'aria-valuemax';
|
964
|
+
|
965
|
+
const tabIndex = 'tabindex';
|
966
|
+
|
967
|
+
/**
|
968
|
+
* Returns all color controls for `ColorPicker`.
|
969
|
+
*
|
970
|
+
* @param {CP.ColorPicker} self the `ColorPicker` instance
|
971
|
+
* @returns {HTMLElement | Element} color controls
|
972
|
+
*/
|
973
|
+
function getColorControls(self) {
|
974
|
+
const { format, componentLabels } = self;
|
975
|
+
const {
|
976
|
+
hueLabel, alphaLabel, lightnessLabel, saturationLabel,
|
977
|
+
whitenessLabel, blacknessLabel,
|
978
|
+
} = componentLabels;
|
979
|
+
|
980
|
+
const max1 = format === 'hsl' ? 360 : 100;
|
981
|
+
const max2 = format === 'hsl' ? 100 : 360;
|
982
|
+
const max3 = 100;
|
983
|
+
|
984
|
+
let ctrl1Label = format === 'hsl'
|
985
|
+
? `${hueLabel} & ${lightnessLabel}`
|
986
|
+
: `${lightnessLabel} & ${saturationLabel}`;
|
987
|
+
|
988
|
+
ctrl1Label = format === 'hwb'
|
989
|
+
? `${whitenessLabel} & ${blacknessLabel}`
|
990
|
+
: ctrl1Label;
|
991
|
+
|
992
|
+
const ctrl2Label = format === 'hsl'
|
993
|
+
? `${saturationLabel}`
|
994
|
+
: `${hueLabel}`;
|
995
|
+
|
996
|
+
const colorControls = createElement({
|
997
|
+
tagName: 'div',
|
998
|
+
className: `color-controls ${format}`,
|
999
|
+
});
|
1000
|
+
|
1001
|
+
const colorPointer = 'color-pointer';
|
1002
|
+
const colorSlider = 'color-slider';
|
1003
|
+
|
1004
|
+
const controls = [
|
1005
|
+
{
|
1006
|
+
i: 1,
|
1007
|
+
c: colorPointer,
|
1008
|
+
l: ctrl1Label,
|
1009
|
+
min: 0,
|
1010
|
+
max: max1,
|
1011
|
+
},
|
1012
|
+
{
|
1013
|
+
i: 2,
|
1014
|
+
c: colorSlider,
|
1015
|
+
l: ctrl2Label,
|
1016
|
+
min: 0,
|
1017
|
+
max: max2,
|
1018
|
+
},
|
1019
|
+
{
|
1020
|
+
i: 3,
|
1021
|
+
c: colorSlider,
|
1022
|
+
l: alphaLabel,
|
1023
|
+
min: 0,
|
1024
|
+
max: max3,
|
1025
|
+
},
|
1026
|
+
];
|
1027
|
+
|
1028
|
+
controls.forEach((template) => {
|
1029
|
+
const {
|
1030
|
+
i, c, l, min, max,
|
1031
|
+
} = template;
|
1032
|
+
const control = createElement({
|
1033
|
+
tagName: 'div',
|
1034
|
+
className: 'color-control',
|
1035
|
+
});
|
1036
|
+
setAttribute(control, 'role', 'presentation');
|
1037
|
+
|
1038
|
+
control.append(
|
1039
|
+
createElement({
|
1040
|
+
tagName: 'div',
|
1041
|
+
className: `visual-control visual-control${i}`,
|
1042
|
+
}),
|
1043
|
+
);
|
1044
|
+
|
1045
|
+
const knob = createElement({
|
1046
|
+
tagName: 'div',
|
1047
|
+
className: `${c} knob`,
|
1048
|
+
ariaLive: 'polite',
|
1049
|
+
});
|
1050
|
+
|
1051
|
+
setAttribute(knob, ariaLabel, l);
|
1052
|
+
setAttribute(knob, 'role', 'slider');
|
1053
|
+
setAttribute(knob, tabIndex, '0');
|
1054
|
+
setAttribute(knob, ariaValueMin, `${min}`);
|
1055
|
+
setAttribute(knob, ariaValueMax, `${max}`);
|
1056
|
+
control.append(knob);
|
1057
|
+
colorControls.append(control);
|
1058
|
+
});
|
1059
|
+
|
1060
|
+
return colorControls;
|
609
1061
|
}
|
610
1062
|
|
611
1063
|
/**
|
612
|
-
*
|
613
|
-
* @param {
|
614
|
-
* @param {
|
615
|
-
* @param {number} width
|
616
|
-
* @param {number} height
|
617
|
-
* @param {string=} labelledby
|
618
|
-
* @returns {HTMLElement | Element}
|
1064
|
+
* Helps setting CSS variables to the color-menu.
|
1065
|
+
* @param {HTMLElement} element
|
1066
|
+
* @param {Record<string,any>} props
|
619
1067
|
*/
|
620
|
-
function
|
621
|
-
|
622
|
-
|
623
|
-
const control = createElement({
|
624
|
-
tagName: 'div',
|
625
|
-
className: 'color-control',
|
626
|
-
});
|
627
|
-
setAttribute(control, 'role', 'presentation');
|
628
|
-
|
629
|
-
control.append(
|
630
|
-
createElement({
|
631
|
-
id: labelID,
|
632
|
-
tagName: 'label',
|
633
|
-
className: `color-label ${vHidden}`,
|
634
|
-
ariaLive: 'polite',
|
635
|
-
}),
|
636
|
-
createElement({
|
637
|
-
tagName: 'canvas',
|
638
|
-
className: `visual-control${iteration}`,
|
639
|
-
ariaHidden: 'true',
|
640
|
-
width: `${width}`,
|
641
|
-
height: `${height}`,
|
642
|
-
}),
|
643
|
-
);
|
644
|
-
|
645
|
-
const knob = createElement({
|
646
|
-
tagName: 'div',
|
647
|
-
className: `${knobClass} knob`,
|
1068
|
+
function setCSSProperties(element, props) {
|
1069
|
+
ObjectKeys(props).forEach((prop) => {
|
1070
|
+
element.style.setProperty(prop, props[prop]);
|
648
1071
|
});
|
649
|
-
setAttribute(knob, ariaLabelledBy, labelledby || labelID);
|
650
|
-
setAttribute(knob, 'tabindex', '0');
|
651
|
-
control.append(knob);
|
652
|
-
return control;
|
653
1072
|
}
|
654
1073
|
|
655
1074
|
/**
|
@@ -663,211 +1082,49 @@
|
|
663
1082
|
}
|
664
1083
|
|
665
1084
|
/**
|
666
|
-
*
|
667
|
-
*
|
668
|
-
*
|
669
|
-
* * If `element` parameter is not an `HTMLElement`, `getComputedStyle`
|
670
|
-
* throws a `ReferenceError`.
|
671
|
-
*
|
672
|
-
* @param {HTMLElement | Element} element target
|
673
|
-
* @param {string} property the css property
|
674
|
-
* @return {string} the css property value
|
1085
|
+
* Round colour components, for all formats except HEX.
|
1086
|
+
* @param {number} v one of the colour components
|
1087
|
+
* @returns {number} the rounded number
|
675
1088
|
*/
|
676
|
-
function
|
677
|
-
const
|
678
|
-
|
679
|
-
// @ts-ignore -- must use camelcase strings,
|
680
|
-
// or non-camelcase strings with `getPropertyValue`
|
681
|
-
return property in computedStyle ? computedStyle[property] : '';
|
1089
|
+
function roundPart(v) {
|
1090
|
+
const floor = Math.floor(v);
|
1091
|
+
return v - floor < 0.5 ? floor : Math.round(v);
|
682
1092
|
}
|
683
1093
|
|
684
|
-
|
685
|
-
|
686
|
-
* @param {HTMLElement | Element} element target element
|
687
|
-
* @param {Partial<CSSStyleDeclaration>} styles attribute value
|
688
|
-
*/
|
689
|
-
// @ts-ignore
|
690
|
-
const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
|
1094
|
+
// Color supported formats
|
1095
|
+
const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
|
691
1096
|
|
692
|
-
|
693
|
-
|
694
|
-
* @see https://github.com/bahamas10/css-color-names/blob/master/css-color-names.json
|
695
|
-
* @type {string[]}
|
696
|
-
*/
|
697
|
-
const colorNames = [
|
698
|
-
'aliceblue',
|
699
|
-
'antiquewhite',
|
700
|
-
'aqua',
|
701
|
-
'aquamarine',
|
702
|
-
'azure',
|
703
|
-
'beige',
|
704
|
-
'bisque',
|
705
|
-
'black',
|
706
|
-
'blanchedalmond',
|
707
|
-
'blue',
|
708
|
-
'blueviolet',
|
709
|
-
'brown',
|
710
|
-
'burlywood',
|
711
|
-
'cadetblue',
|
712
|
-
'chartreuse',
|
713
|
-
'chocolate',
|
714
|
-
'coral',
|
715
|
-
'cornflowerblue',
|
716
|
-
'cornsilk',
|
717
|
-
'crimson',
|
718
|
-
'cyan',
|
719
|
-
'darkblue',
|
720
|
-
'darkcyan',
|
721
|
-
'darkgoldenrod',
|
722
|
-
'darkgray',
|
723
|
-
'darkgreen',
|
724
|
-
'darkgrey',
|
725
|
-
'darkkhaki',
|
726
|
-
'darkmagenta',
|
727
|
-
'darkolivegreen',
|
728
|
-
'darkorange',
|
729
|
-
'darkorchid',
|
730
|
-
'darkred',
|
731
|
-
'darksalmon',
|
732
|
-
'darkseagreen',
|
733
|
-
'darkslateblue',
|
734
|
-
'darkslategray',
|
735
|
-
'darkslategrey',
|
736
|
-
'darkturquoise',
|
737
|
-
'darkviolet',
|
738
|
-
'deeppink',
|
739
|
-
'deepskyblue',
|
740
|
-
'dimgray',
|
741
|
-
'dimgrey',
|
742
|
-
'dodgerblue',
|
743
|
-
'firebrick',
|
744
|
-
'floralwhite',
|
745
|
-
'forestgreen',
|
746
|
-
'fuchsia',
|
747
|
-
'gainsboro',
|
748
|
-
'ghostwhite',
|
749
|
-
'goldenrod',
|
750
|
-
'gold',
|
751
|
-
'gray',
|
752
|
-
'green',
|
753
|
-
'greenyellow',
|
754
|
-
'grey',
|
755
|
-
'honeydew',
|
756
|
-
'hotpink',
|
757
|
-
'indianred',
|
758
|
-
'indigo',
|
759
|
-
'ivory',
|
760
|
-
'khaki',
|
761
|
-
'lavenderblush',
|
762
|
-
'lavender',
|
763
|
-
'lawngreen',
|
764
|
-
'lemonchiffon',
|
765
|
-
'lightblue',
|
766
|
-
'lightcoral',
|
767
|
-
'lightcyan',
|
768
|
-
'lightgoldenrodyellow',
|
769
|
-
'lightgray',
|
770
|
-
'lightgreen',
|
771
|
-
'lightgrey',
|
772
|
-
'lightpink',
|
773
|
-
'lightsalmon',
|
774
|
-
'lightseagreen',
|
775
|
-
'lightskyblue',
|
776
|
-
'lightslategray',
|
777
|
-
'lightslategrey',
|
778
|
-
'lightsteelblue',
|
779
|
-
'lightyellow',
|
780
|
-
'lime',
|
781
|
-
'limegreen',
|
782
|
-
'linen',
|
783
|
-
'magenta',
|
784
|
-
'maroon',
|
785
|
-
'mediumaquamarine',
|
786
|
-
'mediumblue',
|
787
|
-
'mediumorchid',
|
788
|
-
'mediumpurple',
|
789
|
-
'mediumseagreen',
|
790
|
-
'mediumslateblue',
|
791
|
-
'mediumspringgreen',
|
792
|
-
'mediumturquoise',
|
793
|
-
'mediumvioletred',
|
794
|
-
'midnightblue',
|
795
|
-
'mintcream',
|
796
|
-
'mistyrose',
|
797
|
-
'moccasin',
|
798
|
-
'navajowhite',
|
799
|
-
'navy',
|
800
|
-
'oldlace',
|
801
|
-
'olive',
|
802
|
-
'olivedrab',
|
803
|
-
'orange',
|
804
|
-
'orangered',
|
805
|
-
'orchid',
|
806
|
-
'palegoldenrod',
|
807
|
-
'palegreen',
|
808
|
-
'paleturquoise',
|
809
|
-
'palevioletred',
|
810
|
-
'papayawhip',
|
811
|
-
'peachpuff',
|
812
|
-
'peru',
|
813
|
-
'pink',
|
814
|
-
'plum',
|
815
|
-
'powderblue',
|
816
|
-
'purple',
|
817
|
-
'rebeccapurple',
|
818
|
-
'red',
|
819
|
-
'rosybrown',
|
820
|
-
'royalblue',
|
821
|
-
'saddlebrown',
|
822
|
-
'salmon',
|
823
|
-
'sandybrown',
|
824
|
-
'seagreen',
|
825
|
-
'seashell',
|
826
|
-
'sienna',
|
827
|
-
'silver',
|
828
|
-
'skyblue',
|
829
|
-
'slateblue',
|
830
|
-
'slategray',
|
831
|
-
'slategrey',
|
832
|
-
'snow',
|
833
|
-
'springgreen',
|
834
|
-
'steelblue',
|
835
|
-
'tan',
|
836
|
-
'teal',
|
837
|
-
'thistle',
|
838
|
-
'tomato',
|
839
|
-
'turquoise',
|
840
|
-
'violet',
|
841
|
-
'wheat',
|
842
|
-
'white',
|
843
|
-
'whitesmoke',
|
844
|
-
'yellow',
|
845
|
-
'yellowgreen',
|
846
|
-
];
|
1097
|
+
// Hue angles
|
1098
|
+
const ANGLES = 'deg|rad|grad|turn';
|
847
1099
|
|
848
1100
|
// <http://www.w3.org/TR/css3-values/#integers>
|
849
1101
|
const CSS_INTEGER = '[-\\+]?\\d+%?';
|
850
1102
|
|
1103
|
+
// Include CSS3 Module
|
851
1104
|
// <http://www.w3.org/TR/css3-values/#number-value>
|
852
1105
|
const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
|
853
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
|
+
|
854
1111
|
// Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
|
855
1112
|
const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
|
856
1113
|
|
1114
|
+
// Add angles to the mix
|
1115
|
+
const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
|
1116
|
+
|
857
1117
|
// Actual matching.
|
858
1118
|
// Parentheses and commas are optional, but not required.
|
859
1119
|
// Whitespace can take the place of commas or opening paren
|
860
|
-
const
|
861
|
-
const PERMISSIVE_MATCH4 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?`;
|
1120
|
+
const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
|
862
1121
|
|
863
1122
|
const matchers = {
|
864
|
-
CSS_UNIT: new RegExp(
|
865
|
-
|
866
|
-
|
867
|
-
hsl: new RegExp(`hsl
|
868
|
-
|
869
|
-
hsv: new RegExp(`hsv${PERMISSIVE_MATCH3}`),
|
870
|
-
hsva: new RegExp(`hsva${PERMISSIVE_MATCH4}`),
|
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}`),
|
871
1128
|
hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
872
1129
|
hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
|
873
1130
|
hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
@@ -877,27 +1134,46 @@
|
|
877
1134
|
/**
|
878
1135
|
* Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
|
879
1136
|
* <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
|
880
|
-
* @param {string} n
|
881
|
-
* @returns {boolean}
|
1137
|
+
* @param {string} n testing number
|
1138
|
+
* @returns {boolean} the query result
|
882
1139
|
*/
|
883
1140
|
function isOnePointZero(n) {
|
884
|
-
return
|
1141
|
+
return `${n}`.includes('.') && parseFloat(n) === 1;
|
885
1142
|
}
|
886
1143
|
|
887
1144
|
/**
|
888
1145
|
* Check to see if string passed in is a percentage
|
889
|
-
* @param {string} n
|
890
|
-
* @returns {boolean}
|
1146
|
+
* @param {string} n testing number
|
1147
|
+
* @returns {boolean} the query result
|
891
1148
|
*/
|
892
1149
|
function isPercentage(n) {
|
893
|
-
return
|
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
|
+
}
|
1161
|
+
|
1162
|
+
/**
|
1163
|
+
* Check to see if string passed is a web safe colour.
|
1164
|
+
* @param {string} color a colour name, EG: *red*
|
1165
|
+
* @returns {boolean} the query result
|
1166
|
+
*/
|
1167
|
+
function isColorName(color) {
|
1168
|
+
return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
|
1169
|
+
&& !/[0-9]/.test(color);
|
894
1170
|
}
|
895
1171
|
|
896
1172
|
/**
|
897
1173
|
* Check to see if it looks like a CSS unit
|
898
1174
|
* (see `matchers` above for definition).
|
899
|
-
* @param {string | number} color
|
900
|
-
* @returns {boolean}
|
1175
|
+
* @param {string | number} color testing value
|
1176
|
+
* @returns {boolean} the query result
|
901
1177
|
*/
|
902
1178
|
function isValidCSSUnit(color) {
|
903
1179
|
return Boolean(matchers.CSS_UNIT.exec(String(color)));
|
@@ -905,22 +1181,24 @@
|
|
905
1181
|
|
906
1182
|
/**
|
907
1183
|
* Take input from [0, n] and return it as [0, 1]
|
908
|
-
* @param {*}
|
909
|
-
* @param {number} max
|
910
|
-
* @returns {number}
|
1184
|
+
* @param {*} N the input number
|
1185
|
+
* @param {number} max the number maximum value
|
1186
|
+
* @returns {number} the number in [0, 1] value range
|
911
1187
|
*/
|
912
|
-
function bound01(
|
913
|
-
let
|
914
|
-
if (isOnePointZero(n))
|
1188
|
+
function bound01(N, max) {
|
1189
|
+
let n = N;
|
1190
|
+
if (isOnePointZero(n)) n = '100%';
|
915
1191
|
|
916
|
-
|
1192
|
+
n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
|
1193
|
+
|
1194
|
+
// Handle hue angles
|
1195
|
+
if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
|
917
1196
|
|
918
1197
|
// Automatically convert percentage into number
|
919
|
-
if (isPercentage(
|
920
|
-
|
921
|
-
}
|
1198
|
+
if (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
|
1199
|
+
|
922
1200
|
// Handle floating point rounding errors
|
923
|
-
if (Math.abs(
|
1201
|
+
if (Math.abs(n - max) < 0.000001) {
|
924
1202
|
return 1;
|
925
1203
|
}
|
926
1204
|
// Convert into [0, 1] range if it isn't already
|
@@ -928,23 +1206,22 @@
|
|
928
1206
|
// If n is a hue given in degrees,
|
929
1207
|
// wrap around out-of-range values into [0, 360] range
|
930
1208
|
// then convert into [0, 1].
|
931
|
-
|
1209
|
+
n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max));
|
932
1210
|
} else {
|
933
1211
|
// If n not a hue given in degrees
|
934
1212
|
// Convert into [0, 1] range if it isn't already.
|
935
|
-
|
1213
|
+
n = (n % max) / parseFloat(String(max));
|
936
1214
|
}
|
937
|
-
return
|
1215
|
+
return n;
|
938
1216
|
}
|
939
1217
|
|
940
1218
|
/**
|
941
1219
|
* Return a valid alpha value [0,1] with all invalid values being set to 1.
|
942
|
-
* @param {string | number} a
|
943
|
-
* @returns {number}
|
1220
|
+
* @param {string | number} a transparency value
|
1221
|
+
* @returns {number} a transparency value in the [0, 1] range
|
944
1222
|
*/
|
945
1223
|
function boundAlpha(a) {
|
946
|
-
|
947
|
-
let na = parseFloat(a);
|
1224
|
+
let na = parseFloat(`${a}`);
|
948
1225
|
|
949
1226
|
if (Number.isNaN(na) || na < 0 || na > 1) {
|
950
1227
|
na = 1;
|
@@ -954,12 +1231,12 @@
|
|
954
1231
|
}
|
955
1232
|
|
956
1233
|
/**
|
957
|
-
* Force a number between 0 and 1
|
958
|
-
* @param {number}
|
959
|
-
* @returns {number}
|
1234
|
+
* Force a number between 0 and 1.
|
1235
|
+
* @param {number} v the float number
|
1236
|
+
* @returns {number} - the resulting number
|
960
1237
|
*/
|
961
|
-
function clamp01(
|
962
|
-
return Math.min(1, Math.max(0,
|
1238
|
+
function clamp01(v) {
|
1239
|
+
return Math.min(1, Math.max(0, v));
|
963
1240
|
}
|
964
1241
|
|
965
1242
|
/**
|
@@ -967,7 +1244,7 @@
|
|
967
1244
|
* @param {string} name
|
968
1245
|
* @returns {string}
|
969
1246
|
*/
|
970
|
-
function
|
1247
|
+
function getRGBFromName(name) {
|
971
1248
|
const documentHead = getDocumentHead();
|
972
1249
|
setElementStyle(documentHead, { color: name });
|
973
1250
|
const colorName = getElementStyle(documentHead, 'color');
|
@@ -976,59 +1253,53 @@
|
|
976
1253
|
}
|
977
1254
|
|
978
1255
|
/**
|
979
|
-
*
|
980
|
-
* @param {number
|
981
|
-
* @
|
1256
|
+
* Converts a decimal value to hexadecimal.
|
1257
|
+
* @param {number} d the input number
|
1258
|
+
* @returns {string} - the hexadecimal value
|
982
1259
|
*/
|
983
|
-
function
|
984
|
-
|
985
|
-
return `${Number(n) * 100}%`;
|
986
|
-
}
|
987
|
-
return n;
|
1260
|
+
function convertDecimalToHex(d) {
|
1261
|
+
return roundPart(d * 255).toString(16);
|
988
1262
|
}
|
989
1263
|
|
990
1264
|
/**
|
991
|
-
*
|
992
|
-
* @param {string}
|
993
|
-
* @returns {
|
1265
|
+
* Converts a hexadecimal value to decimal.
|
1266
|
+
* @param {string} h hexadecimal value
|
1267
|
+
* @returns {number} number in decimal format
|
994
1268
|
*/
|
995
|
-
function
|
996
|
-
return
|
1269
|
+
function convertHexToDecimal(h) {
|
1270
|
+
return parseIntFromHex(h) / 255;
|
997
1271
|
}
|
998
1272
|
|
999
|
-
// `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
|
1000
|
-
// <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
|
1001
1273
|
/**
|
1002
|
-
*
|
1003
|
-
*
|
1004
|
-
*
|
1005
|
-
* @see http://www.w3.org/TR/css3-color/
|
1006
|
-
* @param {number | string} r
|
1007
|
-
* @param {number | string} g
|
1008
|
-
* @param {number | string} b
|
1009
|
-
* @returns {CP.RGB}
|
1274
|
+
* Converts a base-16 hexadecimal value into a base-10 integer.
|
1275
|
+
* @param {string} val
|
1276
|
+
* @returns {number}
|
1010
1277
|
*/
|
1011
|
-
function
|
1012
|
-
return
|
1013
|
-
r: bound01(r, 255) * 255,
|
1014
|
-
g: bound01(g, 255) * 255,
|
1015
|
-
b: bound01(b, 255) * 255,
|
1016
|
-
};
|
1278
|
+
function parseIntFromHex(val) {
|
1279
|
+
return parseInt(val, 16);
|
1017
1280
|
}
|
1018
1281
|
|
1019
1282
|
/**
|
1020
|
-
*
|
1021
|
-
*
|
1022
|
-
*
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1283
|
+
* Force a hexadecimal value to have 2 characters.
|
1284
|
+
* @param {string} c string with [0-9A-F] ranged values
|
1285
|
+
* @returns {string} 0 => 00, a => 0a
|
1286
|
+
*/
|
1287
|
+
function pad2(c) {
|
1288
|
+
return c.length === 1 ? `0${c}` : String(c);
|
1289
|
+
}
|
1290
|
+
|
1291
|
+
/**
|
1292
|
+
* Converts an RGB colour value to HSL.
|
1293
|
+
*
|
1294
|
+
* @param {number} R Red component [0, 255]
|
1295
|
+
* @param {number} G Green component [0, 255]
|
1296
|
+
* @param {number} B Blue component [0, 255]
|
1297
|
+
* @returns {CP.HSL} {h,s,l} object with [0, 1] ranged values
|
1027
1298
|
*/
|
1028
1299
|
function rgbToHsl(R, G, B) {
|
1029
|
-
const r =
|
1030
|
-
const g =
|
1031
|
-
const b =
|
1300
|
+
const r = R / 255;
|
1301
|
+
const g = G / 255;
|
1302
|
+
const b = B / 255;
|
1032
1303
|
const max = Math.max(r, g, b);
|
1033
1304
|
const min = Math.min(r, g, b);
|
1034
1305
|
let h = 0;
|
@@ -1058,50 +1329,95 @@
|
|
1058
1329
|
|
1059
1330
|
/**
|
1060
1331
|
* Returns a normalized RGB component value.
|
1061
|
-
* @param {number}
|
1062
|
-
* @param {number}
|
1063
|
-
* @param {number}
|
1332
|
+
* @param {number} p
|
1333
|
+
* @param {number} q
|
1334
|
+
* @param {number} t
|
1064
1335
|
* @returns {number}
|
1065
1336
|
*/
|
1066
|
-
function
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
if (
|
1071
|
-
|
1072
|
-
|
1073
|
-
if (t > 1) {
|
1074
|
-
t -= 1;
|
1075
|
-
}
|
1076
|
-
if (t < 1 / 6) {
|
1077
|
-
return p + (q - p) * (6 * t);
|
1078
|
-
}
|
1079
|
-
if (t < 1 / 2) {
|
1080
|
-
return q;
|
1081
|
-
}
|
1082
|
-
if (t < 2 / 3) {
|
1083
|
-
return p + (q - p) * (2 / 3 - t) * 6;
|
1084
|
-
}
|
1337
|
+
function hueToRgb(p, q, t) {
|
1338
|
+
let T = t;
|
1339
|
+
if (T < 0) T += 1;
|
1340
|
+
if (T > 1) T -= 1;
|
1341
|
+
if (T < 1 / 6) return p + (q - p) * (6 * T);
|
1342
|
+
if (T < 1 / 2) return q;
|
1343
|
+
if (T < 2 / 3) return p + (q - p) * (2 / 3 - T) * 6;
|
1085
1344
|
return p;
|
1086
1345
|
}
|
1087
1346
|
|
1347
|
+
/**
|
1348
|
+
* Returns an HWB colour object from an RGB colour object.
|
1349
|
+
* @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
|
1350
|
+
* @link http://alvyray.com/Papers/CG/hwb2rgb.htm
|
1351
|
+
*
|
1352
|
+
* @param {number} R Red component [0, 255]
|
1353
|
+
* @param {number} G Green [0, 255]
|
1354
|
+
* @param {number} B Blue [0, 255]
|
1355
|
+
* @return {CP.HWB} {h,w,b} object with [0, 1] ranged values
|
1356
|
+
*/
|
1357
|
+
function rgbToHwb(R, G, B) {
|
1358
|
+
const r = R / 255;
|
1359
|
+
const g = G / 255;
|
1360
|
+
const b = B / 255;
|
1361
|
+
|
1362
|
+
let f = 0;
|
1363
|
+
let i = 0;
|
1364
|
+
const whiteness = Math.min(r, g, b);
|
1365
|
+
const max = Math.max(r, g, b);
|
1366
|
+
const black = 1 - max;
|
1367
|
+
|
1368
|
+
if (max === whiteness) return { h: 0, w: whiteness, b: black };
|
1369
|
+
if (r === whiteness) {
|
1370
|
+
f = g - b;
|
1371
|
+
i = 3;
|
1372
|
+
} else {
|
1373
|
+
f = g === whiteness ? b - r : r - g;
|
1374
|
+
i = g === whiteness ? 5 : 1;
|
1375
|
+
}
|
1376
|
+
|
1377
|
+
const h = (i - f / (max - whiteness)) / 6;
|
1378
|
+
return {
|
1379
|
+
h: h === 1 ? 0 : h,
|
1380
|
+
w: whiteness,
|
1381
|
+
b: black,
|
1382
|
+
};
|
1383
|
+
}
|
1384
|
+
|
1385
|
+
/**
|
1386
|
+
* Returns an RGB colour object from an HWB colour.
|
1387
|
+
*
|
1388
|
+
* @param {number} H Hue Angle [0, 1]
|
1389
|
+
* @param {number} W Whiteness [0, 1]
|
1390
|
+
* @param {number} B Blackness [0, 1]
|
1391
|
+
* @return {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
1392
|
+
*
|
1393
|
+
* @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
|
1394
|
+
* @link http://alvyray.com/Papers/CG/hwb2rgb.htm
|
1395
|
+
*/
|
1396
|
+
function hwbToRgb(H, W, B) {
|
1397
|
+
if (W + B >= 1) {
|
1398
|
+
const gray = (W / (W + B)) * 255;
|
1399
|
+
return { r: gray, g: gray, b: gray };
|
1400
|
+
}
|
1401
|
+
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
|
+
|
1088
1409
|
/**
|
1089
1410
|
* Converts an HSL colour value to RGB.
|
1090
1411
|
*
|
1091
|
-
*
|
1092
|
-
*
|
1093
|
-
* @param {number
|
1094
|
-
* @
|
1095
|
-
|
1096
|
-
|
1097
|
-
*/
|
1098
|
-
function hslToRgb(H, S, L) {
|
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) {
|
1099
1418
|
let r = 0;
|
1100
1419
|
let g = 0;
|
1101
1420
|
let b = 0;
|
1102
|
-
const h = bound01(H, 360);
|
1103
|
-
const s = bound01(S, 100);
|
1104
|
-
const l = bound01(L, 100);
|
1105
1421
|
|
1106
1422
|
if (s === 0) {
|
1107
1423
|
// achromatic
|
@@ -1111,27 +1427,27 @@
|
|
1111
1427
|
} else {
|
1112
1428
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
1113
1429
|
const p = 2 * l - q;
|
1114
|
-
r =
|
1115
|
-
g =
|
1116
|
-
b =
|
1430
|
+
r = hueToRgb(p, q, h + 1 / 3);
|
1431
|
+
g = hueToRgb(p, q, h);
|
1432
|
+
b = hueToRgb(p, q, h - 1 / 3);
|
1117
1433
|
}
|
1118
|
-
|
1434
|
+
[r, g, b] = [r, g, b].map((x) => x * 255);
|
1435
|
+
|
1436
|
+
return { r, g, b };
|
1119
1437
|
}
|
1120
1438
|
|
1121
1439
|
/**
|
1122
1440
|
* Converts an RGB colour value to HSV.
|
1123
1441
|
*
|
1124
|
-
*
|
1125
|
-
*
|
1126
|
-
* @param {number
|
1127
|
-
* @
|
1128
|
-
* @param {number | string} B
|
1129
|
-
* @returns {CP.HSV}
|
1442
|
+
* @param {number} R Red component [0, 255]
|
1443
|
+
* @param {number} G Green [0, 255]
|
1444
|
+
* @param {number} B Blue [0, 255]
|
1445
|
+
* @returns {CP.HSV} {h,s,v} object with [0, 1] ranged values
|
1130
1446
|
*/
|
1131
1447
|
function rgbToHsv(R, G, B) {
|
1132
|
-
const r =
|
1133
|
-
const g =
|
1134
|
-
const b =
|
1448
|
+
const r = R / 255;
|
1449
|
+
const g = G / 255;
|
1450
|
+
const b = B / 255;
|
1135
1451
|
const max = Math.max(r, g, b);
|
1136
1452
|
const min = Math.min(r, g, b);
|
1137
1453
|
let h = 0;
|
@@ -1158,19 +1474,17 @@
|
|
1158
1474
|
}
|
1159
1475
|
|
1160
1476
|
/**
|
1161
|
-
* Converts an HSV
|
1477
|
+
* Converts an HSV colour value to RGB.
|
1162
1478
|
*
|
1163
|
-
*
|
1164
|
-
*
|
1165
|
-
* @param {number
|
1166
|
-
* @
|
1167
|
-
* @param {number | string} V
|
1168
|
-
* @returns {CP.RGB}
|
1479
|
+
* @param {number} H Hue Angle [0, 1]
|
1480
|
+
* @param {number} S Saturation [0, 1]
|
1481
|
+
* @param {number} V Brightness Angle [0, 1]
|
1482
|
+
* @returns {CP.RGB} {r,g,b} object with [0, 1] ranged values
|
1169
1483
|
*/
|
1170
1484
|
function hsvToRgb(H, S, V) {
|
1171
|
-
const h =
|
1172
|
-
const s =
|
1173
|
-
const v =
|
1485
|
+
const h = H * 6;
|
1486
|
+
const s = S;
|
1487
|
+
const v = V;
|
1174
1488
|
const i = Math.floor(h);
|
1175
1489
|
const f = h - i;
|
1176
1490
|
const p = v * (1 - s);
|
@@ -1184,47 +1498,65 @@
|
|
1184
1498
|
}
|
1185
1499
|
|
1186
1500
|
/**
|
1187
|
-
* Converts an RGB
|
1501
|
+
* Converts an RGB colour to hex
|
1188
1502
|
*
|
1189
1503
|
* Assumes r, g, and b are contained in the set [0, 255]
|
1190
1504
|
* Returns a 3 or 6 character hex
|
1191
|
-
* @param {number} r
|
1192
|
-
* @param {number} g
|
1193
|
-
* @param {number} b
|
1505
|
+
* @param {number} r Red component [0, 255]
|
1506
|
+
* @param {number} g Green [0, 255]
|
1507
|
+
* @param {number} b Blue [0, 255]
|
1508
|
+
* @param {boolean=} allow3Char
|
1194
1509
|
* @returns {string}
|
1195
1510
|
*/
|
1196
|
-
function rgbToHex(r, g, b) {
|
1511
|
+
function rgbToHex(r, g, b, allow3Char) {
|
1197
1512
|
const hex = [
|
1198
|
-
pad2(
|
1199
|
-
pad2(
|
1200
|
-
pad2(
|
1513
|
+
pad2(roundPart(r).toString(16)),
|
1514
|
+
pad2(roundPart(g).toString(16)),
|
1515
|
+
pad2(roundPart(b).toString(16)),
|
1201
1516
|
];
|
1202
1517
|
|
1518
|
+
// Return a 3 character hex if possible
|
1519
|
+
if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
|
1520
|
+
&& hex[1].charAt(0) === hex[1].charAt(1)
|
1521
|
+
&& hex[2].charAt(0) === hex[2].charAt(1)) {
|
1522
|
+
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
|
1523
|
+
}
|
1524
|
+
|
1203
1525
|
return hex.join('');
|
1204
1526
|
}
|
1205
1527
|
|
1206
1528
|
/**
|
1207
|
-
* Converts
|
1208
|
-
*
|
1209
|
-
* @
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1529
|
+
* Converts an RGBA color plus alpha transparency to hex8.
|
1530
|
+
*
|
1531
|
+
* @param {number} r Red component [0, 255]
|
1532
|
+
* @param {number} g Green [0, 255]
|
1533
|
+
* @param {number} b Blue [0, 255]
|
1534
|
+
* @param {number} a Alpha transparency [0, 1]
|
1535
|
+
* @param {boolean=} allow4Char when *true* it will also find hex shorthand
|
1536
|
+
* @returns {string} a hexadecimal value with alpha transparency
|
1537
|
+
*/
|
1538
|
+
function rgbaToHex(r, g, b, a, allow4Char) {
|
1539
|
+
const hex = [
|
1540
|
+
pad2(roundPart(r).toString(16)),
|
1541
|
+
pad2(roundPart(g).toString(16)),
|
1542
|
+
pad2(roundPart(b).toString(16)),
|
1543
|
+
pad2(convertDecimalToHex(a)),
|
1544
|
+
];
|
1214
1545
|
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1546
|
+
// Return a 4 character hex if possible
|
1547
|
+
if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
|
1548
|
+
&& hex[1].charAt(0) === hex[1].charAt(1)
|
1549
|
+
&& hex[2].charAt(0) === hex[2].charAt(1)
|
1550
|
+
&& hex[3].charAt(0) === hex[3].charAt(1)) {
|
1551
|
+
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
|
1552
|
+
}
|
1553
|
+
return hex.join('');
|
1222
1554
|
}
|
1223
1555
|
|
1224
1556
|
/**
|
1225
|
-
* Returns
|
1226
|
-
* @param {number} color
|
1227
|
-
* @returns {CP.RGB}
|
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
|
1228
1560
|
*/
|
1229
1561
|
function numberInputToObject(color) {
|
1230
1562
|
/* eslint-disable no-bitwise */
|
@@ -1237,10 +1569,10 @@
|
|
1237
1569
|
}
|
1238
1570
|
|
1239
1571
|
/**
|
1240
|
-
* Permissive string parsing.
|
1241
|
-
* based on detected format.
|
1242
|
-
* @param {string} input
|
1243
|
-
* @returns {Record<string, (number | string)> | false}
|
1572
|
+
* Permissive string parsing. Take in a number of formats, and output an object
|
1573
|
+
* based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
|
1574
|
+
* @param {string} input colour value in any format
|
1575
|
+
* @returns {Record<string, (number | string)> | false} an object matching the RegExp
|
1244
1576
|
*/
|
1245
1577
|
function stringInputToObject(input) {
|
1246
1578
|
let color = input.trim().toLowerCase();
|
@@ -1250,12 +1582,15 @@
|
|
1250
1582
|
};
|
1251
1583
|
}
|
1252
1584
|
let named = false;
|
1253
|
-
if (
|
1254
|
-
color =
|
1585
|
+
if (isColorName(color)) {
|
1586
|
+
color = getRGBFromName(color);
|
1255
1587
|
named = true;
|
1256
|
-
} else if (color
|
1588
|
+
} else if (nonColors.includes(color)) {
|
1589
|
+
const isTransparent = color === 'transparent';
|
1590
|
+
const rgb = isTransparent ? 0 : 255;
|
1591
|
+
const a = isTransparent ? 0 : 1;
|
1257
1592
|
return {
|
1258
|
-
r:
|
1593
|
+
r: rgb, g: rgb, b: rgb, a, format: 'rgb',
|
1259
1594
|
};
|
1260
1595
|
}
|
1261
1596
|
|
@@ -1264,72 +1599,68 @@
|
|
1264
1599
|
// don't worry about [0,1] or [0,100] or [0,360]
|
1265
1600
|
// Just return an object and let the conversion functions handle that.
|
1266
1601
|
// This way the result will be the same whether Color is initialized with string or object.
|
1267
|
-
let
|
1268
|
-
if (
|
1269
|
-
return { r: match[1], g: match[2], b: match[3] };
|
1270
|
-
}
|
1271
|
-
match = matchers.rgba.exec(color);
|
1272
|
-
if (match) {
|
1602
|
+
let [, m1, m2, m3, m4] = matchers.rgb.exec(color) || [];
|
1603
|
+
if (m1 && m2 && m3/* && m4 */) {
|
1273
1604
|
return {
|
1274
|
-
r:
|
1605
|
+
r: m1, g: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'rgb',
|
1275
1606
|
};
|
1276
1607
|
}
|
1277
|
-
|
1278
|
-
if (
|
1279
|
-
return { h: match[1], s: match[2], l: match[3] };
|
1280
|
-
}
|
1281
|
-
match = matchers.hsla.exec(color);
|
1282
|
-
if (match) {
|
1608
|
+
[, m1, m2, m3, m4] = matchers.hsl.exec(color) || [];
|
1609
|
+
if (m1 && m2 && m3/* && m4 */) {
|
1283
1610
|
return {
|
1284
|
-
h:
|
1611
|
+
h: m1, s: m2, l: m3, a: m4 !== undefined ? m4 : 1, format: 'hsl',
|
1285
1612
|
};
|
1286
1613
|
}
|
1287
|
-
|
1288
|
-
if (
|
1289
|
-
return {
|
1614
|
+
[, m1, m2, m3, m4] = matchers.hsv.exec(color) || [];
|
1615
|
+
if (m1 && m2 && m3/* && m4 */) {
|
1616
|
+
return {
|
1617
|
+
h: m1, s: m2, v: m3, a: m4 !== undefined ? m4 : 1, format: 'hsv',
|
1618
|
+
};
|
1290
1619
|
}
|
1291
|
-
|
1292
|
-
if (
|
1620
|
+
[, m1, m2, m3, m4] = matchers.hwb.exec(color) || [];
|
1621
|
+
if (m1 && m2 && m3) {
|
1293
1622
|
return {
|
1294
|
-
h:
|
1623
|
+
h: m1, w: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'hwb',
|
1295
1624
|
};
|
1296
1625
|
}
|
1297
|
-
|
1298
|
-
if (
|
1626
|
+
[, m1, m2, m3, m4] = matchers.hex8.exec(color) || [];
|
1627
|
+
if (m1 && m2 && m3 && m4) {
|
1299
1628
|
return {
|
1300
|
-
r: parseIntFromHex(
|
1301
|
-
g: parseIntFromHex(
|
1302
|
-
b: parseIntFromHex(
|
1303
|
-
a: convertHexToDecimal(
|
1304
|
-
format: named ? '
|
1629
|
+
r: parseIntFromHex(m1),
|
1630
|
+
g: parseIntFromHex(m2),
|
1631
|
+
b: parseIntFromHex(m3),
|
1632
|
+
a: convertHexToDecimal(m4),
|
1633
|
+
// format: named ? 'rgb' : 'hex8',
|
1634
|
+
format: named ? 'rgb' : 'hex',
|
1305
1635
|
};
|
1306
1636
|
}
|
1307
|
-
|
1308
|
-
if (
|
1637
|
+
[, m1, m2, m3] = matchers.hex6.exec(color) || [];
|
1638
|
+
if (m1 && m2 && m3) {
|
1309
1639
|
return {
|
1310
|
-
r: parseIntFromHex(
|
1311
|
-
g: parseIntFromHex(
|
1312
|
-
b: parseIntFromHex(
|
1313
|
-
format: named ? '
|
1640
|
+
r: parseIntFromHex(m1),
|
1641
|
+
g: parseIntFromHex(m2),
|
1642
|
+
b: parseIntFromHex(m3),
|
1643
|
+
format: named ? 'rgb' : 'hex',
|
1314
1644
|
};
|
1315
1645
|
}
|
1316
|
-
|
1317
|
-
if (
|
1646
|
+
[, m1, m2, m3, m4] = matchers.hex4.exec(color) || [];
|
1647
|
+
if (m1 && m2 && m3 && m4) {
|
1318
1648
|
return {
|
1319
|
-
r: parseIntFromHex(
|
1320
|
-
g: parseIntFromHex(
|
1321
|
-
b: parseIntFromHex(
|
1322
|
-
a: convertHexToDecimal(
|
1323
|
-
format: named ? '
|
1649
|
+
r: parseIntFromHex(m1 + m1),
|
1650
|
+
g: parseIntFromHex(m2 + m2),
|
1651
|
+
b: parseIntFromHex(m3 + m3),
|
1652
|
+
a: convertHexToDecimal(m4 + m4),
|
1653
|
+
// format: named ? 'rgb' : 'hex8',
|
1654
|
+
format: named ? 'rgb' : 'hex',
|
1324
1655
|
};
|
1325
1656
|
}
|
1326
|
-
|
1327
|
-
if (
|
1657
|
+
[, m1, m2, m3] = matchers.hex3.exec(color) || [];
|
1658
|
+
if (m1 && m2 && m3) {
|
1328
1659
|
return {
|
1329
|
-
r: parseIntFromHex(
|
1330
|
-
g: parseIntFromHex(
|
1331
|
-
b: parseIntFromHex(
|
1332
|
-
format: named ? '
|
1660
|
+
r: parseIntFromHex(m1 + m1),
|
1661
|
+
g: parseIntFromHex(m2 + m2),
|
1662
|
+
b: parseIntFromHex(m3 + m3),
|
1663
|
+
format: named ? 'rgb' : 'hex',
|
1333
1664
|
};
|
1334
1665
|
}
|
1335
1666
|
return false;
|
@@ -1343,26 +1674,35 @@
|
|
1343
1674
|
* "red"
|
1344
1675
|
* "#f00" or "f00"
|
1345
1676
|
* "#ff0000" or "ff0000"
|
1346
|
-
* "#ff000000" or "ff000000"
|
1677
|
+
* "#ff000000" or "ff000000" // CSS4 Module
|
1347
1678
|
* "rgb 255 0 0" or "rgb (255, 0, 0)"
|
1348
1679
|
* "rgb 1.0 0 0" or "rgb (1, 0, 0)"
|
1349
|
-
* "rgba
|
1350
|
-
* "rgba
|
1680
|
+
* "rgba(255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
|
1681
|
+
* "rgba(1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
|
1682
|
+
* "rgb(255 0 0 / 10%)" or "rgb 255 0 0 0.1" // CSS4 Module
|
1351
1683
|
* "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
|
1352
1684
|
* "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
|
1685
|
+
* "hsl(0deg 100% 50% / 50%)" or "hsl 0 100 50 50" // CSS4 Module
|
1353
1686
|
* "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
|
1687
|
+
* "hsva(0, 100%, 100%, 0.1)" or "hsva 0 100% 100% 0.1"
|
1688
|
+
* "hsv(0deg 100% 100% / 10%)" or "hsv 0 100 100 0.1" // CSS4 Module
|
1689
|
+
* "hwb(0deg, 100%, 100%, 100%)" or "hwb 0 100% 100% 0.1" // CSS4 Module
|
1354
1690
|
* ```
|
1355
1691
|
* @param {string | Record<string, any>} input
|
1356
1692
|
* @returns {CP.ColorObject}
|
1357
1693
|
*/
|
1358
1694
|
function inputToRGB(input) {
|
1359
|
-
/** @type {CP.RGB} */
|
1360
1695
|
let rgb = { r: 0, g: 0, b: 0 };
|
1361
1696
|
let color = input;
|
1362
|
-
let a;
|
1697
|
+
let a = 1;
|
1363
1698
|
let s = null;
|
1364
1699
|
let v = null;
|
1365
1700
|
let l = null;
|
1701
|
+
let w = null;
|
1702
|
+
let b = null;
|
1703
|
+
let h = null;
|
1704
|
+
let r = null;
|
1705
|
+
let g = null;
|
1366
1706
|
let ok = false;
|
1367
1707
|
let format = 'hex';
|
1368
1708
|
|
@@ -1373,23 +1713,41 @@
|
|
1373
1713
|
}
|
1374
1714
|
if (typeof color === 'object') {
|
1375
1715
|
if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
|
1376
|
-
|
1716
|
+
({ r, g, b } = color);
|
1717
|
+
// RGB values now are all in [0, 255] range
|
1718
|
+
[r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255);
|
1719
|
+
rgb = { r, g, b };
|
1377
1720
|
ok = true;
|
1378
|
-
format =
|
1721
|
+
format = 'rgb';
|
1379
1722
|
} else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
|
1380
|
-
s =
|
1381
|
-
|
1382
|
-
|
1723
|
+
({ h, s, v } = color);
|
1724
|
+
h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
|
1725
|
+
s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
|
1726
|
+
v = typeof v === 'number' ? v : bound01(v, 100); // brightness can be `5%` or a [0, 1] value
|
1727
|
+
rgb = hsvToRgb(h, s, v);
|
1383
1728
|
ok = true;
|
1384
1729
|
format = 'hsv';
|
1385
1730
|
} else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
|
1386
|
-
s =
|
1387
|
-
|
1388
|
-
|
1731
|
+
({ h, s, l } = color);
|
1732
|
+
h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
|
1733
|
+
s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
|
1734
|
+
l = typeof l === 'number' ? l : bound01(l, 100); // lightness can be `5%` or a [0, 1] value
|
1735
|
+
rgb = hslToRgb(h, s, l);
|
1389
1736
|
ok = true;
|
1390
1737
|
format = 'hsl';
|
1738
|
+
} else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
|
1739
|
+
({ h, w, b } = color);
|
1740
|
+
h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
|
1741
|
+
w = typeof w === 'number' ? w : bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
|
1742
|
+
b = typeof b === 'number' ? b : bound01(b, 100); // blackness can be `5%` or a [0, 1] value
|
1743
|
+
rgb = hwbToRgb(h, w, b);
|
1744
|
+
ok = true;
|
1745
|
+
format = 'hwb';
|
1746
|
+
}
|
1747
|
+
if (isValidCSSUnit(color.a)) {
|
1748
|
+
a = color.a;
|
1749
|
+
a = isPercentage(`${a}`) ? bound01(a, 100) : a;
|
1391
1750
|
}
|
1392
|
-
if ('a' in color) a = color.a;
|
1393
1751
|
}
|
1394
1752
|
|
1395
1753
|
return {
|
@@ -1402,27 +1760,21 @@
|
|
1402
1760
|
};
|
1403
1761
|
}
|
1404
1762
|
|
1405
|
-
/** @type {CP.ColorOptions} */
|
1406
|
-
const colorPickerDefaults = {
|
1407
|
-
format: 'hex',
|
1408
|
-
};
|
1409
|
-
|
1410
1763
|
/**
|
1764
|
+
* @class
|
1411
1765
|
* Returns a new `Color` instance.
|
1412
1766
|
* @see https://github.com/bgrins/TinyColor
|
1413
|
-
* @class
|
1414
1767
|
*/
|
1415
1768
|
class Color {
|
1416
1769
|
/**
|
1417
1770
|
* @constructor
|
1418
|
-
* @param {CP.ColorInput} input
|
1419
|
-
* @param {CP.
|
1771
|
+
* @param {CP.ColorInput} input the given colour value
|
1772
|
+
* @param {CP.ColorFormats=} config the given format
|
1420
1773
|
*/
|
1421
1774
|
constructor(input, config) {
|
1422
1775
|
let color = input;
|
1423
|
-
const
|
1424
|
-
?
|
1425
|
-
: ObjectAssign({}, colorPickerDefaults);
|
1776
|
+
const configFormat = config && COLOR_FORMAT.includes(config)
|
1777
|
+
? config : 'rgb';
|
1426
1778
|
|
1427
1779
|
// If input is already a `Color`, return itself
|
1428
1780
|
if (color instanceof Color) {
|
@@ -1435,36 +1787,23 @@
|
|
1435
1787
|
r, g, b, a, ok, format,
|
1436
1788
|
} = inputToRGB(color);
|
1437
1789
|
|
1790
|
+
// bind
|
1791
|
+
const self = this;
|
1792
|
+
|
1438
1793
|
/** @type {CP.ColorInput} */
|
1439
|
-
|
1794
|
+
self.originalInput = color;
|
1440
1795
|
/** @type {number} */
|
1441
|
-
|
1796
|
+
self.r = r;
|
1442
1797
|
/** @type {number} */
|
1443
|
-
|
1798
|
+
self.g = g;
|
1444
1799
|
/** @type {number} */
|
1445
|
-
|
1800
|
+
self.b = b;
|
1446
1801
|
/** @type {number} */
|
1447
|
-
|
1802
|
+
self.a = a;
|
1448
1803
|
/** @type {boolean} */
|
1449
|
-
|
1450
|
-
/** @type {number} */
|
1451
|
-
this.roundA = Math.round(100 * this.a) / 100;
|
1804
|
+
self.ok = ok;
|
1452
1805
|
/** @type {CP.ColorFormats} */
|
1453
|
-
|
1454
|
-
|
1455
|
-
// Don't let the range of [0,255] come back in [0,1].
|
1456
|
-
// Potentially lose a little bit of precision here, but will fix issues where
|
1457
|
-
// .5 gets interpreted as half of the total, instead of half of 1
|
1458
|
-
// If it was supposed to be 128, this was already taken care of by `inputToRgb`
|
1459
|
-
if (this.r < 1) {
|
1460
|
-
this.r = Math.round(this.r);
|
1461
|
-
}
|
1462
|
-
if (this.g < 1) {
|
1463
|
-
this.g = Math.round(this.g);
|
1464
|
-
}
|
1465
|
-
if (this.b < 1) {
|
1466
|
-
this.b = Math.round(this.b);
|
1467
|
-
}
|
1806
|
+
self.format = configFormat || format;
|
1468
1807
|
}
|
1469
1808
|
|
1470
1809
|
/**
|
@@ -1480,44 +1819,44 @@
|
|
1480
1819
|
* @returns {boolean} the query result
|
1481
1820
|
*/
|
1482
1821
|
get isDark() {
|
1483
|
-
return this.brightness <
|
1822
|
+
return this.brightness < 120;
|
1484
1823
|
}
|
1485
1824
|
|
1486
1825
|
/**
|
1487
|
-
* Returns the perceived luminance of a
|
1826
|
+
* Returns the perceived luminance of a colour.
|
1488
1827
|
* @see http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
|
1489
|
-
* @returns {number} a number in [0
|
1828
|
+
* @returns {number} a number in the [0, 1] range
|
1490
1829
|
*/
|
1491
1830
|
get luminance() {
|
1492
1831
|
const { r, g, b } = this;
|
1493
1832
|
let R = 0;
|
1494
1833
|
let G = 0;
|
1495
1834
|
let B = 0;
|
1496
|
-
const
|
1497
|
-
const
|
1498
|
-
const
|
1835
|
+
const rp = r / 255;
|
1836
|
+
const rg = g / 255;
|
1837
|
+
const rb = b / 255;
|
1499
1838
|
|
1500
|
-
if (
|
1501
|
-
R =
|
1839
|
+
if (rp <= 0.03928) {
|
1840
|
+
R = rp / 12.92;
|
1502
1841
|
} else {
|
1503
|
-
R = ((
|
1842
|
+
R = ((rp + 0.055) / 1.055) ** 2.4;
|
1504
1843
|
}
|
1505
|
-
if (
|
1506
|
-
G =
|
1844
|
+
if (rg <= 0.03928) {
|
1845
|
+
G = rg / 12.92;
|
1507
1846
|
} else {
|
1508
|
-
G = ((
|
1847
|
+
G = ((rg + 0.055) / 1.055) ** 2.4;
|
1509
1848
|
}
|
1510
|
-
if (
|
1511
|
-
B =
|
1849
|
+
if (rb <= 0.03928) {
|
1850
|
+
B = rb / 12.92;
|
1512
1851
|
} else {
|
1513
|
-
B = ((
|
1852
|
+
B = ((rb + 0.055) / 1.055) ** 2.4;
|
1514
1853
|
}
|
1515
1854
|
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
|
1516
1855
|
}
|
1517
1856
|
|
1518
1857
|
/**
|
1519
|
-
* Returns the perceived brightness of the
|
1520
|
-
* @returns {number} a number in [0
|
1858
|
+
* Returns the perceived brightness of the colour.
|
1859
|
+
* @returns {number} a number in the [0, 255] range
|
1521
1860
|
*/
|
1522
1861
|
get brightness() {
|
1523
1862
|
const { r, g, b } = this;
|
@@ -1525,123 +1864,287 @@
|
|
1525
1864
|
}
|
1526
1865
|
|
1527
1866
|
/**
|
1528
|
-
* Returns the
|
1529
|
-
* @returns {CP.RGBA}
|
1867
|
+
* Returns the colour as an RGBA object.
|
1868
|
+
* @returns {CP.RGBA} an {r,g,b,a} object with [0, 255] ranged values
|
1530
1869
|
*/
|
1531
1870
|
toRgb() {
|
1871
|
+
const {
|
1872
|
+
r, g, b, a,
|
1873
|
+
} = this;
|
1874
|
+
|
1532
1875
|
return {
|
1533
|
-
r:
|
1534
|
-
g: Math.round(this.g),
|
1535
|
-
b: Math.round(this.b),
|
1536
|
-
a: this.a,
|
1876
|
+
r, g, b, a: roundPart(a * 100) / 100,
|
1537
1877
|
};
|
1538
1878
|
}
|
1539
1879
|
|
1540
1880
|
/**
|
1541
|
-
* Returns the RGBA values concatenated into a string.
|
1542
|
-
*
|
1881
|
+
* Returns the RGBA values concatenated into a CSS3 Module string format.
|
1882
|
+
* * rgb(255,255,255)
|
1883
|
+
* * rgba(255,255,255,0.5)
|
1884
|
+
* @returns {string} the CSS valid colour in RGB/RGBA format
|
1543
1885
|
*/
|
1544
1886
|
toRgbString() {
|
1545
|
-
const
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1887
|
+
const {
|
1888
|
+
r, g, b, a,
|
1889
|
+
} = this.toRgb();
|
1890
|
+
const [R, G, B] = [r, g, b].map(roundPart);
|
1891
|
+
|
1892
|
+
return a === 1
|
1893
|
+
? `rgb(${R}, ${G}, ${B})`
|
1894
|
+
: `rgba(${R}, ${G}, ${B}, ${a})`;
|
1895
|
+
}
|
1896
|
+
|
1897
|
+
/**
|
1898
|
+
* Returns the RGBA values concatenated into a CSS4 Module string format.
|
1899
|
+
* * rgb(255 255 255)
|
1900
|
+
* * rgb(255 255 255 / 50%)
|
1901
|
+
* @returns {string} the CSS valid colour in CSS4 RGB format
|
1902
|
+
*/
|
1903
|
+
toRgbCSS4String() {
|
1904
|
+
const {
|
1905
|
+
r, g, b, a,
|
1906
|
+
} = this.toRgb();
|
1907
|
+
const [R, G, B] = [r, g, b].map(roundPart);
|
1908
|
+
const A = a === 1 ? '' : ` / ${roundPart(a * 100)}%`;
|
1909
|
+
|
1910
|
+
return `rgb(${R} ${G} ${B}${A})`;
|
1911
|
+
}
|
1912
|
+
|
1913
|
+
/**
|
1914
|
+
* Returns the hexadecimal value of the colour. When the parameter is *true*
|
1915
|
+
* it will find a 3 characters shorthand of the decimal value.
|
1916
|
+
*
|
1917
|
+
* @param {boolean=} allow3Char when `true` returns shorthand HEX
|
1918
|
+
* @returns {string} the hexadecimal colour format
|
1919
|
+
*/
|
1920
|
+
toHex(allow3Char) {
|
1921
|
+
const {
|
1922
|
+
r, g, b, a,
|
1923
|
+
} = this.toRgb();
|
1924
|
+
|
1925
|
+
return a === 1
|
1926
|
+
? rgbToHex(r, g, b, allow3Char)
|
1927
|
+
: rgbaToHex(r, g, b, a, allow3Char);
|
1551
1928
|
}
|
1552
1929
|
|
1553
1930
|
/**
|
1554
|
-
* Returns the
|
1555
|
-
*
|
1931
|
+
* Returns the CSS valid hexadecimal vaue of the colour. When the parameter is *true*
|
1932
|
+
* it will find a 3 characters shorthand of the value.
|
1933
|
+
*
|
1934
|
+
* @param {boolean=} allow3Char when `true` returns shorthand HEX
|
1935
|
+
* @returns {string} the CSS valid colour in hexadecimal format
|
1936
|
+
*/
|
1937
|
+
toHexString(allow3Char) {
|
1938
|
+
return `#${this.toHex(allow3Char)}`;
|
1939
|
+
}
|
1940
|
+
|
1941
|
+
/**
|
1942
|
+
* Returns the HEX8 value of the colour.
|
1943
|
+
* @param {boolean=} allow4Char when `true` returns shorthand HEX
|
1944
|
+
* @returns {string} the CSS valid colour in hexadecimal format
|
1556
1945
|
*/
|
1557
|
-
|
1558
|
-
|
1946
|
+
toHex8(allow4Char) {
|
1947
|
+
const {
|
1948
|
+
r, g, b, a,
|
1949
|
+
} = this.toRgb();
|
1950
|
+
|
1951
|
+
return rgbaToHex(r, g, b, a, allow4Char);
|
1559
1952
|
}
|
1560
1953
|
|
1561
1954
|
/**
|
1562
|
-
* Returns the
|
1563
|
-
* @
|
1955
|
+
* Returns the HEX8 value of the colour.
|
1956
|
+
* @param {boolean=} allow4Char when `true` returns shorthand HEX
|
1957
|
+
* @returns {string} the CSS valid colour in hexadecimal format
|
1564
1958
|
*/
|
1565
|
-
|
1566
|
-
return `#${this.
|
1959
|
+
toHex8String(allow4Char) {
|
1960
|
+
return `#${this.toHex8(allow4Char)}`;
|
1567
1961
|
}
|
1568
1962
|
|
1569
1963
|
/**
|
1570
|
-
* Returns the
|
1571
|
-
* @returns {CP.HSVA} the `{h,s,v,a}` object
|
1964
|
+
* Returns the colour as a HSVA object.
|
1965
|
+
* @returns {CP.HSVA} the `{h,s,v,a}` object with [0, 1] ranged values
|
1572
1966
|
*/
|
1573
1967
|
toHsv() {
|
1574
|
-
const {
|
1968
|
+
const {
|
1969
|
+
r, g, b, a,
|
1970
|
+
} = this.toRgb();
|
1971
|
+
const { h, s, v } = rgbToHsv(r, g, b);
|
1972
|
+
|
1575
1973
|
return {
|
1576
|
-
h
|
1974
|
+
h, s, v, a,
|
1577
1975
|
};
|
1578
1976
|
}
|
1579
1977
|
|
1580
1978
|
/**
|
1581
|
-
* Returns the
|
1582
|
-
* @returns {CP.HSLA}
|
1979
|
+
* Returns the colour as an HSLA object.
|
1980
|
+
* @returns {CP.HSLA} the `{h,s,l,a}` object with [0, 1] ranged values
|
1583
1981
|
*/
|
1584
1982
|
toHsl() {
|
1585
|
-
const {
|
1983
|
+
const {
|
1984
|
+
r, g, b, a,
|
1985
|
+
} = this.toRgb();
|
1986
|
+
const { h, s, l } = rgbToHsl(r, g, b);
|
1987
|
+
|
1586
1988
|
return {
|
1587
|
-
h
|
1989
|
+
h, s, l, a,
|
1588
1990
|
};
|
1589
1991
|
}
|
1590
1992
|
|
1591
1993
|
/**
|
1592
|
-
* Returns the HSLA values concatenated into a string.
|
1593
|
-
*
|
1994
|
+
* Returns the HSLA values concatenated into a CSS3 Module format string.
|
1995
|
+
* * `hsl(150, 100%, 50%)`
|
1996
|
+
* * `hsla(150, 100%, 50%, 0.5)`
|
1997
|
+
* @returns {string} the CSS valid colour in HSL/HSLA format
|
1594
1998
|
*/
|
1595
1999
|
toHslString() {
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
|
1600
|
-
|
1601
|
-
|
1602
|
-
|
2000
|
+
let {
|
2001
|
+
h, s, l, a,
|
2002
|
+
} = this.toHsl();
|
2003
|
+
h = roundPart(h * 360);
|
2004
|
+
s = roundPart(s * 100);
|
2005
|
+
l = roundPart(l * 100);
|
2006
|
+
a = roundPart(a * 100) / 100;
|
2007
|
+
|
2008
|
+
return a === 1
|
2009
|
+
? `hsl(${h}, ${s}%, ${l}%)`
|
2010
|
+
: `hsla(${h}, ${s}%, ${l}%, ${a})`;
|
2011
|
+
}
|
2012
|
+
|
2013
|
+
/**
|
2014
|
+
* Returns the HSLA values concatenated into a CSS4 Module format string.
|
2015
|
+
* * `hsl(150deg 100% 50%)`
|
2016
|
+
* * `hsl(150deg 100% 50% / 50%)`
|
2017
|
+
* @returns {string} the CSS valid colour in CSS4 HSL format
|
2018
|
+
*/
|
2019
|
+
toHslCSS4String() {
|
2020
|
+
let {
|
2021
|
+
h, s, l, a,
|
2022
|
+
} = this.toHsl();
|
2023
|
+
h = roundPart(h * 360);
|
2024
|
+
s = roundPart(s * 100);
|
2025
|
+
l = roundPart(l * 100);
|
2026
|
+
a = roundPart(a * 100);
|
2027
|
+
const A = a < 100 ? ` / ${roundPart(a)}%` : '';
|
2028
|
+
|
2029
|
+
return `hsl(${h}deg ${s}% ${l}%${A})`;
|
2030
|
+
}
|
2031
|
+
|
2032
|
+
/**
|
2033
|
+
* Returns the colour as an HWBA object.
|
2034
|
+
* @returns {CP.HWBA} the `{h,w,b,a}` object with [0, 1] ranged values
|
2035
|
+
*/
|
2036
|
+
toHwb() {
|
2037
|
+
const {
|
2038
|
+
r, g, b, a,
|
2039
|
+
} = this;
|
2040
|
+
const { h, w, b: bl } = rgbToHwb(r, g, b);
|
2041
|
+
return {
|
2042
|
+
h, w, b: bl, a,
|
2043
|
+
};
|
1603
2044
|
}
|
1604
2045
|
|
1605
2046
|
/**
|
1606
|
-
*
|
1607
|
-
* @
|
1608
|
-
|
2047
|
+
* Returns the HWBA values concatenated into a string.
|
2048
|
+
* @returns {string} the CSS valid colour in HWB format
|
2049
|
+
*/
|
2050
|
+
toHwbString() {
|
2051
|
+
let {
|
2052
|
+
h, w, b, a,
|
2053
|
+
} = this.toHwb();
|
2054
|
+
h = roundPart(h * 360);
|
2055
|
+
w = roundPart(w * 100);
|
2056
|
+
b = roundPart(b * 100);
|
2057
|
+
a = roundPart(a * 100);
|
2058
|
+
const A = a < 100 ? ` / ${roundPart(a)}%` : '';
|
2059
|
+
|
2060
|
+
return `hwb(${h}deg ${w}% ${b}%${A})`;
|
2061
|
+
}
|
2062
|
+
|
2063
|
+
/**
|
2064
|
+
* Sets the alpha value of the current colour.
|
2065
|
+
* @param {number} alpha a new alpha value in the [0, 1] range.
|
2066
|
+
* @returns {Color} the `Color` instance
|
1609
2067
|
*/
|
1610
2068
|
setAlpha(alpha) {
|
1611
|
-
|
1612
|
-
|
1613
|
-
return
|
2069
|
+
const self = this;
|
2070
|
+
self.a = boundAlpha(alpha);
|
2071
|
+
return self;
|
1614
2072
|
}
|
1615
2073
|
|
1616
2074
|
/**
|
1617
|
-
* Saturate the
|
1618
|
-
* @param {number=} amount a value in [0
|
1619
|
-
* @returns {Color}
|
2075
|
+
* Saturate the colour with a given amount.
|
2076
|
+
* @param {number=} amount a value in the [0, 100] range
|
2077
|
+
* @returns {Color} the `Color` instance
|
1620
2078
|
*/
|
1621
2079
|
saturate(amount) {
|
1622
|
-
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
1626
|
-
|
2080
|
+
const self = this;
|
2081
|
+
if (typeof amount !== 'number') return self;
|
2082
|
+
const { h, s, l } = self.toHsl();
|
2083
|
+
const { r, g, b } = hslToRgb(h, clamp01(s + amount / 100), l);
|
2084
|
+
|
2085
|
+
ObjectAssign(self, { r, g, b });
|
2086
|
+
return self;
|
1627
2087
|
}
|
1628
2088
|
|
1629
2089
|
/**
|
1630
|
-
* Desaturate the
|
1631
|
-
* @param {number=} amount a value in [0
|
1632
|
-
* @returns {Color}
|
2090
|
+
* Desaturate the colour with a given amount.
|
2091
|
+
* @param {number=} amount a value in the [0, 100] range
|
2092
|
+
* @returns {Color} the `Color` instance
|
1633
2093
|
*/
|
1634
2094
|
desaturate(amount) {
|
1635
|
-
return amount ? this.saturate(-amount) : this;
|
2095
|
+
return typeof amount === 'number' ? this.saturate(-amount) : this;
|
1636
2096
|
}
|
1637
2097
|
|
1638
2098
|
/**
|
1639
|
-
* Completely desaturates a
|
2099
|
+
* Completely desaturates a colour into greyscale.
|
1640
2100
|
* Same as calling `desaturate(100)`
|
1641
|
-
* @returns {Color}
|
2101
|
+
* @returns {Color} the `Color` instance
|
1642
2102
|
*/
|
1643
2103
|
greyscale() {
|
1644
|
-
return this.
|
2104
|
+
return this.saturate(-100);
|
2105
|
+
}
|
2106
|
+
|
2107
|
+
/**
|
2108
|
+
* Increase the colour lightness with a given amount.
|
2109
|
+
* @param {number=} amount a value in the [0, 100] range
|
2110
|
+
* @returns {Color} the `Color` instance
|
2111
|
+
*/
|
2112
|
+
lighten(amount) {
|
2113
|
+
const self = this;
|
2114
|
+
if (typeof amount !== 'number') return self;
|
2115
|
+
|
2116
|
+
const { h, s, l } = self.toHsl();
|
2117
|
+
const { r, g, b } = hslToRgb(h, s, clamp01(l + amount / 100));
|
2118
|
+
|
2119
|
+
ObjectAssign(self, { r, g, b });
|
2120
|
+
return self;
|
2121
|
+
}
|
2122
|
+
|
2123
|
+
/**
|
2124
|
+
* Decrease the colour lightness with a given amount.
|
2125
|
+
* @param {number=} amount a value in the [0, 100] range
|
2126
|
+
* @returns {Color} the `Color` instance
|
2127
|
+
*/
|
2128
|
+
darken(amount) {
|
2129
|
+
return typeof amount === 'number' ? this.lighten(-amount) : this;
|
2130
|
+
}
|
2131
|
+
|
2132
|
+
/**
|
2133
|
+
* Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
|
2134
|
+
* Values outside of this range will be wrapped into this range.
|
2135
|
+
*
|
2136
|
+
* @param {number=} amount a value in the [0, 100] range
|
2137
|
+
* @returns {Color} the `Color` instance
|
2138
|
+
*/
|
2139
|
+
spin(amount) {
|
2140
|
+
const self = this;
|
2141
|
+
if (typeof amount !== 'number') return self;
|
2142
|
+
|
2143
|
+
const { h, s, l } = self.toHsl();
|
2144
|
+
const { r, g, b } = hslToRgb(clamp01(((h * 360 + amount) % 360) / 360), s, l);
|
2145
|
+
|
2146
|
+
ObjectAssign(self, { r, g, b });
|
2147
|
+
return self;
|
1645
2148
|
}
|
1646
2149
|
|
1647
2150
|
/** Returns a clone of the current `Color` instance. */
|
@@ -1650,77 +2153,235 @@
|
|
1650
2153
|
}
|
1651
2154
|
|
1652
2155
|
/**
|
1653
|
-
* Returns the
|
1654
|
-
* @
|
2156
|
+
* Returns the colour value in CSS valid string format.
|
2157
|
+
* @param {boolean=} allowShort when *true*, HEX values can be shorthand
|
2158
|
+
* @returns {string} the CSS valid colour in the configured format
|
1655
2159
|
*/
|
1656
|
-
toString() {
|
1657
|
-
const
|
2160
|
+
toString(allowShort) {
|
2161
|
+
const self = this;
|
2162
|
+
const { format } = self;
|
1658
2163
|
|
1659
|
-
if (format === '
|
1660
|
-
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
1664
|
-
}
|
1665
|
-
return this.toHexString();
|
2164
|
+
if (format === 'hex') return self.toHexString(allowShort);
|
2165
|
+
if (format === 'hsl') return self.toHslString();
|
2166
|
+
if (format === 'hwb') return self.toHwbString();
|
2167
|
+
|
2168
|
+
return self.toRgbString();
|
1666
2169
|
}
|
1667
2170
|
}
|
1668
2171
|
|
1669
2172
|
ObjectAssign(Color, {
|
1670
|
-
|
2173
|
+
ANGLES,
|
2174
|
+
CSS_ANGLE,
|
1671
2175
|
CSS_INTEGER,
|
1672
2176
|
CSS_NUMBER,
|
1673
2177
|
CSS_UNIT,
|
1674
|
-
|
1675
|
-
|
2178
|
+
CSS_UNIT2,
|
2179
|
+
PERMISSIVE_MATCH,
|
1676
2180
|
matchers,
|
1677
2181
|
isOnePointZero,
|
1678
2182
|
isPercentage,
|
1679
2183
|
isValidCSSUnit,
|
2184
|
+
pad2,
|
2185
|
+
clamp01,
|
1680
2186
|
bound01,
|
1681
2187
|
boundAlpha,
|
1682
|
-
|
1683
|
-
getHexFromColorName,
|
1684
|
-
convertToPercentage,
|
2188
|
+
getRGBFromName,
|
1685
2189
|
convertHexToDecimal,
|
1686
|
-
|
1687
|
-
rgbToRgb,
|
2190
|
+
convertDecimalToHex,
|
1688
2191
|
rgbToHsl,
|
1689
2192
|
rgbToHex,
|
1690
2193
|
rgbToHsv,
|
2194
|
+
rgbToHwb,
|
2195
|
+
rgbaToHex,
|
1691
2196
|
hslToRgb,
|
1692
2197
|
hsvToRgb,
|
1693
|
-
|
2198
|
+
hueToRgb,
|
2199
|
+
hwbToRgb,
|
1694
2200
|
parseIntFromHex,
|
1695
2201
|
numberInputToObject,
|
1696
2202
|
stringInputToObject,
|
1697
2203
|
inputToRGB,
|
2204
|
+
roundPart,
|
2205
|
+
ObjectAssign,
|
1698
2206
|
});
|
1699
2207
|
|
2208
|
+
/**
|
2209
|
+
* @class
|
2210
|
+
* Returns a color palette with a given set of parameters.
|
2211
|
+
* @example
|
2212
|
+
* new ColorPalette(0, 12, 10);
|
2213
|
+
* // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
|
2214
|
+
*/
|
2215
|
+
class ColorPalette {
|
2216
|
+
/**
|
2217
|
+
* The `hue` parameter is optional, which would be set to 0.
|
2218
|
+
* @param {number[]} args represeinting hue, hueSteps, lightSteps
|
2219
|
+
* * `args.hue` the starting Hue [0, 360]
|
2220
|
+
* * `args.hueSteps` Hue Steps Count [5, 24]
|
2221
|
+
* * `args.lightSteps` Lightness Steps Count [5, 12]
|
2222
|
+
*/
|
2223
|
+
constructor(...args) {
|
2224
|
+
let hue = 0;
|
2225
|
+
let hueSteps = 12;
|
2226
|
+
let lightSteps = 10;
|
2227
|
+
let lightnessArray = [0.5];
|
2228
|
+
|
2229
|
+
if (args.length === 3) {
|
2230
|
+
[hue, hueSteps, lightSteps] = args;
|
2231
|
+
} else if (args.length === 2) {
|
2232
|
+
[hueSteps, lightSteps] = args;
|
2233
|
+
} else {
|
2234
|
+
throw TypeError('ColorPalette requires minimum 2 arguments');
|
2235
|
+
}
|
2236
|
+
|
2237
|
+
/** @type {string[]} */
|
2238
|
+
const colors = [];
|
2239
|
+
|
2240
|
+
const hueStep = 360 / hueSteps;
|
2241
|
+
const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
|
2242
|
+
const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
|
2243
|
+
|
2244
|
+
let lightStep = 0.25;
|
2245
|
+
lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
|
2246
|
+
lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
|
2247
|
+
lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
|
2248
|
+
lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
|
2249
|
+
lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
|
2250
|
+
lightStep = lightSteps > 13 ? estimatedStep : lightStep;
|
2251
|
+
|
2252
|
+
// light tints
|
2253
|
+
for (let i = 1; i < half + 1; i += 1) {
|
2254
|
+
lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
|
2255
|
+
}
|
2256
|
+
|
2257
|
+
// dark tints
|
2258
|
+
for (let i = 1; i < lightSteps - half; i += 1) {
|
2259
|
+
lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
|
2260
|
+
}
|
2261
|
+
|
2262
|
+
// feed `colors` Array
|
2263
|
+
for (let i = 0; i < hueSteps; i += 1) {
|
2264
|
+
const currentHue = ((hue + i * hueStep) % 360) / 360;
|
2265
|
+
lightnessArray.forEach((l) => {
|
2266
|
+
colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
|
2267
|
+
});
|
2268
|
+
}
|
2269
|
+
|
2270
|
+
this.hue = hue;
|
2271
|
+
this.hueSteps = hueSteps;
|
2272
|
+
this.lightSteps = lightSteps;
|
2273
|
+
this.colors = colors;
|
2274
|
+
}
|
2275
|
+
}
|
2276
|
+
|
2277
|
+
/**
|
2278
|
+
* Returns a color-defaults with given values and class.
|
2279
|
+
* @param {CP.ColorPicker} self
|
2280
|
+
* @param {CP.ColorPalette | string[]} colorsSource
|
2281
|
+
* @param {string} menuClass
|
2282
|
+
* @returns {HTMLElement | Element}
|
2283
|
+
*/
|
2284
|
+
function getColorMenu(self, colorsSource, menuClass) {
|
2285
|
+
const { input, format, componentLabels } = self;
|
2286
|
+
const { defaultsLabel, presetsLabel } = componentLabels;
|
2287
|
+
const isOptionsMenu = menuClass === 'color-options';
|
2288
|
+
const isPalette = colorsSource instanceof ColorPalette;
|
2289
|
+
const menuLabel = isOptionsMenu ? presetsLabel : defaultsLabel;
|
2290
|
+
let colorsArray = isPalette ? colorsSource.colors : colorsSource;
|
2291
|
+
colorsArray = colorsArray instanceof Array ? colorsArray : [];
|
2292
|
+
const colorsCount = colorsArray.length;
|
2293
|
+
const { lightSteps } = isPalette ? colorsSource : { lightSteps: null };
|
2294
|
+
const fit = lightSteps || [9, 10].find((x) => colorsCount > x * 2 && !(colorsCount % x)) || 5;
|
2295
|
+
const isMultiLine = isOptionsMenu && colorsCount > fit;
|
2296
|
+
let rowCountHover = 2;
|
2297
|
+
rowCountHover = isMultiLine && colorsCount >= fit * 2 ? 3 : rowCountHover;
|
2298
|
+
rowCountHover = colorsCount >= fit * 3 ? 4 : rowCountHover;
|
2299
|
+
rowCountHover = colorsCount >= fit * 4 ? 5 : rowCountHover;
|
2300
|
+
const rowCount = rowCountHover - (colorsCount < fit * 3 ? 1 : 2);
|
2301
|
+
const isScrollable = isMultiLine && colorsCount > rowCount * fit;
|
2302
|
+
let finalClass = menuClass;
|
2303
|
+
finalClass += isScrollable ? ' scrollable' : '';
|
2304
|
+
finalClass += isMultiLine ? ' multiline' : '';
|
2305
|
+
const gap = isMultiLine ? '1px' : '0.25rem';
|
2306
|
+
let optionSize = isMultiLine ? 1.75 : 2;
|
2307
|
+
optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
|
2308
|
+
const menuHeight = `${(rowCount || 1) * optionSize}rem`;
|
2309
|
+
const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
|
2310
|
+
|
2311
|
+
const menu = createElement({
|
2312
|
+
tagName: 'ul',
|
2313
|
+
className: finalClass,
|
2314
|
+
});
|
2315
|
+
setAttribute(menu, 'role', 'listbox');
|
2316
|
+
setAttribute(menu, ariaLabel, menuLabel);
|
2317
|
+
|
2318
|
+
if (isScrollable) { // @ts-ignore
|
2319
|
+
setCSSProperties(menu, {
|
2320
|
+
'--grid-item-size': `${optionSize}rem`,
|
2321
|
+
'--grid-fit': fit,
|
2322
|
+
'--grid-gap': gap,
|
2323
|
+
'--grid-height': menuHeight,
|
2324
|
+
'--grid-hover-height': menuHeightHover,
|
2325
|
+
});
|
2326
|
+
}
|
2327
|
+
|
2328
|
+
colorsArray.forEach((x) => {
|
2329
|
+
const [value, label] = x.trim().split(':');
|
2330
|
+
const xRealColor = new Color(value, format).toString();
|
2331
|
+
const isActive = xRealColor === getAttribute(input, 'value');
|
2332
|
+
const active = isActive ? ' active' : '';
|
2333
|
+
|
2334
|
+
const option = createElement({
|
2335
|
+
tagName: 'li',
|
2336
|
+
className: `color-option${active}`,
|
2337
|
+
innerText: `${label || x}`,
|
2338
|
+
});
|
2339
|
+
|
2340
|
+
setAttribute(option, tabIndex, '0');
|
2341
|
+
setAttribute(option, 'data-value', `${value}`);
|
2342
|
+
setAttribute(option, 'role', 'option');
|
2343
|
+
setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
|
2344
|
+
|
2345
|
+
if (isOptionsMenu) {
|
2346
|
+
setElementStyle(option, { backgroundColor: x });
|
2347
|
+
}
|
2348
|
+
|
2349
|
+
menu.append(option);
|
2350
|
+
});
|
2351
|
+
return menu;
|
2352
|
+
}
|
2353
|
+
|
2354
|
+
/**
|
2355
|
+
* Check if a string is valid JSON string.
|
2356
|
+
* @param {string} str the string input
|
2357
|
+
* @returns {boolean} the query result
|
2358
|
+
*/
|
2359
|
+
function isValidJSON(str) {
|
2360
|
+
try {
|
2361
|
+
JSON.parse(str);
|
2362
|
+
} catch (e) {
|
2363
|
+
return false;
|
2364
|
+
}
|
2365
|
+
return true;
|
2366
|
+
}
|
2367
|
+
|
2368
|
+
var version = "0.0.1";
|
2369
|
+
|
2370
|
+
// @ts-ignore
|
2371
|
+
|
2372
|
+
const Version = version;
|
2373
|
+
|
1700
2374
|
// ColorPicker GC
|
1701
2375
|
// ==============
|
1702
2376
|
const colorPickerString = 'color-picker';
|
1703
2377
|
const colorPickerSelector = `[data-function="${colorPickerString}"]`;
|
1704
|
-
const
|
1705
|
-
const
|
1706
|
-
|
1707
|
-
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
formatLabel: 'Colour Format',
|
1712
|
-
formatHEX: 'Hexadecimal Format',
|
1713
|
-
formatRGB: 'RGB Format',
|
1714
|
-
formatHSL: 'HSL Format',
|
1715
|
-
alphaLabel: 'Alpha',
|
1716
|
-
appearanceLabel: 'Colour Appearance',
|
1717
|
-
hexLabel: 'Hexadecimal',
|
1718
|
-
hueLabel: 'Hue',
|
1719
|
-
saturationLabel: 'Saturation',
|
1720
|
-
lightnessLabel: 'Lightness',
|
1721
|
-
redLabel: 'Red',
|
1722
|
-
greenLabel: 'Green',
|
1723
|
-
blueLabel: 'Blue',
|
2378
|
+
const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
|
2379
|
+
const colorPickerDefaults = {
|
2380
|
+
componentLabels: colorPickerLabels,
|
2381
|
+
colorLabels: colorNames,
|
2382
|
+
format: 'rgb',
|
2383
|
+
colorPresets: false,
|
2384
|
+
colorKeywords: false,
|
1724
2385
|
};
|
1725
2386
|
|
1726
2387
|
// ColorPicker Static Methods
|
@@ -1735,165 +2396,94 @@
|
|
1735
2396
|
// ColorPicker Private Methods
|
1736
2397
|
// ===========================
|
1737
2398
|
|
1738
|
-
/**
|
1739
|
-
* Add / remove `ColorPicker` main event listeners.
|
1740
|
-
* @param {ColorPicker} self
|
1741
|
-
* @param {boolean=} action
|
1742
|
-
*/
|
1743
|
-
function toggleEvents(self, action) {
|
1744
|
-
const fn = action ? addListener : removeListener;
|
1745
|
-
const { input, pickerToggle, menuToggle } = self;
|
1746
|
-
|
1747
|
-
fn(input, 'focusin', self.showPicker);
|
1748
|
-
fn(pickerToggle, 'click', self.togglePicker);
|
1749
|
-
|
1750
|
-
fn(input, 'keydown', self.keyHandler);
|
1751
|
-
|
1752
|
-
if (menuToggle) {
|
1753
|
-
fn(menuToggle, 'click', self.toggleMenu);
|
1754
|
-
}
|
1755
|
-
}
|
1756
|
-
|
1757
2399
|
/**
|
1758
2400
|
* Generate HTML markup and update instance properties.
|
1759
2401
|
* @param {ColorPicker} self
|
1760
2402
|
*/
|
1761
2403
|
function initCallback(self) {
|
1762
2404
|
const {
|
1763
|
-
input, parent, format, id, componentLabels,
|
2405
|
+
input, parent, format, id, componentLabels, colorKeywords, colorPresets,
|
1764
2406
|
} = self;
|
1765
2407
|
const colorValue = getAttribute(input, 'value') || '#fff';
|
1766
2408
|
|
1767
2409
|
const {
|
1768
|
-
toggleLabel,
|
2410
|
+
toggleLabel, pickerLabel, formatLabel, hexLabel,
|
1769
2411
|
} = componentLabels;
|
1770
2412
|
|
1771
2413
|
// update color
|
1772
2414
|
const color = nonColors.includes(colorValue) ? '#fff' : colorValue;
|
1773
|
-
self.color = new Color(color,
|
2415
|
+
self.color = new Color(color, format);
|
1774
2416
|
|
1775
2417
|
// set initial controls dimensions
|
1776
2418
|
// make the controls smaller on mobile
|
1777
|
-
const cv1w = isMobile ? 150 : 230;
|
1778
|
-
const cvh = isMobile ? 150 : 230;
|
1779
|
-
const cv2w = 21;
|
1780
2419
|
const dropClass = isMobile ? ' mobile' : '';
|
1781
|
-
const
|
1782
|
-
const ctrl2Labelledby = format === 'hsl' ? `appearance2_${id}` : `appearance_${id} appearance2_${id}`;
|
2420
|
+
const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
|
1783
2421
|
|
1784
2422
|
const pickerBtn = createElement({
|
2423
|
+
id: `picker-btn-${id}`,
|
1785
2424
|
tagName: 'button',
|
1786
|
-
className: 'picker-toggle
|
1787
|
-
ariaExpanded: 'false',
|
1788
|
-
ariaHasPopup: 'true',
|
1789
|
-
ariaLive: 'polite',
|
2425
|
+
className: 'picker-toggle btn-appearance',
|
1790
2426
|
});
|
1791
|
-
setAttribute(pickerBtn,
|
2427
|
+
setAttribute(pickerBtn, ariaExpanded, 'false');
|
2428
|
+
setAttribute(pickerBtn, ariaHasPopup, 'true');
|
1792
2429
|
pickerBtn.append(createElement({
|
1793
2430
|
tagName: 'span',
|
1794
2431
|
className: vHidden,
|
1795
|
-
innerText:
|
2432
|
+
innerText: `${pickerLabel}. ${formatLabel}: ${formatString}`,
|
1796
2433
|
}));
|
1797
2434
|
|
1798
|
-
const
|
2435
|
+
const pickerDropdown = createElement({
|
1799
2436
|
tagName: 'div',
|
1800
2437
|
className: `color-dropdown picker${dropClass}`,
|
1801
2438
|
});
|
1802
|
-
setAttribute(
|
1803
|
-
setAttribute(
|
1804
|
-
colorPickerDropdown.append(
|
1805
|
-
createElement({
|
1806
|
-
tagName: 'label',
|
1807
|
-
className: vHidden,
|
1808
|
-
ariaHidden: 'true',
|
1809
|
-
id: `picker-label-${id}`,
|
1810
|
-
innerText: `${pickerLabel}`,
|
1811
|
-
}),
|
1812
|
-
createElement({
|
1813
|
-
tagName: 'label',
|
1814
|
-
className: vHidden,
|
1815
|
-
ariaHidden: 'true',
|
1816
|
-
id: `format-label-${id}`,
|
1817
|
-
innerText: `${formatLabel}`,
|
1818
|
-
}),
|
1819
|
-
createElement({
|
1820
|
-
tagName: 'label',
|
1821
|
-
className: `color-appearance ${vHidden}`,
|
1822
|
-
ariaHidden: 'true',
|
1823
|
-
ariaLive: 'polite',
|
1824
|
-
id: `appearance_${id}`,
|
1825
|
-
innerText: `${appearanceLabel}`,
|
1826
|
-
}),
|
1827
|
-
);
|
1828
|
-
|
1829
|
-
const colorControls = createElement({
|
1830
|
-
tagName: 'div',
|
1831
|
-
className: `color-controls ${format}`,
|
1832
|
-
});
|
1833
|
-
|
1834
|
-
colorControls.append(
|
1835
|
-
getColorControl(1, id, cv1w, cvh, ctrl1Labelledby),
|
1836
|
-
getColorControl(2, id, cv2w, cvh, ctrl2Labelledby),
|
1837
|
-
);
|
2439
|
+
setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
|
2440
|
+
setAttribute(pickerDropdown, 'role', 'group');
|
1838
2441
|
|
1839
|
-
|
1840
|
-
colorControls.append(
|
1841
|
-
getColorControl(3, id, cv2w, cvh),
|
1842
|
-
);
|
1843
|
-
}
|
1844
|
-
|
1845
|
-
// @ts-ignore
|
2442
|
+
const colorControls = getColorControls(self);
|
1846
2443
|
const colorForm = getColorForm(self);
|
1847
|
-
colorPickerDropdown.append(colorControls, colorForm);
|
1848
|
-
parent.append(pickerBtn, colorPickerDropdown);
|
1849
2444
|
|
1850
|
-
|
1851
|
-
|
1852
|
-
|
2445
|
+
pickerDropdown.append(colorControls, colorForm);
|
2446
|
+
input.before(pickerBtn);
|
2447
|
+
parent.append(pickerDropdown);
|
2448
|
+
|
2449
|
+
// set colour key menu template
|
2450
|
+
if (colorKeywords || colorPresets) {
|
1853
2451
|
const presetsDropdown = createElement({
|
1854
2452
|
tagName: 'div',
|
1855
|
-
className: `color-dropdown menu${dropClass}`,
|
1856
|
-
});
|
1857
|
-
const presetsMenu = createElement({
|
1858
|
-
tagName: 'ul',
|
1859
|
-
ariaLabel: `${menuLabel}`,
|
1860
|
-
className: 'color-menu',
|
1861
|
-
});
|
1862
|
-
setAttribute(presetsMenu, 'role', 'listbox');
|
1863
|
-
presetsDropdown.append(presetsMenu);
|
1864
|
-
|
1865
|
-
colorKeys.forEach((x) => {
|
1866
|
-
const xKey = x.trim();
|
1867
|
-
const xRealColor = new Color(xKey, { format }).toString();
|
1868
|
-
const isActive = xRealColor === getAttribute(input, 'value');
|
1869
|
-
const active = isActive ? ' active' : '';
|
1870
|
-
|
1871
|
-
const keyOption = createElement({
|
1872
|
-
tagName: 'li',
|
1873
|
-
className: `color-option${active}`,
|
1874
|
-
ariaSelected: isActive ? 'true' : 'false',
|
1875
|
-
innerText: `${x}`,
|
1876
|
-
});
|
1877
|
-
setAttribute(keyOption, 'role', 'option');
|
1878
|
-
setAttribute(keyOption, 'tabindex', '0');
|
1879
|
-
setAttribute(keyOption, 'data-value', `${xKey}`);
|
1880
|
-
presetsMenu.append(keyOption);
|
2453
|
+
className: `color-dropdown scrollable menu${dropClass}`,
|
1881
2454
|
});
|
2455
|
+
|
2456
|
+
// color presets
|
2457
|
+
if ((colorPresets instanceof Array && colorPresets.length)
|
2458
|
+
|| (colorPresets instanceof ColorPalette && colorPresets.colors)) {
|
2459
|
+
const presetsMenu = getColorMenu(self, colorPresets, 'color-options');
|
2460
|
+
presetsDropdown.append(presetsMenu);
|
2461
|
+
}
|
2462
|
+
|
2463
|
+
// explicit defaults [reset, initial, inherit, transparent, currentColor]
|
2464
|
+
if (colorKeywords && colorKeywords.length) {
|
2465
|
+
const keywordsMenu = getColorMenu(self, colorKeywords, 'color-defaults');
|
2466
|
+
presetsDropdown.append(keywordsMenu);
|
2467
|
+
}
|
2468
|
+
|
1882
2469
|
const presetsBtn = createElement({
|
1883
2470
|
tagName: 'button',
|
1884
|
-
className: 'menu-toggle
|
1885
|
-
ariaExpanded: 'false',
|
1886
|
-
ariaHasPopup: 'true',
|
2471
|
+
className: 'menu-toggle btn-appearance',
|
1887
2472
|
});
|
2473
|
+
setAttribute(presetsBtn, tabIndex, '-1');
|
2474
|
+
setAttribute(presetsBtn, ariaExpanded, 'false');
|
2475
|
+
setAttribute(presetsBtn, ariaHasPopup, 'true');
|
2476
|
+
|
1888
2477
|
const xmlns = encodeURI('http://www.w3.org/2000/svg');
|
1889
2478
|
const presetsIcon = createElementNS(xmlns, { tagName: 'svg' });
|
1890
2479
|
setAttribute(presetsIcon, 'xmlns', xmlns);
|
1891
|
-
setAttribute(presetsIcon, ariaHidden, 'true');
|
1892
2480
|
setAttribute(presetsIcon, 'viewBox', '0 0 512 512');
|
1893
|
-
|
1894
|
-
|
1895
|
-
|
1896
|
-
|
2481
|
+
setAttribute(presetsIcon, ariaHidden, 'true');
|
2482
|
+
|
2483
|
+
const path = createElementNS(xmlns, { tagName: 'path' });
|
2484
|
+
setAttribute(path, 'd', 'M98,158l157,156L411,158l27,27L255,368L71,185L98,158z');
|
2485
|
+
setAttribute(path, 'fill', '#fff');
|
2486
|
+
presetsIcon.append(path);
|
1897
2487
|
presetsBtn.append(createElement({
|
1898
2488
|
tagName: 'span',
|
1899
2489
|
className: vHidden,
|
@@ -1904,9 +2494,29 @@
|
|
1904
2494
|
}
|
1905
2495
|
|
1906
2496
|
// solve non-colors after settings save
|
1907
|
-
if (
|
2497
|
+
if (colorKeywords && nonColors.includes(colorValue)) {
|
1908
2498
|
self.value = colorValue;
|
1909
2499
|
}
|
2500
|
+
setAttribute(input, tabIndex, '-1');
|
2501
|
+
}
|
2502
|
+
|
2503
|
+
/**
|
2504
|
+
* Add / remove `ColorPicker` main event listeners.
|
2505
|
+
* @param {ColorPicker} self
|
2506
|
+
* @param {boolean=} action
|
2507
|
+
*/
|
2508
|
+
function toggleEvents(self, action) {
|
2509
|
+
const fn = action ? addListener : removeListener;
|
2510
|
+
const { input, pickerToggle, menuToggle } = self;
|
2511
|
+
|
2512
|
+
fn(input, focusinEvent, self.showPicker);
|
2513
|
+
fn(pickerToggle, mouseclickEvent, self.togglePicker);
|
2514
|
+
|
2515
|
+
fn(input, keydownEvent, self.keyToggle);
|
2516
|
+
|
2517
|
+
if (menuToggle) {
|
2518
|
+
fn(menuToggle, mouseclickEvent, self.toggleMenu);
|
2519
|
+
}
|
1910
2520
|
}
|
1911
2521
|
|
1912
2522
|
/**
|
@@ -1916,26 +2526,33 @@
|
|
1916
2526
|
*/
|
1917
2527
|
function toggleEventsOnShown(self, action) {
|
1918
2528
|
const fn = action ? addListener : removeListener;
|
1919
|
-
const
|
1920
|
-
|
1921
|
-
|
2529
|
+
const { input, colorMenu, parent } = self;
|
2530
|
+
const doc = getDocument(input);
|
2531
|
+
const win = getWindow(input);
|
2532
|
+
const pointerEvents = `on${touchstartEvent}` in doc
|
2533
|
+
? { down: touchstartEvent, move: touchmoveEvent, up: touchendEvent }
|
2534
|
+
: { down: mousedownEvent, move: mousemoveEvent, up: mouseupEvent };
|
1922
2535
|
|
1923
2536
|
fn(self.controls, pointerEvents.down, self.pointerDown);
|
1924
|
-
self.controlKnobs.forEach((x) => fn(x,
|
2537
|
+
self.controlKnobs.forEach((x) => fn(x, keydownEvent, self.handleKnobs));
|
1925
2538
|
|
1926
|
-
|
2539
|
+
// @ts-ignore -- this is `Window`
|
2540
|
+
fn(win, scrollEvent, self.handleScroll);
|
2541
|
+
// @ts-ignore -- this is `Window`
|
2542
|
+
fn(win, resizeEvent, self.update);
|
1927
2543
|
|
1928
|
-
[
|
2544
|
+
[input, ...self.inputs].forEach((x) => fn(x, changeEvent, self.changeHandler));
|
1929
2545
|
|
1930
|
-
if (
|
1931
|
-
fn(
|
1932
|
-
fn(
|
2546
|
+
if (colorMenu) {
|
2547
|
+
fn(colorMenu, mouseclickEvent, self.menuClickHandler);
|
2548
|
+
fn(colorMenu, keydownEvent, self.menuKeyHandler);
|
1933
2549
|
}
|
1934
2550
|
|
1935
|
-
fn(
|
1936
|
-
fn(
|
1937
|
-
fn(
|
1938
|
-
|
2551
|
+
fn(doc, pointerEvents.move, self.pointerMove);
|
2552
|
+
fn(doc, pointerEvents.up, self.pointerUp);
|
2553
|
+
fn(parent, focusoutEvent, self.handleFocusOut);
|
2554
|
+
// @ts-ignore -- this is `Window`
|
2555
|
+
fn(win, keyupEvent, self.handleDismiss);
|
1939
2556
|
}
|
1940
2557
|
|
1941
2558
|
/**
|
@@ -1947,61 +2564,93 @@
|
|
1947
2564
|
}
|
1948
2565
|
|
1949
2566
|
/**
|
1950
|
-
*
|
2567
|
+
* Hides a visible dropdown.
|
1951
2568
|
* @param {HTMLElement} element
|
1952
|
-
* @
|
1953
|
-
* @returns {void | boolean}
|
2569
|
+
* @returns {void}
|
1954
2570
|
*/
|
1955
|
-
function
|
1956
|
-
const fn1 = !check ? 'forEach' : 'some';
|
1957
|
-
const fn2 = !check ? removeClass : hasClass;
|
1958
|
-
|
2571
|
+
function removePosition(element) {
|
1959
2572
|
if (element) {
|
1960
|
-
|
2573
|
+
['bottom', 'top'].forEach((x) => removeClass(element, x));
|
1961
2574
|
}
|
1962
|
-
|
1963
|
-
return false;
|
1964
2575
|
}
|
1965
2576
|
|
1966
2577
|
/**
|
1967
|
-
* Shows
|
2578
|
+
* Shows a `ColorPicker` dropdown and close the curent open dropdown.
|
1968
2579
|
* @param {ColorPicker} self
|
2580
|
+
* @param {HTMLElement | Element} dropdown
|
1969
2581
|
*/
|
1970
|
-
function
|
1971
|
-
|
1972
|
-
|
1973
|
-
self
|
1974
|
-
|
2582
|
+
function showDropdown(self, dropdown) {
|
2583
|
+
const {
|
2584
|
+
colorPicker, colorMenu, menuToggle, pickerToggle, parent,
|
2585
|
+
} = self;
|
2586
|
+
const isPicker = dropdown === colorPicker;
|
2587
|
+
const openDropdown = isPicker ? colorMenu : colorPicker;
|
2588
|
+
const activeBtn = isPicker ? menuToggle : pickerToggle;
|
2589
|
+
const nextBtn = !isPicker ? menuToggle : pickerToggle;
|
2590
|
+
|
2591
|
+
if (!hasClass(parent, 'open')) {
|
2592
|
+
addClass(parent, 'open');
|
2593
|
+
}
|
2594
|
+
if (openDropdown) {
|
2595
|
+
removeClass(openDropdown, 'show');
|
2596
|
+
removePosition(openDropdown);
|
2597
|
+
}
|
2598
|
+
addClass(dropdown, 'bottom');
|
2599
|
+
reflow(dropdown);
|
2600
|
+
addClass(dropdown, 'show');
|
2601
|
+
|
2602
|
+
if (isPicker) self.update();
|
2603
|
+
|
2604
|
+
if (!self.isOpen) {
|
2605
|
+
toggleEventsOnShown(self, true);
|
2606
|
+
self.updateDropdownPosition();
|
2607
|
+
self.isOpen = true;
|
2608
|
+
setAttribute(self.input, tabIndex, '0');
|
2609
|
+
if (menuToggle) {
|
2610
|
+
setAttribute(menuToggle, tabIndex, '0');
|
2611
|
+
}
|
2612
|
+
}
|
2613
|
+
|
2614
|
+
setAttribute(nextBtn, ariaExpanded, 'true');
|
2615
|
+
if (activeBtn) {
|
2616
|
+
setAttribute(activeBtn, ariaExpanded, 'false');
|
2617
|
+
}
|
1975
2618
|
}
|
1976
2619
|
|
1977
2620
|
/**
|
1978
|
-
* Color Picker
|
2621
|
+
* Color Picker Web Component
|
1979
2622
|
* @see http://thednp.github.io/color-picker
|
1980
2623
|
*/
|
1981
2624
|
class ColorPicker {
|
1982
2625
|
/**
|
1983
|
-
* Returns a new ColorPicker instance.
|
2626
|
+
* Returns a new `ColorPicker` instance. The target of this constructor
|
2627
|
+
* must be an `HTMLInputElement`.
|
2628
|
+
*
|
1984
2629
|
* @param {HTMLInputElement | string} target the target `<input>` element
|
2630
|
+
* @param {CP.ColorPickerOptions=} config instance options
|
1985
2631
|
*/
|
1986
|
-
constructor(target) {
|
2632
|
+
constructor(target, config) {
|
1987
2633
|
const self = this;
|
1988
2634
|
/** @type {HTMLInputElement} */
|
1989
2635
|
// @ts-ignore
|
1990
|
-
|
2636
|
+
const input = querySelector(target);
|
2637
|
+
|
1991
2638
|
// invalidate
|
1992
|
-
if (!
|
1993
|
-
|
2639
|
+
if (!input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
|
2640
|
+
self.input = input;
|
2641
|
+
|
2642
|
+
const parent = closest(input, colorPickerParentSelector);
|
2643
|
+
if (!parent) throw new TypeError('ColorPicker requires a specific markup to work.');
|
1994
2644
|
|
1995
2645
|
/** @type {HTMLElement} */
|
1996
2646
|
// @ts-ignore
|
1997
|
-
self.parent =
|
1998
|
-
if (!self.parent) throw new TypeError('ColorPicker requires a specific markup to work.');
|
2647
|
+
self.parent = parent;
|
1999
2648
|
|
2000
2649
|
/** @type {number} */
|
2001
2650
|
self.id = getUID(input, colorPickerString);
|
2002
2651
|
|
2003
2652
|
// set initial state
|
2004
|
-
/** @type {
|
2653
|
+
/** @type {HTMLElement?} */
|
2005
2654
|
self.dragElement = null;
|
2006
2655
|
/** @type {boolean} */
|
2007
2656
|
self.isOpen = false;
|
@@ -2011,26 +2660,59 @@
|
|
2011
2660
|
};
|
2012
2661
|
/** @type {Record<string, string>} */
|
2013
2662
|
self.colorLabels = {};
|
2014
|
-
/** @type {
|
2015
|
-
self.
|
2016
|
-
/** @type {
|
2017
|
-
self.
|
2663
|
+
/** @type {string[]=} */
|
2664
|
+
self.colorKeywords = undefined;
|
2665
|
+
/** @type {(ColorPalette | string[])=} */
|
2666
|
+
self.colorPresets = undefined;
|
2667
|
+
|
2668
|
+
// process options
|
2669
|
+
const {
|
2670
|
+
format, componentLabels, colorLabels, colorKeywords, colorPresets,
|
2671
|
+
} = normalizeOptions(this.isCE ? parent : input, colorPickerDefaults, config || {});
|
2672
|
+
|
2673
|
+
let translatedColorLabels = colorNames;
|
2674
|
+
if (colorLabels instanceof Array && colorLabels.length === 17) {
|
2675
|
+
translatedColorLabels = colorLabels;
|
2676
|
+
} else if (colorLabels && colorLabels.split(',').length === 17) {
|
2677
|
+
translatedColorLabels = colorLabels.split(',');
|
2678
|
+
}
|
2679
|
+
|
2680
|
+
// expose colour labels to all methods
|
2681
|
+
colorNames.forEach((c, i) => {
|
2682
|
+
self.colorLabels[c] = translatedColorLabels[i].trim();
|
2683
|
+
});
|
2684
|
+
|
2685
|
+
// update and expose component labels
|
2686
|
+
const tempLabels = ObjectAssign({}, colorPickerLabels);
|
2687
|
+
const jsonLabels = componentLabels && isValidJSON(componentLabels)
|
2688
|
+
? JSON.parse(componentLabels) : componentLabels || {};
|
2689
|
+
|
2018
2690
|
/** @type {Record<string, string>} */
|
2019
|
-
self.componentLabels = ObjectAssign(
|
2691
|
+
self.componentLabels = ObjectAssign(tempLabels, jsonLabels);
|
2020
2692
|
|
2021
|
-
|
2022
|
-
|
2023
|
-
self.componentLabels = ObjectAssign(self.componentLabels, temp);
|
2693
|
+
/** @type {Color} */
|
2694
|
+
self.color = new Color('white', format);
|
2024
2695
|
|
2025
|
-
|
2026
|
-
|
2696
|
+
/** @type {CP.ColorFormats} */
|
2697
|
+
self.format = format;
|
2027
2698
|
|
2028
|
-
//
|
2029
|
-
|
2699
|
+
// set colour defaults
|
2700
|
+
if (colorKeywords instanceof Array) {
|
2701
|
+
self.colorKeywords = colorKeywords;
|
2702
|
+
} else if (typeof colorKeywords === 'string' && colorKeywords.length) {
|
2703
|
+
self.colorKeywords = colorKeywords.split(',');
|
2704
|
+
}
|
2030
2705
|
|
2031
2706
|
// set colour presets
|
2032
|
-
if (
|
2033
|
-
self.
|
2707
|
+
if (colorPresets instanceof Array) {
|
2708
|
+
self.colorPresets = colorPresets;
|
2709
|
+
} else if (typeof colorPresets === 'string' && colorPresets.length) {
|
2710
|
+
if (isValidJSON(colorPresets)) {
|
2711
|
+
const { hue, hueSteps, lightSteps } = JSON.parse(colorPresets);
|
2712
|
+
self.colorPresets = new ColorPalette(hue, hueSteps, lightSteps);
|
2713
|
+
} else {
|
2714
|
+
self.colorPresets = colorPresets.split(',').map((x) => x.trim());
|
2715
|
+
}
|
2034
2716
|
}
|
2035
2717
|
|
2036
2718
|
// bind events
|
@@ -2042,17 +2724,18 @@
|
|
2042
2724
|
self.pointerDown = self.pointerDown.bind(self);
|
2043
2725
|
self.pointerMove = self.pointerMove.bind(self);
|
2044
2726
|
self.pointerUp = self.pointerUp.bind(self);
|
2727
|
+
self.update = self.update.bind(self);
|
2045
2728
|
self.handleScroll = self.handleScroll.bind(self);
|
2046
2729
|
self.handleFocusOut = self.handleFocusOut.bind(self);
|
2047
2730
|
self.changeHandler = self.changeHandler.bind(self);
|
2048
2731
|
self.handleDismiss = self.handleDismiss.bind(self);
|
2049
|
-
self.
|
2732
|
+
self.keyToggle = self.keyToggle.bind(self);
|
2050
2733
|
self.handleKnobs = self.handleKnobs.bind(self);
|
2051
2734
|
|
2052
2735
|
// generate markup
|
2053
2736
|
initCallback(self);
|
2054
2737
|
|
2055
|
-
const
|
2738
|
+
const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
|
2056
2739
|
// set main elements
|
2057
2740
|
/** @type {HTMLElement} */
|
2058
2741
|
// @ts-ignore
|
@@ -2062,68 +2745,24 @@
|
|
2062
2745
|
self.menuToggle = querySelector('.menu-toggle', parent);
|
2063
2746
|
/** @type {HTMLElement} */
|
2064
2747
|
// @ts-ignore
|
2065
|
-
self.
|
2066
|
-
/** @type {HTMLElement} */
|
2067
|
-
// @ts-ignore
|
2068
|
-
self.colorPicker = querySelector('.color-dropdown.picker', parent);
|
2748
|
+
self.colorPicker = colorPicker;
|
2069
2749
|
/** @type {HTMLElement} */
|
2070
2750
|
// @ts-ignore
|
2071
|
-
self.
|
2751
|
+
self.colorMenu = colorMenu;
|
2072
2752
|
/** @type {HTMLInputElement[]} */
|
2073
2753
|
// @ts-ignore
|
2074
|
-
self.inputs = [...
|
2754
|
+
self.inputs = [...getElementsByClassName('color-input', parent)];
|
2755
|
+
const [controls] = getElementsByClassName('color-controls', parent);
|
2756
|
+
self.controls = controls;
|
2757
|
+
/** @type {(HTMLElement | Element)[]} */
|
2758
|
+
self.controlKnobs = [...getElementsByClassName('knob', controls)];
|
2075
2759
|
/** @type {(HTMLElement)[]} */
|
2076
2760
|
// @ts-ignore
|
2077
|
-
self.
|
2078
|
-
/** @type {HTMLCanvasElement[]} */
|
2079
|
-
// @ts-ignore
|
2080
|
-
self.visuals = [...querySelectorAll('canvas', self.controls)];
|
2081
|
-
/** @type {HTMLLabelElement[]} */
|
2082
|
-
// @ts-ignore
|
2083
|
-
self.knobLabels = [...querySelectorAll('.color-label', parent)];
|
2084
|
-
/** @type {HTMLLabelElement} */
|
2085
|
-
// @ts-ignore
|
2086
|
-
self.appearance = querySelector('.color-appearance', parent);
|
2087
|
-
|
2088
|
-
const [v1, v2, v3] = self.visuals;
|
2089
|
-
// set dimensions
|
2090
|
-
/** @type {number} */
|
2091
|
-
self.width1 = v1.width;
|
2092
|
-
/** @type {number} */
|
2093
|
-
self.height1 = v1.height;
|
2094
|
-
/** @type {number} */
|
2095
|
-
self.width2 = v2.width;
|
2096
|
-
/** @type {number} */
|
2097
|
-
self.height2 = v2.height;
|
2098
|
-
// set main controls
|
2099
|
-
/** @type {*} */
|
2100
|
-
self.ctx1 = v1.getContext('2d');
|
2101
|
-
/** @type {*} */
|
2102
|
-
self.ctx2 = v2.getContext('2d');
|
2103
|
-
self.ctx1.rect(0, 0, self.width1, self.height1);
|
2104
|
-
self.ctx2.rect(0, 0, self.width2, self.height2);
|
2761
|
+
self.visuals = [...getElementsByClassName('visual-control', controls)];
|
2105
2762
|
|
2106
|
-
|
2107
|
-
self.
|
2108
|
-
/** @type {number} */
|
2109
|
-
self.height3 = 0;
|
2110
|
-
|
2111
|
-
// set alpha control except hex
|
2112
|
-
if (self.format !== 'hex') {
|
2113
|
-
self.width3 = v3.width;
|
2114
|
-
self.height3 = v3.height;
|
2115
|
-
/** @type {*} */
|
2116
|
-
this.ctx3 = v3.getContext('2d');
|
2117
|
-
self.ctx3.rect(0, 0, self.width3, self.height3);
|
2118
|
-
}
|
2763
|
+
// update colour picker controls, inputs and visuals
|
2764
|
+
self.update();
|
2119
2765
|
|
2120
|
-
// update color picker controls, inputs and visuals
|
2121
|
-
this.setControlPositions();
|
2122
|
-
this.setColorAppearence();
|
2123
|
-
// don't trigger change at initialization
|
2124
|
-
this.updateInputs(true);
|
2125
|
-
this.updateControls();
|
2126
|
-
this.updateVisuals();
|
2127
2766
|
// add main events listeners
|
2128
2767
|
toggleEvents(self, true);
|
2129
2768
|
|
@@ -2131,65 +2770,52 @@
|
|
2131
2770
|
Data.set(input, colorPickerString, self);
|
2132
2771
|
}
|
2133
2772
|
|
2134
|
-
/** Returns the current
|
2773
|
+
/** Returns the current colour value */
|
2135
2774
|
get value() { return this.input.value; }
|
2136
2775
|
|
2137
2776
|
/**
|
2138
|
-
* Sets a new
|
2139
|
-
* @param {string} v new
|
2777
|
+
* Sets a new colour value.
|
2778
|
+
* @param {string} v new colour value
|
2140
2779
|
*/
|
2141
2780
|
set value(v) { this.input.value = v; }
|
2142
2781
|
|
2143
|
-
/** Check if the
|
2144
|
-
get
|
2145
|
-
|
2146
|
-
|
2147
|
-
* Returns the colour format.
|
2148
|
-
* @returns {CP.ColorFormats | string}
|
2149
|
-
*/
|
2150
|
-
get format() { return getAttribute(this.input, 'format') || 'hex'; }
|
2151
|
-
|
2152
|
-
/** Returns the input name. */
|
2153
|
-
get name() { return getAttribute(this.input, 'name'); }
|
2154
|
-
|
2155
|
-
/**
|
2156
|
-
* Returns the label associated to the input.
|
2157
|
-
* @returns {HTMLLabelElement?}
|
2158
|
-
*/
|
2159
|
-
// @ts-ignore
|
2160
|
-
get label() { return querySelector(`[for="${this.input.id}"]`); }
|
2161
|
-
|
2162
|
-
/** Check if the color presets include any non-color. */
|
2163
|
-
get includeNonColor() {
|
2164
|
-
return this.keywords instanceof Array
|
2165
|
-
&& this.keywords.some((x) => nonColors.includes(x));
|
2782
|
+
/** Check if the colour presets include any non-colour. */
|
2783
|
+
get hasNonColor() {
|
2784
|
+
return this.colorKeywords instanceof Array
|
2785
|
+
&& this.colorKeywords.some((x) => nonColors.includes(x));
|
2166
2786
|
}
|
2167
2787
|
|
2168
|
-
/**
|
2169
|
-
get
|
2788
|
+
/** Check if the parent of the target is a `ColorPickerElement` instance. */
|
2789
|
+
get isCE() { return this.parent.localName === colorPickerString; }
|
2790
|
+
|
2791
|
+
/** Returns hexadecimal value of the current colour. */
|
2792
|
+
get hex() { return this.color.toHex(true); }
|
2170
2793
|
|
2171
|
-
/** Returns the current
|
2794
|
+
/** Returns the current colour value in {h,s,v,a} object format. */
|
2172
2795
|
get hsv() { return this.color.toHsv(); }
|
2173
2796
|
|
2174
|
-
/** Returns the current
|
2797
|
+
/** Returns the current colour value in {h,s,l,a} object format. */
|
2175
2798
|
get hsl() { return this.color.toHsl(); }
|
2176
2799
|
|
2177
|
-
/** Returns the current
|
2800
|
+
/** Returns the current colour value in {h,w,b,a} object format. */
|
2801
|
+
get hwb() { return this.color.toHwb(); }
|
2802
|
+
|
2803
|
+
/** Returns the current colour value in {r,g,b,a} object format. */
|
2178
2804
|
get rgb() { return this.color.toRgb(); }
|
2179
2805
|
|
2180
|
-
/** Returns the current
|
2806
|
+
/** Returns the current colour brightness. */
|
2181
2807
|
get brightness() { return this.color.brightness; }
|
2182
2808
|
|
2183
|
-
/** Returns the current
|
2809
|
+
/** Returns the current colour luminance. */
|
2184
2810
|
get luminance() { return this.color.luminance; }
|
2185
2811
|
|
2186
|
-
/** Checks if the current colour requires a light text
|
2812
|
+
/** Checks if the current colour requires a light text colour. */
|
2187
2813
|
get isDark() {
|
2188
|
-
const {
|
2189
|
-
return brightness < 120 &&
|
2814
|
+
const { color, brightness } = this;
|
2815
|
+
return brightness < 120 && color.a > 0.33;
|
2190
2816
|
}
|
2191
2817
|
|
2192
|
-
/** Checks if the current input value is a valid
|
2818
|
+
/** Checks if the current input value is a valid colour. */
|
2193
2819
|
get isValid() {
|
2194
2820
|
const inputValue = this.input.value;
|
2195
2821
|
return inputValue !== '' && new Color(inputValue).isValid;
|
@@ -2199,89 +2825,79 @@
|
|
2199
2825
|
updateVisuals() {
|
2200
2826
|
const self = this;
|
2201
2827
|
const {
|
2202
|
-
|
2203
|
-
width1, width2, width3,
|
2204
|
-
height1, height2, height3,
|
2205
|
-
ctx1, ctx2, ctx3,
|
2828
|
+
format, controlPositions, visuals,
|
2206
2829
|
} = self;
|
2207
|
-
const
|
2830
|
+
const [v1, v2, v3] = visuals;
|
2831
|
+
const { offsetWidth, offsetHeight } = v1;
|
2832
|
+
const hue = format === 'hsl'
|
2833
|
+
? controlPositions.c1x / offsetWidth
|
2834
|
+
: controlPositions.c2y / offsetHeight;
|
2835
|
+
// @ts-ignore - `hslToRgb` is assigned to `Color` as static method
|
2836
|
+
const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
|
2837
|
+
const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
|
2838
|
+
const alpha = 1 - controlPositions.c3y / offsetHeight;
|
2839
|
+
const roundA = roundPart((alpha * 100)) / 100;
|
2208
2840
|
|
2209
2841
|
if (format !== 'hsl') {
|
2210
|
-
const
|
2211
|
-
|
2212
|
-
|
2213
|
-
|
2214
|
-
|
2215
|
-
|
2216
|
-
|
2217
|
-
|
2218
|
-
|
2219
|
-
|
2220
|
-
|
2221
|
-
|
2222
|
-
|
2223
|
-
|
2224
|
-
ctx1.fillRect(0, 0, width1, height1);
|
2225
|
-
|
2226
|
-
const hueGrad = ctx2.createLinearGradient(0, 0, 0, height1);
|
2227
|
-
hueGrad.addColorStop(0, 'rgba(255,0,0,1)');
|
2228
|
-
hueGrad.addColorStop(0.17, 'rgba(255,255,0,1)');
|
2229
|
-
hueGrad.addColorStop(0.34, 'rgba(0,255,0,1)');
|
2230
|
-
hueGrad.addColorStop(0.51, 'rgba(0,255,255,1)');
|
2231
|
-
hueGrad.addColorStop(0.68, 'rgba(0,0,255,1)');
|
2232
|
-
hueGrad.addColorStop(0.85, 'rgba(255,0,255,1)');
|
2233
|
-
hueGrad.addColorStop(1, 'rgba(255,0,0,1)');
|
2234
|
-
ctx2.fillStyle = hueGrad;
|
2235
|
-
ctx2.fillRect(0, 0, width2, height2);
|
2842
|
+
const fill = new Color({
|
2843
|
+
h: hue, s: 1, l: 0.5, a: alpha,
|
2844
|
+
}).toRgbString();
|
2845
|
+
const hueGradient = `linear-gradient(
|
2846
|
+
rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
|
2847
|
+
rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
|
2848
|
+
rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
|
2849
|
+
rgb(255,0,0) 100%)`;
|
2850
|
+
setElementStyle(v1, {
|
2851
|
+
background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
|
2852
|
+
linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
|
2853
|
+
${whiteGrad}`,
|
2854
|
+
});
|
2855
|
+
setElementStyle(v2, { background: hueGradient });
|
2236
2856
|
} else {
|
2237
|
-
const
|
2238
|
-
const
|
2239
|
-
|
2240
|
-
|
2241
|
-
|
2242
|
-
|
2243
|
-
|
2244
|
-
|
2245
|
-
|
2246
|
-
|
2247
|
-
|
2248
|
-
|
2249
|
-
|
2250
|
-
|
2251
|
-
|
2252
|
-
|
2253
|
-
|
2254
|
-
|
2255
|
-
|
2256
|
-
|
2257
|
-
|
2258
|
-
|
2259
|
-
|
2260
|
-
|
2261
|
-
|
2262
|
-
|
2263
|
-
|
2264
|
-
|
2265
|
-
|
2266
|
-
|
2267
|
-
|
2268
|
-
|
2269
|
-
|
2270
|
-
|
2271
|
-
|
2272
|
-
|
2273
|
-
if (format !== 'hex') {
|
2274
|
-
ctx3.clearRect(0, 0, width3, height3);
|
2275
|
-
const alphaGrad = ctx3.createLinearGradient(0, 0, 0, height3);
|
2276
|
-
alphaGrad.addColorStop(0, `rgba(${r},${g},${b},1)`);
|
2277
|
-
alphaGrad.addColorStop(1, `rgba(${r},${g},${b},0)`);
|
2278
|
-
ctx3.fillStyle = alphaGrad;
|
2279
|
-
ctx3.fillRect(0, 0, width3, height3);
|
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();
|
2889
|
+
|
2890
|
+
setElementStyle(v2, {
|
2891
|
+
background: `linear-gradient(rgb(${r},${g},${b}) 0%, rgb(${gr},${gg},${gb}) 100%)`,
|
2892
|
+
});
|
2280
2893
|
}
|
2894
|
+
setElementStyle(v3, {
|
2895
|
+
background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
|
2896
|
+
});
|
2281
2897
|
}
|
2282
2898
|
|
2283
2899
|
/**
|
2284
|
-
*
|
2900
|
+
* The `ColorPicker` *focusout* event listener when open.
|
2285
2901
|
* @param {FocusEvent} e
|
2286
2902
|
* @this {ColorPicker}
|
2287
2903
|
*/
|
@@ -2293,7 +2909,7 @@
|
|
2293
2909
|
}
|
2294
2910
|
|
2295
2911
|
/**
|
2296
|
-
*
|
2912
|
+
* The `ColorPicker` *keyup* event listener when open.
|
2297
2913
|
* @param {KeyboardEvent} e
|
2298
2914
|
* @this {ColorPicker}
|
2299
2915
|
*/
|
@@ -2305,14 +2921,13 @@
|
|
2305
2921
|
}
|
2306
2922
|
|
2307
2923
|
/**
|
2308
|
-
*
|
2924
|
+
* The `ColorPicker` *scroll* event listener when open.
|
2309
2925
|
* @param {Event} e
|
2310
2926
|
* @this {ColorPicker}
|
2311
2927
|
*/
|
2312
2928
|
handleScroll(e) {
|
2313
2929
|
const self = this;
|
2314
|
-
|
2315
|
-
const { activeElement } = document;
|
2930
|
+
const { activeElement } = getDocument(self.input);
|
2316
2931
|
|
2317
2932
|
if ((isMobile && self.dragElement)
|
2318
2933
|
|| (activeElement && self.controlKnobs.includes(activeElement))) {
|
@@ -2324,22 +2939,51 @@
|
|
2324
2939
|
}
|
2325
2940
|
|
2326
2941
|
/**
|
2327
|
-
*
|
2942
|
+
* The `ColorPicker` keyboard event listener for menu navigation.
|
2328
2943
|
* @param {KeyboardEvent} e
|
2329
2944
|
* @this {ColorPicker}
|
2330
2945
|
*/
|
2331
2946
|
menuKeyHandler(e) {
|
2332
2947
|
const { target, code } = e;
|
2333
|
-
|
2334
|
-
|
2948
|
+
// @ts-ignore
|
2949
|
+
const { previousElementSibling, nextElementSibling, parentElement } = target;
|
2950
|
+
const isColorOptionsMenu = parentElement && hasClass(parentElement, 'color-options');
|
2951
|
+
const allSiblings = [...parentElement.children];
|
2952
|
+
const columnsCount = isColorOptionsMenu
|
2953
|
+
&& getElementStyle(parentElement, 'grid-template-columns').split(' ').length;
|
2954
|
+
const currentIndex = allSiblings.indexOf(target);
|
2955
|
+
const previousElement = currentIndex > -1
|
2956
|
+
&& columnsCount && allSiblings[currentIndex - columnsCount];
|
2957
|
+
const nextElement = currentIndex > -1
|
2958
|
+
&& columnsCount && allSiblings[currentIndex + columnsCount];
|
2959
|
+
|
2960
|
+
if ([keyArrowDown, keyArrowUp, keySpace].includes(code)) {
|
2961
|
+
// prevent scroll when navigating the menu via arrow keys / Space
|
2335
2962
|
e.preventDefault();
|
2336
|
-
}
|
2963
|
+
}
|
2964
|
+
if (isColorOptionsMenu) {
|
2965
|
+
if (previousElement && code === keyArrowUp) {
|
2966
|
+
focus(previousElement);
|
2967
|
+
} else if (nextElement && code === keyArrowDown) {
|
2968
|
+
focus(nextElement);
|
2969
|
+
} else if (previousElementSibling && code === keyArrowLeft) {
|
2970
|
+
focus(previousElementSibling);
|
2971
|
+
} else if (nextElementSibling && code === keyArrowRight) {
|
2972
|
+
focus(nextElementSibling);
|
2973
|
+
}
|
2974
|
+
} else if (previousElementSibling && [keyArrowLeft, keyArrowUp].includes(code)) {
|
2975
|
+
focus(previousElementSibling);
|
2976
|
+
} else if (nextElementSibling && [keyArrowRight, keyArrowDown].includes(code)) {
|
2977
|
+
focus(nextElementSibling);
|
2978
|
+
}
|
2979
|
+
|
2980
|
+
if ([keyEnter, keySpace].includes(code)) {
|
2337
2981
|
this.menuClickHandler({ target });
|
2338
2982
|
}
|
2339
2983
|
}
|
2340
2984
|
|
2341
2985
|
/**
|
2342
|
-
*
|
2986
|
+
* The `ColorPicker` click event listener for the colour menu presets / defaults.
|
2343
2987
|
* @param {Partial<Event>} e
|
2344
2988
|
* @this {ColorPicker}
|
2345
2989
|
*/
|
@@ -2347,51 +2991,57 @@
|
|
2347
2991
|
const self = this;
|
2348
2992
|
/** @type {*} */
|
2349
2993
|
const { target } = e;
|
2350
|
-
const {
|
2994
|
+
const { colorMenu } = self;
|
2351
2995
|
const newOption = (getAttribute(target, 'data-value') || '').trim();
|
2352
|
-
|
2353
|
-
|
2354
|
-
|
2355
|
-
|
2356
|
-
|
2357
|
-
self.updateInputs(true);
|
2358
|
-
self.updateControls();
|
2359
|
-
self.updateVisuals();
|
2996
|
+
// invalidate for targets other than color options
|
2997
|
+
if (!newOption.length) return;
|
2998
|
+
const currentActive = querySelector('li.active', colorMenu);
|
2999
|
+
let newColor = nonColors.includes(newOption) ? 'white' : newOption;
|
3000
|
+
newColor = newOption === 'transparent' ? 'rgba(0,0,0,0)' : newOption;
|
2360
3001
|
|
2361
|
-
|
2362
|
-
|
2363
|
-
|
2364
|
-
|
3002
|
+
const {
|
3003
|
+
r, g, b, a,
|
3004
|
+
} = new Color(newColor);
|
3005
|
+
|
3006
|
+
ObjectAssign(self.color, {
|
3007
|
+
r, g, b, a,
|
3008
|
+
});
|
3009
|
+
|
3010
|
+
self.update();
|
2365
3011
|
|
2366
3012
|
if (currentActive !== target) {
|
3013
|
+
if (currentActive) {
|
3014
|
+
removeClass(currentActive, 'active');
|
3015
|
+
removeAttribute(currentActive, ariaSelected);
|
3016
|
+
}
|
3017
|
+
|
2367
3018
|
addClass(target, 'active');
|
2368
3019
|
setAttribute(target, ariaSelected, 'true');
|
2369
3020
|
|
2370
3021
|
if (nonColors.includes(newOption)) {
|
2371
3022
|
self.value = newOption;
|
2372
|
-
firePickerChange(self);
|
2373
3023
|
}
|
3024
|
+
firePickerChange(self);
|
2374
3025
|
}
|
2375
3026
|
}
|
2376
3027
|
|
2377
3028
|
/**
|
2378
|
-
*
|
3029
|
+
* The `ColorPicker` *touchstart* / *mousedown* events listener for control knobs.
|
2379
3030
|
* @param {TouchEvent} e
|
2380
3031
|
* @this {ColorPicker}
|
2381
3032
|
*/
|
2382
3033
|
pointerDown(e) {
|
2383
3034
|
const self = this;
|
3035
|
+
/** @type {*} */
|
2384
3036
|
const {
|
2385
|
-
// @ts-ignore
|
2386
3037
|
type, target, touches, pageX, pageY,
|
2387
3038
|
} = e;
|
2388
|
-
const { visuals, controlKnobs
|
3039
|
+
const { colorMenu, visuals, controlKnobs } = self;
|
2389
3040
|
const [v1, v2, v3] = visuals;
|
2390
3041
|
const [c1, c2, c3] = controlKnobs;
|
2391
|
-
/** @type {
|
2392
|
-
|
2393
|
-
|
2394
|
-
? target : querySelector('canvas', target.parentElement);
|
3042
|
+
/** @type {HTMLElement} */
|
3043
|
+
const visual = hasClass(target, 'visual-control')
|
3044
|
+
? target : querySelector('.visual-control', target.parentElement);
|
2395
3045
|
const visualRect = getBoundingClientRect(visual);
|
2396
3046
|
const X = type === 'touchstart' ? touches[0].pageX : pageX;
|
2397
3047
|
const Y = type === 'touchstart' ? touches[0].pageY : pageY;
|
@@ -2400,42 +3050,53 @@
|
|
2400
3050
|
|
2401
3051
|
if (target === v1 || target === c1) {
|
2402
3052
|
self.dragElement = visual;
|
2403
|
-
self.changeControl1(
|
3053
|
+
self.changeControl1(offsetX, offsetY);
|
2404
3054
|
} else if (target === v2 || target === c2) {
|
2405
3055
|
self.dragElement = visual;
|
2406
|
-
self.changeControl2(
|
2407
|
-
} else if (
|
3056
|
+
self.changeControl2(offsetY);
|
3057
|
+
} else if (target === v3 || target === c3) {
|
2408
3058
|
self.dragElement = visual;
|
2409
|
-
self.changeAlpha(
|
3059
|
+
self.changeAlpha(offsetY);
|
3060
|
+
}
|
3061
|
+
|
3062
|
+
if (colorMenu) {
|
3063
|
+
const currentActive = querySelector('li.active', colorMenu);
|
3064
|
+
if (currentActive) {
|
3065
|
+
removeClass(currentActive, 'active');
|
3066
|
+
removeAttribute(currentActive, ariaSelected);
|
3067
|
+
}
|
2410
3068
|
}
|
2411
3069
|
e.preventDefault();
|
2412
3070
|
}
|
2413
3071
|
|
2414
3072
|
/**
|
2415
|
-
*
|
3073
|
+
* The `ColorPicker` *touchend* / *mouseup* events listener for control knobs.
|
2416
3074
|
* @param {TouchEvent} e
|
2417
3075
|
* @this {ColorPicker}
|
2418
3076
|
*/
|
2419
3077
|
pointerUp({ target }) {
|
2420
3078
|
const self = this;
|
2421
|
-
const
|
3079
|
+
const { parent } = self;
|
3080
|
+
const doc = getDocument(parent);
|
3081
|
+
const currentOpen = querySelector(`${colorPickerParentSelector}.open`, doc) !== null;
|
3082
|
+
const selection = doc.getSelection();
|
2422
3083
|
// @ts-ignore
|
2423
3084
|
if (!self.dragElement && !selection.toString().length
|
2424
3085
|
// @ts-ignore
|
2425
|
-
&& !
|
2426
|
-
self.hide();
|
3086
|
+
&& !parent.contains(target)) {
|
3087
|
+
self.hide(currentOpen);
|
2427
3088
|
}
|
2428
3089
|
|
2429
3090
|
self.dragElement = null;
|
2430
3091
|
}
|
2431
3092
|
|
2432
3093
|
/**
|
2433
|
-
*
|
3094
|
+
* The `ColorPicker` *touchmove* / *mousemove* events listener for control knobs.
|
2434
3095
|
* @param {TouchEvent} e
|
2435
3096
|
*/
|
2436
3097
|
pointerMove(e) {
|
2437
3098
|
const self = this;
|
2438
|
-
const { dragElement, visuals
|
3099
|
+
const { dragElement, visuals } = self;
|
2439
3100
|
const [v1, v2, v3] = visuals;
|
2440
3101
|
const {
|
2441
3102
|
// @ts-ignore
|
@@ -2451,20 +3112,20 @@
|
|
2451
3112
|
const offsetY = Y - window.pageYOffset - controlRect.top;
|
2452
3113
|
|
2453
3114
|
if (dragElement === v1) {
|
2454
|
-
self.changeControl1(
|
3115
|
+
self.changeControl1(offsetX, offsetY);
|
2455
3116
|
}
|
2456
3117
|
|
2457
3118
|
if (dragElement === v2) {
|
2458
|
-
self.changeControl2(
|
3119
|
+
self.changeControl2(offsetY);
|
2459
3120
|
}
|
2460
3121
|
|
2461
|
-
if (dragElement === v3
|
2462
|
-
self.changeAlpha(
|
3122
|
+
if (dragElement === v3) {
|
3123
|
+
self.changeAlpha(offsetY);
|
2463
3124
|
}
|
2464
3125
|
}
|
2465
3126
|
|
2466
3127
|
/**
|
2467
|
-
*
|
3128
|
+
* The `ColorPicker` *keydown* event listener for control knobs.
|
2468
3129
|
* @param {KeyboardEvent} e
|
2469
3130
|
*/
|
2470
3131
|
handleKnobs(e) {
|
@@ -2475,54 +3136,64 @@
|
|
2475
3136
|
if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
|
2476
3137
|
e.preventDefault();
|
2477
3138
|
|
2478
|
-
const {
|
2479
|
-
const {
|
2480
|
-
const currentKnob = controlKnobs.find((x) => x === activeElement);
|
3139
|
+
const { format, controlKnobs, visuals } = self;
|
3140
|
+
const { offsetWidth, offsetHeight } = visuals[0];
|
2481
3141
|
const [c1, c2, c3] = controlKnobs;
|
3142
|
+
const { activeElement } = getDocument(c1);
|
3143
|
+
const currentKnob = controlKnobs.find((x) => x === activeElement);
|
3144
|
+
const yRatio = offsetHeight / (format === 'hsl' ? 100 : 360);
|
2482
3145
|
|
2483
3146
|
if (currentKnob) {
|
2484
3147
|
let offsetX = 0;
|
2485
3148
|
let offsetY = 0;
|
3149
|
+
|
2486
3150
|
if (target === c1) {
|
3151
|
+
const xRatio = offsetWidth / (format === 'hsl' ? 360 : 100);
|
3152
|
+
|
2487
3153
|
if ([keyArrowLeft, keyArrowRight].includes(code)) {
|
2488
|
-
self.controlPositions.c1x += code === keyArrowRight ?
|
3154
|
+
self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
|
2489
3155
|
} else if ([keyArrowUp, keyArrowDown].includes(code)) {
|
2490
|
-
self.controlPositions.c1y += code === keyArrowDown ?
|
3156
|
+
self.controlPositions.c1y += code === keyArrowDown ? yRatio : -yRatio;
|
2491
3157
|
}
|
2492
3158
|
|
2493
3159
|
offsetX = self.controlPositions.c1x;
|
2494
3160
|
offsetY = self.controlPositions.c1y;
|
2495
|
-
self.changeControl1(
|
3161
|
+
self.changeControl1(offsetX, offsetY);
|
2496
3162
|
} else if (target === c2) {
|
2497
|
-
self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code)
|
3163
|
+
self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code)
|
3164
|
+
? yRatio
|
3165
|
+
: -yRatio;
|
3166
|
+
|
2498
3167
|
offsetY = self.controlPositions.c2y;
|
2499
|
-
self.changeControl2(
|
3168
|
+
self.changeControl2(offsetY);
|
2500
3169
|
} else if (target === c3) {
|
2501
|
-
self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code)
|
3170
|
+
self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code)
|
3171
|
+
? yRatio
|
3172
|
+
: -yRatio;
|
3173
|
+
|
2502
3174
|
offsetY = self.controlPositions.c3y;
|
2503
|
-
self.changeAlpha(
|
3175
|
+
self.changeAlpha(offsetY);
|
2504
3176
|
}
|
2505
|
-
|
2506
|
-
self.setColorAppearence();
|
2507
|
-
self.updateInputs();
|
2508
|
-
self.updateControls();
|
2509
|
-
self.updateVisuals();
|
2510
3177
|
self.handleScroll(e);
|
2511
3178
|
}
|
2512
3179
|
}
|
2513
3180
|
|
2514
|
-
/**
|
3181
|
+
/** The event listener of the colour form inputs. */
|
2515
3182
|
changeHandler() {
|
2516
3183
|
const self = this;
|
2517
3184
|
let colorSource;
|
2518
|
-
/** @type {HTMLInputElement} */
|
2519
|
-
// @ts-ignore
|
2520
|
-
const { activeElement } = document;
|
2521
3185
|
const {
|
2522
|
-
inputs, format, value: currentValue, input,
|
3186
|
+
inputs, format, value: currentValue, input, controlPositions, visuals,
|
2523
3187
|
} = self;
|
2524
|
-
|
2525
|
-
const
|
3188
|
+
/** @type {*} */
|
3189
|
+
const { activeElement } = getDocument(input);
|
3190
|
+
const { offsetHeight } = visuals[0];
|
3191
|
+
const [i1,,, i4] = inputs;
|
3192
|
+
const [v1, v2, v3, v4] = format === 'rgb'
|
3193
|
+
? inputs.map((i) => parseFloat(i.value) / (i === i4 ? 100 : 1))
|
3194
|
+
: inputs.map((i) => parseFloat(i.value) / (i !== i1 ? 100 : 360));
|
3195
|
+
const isNonColorValue = self.hasNonColor && nonColors.includes(currentValue);
|
3196
|
+
const alpha = i4 ? v4 : (1 - controlPositions.c3y / offsetHeight);
|
2526
3197
|
|
2527
3198
|
if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
|
2528
3199
|
if (activeElement === input) {
|
@@ -2534,14 +3205,28 @@
|
|
2534
3205
|
} else if (format === 'hex') {
|
2535
3206
|
colorSource = i1.value;
|
2536
3207
|
} else if (format === 'hsl') {
|
2537
|
-
colorSource =
|
3208
|
+
colorSource = {
|
3209
|
+
h: v1, s: v2, l: v3, a: alpha,
|
3210
|
+
};
|
3211
|
+
} else if (format === 'hwb') {
|
3212
|
+
colorSource = {
|
3213
|
+
h: v1, w: v2, b: v3, a: alpha,
|
3214
|
+
};
|
2538
3215
|
} else {
|
2539
|
-
colorSource =
|
3216
|
+
colorSource = {
|
3217
|
+
r: v1, g: v2, b: v3, a: alpha,
|
3218
|
+
};
|
2540
3219
|
}
|
2541
3220
|
|
2542
|
-
|
3221
|
+
const {
|
3222
|
+
r, g, b, a,
|
3223
|
+
} = new Color(colorSource);
|
3224
|
+
|
3225
|
+
ObjectAssign(self.color, {
|
3226
|
+
r, g, b, a,
|
3227
|
+
});
|
2543
3228
|
self.setControlPositions();
|
2544
|
-
self.
|
3229
|
+
self.updateAppearance();
|
2545
3230
|
self.updateInputs();
|
2546
3231
|
self.updateControls();
|
2547
3232
|
self.updateVisuals();
|
@@ -2558,49 +3243,57 @@
|
|
2558
3243
|
* * `lightness` and `saturation` for HEX/RGB;
|
2559
3244
|
* * `lightness` and `hue` for HSL.
|
2560
3245
|
*
|
2561
|
-
* @param {
|
3246
|
+
* @param {number} X the X component of the offset
|
3247
|
+
* @param {number} Y the Y component of the offset
|
2562
3248
|
*/
|
2563
|
-
changeControl1(
|
3249
|
+
changeControl1(X, Y) {
|
2564
3250
|
const self = this;
|
2565
3251
|
let [offsetX, offsetY] = [0, 0];
|
2566
|
-
const { offsetX: X, offsetY: Y } = offsets;
|
2567
3252
|
const {
|
2568
|
-
format, controlPositions,
|
2569
|
-
height1, height2, height3, width1,
|
3253
|
+
format, controlPositions, visuals,
|
2570
3254
|
} = self;
|
3255
|
+
const { offsetHeight, offsetWidth } = visuals[0];
|
2571
3256
|
|
2572
|
-
if (X >
|
2573
|
-
|
2574
|
-
} else if (X >= 0) {
|
2575
|
-
offsetX = X;
|
2576
|
-
}
|
3257
|
+
if (X > offsetWidth) offsetX = offsetWidth;
|
3258
|
+
else if (X >= 0) offsetX = X;
|
2577
3259
|
|
2578
|
-
if (Y >
|
2579
|
-
|
2580
|
-
|
2581
|
-
|
2582
|
-
|
3260
|
+
if (Y > offsetHeight) offsetY = offsetHeight;
|
3261
|
+
else if (Y >= 0) offsetY = Y;
|
3262
|
+
|
3263
|
+
const hue = format === 'hsl'
|
3264
|
+
? offsetX / offsetWidth
|
3265
|
+
: controlPositions.c2y / offsetHeight;
|
2583
3266
|
|
2584
|
-
const
|
2585
|
-
?
|
2586
|
-
:
|
3267
|
+
const saturation = format === 'hsl'
|
3268
|
+
? 1 - controlPositions.c2y / offsetHeight
|
3269
|
+
: offsetX / offsetWidth;
|
2587
3270
|
|
2588
|
-
const
|
2589
|
-
|
2590
|
-
: Math.round((1 - controlPositions.c2y / height2) * 100);
|
3271
|
+
const lightness = 1 - offsetY / offsetHeight;
|
3272
|
+
const alpha = 1 - controlPositions.c3y / offsetHeight;
|
2591
3273
|
|
2592
|
-
const
|
2593
|
-
|
2594
|
-
|
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
|
+
};
|
2595
3281
|
|
2596
3282
|
// new color
|
2597
|
-
|
3283
|
+
const {
|
3284
|
+
r, g, b, a,
|
3285
|
+
} = new Color(colorObject);
|
3286
|
+
|
3287
|
+
ObjectAssign(self.color, {
|
3288
|
+
r, g, b, a,
|
3289
|
+
});
|
3290
|
+
|
2598
3291
|
// new positions
|
2599
3292
|
self.controlPositions.c1x = offsetX;
|
2600
3293
|
self.controlPositions.c1y = offsetY;
|
2601
3294
|
|
2602
3295
|
// update color picker
|
2603
|
-
self.
|
3296
|
+
self.updateAppearance();
|
2604
3297
|
self.updateInputs();
|
2605
3298
|
self.updateControls();
|
2606
3299
|
self.updateVisuals();
|
@@ -2608,37 +3301,52 @@
|
|
2608
3301
|
|
2609
3302
|
/**
|
2610
3303
|
* Updates `ColorPicker` second control:
|
2611
|
-
* * `hue` for HEX/RGB;
|
3304
|
+
* * `hue` for HEX/RGB/HWB;
|
2612
3305
|
* * `saturation` for HSL.
|
2613
3306
|
*
|
2614
|
-
* @param {
|
3307
|
+
* @param {number} Y the Y offset
|
2615
3308
|
*/
|
2616
|
-
changeControl2(
|
3309
|
+
changeControl2(Y) {
|
2617
3310
|
const self = this;
|
2618
|
-
const { offsetY: Y } = offset;
|
2619
3311
|
const {
|
2620
|
-
format,
|
3312
|
+
format, controlPositions, visuals,
|
2621
3313
|
} = self;
|
2622
|
-
|
3314
|
+
const { offsetHeight, offsetWidth } = visuals[0];
|
2623
3315
|
|
2624
|
-
|
2625
|
-
offsetY = height2;
|
2626
|
-
} else if (Y >= 0) {
|
2627
|
-
offsetY = Y;
|
2628
|
-
}
|
3316
|
+
let offsetY = 0;
|
2629
3317
|
|
2630
|
-
|
2631
|
-
|
2632
|
-
|
2633
|
-
const
|
2634
|
-
|
3318
|
+
if (Y > offsetHeight) offsetY = offsetHeight;
|
3319
|
+
else if (Y >= 0) offsetY = Y;
|
3320
|
+
|
3321
|
+
const hue = format === 'hsl'
|
3322
|
+
? controlPositions.c1x / offsetWidth
|
3323
|
+
: offsetY / offsetHeight;
|
3324
|
+
const saturation = format === 'hsl'
|
3325
|
+
? 1 - offsetY / offsetHeight
|
3326
|
+
: controlPositions.c1x / offsetWidth;
|
3327
|
+
const lightness = 1 - controlPositions.c1y / offsetHeight;
|
3328
|
+
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
|
+
};
|
2635
3336
|
|
2636
3337
|
// new color
|
2637
|
-
|
3338
|
+
const {
|
3339
|
+
r, g, b, a,
|
3340
|
+
} = new Color(colorObject);
|
3341
|
+
|
3342
|
+
ObjectAssign(self.color, {
|
3343
|
+
r, g, b, a,
|
3344
|
+
});
|
3345
|
+
|
2638
3346
|
// new position
|
2639
3347
|
self.controlPositions.c2y = offsetY;
|
2640
3348
|
// update color picker
|
2641
|
-
self.
|
3349
|
+
self.updateAppearance();
|
2642
3350
|
self.updateInputs();
|
2643
3351
|
self.updateControls();
|
2644
3352
|
self.updateVisuals();
|
@@ -2646,95 +3354,108 @@
|
|
2646
3354
|
|
2647
3355
|
/**
|
2648
3356
|
* Updates `ColorPicker` last control,
|
2649
|
-
* the `alpha` channel
|
3357
|
+
* the `alpha` channel.
|
2650
3358
|
*
|
2651
|
-
* @param {
|
3359
|
+
* @param {number} Y
|
2652
3360
|
*/
|
2653
|
-
changeAlpha(
|
3361
|
+
changeAlpha(Y) {
|
2654
3362
|
const self = this;
|
2655
|
-
const {
|
2656
|
-
const {
|
3363
|
+
const { visuals } = self;
|
3364
|
+
const { offsetHeight } = visuals[0];
|
2657
3365
|
let offsetY = 0;
|
2658
3366
|
|
2659
|
-
if (Y >
|
2660
|
-
|
2661
|
-
} else if (Y >= 0) {
|
2662
|
-
offsetY = Y;
|
2663
|
-
}
|
3367
|
+
if (Y > offsetHeight) offsetY = offsetHeight;
|
3368
|
+
else if (Y >= 0) offsetY = Y;
|
2664
3369
|
|
2665
3370
|
// update color alpha
|
2666
|
-
const alpha =
|
2667
|
-
self.color.setAlpha(alpha
|
3371
|
+
const alpha = 1 - offsetY / offsetHeight;
|
3372
|
+
self.color.setAlpha(alpha);
|
2668
3373
|
// update position
|
2669
3374
|
self.controlPositions.c3y = offsetY;
|
2670
3375
|
// update color picker
|
3376
|
+
self.updateAppearance();
|
2671
3377
|
self.updateInputs();
|
2672
3378
|
self.updateControls();
|
2673
|
-
// alpha?
|
2674
3379
|
self.updateVisuals();
|
2675
3380
|
}
|
2676
3381
|
|
2677
|
-
/**
|
3382
|
+
/**
|
3383
|
+
* Updates `ColorPicker` control positions on:
|
3384
|
+
* * initialization
|
3385
|
+
* * window resize
|
3386
|
+
*/
|
3387
|
+
update() {
|
3388
|
+
const self = this;
|
3389
|
+
self.updateDropdownPosition();
|
3390
|
+
self.updateAppearance();
|
3391
|
+
self.setControlPositions();
|
3392
|
+
self.updateInputs(true);
|
3393
|
+
self.updateControls();
|
3394
|
+
self.updateVisuals();
|
3395
|
+
}
|
3396
|
+
|
3397
|
+
/** Updates the open dropdown position on *scroll* event. */
|
2678
3398
|
updateDropdownPosition() {
|
2679
3399
|
const self = this;
|
2680
3400
|
const { input, colorPicker, colorMenu } = self;
|
2681
3401
|
const elRect = getBoundingClientRect(input);
|
3402
|
+
const { top, bottom } = elRect;
|
2682
3403
|
const { offsetHeight: elHeight } = input;
|
2683
|
-
const windowHeight =
|
2684
|
-
const isPicker =
|
3404
|
+
const windowHeight = getDocumentElement(input).clientHeight;
|
3405
|
+
const isPicker = hasClass(colorPicker, 'show');
|
2685
3406
|
const dropdown = isPicker ? colorPicker : colorMenu;
|
3407
|
+
if (!dropdown) return;
|
2686
3408
|
const { offsetHeight: dropHeight } = dropdown;
|
2687
|
-
const distanceBottom = windowHeight -
|
2688
|
-
const distanceTop =
|
2689
|
-
const bottomExceed =
|
2690
|
-
const topExceed =
|
2691
|
-
|
2692
|
-
if (hasClass(dropdown, '
|
2693
|
-
removeClass(dropdown, '
|
2694
|
-
addClass(dropdown, '
|
2695
|
-
}
|
2696
|
-
|
2697
|
-
|
2698
|
-
addClass(dropdown, 'show');
|
3409
|
+
const distanceBottom = windowHeight - bottom;
|
3410
|
+
const distanceTop = top;
|
3411
|
+
const bottomExceed = top + dropHeight + elHeight > windowHeight; // show
|
3412
|
+
const topExceed = top - dropHeight < 0; // show-top
|
3413
|
+
|
3414
|
+
if ((hasClass(dropdown, 'bottom') || !topExceed) && distanceBottom < distanceTop && bottomExceed) {
|
3415
|
+
removeClass(dropdown, 'bottom');
|
3416
|
+
addClass(dropdown, 'top');
|
3417
|
+
} else {
|
3418
|
+
removeClass(dropdown, 'top');
|
3419
|
+
addClass(dropdown, 'bottom');
|
2699
3420
|
}
|
2700
3421
|
}
|
2701
3422
|
|
2702
|
-
/**
|
3423
|
+
/** Updates control knobs' positions. */
|
2703
3424
|
setControlPositions() {
|
2704
3425
|
const self = this;
|
2705
3426
|
const {
|
2706
|
-
|
3427
|
+
format, visuals, color, hsl, hsv,
|
2707
3428
|
} = self;
|
3429
|
+
const { offsetHeight, offsetWidth } = visuals[0];
|
3430
|
+
const alpha = color.a;
|
2708
3431
|
const hue = hsl.h;
|
3432
|
+
|
2709
3433
|
const saturation = format !== 'hsl' ? hsv.s : hsl.s;
|
2710
3434
|
const lightness = format !== 'hsl' ? hsv.v : hsl.l;
|
2711
|
-
const alpha = hsv.a;
|
2712
|
-
|
2713
|
-
self.controlPositions.c1x = format !== 'hsl' ? saturation * width1 : (hue / 360) * width1;
|
2714
|
-
self.controlPositions.c1y = (1 - lightness) * height1;
|
2715
|
-
self.controlPositions.c2y = format !== 'hsl' ? (hue / 360) * height2 : (1 - saturation) * height2;
|
2716
3435
|
|
2717
|
-
|
2718
|
-
|
2719
|
-
|
3436
|
+
self.controlPositions.c1x = format !== 'hsl' ? saturation * offsetWidth : hue * offsetWidth;
|
3437
|
+
self.controlPositions.c1y = (1 - lightness) * offsetHeight;
|
3438
|
+
self.controlPositions.c2y = format !== 'hsl' ? hue * offsetHeight : (1 - saturation) * offsetHeight;
|
3439
|
+
self.controlPositions.c3y = (1 - alpha) * offsetHeight;
|
2720
3440
|
}
|
2721
3441
|
|
2722
|
-
/** Update the visual appearance label. */
|
2723
|
-
|
3442
|
+
/** Update the visual appearance label and control knob labels. */
|
3443
|
+
updateAppearance() {
|
2724
3444
|
const self = this;
|
2725
3445
|
const {
|
2726
|
-
componentLabels, colorLabels,
|
3446
|
+
componentLabels, colorLabels, color, parent,
|
3447
|
+
hsl, hsv, hex, format, controlKnobs,
|
2727
3448
|
} = self;
|
2728
3449
|
const {
|
2729
|
-
|
3450
|
+
appearanceLabel, hexLabel, valueLabel,
|
2730
3451
|
} = componentLabels;
|
2731
|
-
|
2732
|
-
const [
|
2733
|
-
const hue =
|
2734
|
-
const alpha =
|
3452
|
+
const { r, g, b } = color.toRgb();
|
3453
|
+
const [knob1, knob2, knob3] = controlKnobs;
|
3454
|
+
const hue = roundPart(hsl.h * 360);
|
3455
|
+
const alpha = color.a;
|
2735
3456
|
const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
|
2736
|
-
const saturation =
|
2737
|
-
const lightness =
|
3457
|
+
const saturation = roundPart(saturationSource * 100);
|
3458
|
+
const lightness = roundPart(hsl.l * 100);
|
2738
3459
|
const hsvl = hsv.v * 100;
|
2739
3460
|
let colorName;
|
2740
3461
|
|
@@ -2770,99 +3491,118 @@
|
|
2770
3491
|
colorName = colorLabels.pink;
|
2771
3492
|
}
|
2772
3493
|
|
3494
|
+
let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
|
3495
|
+
|
2773
3496
|
if (format === 'hsl') {
|
2774
|
-
|
2775
|
-
|
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') {
|
3504
|
+
const { hwb } = self;
|
3505
|
+
const whiteness = roundPart(hwb.w * 100);
|
3506
|
+
const blackness = roundPart(hwb.b * 100);
|
3507
|
+
colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
|
3508
|
+
setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
|
3509
|
+
setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
|
3510
|
+
setAttribute(knob1, ariaValueNow, `${whiteness}`);
|
3511
|
+
setAttribute(knob2, ariaValueText, `${hue}%`);
|
3512
|
+
setAttribute(knob2, ariaValueNow, `${hue}`);
|
2776
3513
|
} else {
|
2777
|
-
|
2778
|
-
|
3514
|
+
colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
|
3515
|
+
setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
|
3516
|
+
setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
|
3517
|
+
setAttribute(knob1, ariaValueNow, `${lightness}`);
|
3518
|
+
setAttribute(knob2, ariaValueText, `${hue}°`);
|
3519
|
+
setAttribute(knob2, ariaValueNow, `${hue}`);
|
2779
3520
|
}
|
2780
3521
|
|
2781
|
-
|
2782
|
-
|
2783
|
-
|
2784
|
-
}
|
3522
|
+
const alphaValue = roundPart(alpha * 100);
|
3523
|
+
setAttribute(knob3, ariaValueText, `${alphaValue}%`);
|
3524
|
+
setAttribute(knob3, ariaValueNow, `${alphaValue}`);
|
2785
3525
|
|
2786
|
-
// update
|
2787
|
-
|
2788
|
-
|
2789
|
-
? `${hexLabel} ${hex.split('').join(' ')}.`
|
2790
|
-
: self.value.toUpperCase();
|
3526
|
+
// update the input backgroundColor
|
3527
|
+
const newColor = color.toString();
|
3528
|
+
setElementStyle(self.input, { backgroundColor: newColor });
|
2791
3529
|
|
2792
|
-
|
2793
|
-
|
2794
|
-
|
2795
|
-
|
2796
|
-
|
2797
|
-
|
2798
|
-
|
3530
|
+
// toggle dark/light classes will also style the placeholder
|
3531
|
+
// dark sets color white, light sets color black
|
3532
|
+
// isDark ? '#000' : '#fff'
|
3533
|
+
if (!self.isDark) {
|
3534
|
+
if (hasClass(parent, 'txt-dark')) removeClass(parent, 'txt-dark');
|
3535
|
+
if (!hasClass(parent, 'txt-light')) addClass(parent, 'txt-light');
|
3536
|
+
} else {
|
3537
|
+
if (hasClass(parent, 'txt-light')) removeClass(parent, 'txt-light');
|
3538
|
+
if (!hasClass(parent, 'txt-dark')) addClass(parent, 'txt-dark');
|
2799
3539
|
}
|
2800
3540
|
}
|
2801
3541
|
|
2802
|
-
/** Updates the control knobs positions. */
|
3542
|
+
/** Updates the control knobs actual positions. */
|
2803
3543
|
updateControls() {
|
2804
|
-
const {
|
3544
|
+
const { controlKnobs, controlPositions } = this;
|
3545
|
+
let {
|
3546
|
+
c1x, c1y, c2y, c3y,
|
3547
|
+
} = controlPositions;
|
2805
3548
|
const [control1, control2, control3] = controlKnobs;
|
2806
|
-
|
2807
|
-
|
3549
|
+
// round control positions
|
3550
|
+
[c1x, c1y, c2y, c3y] = [c1x, c1y, c2y, c3y].map(roundPart);
|
2808
3551
|
|
2809
|
-
|
2810
|
-
|
2811
|
-
}
|
3552
|
+
setElementStyle(control1, { transform: `translate3d(${c1x - 4}px,${c1y - 4}px,0)` });
|
3553
|
+
setElementStyle(control2, { transform: `translate3d(0,${c2y - 4}px,0)` });
|
3554
|
+
setElementStyle(control3, { transform: `translate3d(0,${c3y - 4}px,0)` });
|
2812
3555
|
}
|
2813
3556
|
|
2814
3557
|
/**
|
2815
|
-
*
|
3558
|
+
* Updates all color form inputs.
|
2816
3559
|
* @param {boolean=} isPrevented when `true`, the component original event is prevented
|
2817
3560
|
*/
|
2818
3561
|
updateInputs(isPrevented) {
|
2819
3562
|
const self = this;
|
2820
3563
|
const {
|
2821
|
-
value: oldColor,
|
3564
|
+
value: oldColor, format, inputs, color, hsl,
|
2822
3565
|
} = self;
|
2823
3566
|
const [i1, i2, i3, i4] = inputs;
|
2824
|
-
|
2825
|
-
const
|
2826
|
-
const hue = Math.round(hsl.h);
|
2827
|
-
const saturation = Math.round(hsl.s * 100);
|
2828
|
-
const lightSource = format === 'hsl' ? hsl.l : hsv.v;
|
2829
|
-
const lightness = Math.round(lightSource * 100);
|
3567
|
+
const alpha = roundPart(color.a * 100);
|
3568
|
+
const hue = roundPart(hsl.h * 360);
|
2830
3569
|
let newColor;
|
2831
3570
|
|
2832
3571
|
if (format === 'hex') {
|
2833
|
-
newColor = self.color.toHexString();
|
3572
|
+
newColor = self.color.toHexString(true);
|
2834
3573
|
i1.value = self.hex;
|
2835
3574
|
} else if (format === 'hsl') {
|
3575
|
+
const lightness = roundPart(hsl.l * 100);
|
3576
|
+
const saturation = roundPart(hsl.s * 100);
|
2836
3577
|
newColor = self.color.toHslString();
|
2837
3578
|
i1.value = `${hue}`;
|
2838
3579
|
i2.value = `${saturation}`;
|
2839
3580
|
i3.value = `${lightness}`;
|
2840
3581
|
i4.value = `${alpha}`;
|
3582
|
+
} else if (format === 'hwb') {
|
3583
|
+
const { w, b } = self.hwb;
|
3584
|
+
const whiteness = roundPart(w * 100);
|
3585
|
+
const blackness = roundPart(b * 100);
|
3586
|
+
|
3587
|
+
newColor = self.color.toHwbString();
|
3588
|
+
i1.value = `${hue}`;
|
3589
|
+
i2.value = `${whiteness}`;
|
3590
|
+
i3.value = `${blackness}`;
|
3591
|
+
i4.value = `${alpha}`;
|
2841
3592
|
} else if (format === 'rgb') {
|
3593
|
+
let { r, g, b } = self.rgb;
|
3594
|
+
[r, g, b] = [r, g, b].map(roundPart);
|
3595
|
+
|
2842
3596
|
newColor = self.color.toRgbString();
|
2843
|
-
i1.value = `${
|
2844
|
-
i2.value = `${
|
2845
|
-
i3.value = `${
|
3597
|
+
i1.value = `${r}`;
|
3598
|
+
i2.value = `${g}`;
|
3599
|
+
i3.value = `${b}`;
|
2846
3600
|
i4.value = `${alpha}`;
|
2847
3601
|
}
|
2848
3602
|
|
2849
3603
|
// update the color value
|
2850
3604
|
self.value = `${newColor}`;
|
2851
3605
|
|
2852
|
-
// update the input backgroundColor
|
2853
|
-
ObjectAssign(input.style, { backgroundColor: newColor });
|
2854
|
-
|
2855
|
-
// toggle dark/light classes will also style the placeholder
|
2856
|
-
// dark sets color white, light sets color black
|
2857
|
-
// isDark ? '#000' : '#fff'
|
2858
|
-
if (!self.isDark) {
|
2859
|
-
if (hasClass(parent, 'dark')) removeClass(parent, 'dark');
|
2860
|
-
if (!hasClass(parent, 'light')) addClass(parent, 'light');
|
2861
|
-
} else {
|
2862
|
-
if (hasClass(parent, 'light')) removeClass(parent, 'light');
|
2863
|
-
if (!hasClass(parent, 'dark')) addClass(parent, 'dark');
|
2864
|
-
}
|
2865
|
-
|
2866
3606
|
// don't trigger the custom event unless it's really changed
|
2867
3607
|
if (!isPrevented && newColor !== oldColor) {
|
2868
3608
|
firePickerChange(self);
|
@@ -2870,14 +3610,15 @@
|
|
2870
3610
|
}
|
2871
3611
|
|
2872
3612
|
/**
|
2873
|
-
*
|
3613
|
+
* The `Space` & `Enter` keys specific event listener.
|
3614
|
+
* Toggle visibility of the `ColorPicker` / the presets menu, showing one will hide the other.
|
2874
3615
|
* @param {KeyboardEvent} e
|
2875
3616
|
* @this {ColorPicker}
|
2876
3617
|
*/
|
2877
|
-
|
3618
|
+
keyToggle(e) {
|
2878
3619
|
const self = this;
|
2879
3620
|
const { menuToggle } = self;
|
2880
|
-
const { activeElement } =
|
3621
|
+
const { activeElement } = getDocument(menuToggle);
|
2881
3622
|
const { code } = e;
|
2882
3623
|
|
2883
3624
|
if ([keyEnter, keySpace].includes(code)) {
|
@@ -2900,80 +3641,79 @@
|
|
2900
3641
|
togglePicker(e) {
|
2901
3642
|
e.preventDefault();
|
2902
3643
|
const self = this;
|
2903
|
-
const
|
3644
|
+
const { colorPicker } = self;
|
2904
3645
|
|
2905
|
-
if (self.isOpen &&
|
3646
|
+
if (self.isOpen && hasClass(colorPicker, 'show')) {
|
2906
3647
|
self.hide(true);
|
2907
3648
|
} else {
|
2908
|
-
self
|
3649
|
+
showDropdown(self, colorPicker);
|
2909
3650
|
}
|
2910
3651
|
}
|
2911
3652
|
|
2912
3653
|
/** Shows the `ColorPicker` dropdown. */
|
2913
3654
|
showPicker() {
|
2914
3655
|
const self = this;
|
2915
|
-
|
2916
|
-
|
2917
|
-
|
2918
|
-
|
2919
|
-
|
3656
|
+
const { colorPicker } = self;
|
3657
|
+
|
3658
|
+
if (!['top', 'bottom'].some((c) => hasClass(colorPicker, c))) {
|
3659
|
+
showDropdown(self, colorPicker);
|
3660
|
+
}
|
2920
3661
|
}
|
2921
3662
|
|
2922
3663
|
/** Toggles the visibility of the `ColorPicker` presets menu. */
|
2923
3664
|
toggleMenu() {
|
2924
3665
|
const self = this;
|
2925
|
-
const
|
3666
|
+
const { colorMenu } = self;
|
2926
3667
|
|
2927
|
-
if (self.isOpen &&
|
3668
|
+
if (self.isOpen && hasClass(colorMenu, 'show')) {
|
2928
3669
|
self.hide(true);
|
2929
3670
|
} else {
|
2930
|
-
|
2931
|
-
}
|
2932
|
-
}
|
2933
|
-
|
2934
|
-
/** Show the dropdown. */
|
2935
|
-
show() {
|
2936
|
-
const self = this;
|
2937
|
-
if (!self.isOpen) {
|
2938
|
-
addClass(self.parent, 'open');
|
2939
|
-
toggleEventsOnShown(self, true);
|
2940
|
-
self.updateDropdownPosition();
|
2941
|
-
self.isOpen = true;
|
3671
|
+
showDropdown(self, colorMenu);
|
2942
3672
|
}
|
2943
3673
|
}
|
2944
3674
|
|
2945
3675
|
/**
|
2946
|
-
* Hides the currently
|
3676
|
+
* Hides the currently open `ColorPicker` dropdown.
|
2947
3677
|
* @param {boolean=} focusPrevented
|
2948
3678
|
*/
|
2949
3679
|
hide(focusPrevented) {
|
2950
3680
|
const self = this;
|
2951
3681
|
if (self.isOpen) {
|
2952
|
-
const {
|
2953
|
-
|
2954
|
-
|
2955
|
-
|
2956
|
-
|
2957
|
-
|
2958
|
-
|
2959
|
-
|
2960
|
-
if (
|
2961
|
-
|
2962
|
-
setAttribute(
|
3682
|
+
const {
|
3683
|
+
pickerToggle, menuToggle, colorPicker, colorMenu, parent, input,
|
3684
|
+
} = self;
|
3685
|
+
const openPicker = hasClass(colorPicker, 'show');
|
3686
|
+
const openDropdown = openPicker ? colorPicker : colorMenu;
|
3687
|
+
const relatedBtn = openPicker ? pickerToggle : menuToggle;
|
3688
|
+
const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
|
3689
|
+
|
3690
|
+
if (openDropdown) {
|
3691
|
+
removeClass(openDropdown, 'show');
|
3692
|
+
setAttribute(relatedBtn, ariaExpanded, 'false');
|
3693
|
+
setTimeout(() => {
|
3694
|
+
removePosition(openDropdown);
|
3695
|
+
if (!querySelector('.show', parent)) {
|
3696
|
+
removeClass(parent, 'open');
|
3697
|
+
toggleEventsOnShown(self);
|
3698
|
+
self.isOpen = false;
|
3699
|
+
}
|
3700
|
+
}, animationDuration);
|
2963
3701
|
}
|
2964
3702
|
|
2965
3703
|
if (!self.isValid) {
|
2966
3704
|
self.value = self.color.toString();
|
2967
3705
|
}
|
2968
|
-
|
2969
|
-
self.isOpen = false;
|
2970
|
-
|
2971
3706
|
if (!focusPrevented) {
|
2972
|
-
|
3707
|
+
focus(pickerToggle);
|
3708
|
+
}
|
3709
|
+
setAttribute(input, tabIndex, '-1');
|
3710
|
+
if (menuToggle) {
|
3711
|
+
setAttribute(menuToggle, tabIndex, '-1');
|
2973
3712
|
}
|
2974
3713
|
}
|
2975
3714
|
}
|
2976
3715
|
|
3716
|
+
/** Removes `ColorPicker` from target `<input>`. */
|
2977
3717
|
dispose() {
|
2978
3718
|
const self = this;
|
2979
3719
|
const { input, parent } = self;
|
@@ -2982,25 +3722,29 @@
|
|
2982
3722
|
[...parent.children].forEach((el) => {
|
2983
3723
|
if (el !== input) el.remove();
|
2984
3724
|
});
|
3725
|
+
|
3726
|
+
removeAttribute(input, tabIndex);
|
3727
|
+
setElementStyle(input, { backgroundColor: '' });
|
3728
|
+
|
3729
|
+
['txt-light', 'txt-dark'].forEach((c) => removeClass(parent, c));
|
2985
3730
|
Data.remove(input, colorPickerString);
|
2986
3731
|
}
|
2987
3732
|
}
|
2988
3733
|
|
2989
3734
|
ObjectAssign(ColorPicker, {
|
2990
3735
|
Color,
|
3736
|
+
ColorPalette,
|
3737
|
+
Version,
|
2991
3738
|
getInstance: getColorPickerInstance,
|
2992
3739
|
init: initColorPicker,
|
2993
3740
|
selector: colorPickerSelector,
|
3741
|
+
// utils important for render
|
3742
|
+
roundPart,
|
3743
|
+
setElementStyle,
|
3744
|
+
setAttribute,
|
3745
|
+
getBoundingClientRect,
|
2994
3746
|
});
|
2995
3747
|
|
2996
|
-
function initCallBack() {
|
2997
|
-
const { init, selector } = ColorPicker;
|
2998
|
-
[...querySelectorAll(selector)].forEach(init);
|
2999
|
-
}
|
3000
|
-
|
3001
|
-
if (document.body) initCallBack();
|
3002
|
-
else document.addEventListener('DOMContentLoaded', initCallBack, { once: true });
|
3003
|
-
|
3004
3748
|
return ColorPicker;
|
3005
3749
|
|
3006
3750
|
})));
|