@thednp/color-picker 0.0.1-alpha1 → 0.0.1-alpha2
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 +40 -19
- package/dist/css/color-picker.css +481 -337
- package/dist/css/color-picker.min.css +2 -0
- package/dist/css/color-picker.rtl.css +506 -0
- package/dist/css/color-picker.rtl.min.css +2 -0
- package/dist/js/color-picker-element-esm.js +3810 -2
- package/dist/js/color-picker-element-esm.min.js +2 -0
- package/dist/js/color-picker-element.js +2009 -1242
- package/dist/js/color-picker-element.min.js +2 -2
- package/dist/js/color-picker-esm.js +3704 -0
- package/dist/js/color-picker-esm.min.js +2 -0
- package/dist/js/color-picker.js +1962 -1256
- package/dist/js/color-picker.min.js +2 -2
- package/package.json +18 -9
- package/src/js/color-palette.js +62 -0
- package/src/js/color-picker-element.js +55 -13
- package/src/js/color-picker.js +686 -595
- package/src/js/color.js +615 -349
- 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 +27 -19
- package/src/js/util/getColorMenu.js +95 -0
- package/src/js/util/isValidJSON.js +13 -0
- package/src/js/util/nonColors.js +5 -0
- package/src/js/util/templates.js +1 -0
- package/src/scss/color-picker.rtl.scss +23 -0
- package/src/scss/color-picker.scss +430 -0
- package/types/cp.d.ts +263 -160
- 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.1alpha2 (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 multiple uses of `HTMLElement.style.propertyName` method.
|
672
|
+
* @param {HTMLElement | Element} element target element
|
673
|
+
* @param {Partial<CSSStyleDeclaration>} styles attribute value
|
674
|
+
*/
|
675
|
+
// @ts-ignore
|
676
|
+
const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
|
677
|
+
|
678
|
+
/**
|
679
|
+
* Shortcut for `HTMLElement.getAttribute()` method.
|
680
|
+
* @param {HTMLElement | Element} element target element
|
681
|
+
* @param {string} attribute attribute name
|
682
|
+
* @returns {string?} attribute value
|
683
|
+
*/
|
684
|
+
const getAttribute = (element, attribute) => element.getAttribute(attribute);
|
685
|
+
|
686
|
+
/**
|
687
|
+
* The raw value or a given component option.
|
688
|
+
*
|
689
|
+
* @typedef {string | HTMLElement | Function | number | boolean | null} niceValue
|
690
|
+
*/
|
691
|
+
|
692
|
+
/**
|
693
|
+
* Utility to normalize component options
|
694
|
+
*
|
695
|
+
* @param {any} value the input value
|
696
|
+
* @return {niceValue} the normalized value
|
697
|
+
*/
|
698
|
+
function normalizeValue(value) {
|
699
|
+
if (value === 'true') { // boolean
|
700
|
+
return true;
|
701
|
+
}
|
702
|
+
|
703
|
+
if (value === 'false') { // boolean
|
704
|
+
return false;
|
705
|
+
}
|
706
|
+
|
707
|
+
if (!Number.isNaN(+value)) { // number
|
708
|
+
return +value;
|
709
|
+
}
|
710
|
+
|
711
|
+
if (value === '' || value === 'null') { // null
|
712
|
+
return null;
|
713
|
+
}
|
714
|
+
|
715
|
+
// string / function / HTMLElement / object
|
716
|
+
return value;
|
717
|
+
}
|
718
|
+
|
719
|
+
/**
|
720
|
+
* Shortcut for `Object.keys()` static method.
|
721
|
+
* @param {Record<string, any>} obj a target object
|
722
|
+
* @returns {string[]}
|
723
|
+
*/
|
724
|
+
const ObjectKeys = (obj) => Object.keys(obj);
|
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,164 @@
|
|
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
|
+
});
|
942
|
+
// }
|
606
943
|
colorForm.append(cInputLabel, cInput);
|
607
944
|
});
|
608
945
|
return colorForm;
|
609
946
|
}
|
610
947
|
|
611
|
-
/**
|
612
|
-
*
|
613
|
-
* @
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
*
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
948
|
+
/**
|
949
|
+
* A global namespace for aria-label.
|
950
|
+
* @type {string}
|
951
|
+
*/
|
952
|
+
const ariaLabel = 'aria-label';
|
953
|
+
|
954
|
+
/**
|
955
|
+
* A global namespace for aria-valuemin.
|
956
|
+
* @type {string}
|
957
|
+
*/
|
958
|
+
const ariaValueMin = 'aria-valuemin';
|
959
|
+
|
960
|
+
/**
|
961
|
+
* A global namespace for aria-valuemax.
|
962
|
+
* @type {string}
|
963
|
+
*/
|
964
|
+
const ariaValueMax = 'aria-valuemax';
|
965
|
+
|
966
|
+
/**
|
967
|
+
* Returns all color controls for `ColorPicker`.
|
968
|
+
*
|
969
|
+
* @param {CP.ColorPicker} self the `ColorPicker` instance
|
970
|
+
* @returns {HTMLElement | Element} color controls
|
971
|
+
*/
|
972
|
+
function getColorControls(self) {
|
973
|
+
const { format, componentLabels } = self;
|
974
|
+
const {
|
975
|
+
hueLabel, alphaLabel, lightnessLabel, saturationLabel,
|
976
|
+
whitenessLabel, blacknessLabel,
|
977
|
+
} = componentLabels;
|
978
|
+
|
979
|
+
const max1 = format === 'hsl' ? 360 : 100;
|
980
|
+
const max2 = format === 'hsl' ? 100 : 360;
|
981
|
+
const max3 = 100;
|
982
|
+
|
983
|
+
let ctrl1Label = format === 'hsl'
|
984
|
+
? `${hueLabel} & ${lightnessLabel}`
|
985
|
+
: `${lightnessLabel} & ${saturationLabel}`;
|
986
|
+
|
987
|
+
ctrl1Label = format === 'hwb'
|
988
|
+
? `${whitenessLabel} & ${blacknessLabel}`
|
989
|
+
: ctrl1Label;
|
990
|
+
|
991
|
+
const ctrl2Label = format === 'hsl'
|
992
|
+
? `${saturationLabel}`
|
993
|
+
: `${hueLabel}`;
|
994
|
+
|
995
|
+
const colorControls = createElement({
|
624
996
|
tagName: 'div',
|
625
|
-
className:
|
997
|
+
className: `color-controls ${format}`,
|
626
998
|
});
|
627
|
-
setAttribute(control, 'role', 'presentation');
|
628
999
|
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
1000
|
+
const colorPointer = 'color-pointer';
|
1001
|
+
const colorSlider = 'color-slider';
|
1002
|
+
|
1003
|
+
const controls = [
|
1004
|
+
{
|
1005
|
+
i: 1,
|
1006
|
+
c: colorPointer,
|
1007
|
+
l: ctrl1Label,
|
1008
|
+
min: 0,
|
1009
|
+
max: max1,
|
1010
|
+
},
|
1011
|
+
{
|
1012
|
+
i: 2,
|
1013
|
+
c: colorSlider,
|
1014
|
+
l: ctrl2Label,
|
1015
|
+
min: 0,
|
1016
|
+
max: max2,
|
1017
|
+
},
|
1018
|
+
{
|
1019
|
+
i: 3,
|
1020
|
+
c: colorSlider,
|
1021
|
+
l: alphaLabel,
|
1022
|
+
min: 0,
|
1023
|
+
max: max3,
|
1024
|
+
},
|
1025
|
+
];
|
1026
|
+
|
1027
|
+
controls.forEach((template) => {
|
1028
|
+
const {
|
1029
|
+
i, c, l, min, max,
|
1030
|
+
} = template;
|
1031
|
+
// const hidden = i === 2 && format === 'hwb' ? ' v-hidden' : '';
|
1032
|
+
const control = createElement({
|
1033
|
+
tagName: 'div',
|
1034
|
+
// className: `color-control${hidden}`,
|
1035
|
+
className: 'color-control',
|
1036
|
+
});
|
1037
|
+
setAttribute(control, 'role', 'presentation');
|
1038
|
+
|
1039
|
+
control.append(
|
1040
|
+
createElement({
|
1041
|
+
tagName: 'div',
|
1042
|
+
className: `visual-control visual-control${i}`,
|
1043
|
+
}),
|
1044
|
+
);
|
1045
|
+
|
1046
|
+
const knob = createElement({
|
1047
|
+
tagName: 'div',
|
1048
|
+
className: `${c} knob`,
|
634
1049
|
ariaLive: 'polite',
|
635
|
-
})
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
const knob = createElement({
|
646
|
-
tagName: 'div',
|
647
|
-
className: `${knobClass} knob`,
|
1050
|
+
});
|
1051
|
+
|
1052
|
+
setAttribute(knob, ariaLabel, l);
|
1053
|
+
setAttribute(knob, 'role', 'slider');
|
1054
|
+
setAttribute(knob, 'tabindex', '0');
|
1055
|
+
setAttribute(knob, ariaValueMin, `${min}`);
|
1056
|
+
setAttribute(knob, ariaValueMax, `${max}`);
|
1057
|
+
control.append(knob);
|
1058
|
+
colorControls.append(control);
|
648
1059
|
});
|
649
|
-
|
650
|
-
|
651
|
-
control.append(knob);
|
652
|
-
return control;
|
1060
|
+
|
1061
|
+
return colorControls;
|
653
1062
|
}
|
654
1063
|
|
655
1064
|
/**
|
@@ -662,212 +1071,40 @@
|
|
662
1071
|
return getDocument(node).head;
|
663
1072
|
}
|
664
1073
|
|
665
|
-
|
666
|
-
|
667
|
-
* static method.
|
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
|
675
|
-
*/
|
676
|
-
function getElementStyle(element, property) {
|
677
|
-
const computedStyle = getComputedStyle(element);
|
678
|
-
|
679
|
-
// @ts-ignore -- must use camelcase strings,
|
680
|
-
// or non-camelcase strings with `getPropertyValue`
|
681
|
-
return property in computedStyle ? computedStyle[property] : '';
|
682
|
-
}
|
683
|
-
|
684
|
-
/**
|
685
|
-
* Shortcut for multiple uses of `HTMLElement.style.propertyName` method.
|
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); };
|
1074
|
+
// Color supported formats
|
1075
|
+
const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
|
691
1076
|
|
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
|
-
];
|
1077
|
+
// Hue angles
|
1078
|
+
const ANGLES = 'deg|rad|grad|turn';
|
847
1079
|
|
848
1080
|
// <http://www.w3.org/TR/css3-values/#integers>
|
849
1081
|
const CSS_INTEGER = '[-\\+]?\\d+%?';
|
850
1082
|
|
1083
|
+
// Include CSS3 Module
|
851
1084
|
// <http://www.w3.org/TR/css3-values/#number-value>
|
852
1085
|
const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
|
853
1086
|
|
1087
|
+
// Include CSS4 Module Hue degrees unit
|
1088
|
+
// <https://www.w3.org/TR/css3-values/#angle-value>
|
1089
|
+
const CSS_ANGLE = `[-\\+]?\\d*\\.?\\d+(?:${ANGLES})?`;
|
1090
|
+
|
854
1091
|
// Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
|
855
1092
|
const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
|
856
1093
|
|
1094
|
+
// Add angles to the mix
|
1095
|
+
const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
|
1096
|
+
|
857
1097
|
// Actual matching.
|
858
1098
|
// Parentheses and commas are optional, but not required.
|
859
1099
|
// 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*\\)?`;
|
1100
|
+
const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
|
862
1101
|
|
863
1102
|
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}`),
|
1103
|
+
CSS_UNIT: new RegExp(CSS_UNIT2),
|
1104
|
+
hwb: new RegExp(`hwb${PERMISSIVE_MATCH}`),
|
1105
|
+
rgb: new RegExp(`rgb(?:a)?${PERMISSIVE_MATCH}`),
|
1106
|
+
hsl: new RegExp(`hsl(?:a)?${PERMISSIVE_MATCH}`),
|
1107
|
+
hsv: new RegExp(`hsv(?:a)?${PERMISSIVE_MATCH}`),
|
871
1108
|
hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
872
1109
|
hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
|
873
1110
|
hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
@@ -877,27 +1114,46 @@
|
|
877
1114
|
/**
|
878
1115
|
* Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
|
879
1116
|
* <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
|
880
|
-
* @param {string} n
|
881
|
-
* @returns {boolean}
|
1117
|
+
* @param {string} n testing number
|
1118
|
+
* @returns {boolean} the query result
|
882
1119
|
*/
|
883
1120
|
function isOnePointZero(n) {
|
884
|
-
return
|
1121
|
+
return `${n}`.includes('.') && parseFloat(n) === 1;
|
885
1122
|
}
|
886
1123
|
|
887
1124
|
/**
|
888
1125
|
* Check to see if string passed in is a percentage
|
889
|
-
* @param {string} n
|
890
|
-
* @returns {boolean}
|
1126
|
+
* @param {string} n testing number
|
1127
|
+
* @returns {boolean} the query result
|
891
1128
|
*/
|
892
1129
|
function isPercentage(n) {
|
893
|
-
return
|
1130
|
+
return `${n}`.includes('%');
|
1131
|
+
}
|
1132
|
+
|
1133
|
+
/**
|
1134
|
+
* Check to see if string passed in is an angle
|
1135
|
+
* @param {string} n testing string
|
1136
|
+
* @returns {boolean} the query result
|
1137
|
+
*/
|
1138
|
+
function isAngle(n) {
|
1139
|
+
return ANGLES.split('|').some((a) => `${n}`.includes(a));
|
1140
|
+
}
|
1141
|
+
|
1142
|
+
/**
|
1143
|
+
* Check to see if string passed is a web safe colour.
|
1144
|
+
* @param {string} color a colour name, EG: *red*
|
1145
|
+
* @returns {boolean} the query result
|
1146
|
+
*/
|
1147
|
+
function isColorName(color) {
|
1148
|
+
return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
|
1149
|
+
&& !/[0-9]/.test(color);
|
894
1150
|
}
|
895
1151
|
|
896
1152
|
/**
|
897
1153
|
* Check to see if it looks like a CSS unit
|
898
1154
|
* (see `matchers` above for definition).
|
899
|
-
* @param {string | number} color
|
900
|
-
* @returns {boolean}
|
1155
|
+
* @param {string | number} color testing value
|
1156
|
+
* @returns {boolean} the query result
|
901
1157
|
*/
|
902
1158
|
function isValidCSSUnit(color) {
|
903
1159
|
return Boolean(matchers.CSS_UNIT.exec(String(color)));
|
@@ -905,22 +1161,24 @@
|
|
905
1161
|
|
906
1162
|
/**
|
907
1163
|
* Take input from [0, n] and return it as [0, 1]
|
908
|
-
* @param {*}
|
909
|
-
* @param {number} max
|
910
|
-
* @returns {number}
|
1164
|
+
* @param {*} N the input number
|
1165
|
+
* @param {number} max the number maximum value
|
1166
|
+
* @returns {number} the number in [0, 1] value range
|
911
1167
|
*/
|
912
|
-
function bound01(
|
913
|
-
let
|
914
|
-
if (isOnePointZero(n))
|
1168
|
+
function bound01(N, max) {
|
1169
|
+
let n = N;
|
1170
|
+
if (isOnePointZero(n)) n = '100%';
|
915
1171
|
|
916
|
-
|
1172
|
+
n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
|
1173
|
+
|
1174
|
+
// Handle hue angles
|
1175
|
+
if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
|
917
1176
|
|
918
1177
|
// Automatically convert percentage into number
|
919
|
-
if (isPercentage(
|
920
|
-
|
921
|
-
}
|
1178
|
+
if (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
|
1179
|
+
|
922
1180
|
// Handle floating point rounding errors
|
923
|
-
if (Math.abs(
|
1181
|
+
if (Math.abs(n - max) < 0.000001) {
|
924
1182
|
return 1;
|
925
1183
|
}
|
926
1184
|
// Convert into [0, 1] range if it isn't already
|
@@ -928,23 +1186,22 @@
|
|
928
1186
|
// If n is a hue given in degrees,
|
929
1187
|
// wrap around out-of-range values into [0, 360] range
|
930
1188
|
// then convert into [0, 1].
|
931
|
-
|
1189
|
+
n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max));
|
932
1190
|
} else {
|
933
1191
|
// If n not a hue given in degrees
|
934
1192
|
// Convert into [0, 1] range if it isn't already.
|
935
|
-
|
1193
|
+
n = (n % max) / parseFloat(String(max));
|
936
1194
|
}
|
937
|
-
return
|
1195
|
+
return n;
|
938
1196
|
}
|
939
1197
|
|
940
1198
|
/**
|
941
1199
|
* Return a valid alpha value [0,1] with all invalid values being set to 1.
|
942
|
-
* @param {string | number} a
|
943
|
-
* @returns {number}
|
1200
|
+
* @param {string | number} a transparency value
|
1201
|
+
* @returns {number} a transparency value in the [0, 1] range
|
944
1202
|
*/
|
945
1203
|
function boundAlpha(a) {
|
946
|
-
|
947
|
-
let na = parseFloat(a);
|
1204
|
+
let na = parseFloat(`${a}`);
|
948
1205
|
|
949
1206
|
if (Number.isNaN(na) || na < 0 || na > 1) {
|
950
1207
|
na = 1;
|
@@ -954,12 +1211,12 @@
|
|
954
1211
|
}
|
955
1212
|
|
956
1213
|
/**
|
957
|
-
* Force a number between 0 and 1
|
958
|
-
* @param {number}
|
959
|
-
* @returns {number}
|
1214
|
+
* Force a number between 0 and 1.
|
1215
|
+
* @param {number} v the float number
|
1216
|
+
* @returns {number} - the resulting number
|
960
1217
|
*/
|
961
|
-
function clamp01(
|
962
|
-
return Math.min(1, Math.max(0,
|
1218
|
+
function clamp01(v) {
|
1219
|
+
return Math.min(1, Math.max(0, v));
|
963
1220
|
}
|
964
1221
|
|
965
1222
|
/**
|
@@ -967,7 +1224,7 @@
|
|
967
1224
|
* @param {string} name
|
968
1225
|
* @returns {string}
|
969
1226
|
*/
|
970
|
-
function
|
1227
|
+
function getRGBFromName(name) {
|
971
1228
|
const documentHead = getDocumentHead();
|
972
1229
|
setElementStyle(documentHead, { color: name });
|
973
1230
|
const colorName = getElementStyle(documentHead, 'color');
|
@@ -976,59 +1233,53 @@
|
|
976
1233
|
}
|
977
1234
|
|
978
1235
|
/**
|
979
|
-
*
|
980
|
-
* @param {number
|
981
|
-
* @
|
1236
|
+
* Converts a decimal value to hexadecimal.
|
1237
|
+
* @param {number} d the input number
|
1238
|
+
* @returns {string} - the hexadecimal value
|
982
1239
|
*/
|
983
|
-
function
|
984
|
-
|
985
|
-
return `${Number(n) * 100}%`;
|
986
|
-
}
|
987
|
-
return n;
|
1240
|
+
function convertDecimalToHex(d) {
|
1241
|
+
return Math.round(d * 255).toString(16);
|
988
1242
|
}
|
989
1243
|
|
990
1244
|
/**
|
991
|
-
*
|
992
|
-
* @param {string}
|
993
|
-
* @returns {
|
1245
|
+
* Converts a hexadecimal value to decimal.
|
1246
|
+
* @param {string} h hexadecimal value
|
1247
|
+
* @returns {number} number in decimal format
|
994
1248
|
*/
|
995
|
-
function
|
996
|
-
return
|
1249
|
+
function convertHexToDecimal(h) {
|
1250
|
+
return parseIntFromHex(h) / 255;
|
997
1251
|
}
|
998
1252
|
|
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
1253
|
/**
|
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}
|
1254
|
+
* Converts a base-16 hexadecimal value into a base-10 integer.
|
1255
|
+
* @param {string} val
|
1256
|
+
* @returns {number}
|
1010
1257
|
*/
|
1011
|
-
function
|
1012
|
-
return
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1258
|
+
function parseIntFromHex(val) {
|
1259
|
+
return parseInt(val, 16);
|
1260
|
+
}
|
1261
|
+
|
1262
|
+
/**
|
1263
|
+
* Force a hexadecimal value to have 2 characters.
|
1264
|
+
* @param {string} c string with [0-9A-F] ranged values
|
1265
|
+
* @returns {string} 0 => 00, a => 0a
|
1266
|
+
*/
|
1267
|
+
function pad2(c) {
|
1268
|
+
return c.length === 1 ? `0${c}` : String(c);
|
1017
1269
|
}
|
1018
1270
|
|
1019
1271
|
/**
|
1020
|
-
* Converts an RGB
|
1021
|
-
*
|
1022
|
-
*
|
1023
|
-
* @param {number}
|
1024
|
-
* @param {number}
|
1025
|
-
* @
|
1026
|
-
* @returns {CP.HSL}
|
1272
|
+
* Converts an RGB colour value to HSL.
|
1273
|
+
*
|
1274
|
+
* @param {number} R Red component [0, 255]
|
1275
|
+
* @param {number} G Green component [0, 255]
|
1276
|
+
* @param {number} B Blue component [0, 255]
|
1277
|
+
* @returns {CP.HSL} {h,s,l} object with [0, 1] ranged values
|
1027
1278
|
*/
|
1028
1279
|
function rgbToHsl(R, G, B) {
|
1029
|
-
const r =
|
1030
|
-
const g =
|
1031
|
-
const b =
|
1280
|
+
const r = R / 255;
|
1281
|
+
const g = G / 255;
|
1282
|
+
const b = B / 255;
|
1032
1283
|
const max = Math.max(r, g, b);
|
1033
1284
|
const min = Math.min(r, g, b);
|
1034
1285
|
let h = 0;
|
@@ -1058,50 +1309,95 @@
|
|
1058
1309
|
|
1059
1310
|
/**
|
1060
1311
|
* Returns a normalized RGB component value.
|
1061
|
-
* @param {number}
|
1062
|
-
* @param {number}
|
1063
|
-
* @param {number}
|
1312
|
+
* @param {number} p
|
1313
|
+
* @param {number} q
|
1314
|
+
* @param {number} t
|
1064
1315
|
* @returns {number}
|
1065
1316
|
*/
|
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
|
-
}
|
1317
|
+
function hueToRgb(p, q, t) {
|
1318
|
+
let T = t;
|
1319
|
+
if (T < 0) T += 1;
|
1320
|
+
if (T > 1) T -= 1;
|
1321
|
+
if (T < 1 / 6) return p + (q - p) * (6 * T);
|
1322
|
+
if (T < 1 / 2) return q;
|
1323
|
+
if (T < 2 / 3) return p + (q - p) * (2 / 3 - T) * 6;
|
1085
1324
|
return p;
|
1086
1325
|
}
|
1087
1326
|
|
1327
|
+
/**
|
1328
|
+
* Returns an HWB colour object from an RGB colour object.
|
1329
|
+
* @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
|
1330
|
+
* @link http://alvyray.com/Papers/CG/hwb2rgb.htm
|
1331
|
+
*
|
1332
|
+
* @param {number} R Red component [0, 255]
|
1333
|
+
* @param {number} G Green [0, 255]
|
1334
|
+
* @param {number} B Blue [0, 255]
|
1335
|
+
* @return {CP.HWB} {h,w,b} object with [0, 1] ranged values
|
1336
|
+
*/
|
1337
|
+
function rgbToHwb(R, G, B) {
|
1338
|
+
const r = R / 255;
|
1339
|
+
const g = G / 255;
|
1340
|
+
const b = B / 255;
|
1341
|
+
|
1342
|
+
let f = 0;
|
1343
|
+
let i = 0;
|
1344
|
+
const whiteness = Math.min(r, g, b);
|
1345
|
+
const max = Math.max(r, g, b);
|
1346
|
+
const black = 1 - max;
|
1347
|
+
|
1348
|
+
if (max === whiteness) return { h: 0, w: whiteness, b: black };
|
1349
|
+
if (r === whiteness) {
|
1350
|
+
f = g - b;
|
1351
|
+
i = 3;
|
1352
|
+
} else {
|
1353
|
+
f = g === whiteness ? b - r : r - g;
|
1354
|
+
i = g === whiteness ? 5 : 1;
|
1355
|
+
}
|
1356
|
+
|
1357
|
+
const h = (i - f / (max - whiteness)) / 6;
|
1358
|
+
return {
|
1359
|
+
h: h === 1 ? 0 : h,
|
1360
|
+
w: whiteness,
|
1361
|
+
b: black,
|
1362
|
+
};
|
1363
|
+
}
|
1364
|
+
|
1365
|
+
/**
|
1366
|
+
* Returns an RGB colour object from an HWB colour.
|
1367
|
+
*
|
1368
|
+
* @param {number} H Hue Angle [0, 1]
|
1369
|
+
* @param {number} W Whiteness [0, 1]
|
1370
|
+
* @param {number} B Blackness [0, 1]
|
1371
|
+
* @return {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
1372
|
+
*
|
1373
|
+
* @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
|
1374
|
+
* @link http://alvyray.com/Papers/CG/hwb2rgb.htm
|
1375
|
+
*/
|
1376
|
+
function hwbToRgb(H, W, B) {
|
1377
|
+
if (W + B >= 1) {
|
1378
|
+
const gray = (W / (W + B)) * 255;
|
1379
|
+
return { r: gray, g: gray, b: gray };
|
1380
|
+
}
|
1381
|
+
let { r, g, b } = hslToRgb(H, 1, 0.5);
|
1382
|
+
[r, g, b] = [r, g, b]
|
1383
|
+
.map((v) => (v / 255) * (1 - W - B) + W)
|
1384
|
+
.map((v) => v * 255);
|
1385
|
+
|
1386
|
+
return { r, g, b };
|
1387
|
+
}
|
1388
|
+
|
1088
1389
|
/**
|
1089
1390
|
* Converts an HSL colour value to RGB.
|
1090
1391
|
*
|
1091
|
-
*
|
1092
|
-
*
|
1093
|
-
* @param {number
|
1094
|
-
* @
|
1095
|
-
|
1096
|
-
|
1097
|
-
*/
|
1098
|
-
function hslToRgb(H, S, L) {
|
1392
|
+
* @param {number} h Hue Angle [0, 1]
|
1393
|
+
* @param {number} s Saturation [0, 1]
|
1394
|
+
* @param {number} l Lightness Angle [0, 1]
|
1395
|
+
* @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
1396
|
+
*/
|
1397
|
+
function hslToRgb(h, s, l) {
|
1099
1398
|
let r = 0;
|
1100
1399
|
let g = 0;
|
1101
1400
|
let b = 0;
|
1102
|
-
const h = bound01(H, 360);
|
1103
|
-
const s = bound01(S, 100);
|
1104
|
-
const l = bound01(L, 100);
|
1105
1401
|
|
1106
1402
|
if (s === 0) {
|
1107
1403
|
// achromatic
|
@@ -1111,27 +1407,27 @@
|
|
1111
1407
|
} else {
|
1112
1408
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
1113
1409
|
const p = 2 * l - q;
|
1114
|
-
r =
|
1115
|
-
g =
|
1116
|
-
b =
|
1410
|
+
r = hueToRgb(p, q, h + 1 / 3);
|
1411
|
+
g = hueToRgb(p, q, h);
|
1412
|
+
b = hueToRgb(p, q, h - 1 / 3);
|
1117
1413
|
}
|
1118
|
-
|
1414
|
+
[r, g, b] = [r, g, b].map((x) => x * 255);
|
1415
|
+
|
1416
|
+
return { r, g, b };
|
1119
1417
|
}
|
1120
1418
|
|
1121
1419
|
/**
|
1122
1420
|
* Converts an RGB colour value to HSV.
|
1123
1421
|
*
|
1124
|
-
*
|
1125
|
-
*
|
1126
|
-
* @param {number
|
1127
|
-
* @
|
1128
|
-
* @param {number | string} B
|
1129
|
-
* @returns {CP.HSV}
|
1422
|
+
* @param {number} R Red component [0, 255]
|
1423
|
+
* @param {number} G Green [0, 255]
|
1424
|
+
* @param {number} B Blue [0, 255]
|
1425
|
+
* @returns {CP.HSV} {h,s,v} object with [0, 1] ranged values
|
1130
1426
|
*/
|
1131
1427
|
function rgbToHsv(R, G, B) {
|
1132
|
-
const r =
|
1133
|
-
const g =
|
1134
|
-
const b =
|
1428
|
+
const r = R / 255;
|
1429
|
+
const g = G / 255;
|
1430
|
+
const b = B / 255;
|
1135
1431
|
const max = Math.max(r, g, b);
|
1136
1432
|
const min = Math.min(r, g, b);
|
1137
1433
|
let h = 0;
|
@@ -1158,19 +1454,17 @@
|
|
1158
1454
|
}
|
1159
1455
|
|
1160
1456
|
/**
|
1161
|
-
* Converts an HSV
|
1457
|
+
* Converts an HSV colour value to RGB.
|
1162
1458
|
*
|
1163
|
-
*
|
1164
|
-
*
|
1165
|
-
* @param {number
|
1166
|
-
* @
|
1167
|
-
* @param {number | string} V
|
1168
|
-
* @returns {CP.RGB}
|
1459
|
+
* @param {number} H Hue Angle [0, 1]
|
1460
|
+
* @param {number} S Saturation [0, 1]
|
1461
|
+
* @param {number} V Brightness Angle [0, 1]
|
1462
|
+
* @returns {CP.RGB} {r,g,b} object with [0, 1] ranged values
|
1169
1463
|
*/
|
1170
1464
|
function hsvToRgb(H, S, V) {
|
1171
|
-
const h =
|
1172
|
-
const s =
|
1173
|
-
const v =
|
1465
|
+
const h = H * 6;
|
1466
|
+
const s = S;
|
1467
|
+
const v = V;
|
1174
1468
|
const i = Math.floor(h);
|
1175
1469
|
const f = h - i;
|
1176
1470
|
const p = v * (1 - s);
|
@@ -1184,47 +1478,65 @@
|
|
1184
1478
|
}
|
1185
1479
|
|
1186
1480
|
/**
|
1187
|
-
* Converts an RGB
|
1481
|
+
* Converts an RGB colour to hex
|
1188
1482
|
*
|
1189
1483
|
* Assumes r, g, and b are contained in the set [0, 255]
|
1190
1484
|
* Returns a 3 or 6 character hex
|
1191
|
-
* @param {number} r
|
1192
|
-
* @param {number} g
|
1193
|
-
* @param {number} b
|
1485
|
+
* @param {number} r Red component [0, 255]
|
1486
|
+
* @param {number} g Green [0, 255]
|
1487
|
+
* @param {number} b Blue [0, 255]
|
1488
|
+
* @param {boolean=} allow3Char
|
1194
1489
|
* @returns {string}
|
1195
1490
|
*/
|
1196
|
-
function rgbToHex(r, g, b) {
|
1491
|
+
function rgbToHex(r, g, b, allow3Char) {
|
1197
1492
|
const hex = [
|
1198
1493
|
pad2(Math.round(r).toString(16)),
|
1199
1494
|
pad2(Math.round(g).toString(16)),
|
1200
1495
|
pad2(Math.round(b).toString(16)),
|
1201
1496
|
];
|
1202
1497
|
|
1498
|
+
// Return a 3 character hex if possible
|
1499
|
+
if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
|
1500
|
+
&& hex[1].charAt(0) === hex[1].charAt(1)
|
1501
|
+
&& hex[2].charAt(0) === hex[2].charAt(1)) {
|
1502
|
+
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
|
1503
|
+
}
|
1504
|
+
|
1203
1505
|
return hex.join('');
|
1204
1506
|
}
|
1205
1507
|
|
1206
1508
|
/**
|
1207
|
-
* Converts
|
1208
|
-
*
|
1209
|
-
* @
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1509
|
+
* Converts an RGBA color plus alpha transparency to hex8.
|
1510
|
+
*
|
1511
|
+
* @param {number} r Red component [0, 255]
|
1512
|
+
* @param {number} g Green [0, 255]
|
1513
|
+
* @param {number} b Blue [0, 255]
|
1514
|
+
* @param {number} a Alpha transparency [0, 1]
|
1515
|
+
* @param {boolean=} allow4Char when *true* it will also find hex shorthand
|
1516
|
+
* @returns {string} a hexadecimal value with alpha transparency
|
1517
|
+
*/
|
1518
|
+
function rgbaToHex(r, g, b, a, allow4Char) {
|
1519
|
+
const hex = [
|
1520
|
+
pad2(Math.round(r).toString(16)),
|
1521
|
+
pad2(Math.round(g).toString(16)),
|
1522
|
+
pad2(Math.round(b).toString(16)),
|
1523
|
+
pad2(convertDecimalToHex(a)),
|
1524
|
+
];
|
1214
1525
|
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1526
|
+
// Return a 4 character hex if possible
|
1527
|
+
if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
|
1528
|
+
&& hex[1].charAt(0) === hex[1].charAt(1)
|
1529
|
+
&& hex[2].charAt(0) === hex[2].charAt(1)
|
1530
|
+
&& hex[3].charAt(0) === hex[3].charAt(1)) {
|
1531
|
+
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
|
1532
|
+
}
|
1533
|
+
return hex.join('');
|
1222
1534
|
}
|
1223
1535
|
|
1224
1536
|
/**
|
1225
|
-
* Returns
|
1226
|
-
* @param {number} color
|
1227
|
-
* @returns {CP.RGB}
|
1537
|
+
* Returns a colour object corresponding to a given number.
|
1538
|
+
* @param {number} color input number
|
1539
|
+
* @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
1228
1540
|
*/
|
1229
1541
|
function numberInputToObject(color) {
|
1230
1542
|
/* eslint-disable no-bitwise */
|
@@ -1237,10 +1549,10 @@
|
|
1237
1549
|
}
|
1238
1550
|
|
1239
1551
|
/**
|
1240
|
-
* Permissive string parsing.
|
1241
|
-
* based on detected format.
|
1242
|
-
* @param {string} input
|
1243
|
-
* @returns {Record<string, (number | string)> | false}
|
1552
|
+
* Permissive string parsing. Take in a number of formats, and output an object
|
1553
|
+
* based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
|
1554
|
+
* @param {string} input colour value in any format
|
1555
|
+
* @returns {Record<string, (number | string)> | false} an object matching the RegExp
|
1244
1556
|
*/
|
1245
1557
|
function stringInputToObject(input) {
|
1246
1558
|
let color = input.trim().toLowerCase();
|
@@ -1250,12 +1562,15 @@
|
|
1250
1562
|
};
|
1251
1563
|
}
|
1252
1564
|
let named = false;
|
1253
|
-
if (
|
1254
|
-
color =
|
1565
|
+
if (isColorName(color)) {
|
1566
|
+
color = getRGBFromName(color);
|
1255
1567
|
named = true;
|
1256
|
-
} else if (color
|
1568
|
+
} else if (nonColors.includes(color)) {
|
1569
|
+
const isTransparent = color === 'transparent';
|
1570
|
+
const rgb = isTransparent ? 0 : 255;
|
1571
|
+
const a = isTransparent ? 0 : 1;
|
1257
1572
|
return {
|
1258
|
-
r:
|
1573
|
+
r: rgb, g: rgb, b: rgb, a, format: 'rgb',
|
1259
1574
|
};
|
1260
1575
|
}
|
1261
1576
|
|
@@ -1264,72 +1579,68 @@
|
|
1264
1579
|
// don't worry about [0,1] or [0,100] or [0,360]
|
1265
1580
|
// Just return an object and let the conversion functions handle that.
|
1266
1581
|
// 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) {
|
1582
|
+
let [, m1, m2, m3, m4] = matchers.rgb.exec(color) || [];
|
1583
|
+
if (m1 && m2 && m3/* && m4 */) {
|
1273
1584
|
return {
|
1274
|
-
r:
|
1585
|
+
r: m1, g: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'rgb',
|
1275
1586
|
};
|
1276
1587
|
}
|
1277
|
-
|
1278
|
-
if (
|
1279
|
-
return { h: match[1], s: match[2], l: match[3] };
|
1280
|
-
}
|
1281
|
-
match = matchers.hsla.exec(color);
|
1282
|
-
if (match) {
|
1588
|
+
[, m1, m2, m3, m4] = matchers.hsl.exec(color) || [];
|
1589
|
+
if (m1 && m2 && m3/* && m4 */) {
|
1283
1590
|
return {
|
1284
|
-
h:
|
1591
|
+
h: m1, s: m2, l: m3, a: m4 !== undefined ? m4 : 1, format: 'hsl',
|
1285
1592
|
};
|
1286
1593
|
}
|
1287
|
-
|
1288
|
-
if (
|
1289
|
-
return {
|
1594
|
+
[, m1, m2, m3, m4] = matchers.hsv.exec(color) || [];
|
1595
|
+
if (m1 && m2 && m3/* && m4 */) {
|
1596
|
+
return {
|
1597
|
+
h: m1, s: m2, v: m3, a: m4 !== undefined ? m4 : 1, format: 'hsv',
|
1598
|
+
};
|
1290
1599
|
}
|
1291
|
-
|
1292
|
-
if (
|
1600
|
+
[, m1, m2, m3, m4] = matchers.hwb.exec(color) || [];
|
1601
|
+
if (m1 && m2 && m3) {
|
1293
1602
|
return {
|
1294
|
-
h:
|
1603
|
+
h: m1, w: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'hwb',
|
1295
1604
|
};
|
1296
1605
|
}
|
1297
|
-
|
1298
|
-
if (
|
1606
|
+
[, m1, m2, m3, m4] = matchers.hex8.exec(color) || [];
|
1607
|
+
if (m1 && m2 && m3 && m4) {
|
1299
1608
|
return {
|
1300
|
-
r: parseIntFromHex(
|
1301
|
-
g: parseIntFromHex(
|
1302
|
-
b: parseIntFromHex(
|
1303
|
-
a: convertHexToDecimal(
|
1304
|
-
format: named ? '
|
1609
|
+
r: parseIntFromHex(m1),
|
1610
|
+
g: parseIntFromHex(m2),
|
1611
|
+
b: parseIntFromHex(m3),
|
1612
|
+
a: convertHexToDecimal(m4),
|
1613
|
+
// format: named ? 'rgb' : 'hex8',
|
1614
|
+
format: named ? 'rgb' : 'hex',
|
1305
1615
|
};
|
1306
1616
|
}
|
1307
|
-
|
1308
|
-
if (
|
1617
|
+
[, m1, m2, m3] = matchers.hex6.exec(color) || [];
|
1618
|
+
if (m1 && m2 && m3) {
|
1309
1619
|
return {
|
1310
|
-
r: parseIntFromHex(
|
1311
|
-
g: parseIntFromHex(
|
1312
|
-
b: parseIntFromHex(
|
1313
|
-
format: named ? '
|
1620
|
+
r: parseIntFromHex(m1),
|
1621
|
+
g: parseIntFromHex(m2),
|
1622
|
+
b: parseIntFromHex(m3),
|
1623
|
+
format: named ? 'rgb' : 'hex',
|
1314
1624
|
};
|
1315
1625
|
}
|
1316
|
-
|
1317
|
-
if (
|
1626
|
+
[, m1, m2, m3, m4] = matchers.hex4.exec(color) || [];
|
1627
|
+
if (m1 && m2 && m3 && m4) {
|
1318
1628
|
return {
|
1319
|
-
r: parseIntFromHex(
|
1320
|
-
g: parseIntFromHex(
|
1321
|
-
b: parseIntFromHex(
|
1322
|
-
a: convertHexToDecimal(
|
1323
|
-
format: named ? '
|
1629
|
+
r: parseIntFromHex(m1 + m1),
|
1630
|
+
g: parseIntFromHex(m2 + m2),
|
1631
|
+
b: parseIntFromHex(m3 + m3),
|
1632
|
+
a: convertHexToDecimal(m4 + m4),
|
1633
|
+
// format: named ? 'rgb' : 'hex8',
|
1634
|
+
format: named ? 'rgb' : 'hex',
|
1324
1635
|
};
|
1325
1636
|
}
|
1326
|
-
|
1327
|
-
if (
|
1637
|
+
[, m1, m2, m3] = matchers.hex3.exec(color) || [];
|
1638
|
+
if (m1 && m2 && m3) {
|
1328
1639
|
return {
|
1329
|
-
r: parseIntFromHex(
|
1330
|
-
g: parseIntFromHex(
|
1331
|
-
b: parseIntFromHex(
|
1332
|
-
format: named ? '
|
1640
|
+
r: parseIntFromHex(m1 + m1),
|
1641
|
+
g: parseIntFromHex(m2 + m2),
|
1642
|
+
b: parseIntFromHex(m3 + m3),
|
1643
|
+
format: named ? 'rgb' : 'hex',
|
1333
1644
|
};
|
1334
1645
|
}
|
1335
1646
|
return false;
|
@@ -1343,26 +1654,33 @@
|
|
1343
1654
|
* "red"
|
1344
1655
|
* "#f00" or "f00"
|
1345
1656
|
* "#ff0000" or "ff0000"
|
1346
|
-
* "#ff000000" or "ff000000"
|
1657
|
+
* "#ff000000" or "ff000000" // CSS4 Module
|
1347
1658
|
* "rgb 255 0 0" or "rgb (255, 0, 0)"
|
1348
1659
|
* "rgb 1.0 0 0" or "rgb (1, 0, 0)"
|
1349
|
-
* "rgba
|
1350
|
-
* "rgba
|
1660
|
+
* "rgba(255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
|
1661
|
+
* "rgba(1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
|
1662
|
+
* "rgb(255 0 0 / 10%)" or "rgb 255 0 0 0.1" // CSS4 Module
|
1351
1663
|
* "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
|
1352
1664
|
* "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
|
1665
|
+
* "hsl(0deg 100% 50% / 50%)" or "hsl 0 100 50 50" // CSS4 Module
|
1353
1666
|
* "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
|
1667
|
+
* "hsva(0, 100%, 100%, 0.1)" or "hsva 0 100% 100% 0.1"
|
1668
|
+
* "hsv(0deg 100% 100% / 10%)" or "hsv 0 100 100 0.1" // CSS4 Module
|
1669
|
+
* "hwb(0deg, 100%, 100%, 100%)" or "hwb 0 100% 100% 0.1" // CSS4 Module
|
1354
1670
|
* ```
|
1355
1671
|
* @param {string | Record<string, any>} input
|
1356
1672
|
* @returns {CP.ColorObject}
|
1357
1673
|
*/
|
1358
1674
|
function inputToRGB(input) {
|
1359
|
-
/** @type {CP.RGB} */
|
1360
1675
|
let rgb = { r: 0, g: 0, b: 0 };
|
1361
1676
|
let color = input;
|
1362
|
-
let a;
|
1677
|
+
let a = 1;
|
1363
1678
|
let s = null;
|
1364
1679
|
let v = null;
|
1365
1680
|
let l = null;
|
1681
|
+
let w = null;
|
1682
|
+
let b = null;
|
1683
|
+
let h = null;
|
1366
1684
|
let ok = false;
|
1367
1685
|
let format = 'hex';
|
1368
1686
|
|
@@ -1373,23 +1691,38 @@
|
|
1373
1691
|
}
|
1374
1692
|
if (typeof color === 'object') {
|
1375
1693
|
if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
|
1376
|
-
rgb =
|
1694
|
+
rgb = { r: color.r, g: color.g, b: color.b }; // RGB values in [0, 255] range
|
1377
1695
|
ok = true;
|
1378
|
-
format =
|
1696
|
+
format = 'rgb';
|
1379
1697
|
} else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
|
1380
|
-
s =
|
1381
|
-
|
1382
|
-
|
1698
|
+
({ h, s, v } = color);
|
1699
|
+
h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
|
1700
|
+
s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
|
1701
|
+
v = typeof v === 'number' ? v : bound01(v, 100); // brightness can be `5%` or a [0, 1] value
|
1702
|
+
rgb = hsvToRgb(h, s, v);
|
1383
1703
|
ok = true;
|
1384
1704
|
format = 'hsv';
|
1385
1705
|
} else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
|
1386
|
-
s =
|
1387
|
-
|
1388
|
-
|
1706
|
+
({ h, s, l } = color);
|
1707
|
+
h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
|
1708
|
+
s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
|
1709
|
+
l = typeof l === 'number' ? l : bound01(l, 100); // lightness can be `5%` or a [0, 1] value
|
1710
|
+
rgb = hslToRgb(h, s, l);
|
1389
1711
|
ok = true;
|
1390
1712
|
format = 'hsl';
|
1713
|
+
} else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
|
1714
|
+
({ h, w, b } = color);
|
1715
|
+
h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
|
1716
|
+
w = typeof w === 'number' ? w : bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
|
1717
|
+
b = typeof b === 'number' ? b : bound01(b, 100); // blackness can be `5%` or a [0, 1] value
|
1718
|
+
rgb = hwbToRgb(h, w, b);
|
1719
|
+
ok = true;
|
1720
|
+
format = 'hwb';
|
1721
|
+
}
|
1722
|
+
if (isValidCSSUnit(color.a)) {
|
1723
|
+
a = color.a;
|
1724
|
+
a = isPercentage(`${a}`) ? bound01(a, 100) : a;
|
1391
1725
|
}
|
1392
|
-
if ('a' in color) a = color.a;
|
1393
1726
|
}
|
1394
1727
|
|
1395
1728
|
return {
|
@@ -1402,27 +1735,21 @@
|
|
1402
1735
|
};
|
1403
1736
|
}
|
1404
1737
|
|
1405
|
-
/** @type {CP.ColorOptions} */
|
1406
|
-
const colorPickerDefaults = {
|
1407
|
-
format: 'hex',
|
1408
|
-
};
|
1409
|
-
|
1410
1738
|
/**
|
1739
|
+
* @class
|
1411
1740
|
* Returns a new `Color` instance.
|
1412
1741
|
* @see https://github.com/bgrins/TinyColor
|
1413
|
-
* @class
|
1414
1742
|
*/
|
1415
1743
|
class Color {
|
1416
1744
|
/**
|
1417
1745
|
* @constructor
|
1418
|
-
* @param {CP.ColorInput} input
|
1419
|
-
* @param {CP.
|
1746
|
+
* @param {CP.ColorInput} input the given colour value
|
1747
|
+
* @param {CP.ColorFormats=} config the given format
|
1420
1748
|
*/
|
1421
1749
|
constructor(input, config) {
|
1422
1750
|
let color = input;
|
1423
|
-
const
|
1424
|
-
?
|
1425
|
-
: ObjectAssign({}, colorPickerDefaults);
|
1751
|
+
const configFormat = config && COLOR_FORMAT.includes(config)
|
1752
|
+
? config : 'rgb';
|
1426
1753
|
|
1427
1754
|
// If input is already a `Color`, return itself
|
1428
1755
|
if (color instanceof Color) {
|
@@ -1435,36 +1762,31 @@
|
|
1435
1762
|
r, g, b, a, ok, format,
|
1436
1763
|
} = inputToRGB(color);
|
1437
1764
|
|
1765
|
+
// bind
|
1766
|
+
const self = this;
|
1767
|
+
|
1438
1768
|
/** @type {CP.ColorInput} */
|
1439
|
-
|
1769
|
+
self.originalInput = color;
|
1440
1770
|
/** @type {number} */
|
1441
|
-
|
1771
|
+
self.r = r;
|
1442
1772
|
/** @type {number} */
|
1443
|
-
|
1773
|
+
self.g = g;
|
1444
1774
|
/** @type {number} */
|
1445
|
-
|
1775
|
+
self.b = b;
|
1446
1776
|
/** @type {number} */
|
1447
|
-
|
1777
|
+
self.a = a;
|
1448
1778
|
/** @type {boolean} */
|
1449
|
-
|
1450
|
-
/** @type {number} */
|
1451
|
-
this.roundA = Math.round(100 * this.a) / 100;
|
1779
|
+
self.ok = ok;
|
1452
1780
|
/** @type {CP.ColorFormats} */
|
1453
|
-
|
1781
|
+
self.format = configFormat || format;
|
1454
1782
|
|
1455
1783
|
// Don't let the range of [0,255] come back in [0,1].
|
1456
1784
|
// Potentially lose a little bit of precision here, but will fix issues where
|
1457
1785
|
// .5 gets interpreted as half of the total, instead of half of 1
|
1458
1786
|
// If it was supposed to be 128, this was already taken care of by `inputToRgb`
|
1459
|
-
if (
|
1460
|
-
|
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
|
-
}
|
1787
|
+
if (r < 1) self.r = Math.round(r);
|
1788
|
+
if (g < 1) self.g = Math.round(g);
|
1789
|
+
if (b < 1) self.b = Math.round(b);
|
1468
1790
|
}
|
1469
1791
|
|
1470
1792
|
/**
|
@@ -1484,40 +1806,40 @@
|
|
1484
1806
|
}
|
1485
1807
|
|
1486
1808
|
/**
|
1487
|
-
* Returns the perceived luminance of a
|
1809
|
+
* Returns the perceived luminance of a colour.
|
1488
1810
|
* @see http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
|
1489
|
-
* @returns {number} a number in [0
|
1811
|
+
* @returns {number} a number in the [0, 1] range
|
1490
1812
|
*/
|
1491
1813
|
get luminance() {
|
1492
1814
|
const { r, g, b } = this;
|
1493
1815
|
let R = 0;
|
1494
1816
|
let G = 0;
|
1495
1817
|
let B = 0;
|
1496
|
-
const
|
1497
|
-
const
|
1498
|
-
const
|
1818
|
+
const rp = r / 255;
|
1819
|
+
const rg = g / 255;
|
1820
|
+
const rb = b / 255;
|
1499
1821
|
|
1500
|
-
if (
|
1501
|
-
R =
|
1822
|
+
if (rp <= 0.03928) {
|
1823
|
+
R = rp / 12.92;
|
1502
1824
|
} else {
|
1503
|
-
R = ((
|
1825
|
+
R = ((rp + 0.055) / 1.055) ** 2.4;
|
1504
1826
|
}
|
1505
|
-
if (
|
1506
|
-
G =
|
1827
|
+
if (rg <= 0.03928) {
|
1828
|
+
G = rg / 12.92;
|
1507
1829
|
} else {
|
1508
|
-
G = ((
|
1830
|
+
G = ((rg + 0.055) / 1.055) ** 2.4;
|
1509
1831
|
}
|
1510
|
-
if (
|
1511
|
-
B =
|
1832
|
+
if (rb <= 0.03928) {
|
1833
|
+
B = rb / 12.92;
|
1512
1834
|
} else {
|
1513
|
-
B = ((
|
1835
|
+
B = ((rb + 0.055) / 1.055) ** 2.4;
|
1514
1836
|
}
|
1515
1837
|
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
|
1516
1838
|
}
|
1517
1839
|
|
1518
1840
|
/**
|
1519
|
-
* Returns the perceived brightness of the
|
1520
|
-
* @returns {number} a number in [0
|
1841
|
+
* Returns the perceived brightness of the colour.
|
1842
|
+
* @returns {number} a number in the [0, 255] range
|
1521
1843
|
*/
|
1522
1844
|
get brightness() {
|
1523
1845
|
const { r, g, b } = this;
|
@@ -1525,123 +1847,289 @@
|
|
1525
1847
|
}
|
1526
1848
|
|
1527
1849
|
/**
|
1528
|
-
* Returns the
|
1529
|
-
* @returns {CP.RGBA}
|
1850
|
+
* Returns the colour as an RGBA object.
|
1851
|
+
* @returns {CP.RGBA} an {r,g,b,a} object with [0, 255] ranged values
|
1530
1852
|
*/
|
1531
1853
|
toRgb() {
|
1854
|
+
const {
|
1855
|
+
r, g, b, a,
|
1856
|
+
} = this;
|
1857
|
+
const [R, G, B] = [r, g, b].map((x) => Math.round(x));
|
1858
|
+
|
1532
1859
|
return {
|
1533
|
-
r:
|
1534
|
-
g:
|
1535
|
-
b:
|
1536
|
-
a:
|
1860
|
+
r: R,
|
1861
|
+
g: G,
|
1862
|
+
b: B,
|
1863
|
+
a: Math.round(a * 100) / 100,
|
1537
1864
|
};
|
1538
1865
|
}
|
1539
1866
|
|
1540
1867
|
/**
|
1541
|
-
* Returns the RGBA values concatenated into a string.
|
1542
|
-
*
|
1868
|
+
* Returns the RGBA values concatenated into a CSS3 Module string format.
|
1869
|
+
* * rgb(255,255,255)
|
1870
|
+
* * rgba(255,255,255,0.5)
|
1871
|
+
* @returns {string} the CSS valid colour in RGB/RGBA format
|
1543
1872
|
*/
|
1544
1873
|
toRgbString() {
|
1545
|
-
const
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1874
|
+
const {
|
1875
|
+
r, g, b, a,
|
1876
|
+
} = this.toRgb();
|
1877
|
+
|
1878
|
+
return a === 1
|
1879
|
+
? `rgb(${r}, ${g}, ${b})`
|
1880
|
+
: `rgba(${r}, ${g}, ${b}, ${a})`;
|
1881
|
+
}
|
1882
|
+
|
1883
|
+
/**
|
1884
|
+
* Returns the RGBA values concatenated into a CSS4 Module string format.
|
1885
|
+
* * rgb(255 255 255)
|
1886
|
+
* * rgb(255 255 255 / 50%)
|
1887
|
+
* @returns {string} the CSS valid colour in CSS4 RGB format
|
1888
|
+
*/
|
1889
|
+
toRgbCSS4String() {
|
1890
|
+
const {
|
1891
|
+
r, g, b, a,
|
1892
|
+
} = this.toRgb();
|
1893
|
+
const A = a === 1 ? '' : ` / ${Math.round(a * 100)}%`;
|
1894
|
+
|
1895
|
+
return `rgb(${r} ${g} ${b}${A})`;
|
1896
|
+
}
|
1897
|
+
|
1898
|
+
/**
|
1899
|
+
* Returns the hexadecimal value of the colour. When the parameter is *true*
|
1900
|
+
* it will find a 3 characters shorthand of the decimal value.
|
1901
|
+
*
|
1902
|
+
* @param {boolean=} allow3Char when `true` returns shorthand HEX
|
1903
|
+
* @returns {string} the hexadecimal colour format
|
1904
|
+
*/
|
1905
|
+
toHex(allow3Char) {
|
1906
|
+
const {
|
1907
|
+
r, g, b, a,
|
1908
|
+
} = this.toRgb();
|
1909
|
+
|
1910
|
+
return a === 1
|
1911
|
+
? rgbToHex(r, g, b, allow3Char)
|
1912
|
+
: rgbaToHex(r, g, b, a, allow3Char);
|
1913
|
+
}
|
1914
|
+
|
1915
|
+
/**
|
1916
|
+
* Returns the CSS valid hexadecimal vaue of the colour. When the parameter is *true*
|
1917
|
+
* it will find a 3 characters shorthand of the value.
|
1918
|
+
*
|
1919
|
+
* @param {boolean=} allow3Char when `true` returns shorthand HEX
|
1920
|
+
* @returns {string} the CSS valid colour in hexadecimal format
|
1921
|
+
*/
|
1922
|
+
toHexString(allow3Char) {
|
1923
|
+
return `#${this.toHex(allow3Char)}`;
|
1551
1924
|
}
|
1552
1925
|
|
1553
1926
|
/**
|
1554
|
-
* Returns the
|
1555
|
-
* @
|
1927
|
+
* Returns the HEX8 value of the colour.
|
1928
|
+
* @param {boolean=} allow4Char when `true` returns shorthand HEX
|
1929
|
+
* @returns {string} the CSS valid colour in hexadecimal format
|
1556
1930
|
*/
|
1557
|
-
|
1558
|
-
|
1931
|
+
toHex8(allow4Char) {
|
1932
|
+
const {
|
1933
|
+
r, g, b, a,
|
1934
|
+
} = this.toRgb();
|
1935
|
+
|
1936
|
+
return rgbaToHex(r, g, b, a, allow4Char);
|
1559
1937
|
}
|
1560
1938
|
|
1561
1939
|
/**
|
1562
|
-
* Returns the
|
1563
|
-
* @
|
1940
|
+
* Returns the HEX8 value of the colour.
|
1941
|
+
* @param {boolean=} allow4Char when `true` returns shorthand HEX
|
1942
|
+
* @returns {string} the CSS valid colour in hexadecimal format
|
1564
1943
|
*/
|
1565
|
-
|
1566
|
-
return `#${this.
|
1944
|
+
toHex8String(allow4Char) {
|
1945
|
+
return `#${this.toHex8(allow4Char)}`;
|
1567
1946
|
}
|
1568
1947
|
|
1569
1948
|
/**
|
1570
|
-
* Returns the
|
1571
|
-
* @returns {CP.HSVA} the `{h,s,v,a}` object
|
1949
|
+
* Returns the colour as a HSVA object.
|
1950
|
+
* @returns {CP.HSVA} the `{h,s,v,a}` object with [0, 1] ranged values
|
1572
1951
|
*/
|
1573
1952
|
toHsv() {
|
1574
|
-
const {
|
1953
|
+
const {
|
1954
|
+
r, g, b, a,
|
1955
|
+
} = this.toRgb();
|
1956
|
+
const { h, s, v } = rgbToHsv(r, g, b);
|
1957
|
+
|
1575
1958
|
return {
|
1576
|
-
h
|
1959
|
+
h, s, v, a,
|
1577
1960
|
};
|
1578
1961
|
}
|
1579
1962
|
|
1580
1963
|
/**
|
1581
|
-
* Returns the
|
1582
|
-
* @returns {CP.HSLA}
|
1964
|
+
* Returns the colour as an HSLA object.
|
1965
|
+
* @returns {CP.HSLA} the `{h,s,l,a}` object with [0, 1] ranged values
|
1583
1966
|
*/
|
1584
1967
|
toHsl() {
|
1585
|
-
const {
|
1968
|
+
const {
|
1969
|
+
r, g, b, a,
|
1970
|
+
} = this.toRgb();
|
1971
|
+
const { h, s, l } = rgbToHsl(r, g, b);
|
1972
|
+
|
1586
1973
|
return {
|
1587
|
-
h
|
1974
|
+
h, s, l, a,
|
1588
1975
|
};
|
1589
1976
|
}
|
1590
1977
|
|
1591
1978
|
/**
|
1592
|
-
* Returns the HSLA values concatenated into a string.
|
1593
|
-
*
|
1979
|
+
* Returns the HSLA values concatenated into a CSS3 Module format string.
|
1980
|
+
* * `hsl(150, 100%, 50%)`
|
1981
|
+
* * `hsla(150, 100%, 50%, 0.5)`
|
1982
|
+
* @returns {string} the CSS valid colour in HSL/HSLA format
|
1594
1983
|
*/
|
1595
1984
|
toHslString() {
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
|
1600
|
-
|
1601
|
-
|
1602
|
-
|
1985
|
+
let {
|
1986
|
+
h, s, l, a,
|
1987
|
+
} = this.toHsl();
|
1988
|
+
h = Math.round(h * 360);
|
1989
|
+
s = Math.round(s * 100);
|
1990
|
+
l = Math.round(l * 100);
|
1991
|
+
a = Math.round(a * 100) / 100;
|
1992
|
+
|
1993
|
+
return a === 1
|
1994
|
+
? `hsl(${h}, ${s}%, ${l}%)`
|
1995
|
+
: `hsla(${h}, ${s}%, ${l}%, ${a})`;
|
1996
|
+
}
|
1997
|
+
|
1998
|
+
/**
|
1999
|
+
* Returns the HSLA values concatenated into a CSS4 Module format string.
|
2000
|
+
* * `hsl(150deg 100% 50%)`
|
2001
|
+
* * `hsl(150deg 100% 50% / 50%)`
|
2002
|
+
* @returns {string} the CSS valid colour in CSS4 HSL format
|
2003
|
+
*/
|
2004
|
+
toHslCSS4String() {
|
2005
|
+
let {
|
2006
|
+
h, s, l, a,
|
2007
|
+
} = this.toHsl();
|
2008
|
+
h = Math.round(h * 360);
|
2009
|
+
s = Math.round(s * 100);
|
2010
|
+
l = Math.round(l * 100);
|
2011
|
+
a = Math.round(a * 100);
|
2012
|
+
const A = a < 100 ? ` / ${Math.round(a)}%` : '';
|
2013
|
+
|
2014
|
+
return `hsl(${h}deg ${s}% ${l}%${A})`;
|
2015
|
+
}
|
2016
|
+
|
2017
|
+
/**
|
2018
|
+
* Returns the colour as an HWBA object.
|
2019
|
+
* @returns {CP.HWBA} the `{h,w,b,a}` object with [0, 1] ranged values
|
2020
|
+
*/
|
2021
|
+
toHwb() {
|
2022
|
+
const {
|
2023
|
+
r, g, b, a,
|
2024
|
+
} = this;
|
2025
|
+
const { h, w, b: bl } = rgbToHwb(r, g, b);
|
2026
|
+
return {
|
2027
|
+
h, w, b: bl, a,
|
2028
|
+
};
|
2029
|
+
}
|
2030
|
+
|
2031
|
+
/**
|
2032
|
+
* Returns the HWBA values concatenated into a string.
|
2033
|
+
* @returns {string} the CSS valid colour in HWB format
|
2034
|
+
*/
|
2035
|
+
toHwbString() {
|
2036
|
+
let {
|
2037
|
+
h, w, b, a,
|
2038
|
+
} = this.toHwb();
|
2039
|
+
h = Math.round(h * 360);
|
2040
|
+
w = Math.round(w * 100);
|
2041
|
+
b = Math.round(b * 100);
|
2042
|
+
a = Math.round(a * 100);
|
2043
|
+
const A = a < 100 ? ` / ${Math.round(a)}%` : '';
|
2044
|
+
|
2045
|
+
return `hwb(${h}deg ${w}% ${b}%${A})`;
|
1603
2046
|
}
|
1604
2047
|
|
1605
2048
|
/**
|
1606
|
-
* Sets the alpha value
|
1607
|
-
* @param {number} alpha a new alpha value in [0
|
1608
|
-
* @returns {Color}
|
2049
|
+
* Sets the alpha value of the current colour.
|
2050
|
+
* @param {number} alpha a new alpha value in the [0, 1] range.
|
2051
|
+
* @returns {Color} the `Color` instance
|
1609
2052
|
*/
|
1610
2053
|
setAlpha(alpha) {
|
1611
|
-
|
1612
|
-
|
1613
|
-
return
|
2054
|
+
const self = this;
|
2055
|
+
self.a = boundAlpha(alpha);
|
2056
|
+
return self;
|
1614
2057
|
}
|
1615
2058
|
|
1616
2059
|
/**
|
1617
|
-
* Saturate the
|
1618
|
-
* @param {number=} amount a value in [0
|
1619
|
-
* @returns {Color}
|
2060
|
+
* Saturate the colour with a given amount.
|
2061
|
+
* @param {number=} amount a value in the [0, 100] range
|
2062
|
+
* @returns {Color} the `Color` instance
|
1620
2063
|
*/
|
1621
2064
|
saturate(amount) {
|
1622
|
-
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
1626
|
-
|
2065
|
+
const self = this;
|
2066
|
+
if (typeof amount !== 'number') return self;
|
2067
|
+
const { h, s, l } = self.toHsl();
|
2068
|
+
const { r, g, b } = hslToRgb(h, clamp01(s + amount / 100), l);
|
2069
|
+
|
2070
|
+
ObjectAssign(self, { r, g, b });
|
2071
|
+
return self;
|
1627
2072
|
}
|
1628
2073
|
|
1629
2074
|
/**
|
1630
|
-
* Desaturate the
|
1631
|
-
* @param {number=} amount a value in [0
|
1632
|
-
* @returns {Color}
|
2075
|
+
* Desaturate the colour with a given amount.
|
2076
|
+
* @param {number=} amount a value in the [0, 100] range
|
2077
|
+
* @returns {Color} the `Color` instance
|
1633
2078
|
*/
|
1634
2079
|
desaturate(amount) {
|
1635
|
-
return amount ? this.saturate(-amount) : this;
|
2080
|
+
return typeof amount === 'number' ? this.saturate(-amount) : this;
|
1636
2081
|
}
|
1637
2082
|
|
1638
2083
|
/**
|
1639
|
-
* Completely desaturates a
|
2084
|
+
* Completely desaturates a colour into greyscale.
|
1640
2085
|
* Same as calling `desaturate(100)`
|
1641
|
-
* @returns {Color}
|
2086
|
+
* @returns {Color} the `Color` instance
|
1642
2087
|
*/
|
1643
2088
|
greyscale() {
|
1644
|
-
return this.
|
2089
|
+
return this.saturate(-100);
|
2090
|
+
}
|
2091
|
+
|
2092
|
+
/**
|
2093
|
+
* Increase the colour lightness with a given amount.
|
2094
|
+
* @param {number=} amount a value in the [0, 100] range
|
2095
|
+
* @returns {Color} the `Color` instance
|
2096
|
+
*/
|
2097
|
+
lighten(amount) {
|
2098
|
+
const self = this;
|
2099
|
+
if (typeof amount !== 'number') return self;
|
2100
|
+
|
2101
|
+
const { h, s, l } = self.toHsl();
|
2102
|
+
const { r, g, b } = hslToRgb(h, s, clamp01(l + amount / 100));
|
2103
|
+
|
2104
|
+
ObjectAssign(self, { r, g, b });
|
2105
|
+
return self;
|
2106
|
+
}
|
2107
|
+
|
2108
|
+
/**
|
2109
|
+
* Decrease the colour lightness with a given amount.
|
2110
|
+
* @param {number=} amount a value in the [0, 100] range
|
2111
|
+
* @returns {Color} the `Color` instance
|
2112
|
+
*/
|
2113
|
+
darken(amount) {
|
2114
|
+
return typeof amount === 'number' ? this.lighten(-amount) : this;
|
2115
|
+
}
|
2116
|
+
|
2117
|
+
/**
|
2118
|
+
* Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
|
2119
|
+
* Values outside of this range will be wrapped into this range.
|
2120
|
+
*
|
2121
|
+
* @param {number=} amount a value in the [0, 100] range
|
2122
|
+
* @returns {Color} the `Color` instance
|
2123
|
+
*/
|
2124
|
+
spin(amount) {
|
2125
|
+
const self = this;
|
2126
|
+
if (typeof amount !== 'number') return self;
|
2127
|
+
|
2128
|
+
const { h, s, l } = self.toHsl();
|
2129
|
+
const { r, g, b } = hslToRgb(clamp01(((h * 360 + amount) % 360) / 360), s, l);
|
2130
|
+
|
2131
|
+
ObjectAssign(self, { r, g, b });
|
2132
|
+
return self;
|
1645
2133
|
}
|
1646
2134
|
|
1647
2135
|
/** Returns a clone of the current `Color` instance. */
|
@@ -1650,77 +2138,235 @@
|
|
1650
2138
|
}
|
1651
2139
|
|
1652
2140
|
/**
|
1653
|
-
* Returns the
|
1654
|
-
* @
|
2141
|
+
* Returns the colour value in CSS valid string format.
|
2142
|
+
* @param {boolean=} allowShort when *true*, HEX values can be shorthand
|
2143
|
+
* @returns {string} the CSS valid colour in the configured format
|
1655
2144
|
*/
|
1656
|
-
toString() {
|
1657
|
-
const
|
2145
|
+
toString(allowShort) {
|
2146
|
+
const self = this;
|
2147
|
+
const { format } = self;
|
1658
2148
|
|
1659
|
-
if (format === '
|
1660
|
-
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
1664
|
-
}
|
1665
|
-
return this.toHexString();
|
2149
|
+
if (format === 'hex') return self.toHexString(allowShort);
|
2150
|
+
if (format === 'hsl') return self.toHslString();
|
2151
|
+
if (format === 'hwb') return self.toHwbString();
|
2152
|
+
|
2153
|
+
return self.toRgbString();
|
1666
2154
|
}
|
1667
2155
|
}
|
1668
2156
|
|
1669
2157
|
ObjectAssign(Color, {
|
1670
|
-
|
2158
|
+
ANGLES,
|
2159
|
+
CSS_ANGLE,
|
1671
2160
|
CSS_INTEGER,
|
1672
2161
|
CSS_NUMBER,
|
1673
2162
|
CSS_UNIT,
|
1674
|
-
|
1675
|
-
|
2163
|
+
CSS_UNIT2,
|
2164
|
+
PERMISSIVE_MATCH,
|
1676
2165
|
matchers,
|
1677
2166
|
isOnePointZero,
|
1678
2167
|
isPercentage,
|
1679
2168
|
isValidCSSUnit,
|
2169
|
+
pad2,
|
2170
|
+
clamp01,
|
1680
2171
|
bound01,
|
1681
2172
|
boundAlpha,
|
1682
|
-
|
1683
|
-
getHexFromColorName,
|
1684
|
-
convertToPercentage,
|
2173
|
+
getRGBFromName,
|
1685
2174
|
convertHexToDecimal,
|
1686
|
-
|
1687
|
-
rgbToRgb,
|
2175
|
+
convertDecimalToHex,
|
1688
2176
|
rgbToHsl,
|
1689
2177
|
rgbToHex,
|
1690
2178
|
rgbToHsv,
|
2179
|
+
rgbToHwb,
|
2180
|
+
rgbaToHex,
|
1691
2181
|
hslToRgb,
|
1692
2182
|
hsvToRgb,
|
1693
|
-
|
2183
|
+
hueToRgb,
|
2184
|
+
hwbToRgb,
|
1694
2185
|
parseIntFromHex,
|
1695
2186
|
numberInputToObject,
|
1696
2187
|
stringInputToObject,
|
1697
2188
|
inputToRGB,
|
2189
|
+
ObjectAssign,
|
1698
2190
|
});
|
1699
2191
|
|
2192
|
+
/**
|
2193
|
+
* @class
|
2194
|
+
* Returns a color palette with a given set of parameters.
|
2195
|
+
* @example
|
2196
|
+
* new ColorPalette(0, 12, 10);
|
2197
|
+
* // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
|
2198
|
+
*/
|
2199
|
+
class ColorPalette {
|
2200
|
+
/**
|
2201
|
+
* The `hue` parameter is optional, which would be set to 0.
|
2202
|
+
* @param {number[]} args represeinting hue, hueSteps, lightSteps
|
2203
|
+
* * `args.hue` the starting Hue [0, 360]
|
2204
|
+
* * `args.hueSteps` Hue Steps Count [5, 13]
|
2205
|
+
* * `args.lightSteps` Lightness Steps Count [8, 10]
|
2206
|
+
*/
|
2207
|
+
constructor(...args) {
|
2208
|
+
let hue = 0;
|
2209
|
+
let hueSteps = 12;
|
2210
|
+
let lightSteps = 10;
|
2211
|
+
let lightnessArray = [0.5];
|
2212
|
+
|
2213
|
+
if (args.length === 3) {
|
2214
|
+
[hue, hueSteps, lightSteps] = args;
|
2215
|
+
} else if (args.length === 2) {
|
2216
|
+
[hueSteps, lightSteps] = args;
|
2217
|
+
} else {
|
2218
|
+
throw TypeError('The ColorPalette requires minimum 2 arguments');
|
2219
|
+
}
|
2220
|
+
|
2221
|
+
/** @type {string[]} */
|
2222
|
+
const colors = [];
|
2223
|
+
|
2224
|
+
const hueStep = 360 / hueSteps;
|
2225
|
+
const lightStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
|
2226
|
+
const half = Math.round((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
|
2227
|
+
|
2228
|
+
// light tints
|
2229
|
+
for (let i = 0; i < half; i += 1) {
|
2230
|
+
lightnessArray = [...lightnessArray, (0.5 + lightStep * (i + 1))];
|
2231
|
+
}
|
2232
|
+
|
2233
|
+
// dark tints
|
2234
|
+
for (let i = 0; i < lightSteps - half - 1; i += 1) {
|
2235
|
+
lightnessArray = [(0.5 - lightStep * (i + 1)), ...lightnessArray];
|
2236
|
+
}
|
2237
|
+
|
2238
|
+
// feed `colors` Array
|
2239
|
+
for (let i = 0; i < hueSteps; i += 1) {
|
2240
|
+
const currentHue = ((hue + i * hueStep) % 360) / 360;
|
2241
|
+
lightnessArray.forEach((l) => {
|
2242
|
+
colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
|
2243
|
+
});
|
2244
|
+
}
|
2245
|
+
|
2246
|
+
this.hue = hue;
|
2247
|
+
this.hueSteps = hueSteps;
|
2248
|
+
this.lightSteps = lightSteps;
|
2249
|
+
this.colors = colors;
|
2250
|
+
}
|
2251
|
+
}
|
2252
|
+
|
2253
|
+
/**
|
2254
|
+
* Returns a color-defaults with given values and class.
|
2255
|
+
* @param {CP.ColorPicker} self
|
2256
|
+
* @param {CP.ColorPalette | string[]} colorsSource
|
2257
|
+
* @param {string} menuClass
|
2258
|
+
* @returns {HTMLElement | Element}
|
2259
|
+
*/
|
2260
|
+
function getColorMenu(self, colorsSource, menuClass) {
|
2261
|
+
const { input, format, componentLabels } = self;
|
2262
|
+
const { defaultsLabel, presetsLabel } = componentLabels;
|
2263
|
+
const isOptionsMenu = menuClass === 'color-options';
|
2264
|
+
const isPalette = colorsSource instanceof ColorPalette;
|
2265
|
+
const menuLabel = isOptionsMenu ? presetsLabel : defaultsLabel;
|
2266
|
+
let colorsArray = isPalette ? colorsSource.colors : colorsSource;
|
2267
|
+
colorsArray = colorsArray instanceof Array ? colorsArray : [];
|
2268
|
+
const colorsCount = colorsArray.length;
|
2269
|
+
const { lightSteps } = isPalette ? colorsSource : { lightSteps: null };
|
2270
|
+
let fit = lightSteps
|
2271
|
+
|| Math.max(...[5, 6, 7, 8, 9, 10].filter((x) => colorsCount > (x * 2) && !(colorsCount % x)));
|
2272
|
+
fit = Number.isFinite(fit) ? fit : 5;
|
2273
|
+
const isMultiLine = isOptionsMenu && colorsCount > fit;
|
2274
|
+
let rowCountHover = 1;
|
2275
|
+
rowCountHover = isMultiLine && colorsCount < 27 ? 2 : rowCountHover;
|
2276
|
+
rowCountHover = colorsCount >= 27 ? 3 : rowCountHover;
|
2277
|
+
rowCountHover = colorsCount >= 36 ? 4 : rowCountHover;
|
2278
|
+
rowCountHover = colorsCount >= 45 ? 5 : rowCountHover;
|
2279
|
+
const rowCount = rowCountHover - (colorsCount < 27 ? 1 : 2);
|
2280
|
+
const isScrollable = isMultiLine && colorsCount > rowCountHover * fit;
|
2281
|
+
let finalClass = menuClass;
|
2282
|
+
finalClass += isScrollable ? ' scrollable' : '';
|
2283
|
+
finalClass += isMultiLine ? ' multiline' : '';
|
2284
|
+
const gap = isMultiLine ? '1px' : '0.25rem';
|
2285
|
+
let optionSize = isMultiLine ? 1.75 : 2;
|
2286
|
+
optionSize = !(colorsCount % 10) && isMultiLine ? 1.5 : optionSize;
|
2287
|
+
const menuHeight = `${(rowCount || 1) * optionSize}rem`;
|
2288
|
+
const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
|
2289
|
+
const gridTemplateColumns = `repeat(${fit}, ${optionSize}rem)`;
|
2290
|
+
const gridTemplateRows = `repeat(auto-fill, ${optionSize}rem)`;
|
2291
|
+
|
2292
|
+
const menu = createElement({
|
2293
|
+
tagName: 'ul',
|
2294
|
+
className: finalClass,
|
2295
|
+
});
|
2296
|
+
setAttribute(menu, 'role', 'listbox');
|
2297
|
+
setAttribute(menu, ariaLabel, `${menuLabel}`);
|
2298
|
+
|
2299
|
+
if (isOptionsMenu) {
|
2300
|
+
if (isScrollable) {
|
2301
|
+
const styleText = 'this.style.height=';
|
2302
|
+
setAttribute(menu, 'onmouseout', `${styleText}'${menuHeight}'`);
|
2303
|
+
setAttribute(menu, 'onmouseover', `${styleText}'${menuHeightHover}'`);
|
2304
|
+
}
|
2305
|
+
const menuStyle = {
|
2306
|
+
height: isScrollable ? menuHeight : '', gridTemplateColumns, gridTemplateRows, gap,
|
2307
|
+
};
|
2308
|
+
setElementStyle(menu, menuStyle);
|
2309
|
+
}
|
2310
|
+
|
2311
|
+
colorsArray.forEach((x) => {
|
2312
|
+
const [value, label] = x.trim().split(':');
|
2313
|
+
const xRealColor = new Color(value, format).toString();
|
2314
|
+
const isActive = xRealColor === getAttribute(input, 'value');
|
2315
|
+
const active = isActive ? ' active' : '';
|
2316
|
+
|
2317
|
+
const option = createElement({
|
2318
|
+
tagName: 'li',
|
2319
|
+
className: `color-option${active}`,
|
2320
|
+
innerText: `${label || x}`,
|
2321
|
+
});
|
2322
|
+
|
2323
|
+
setAttribute(option, 'tabindex', '0');
|
2324
|
+
setAttribute(option, 'data-value', `${value}`);
|
2325
|
+
setAttribute(option, 'role', 'option');
|
2326
|
+
setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
|
2327
|
+
|
2328
|
+
if (isOptionsMenu) {
|
2329
|
+
setElementStyle(option, {
|
2330
|
+
width: `${optionSize}rem`, height: `${optionSize}rem`, backgroundColor: x,
|
2331
|
+
});
|
2332
|
+
}
|
2333
|
+
|
2334
|
+
menu.append(option);
|
2335
|
+
});
|
2336
|
+
return menu;
|
2337
|
+
}
|
2338
|
+
|
2339
|
+
/**
|
2340
|
+
* Check if a string is valid JSON string.
|
2341
|
+
* @param {string} str the string input
|
2342
|
+
* @returns {boolean} the query result
|
2343
|
+
*/
|
2344
|
+
function isValidJSON(str) {
|
2345
|
+
try {
|
2346
|
+
JSON.parse(str);
|
2347
|
+
} catch (e) {
|
2348
|
+
return false;
|
2349
|
+
}
|
2350
|
+
return true;
|
2351
|
+
}
|
2352
|
+
|
2353
|
+
var version = "0.0.1alpha2";
|
2354
|
+
|
2355
|
+
// @ts-ignore
|
2356
|
+
|
2357
|
+
const Version = version;
|
2358
|
+
|
1700
2359
|
// ColorPicker GC
|
1701
2360
|
// ==============
|
1702
2361
|
const colorPickerString = 'color-picker';
|
1703
2362
|
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',
|
2363
|
+
const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
|
2364
|
+
const colorPickerDefaults = {
|
2365
|
+
componentLabels: colorPickerLabels,
|
2366
|
+
colorLabels: colorNames,
|
2367
|
+
format: 'rgb',
|
2368
|
+
colorPresets: undefined,
|
2369
|
+
colorKeywords: nonColors,
|
1724
2370
|
};
|
1725
2371
|
|
1726
2372
|
// ColorPicker Static Methods
|
@@ -1735,165 +2381,94 @@
|
|
1735
2381
|
// ColorPicker Private Methods
|
1736
2382
|
// ===========================
|
1737
2383
|
|
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
2384
|
/**
|
1758
2385
|
* Generate HTML markup and update instance properties.
|
1759
2386
|
* @param {ColorPicker} self
|
1760
2387
|
*/
|
1761
2388
|
function initCallback(self) {
|
1762
2389
|
const {
|
1763
|
-
input, parent, format, id, componentLabels,
|
2390
|
+
input, parent, format, id, componentLabels, colorKeywords, colorPresets,
|
1764
2391
|
} = self;
|
1765
2392
|
const colorValue = getAttribute(input, 'value') || '#fff';
|
1766
2393
|
|
1767
2394
|
const {
|
1768
|
-
toggleLabel,
|
2395
|
+
toggleLabel, pickerLabel, formatLabel, hexLabel,
|
1769
2396
|
} = componentLabels;
|
1770
2397
|
|
1771
2398
|
// update color
|
1772
2399
|
const color = nonColors.includes(colorValue) ? '#fff' : colorValue;
|
1773
|
-
self.color = new Color(color,
|
2400
|
+
self.color = new Color(color, format);
|
1774
2401
|
|
1775
2402
|
// set initial controls dimensions
|
1776
2403
|
// make the controls smaller on mobile
|
1777
|
-
const cv1w = isMobile ? 150 : 230;
|
1778
|
-
const cvh = isMobile ? 150 : 230;
|
1779
|
-
const cv2w = 21;
|
1780
2404
|
const dropClass = isMobile ? ' mobile' : '';
|
1781
|
-
const
|
1782
|
-
const ctrl2Labelledby = format === 'hsl' ? `appearance2_${id}` : `appearance_${id} appearance2_${id}`;
|
2405
|
+
const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
|
1783
2406
|
|
1784
2407
|
const pickerBtn = createElement({
|
2408
|
+
id: `picker-btn-${id}`,
|
1785
2409
|
tagName: 'button',
|
1786
|
-
className: 'picker-toggle
|
1787
|
-
ariaExpanded: 'false',
|
1788
|
-
ariaHasPopup: 'true',
|
1789
|
-
ariaLive: 'polite',
|
2410
|
+
className: 'picker-toggle btn-appearance',
|
1790
2411
|
});
|
1791
|
-
setAttribute(pickerBtn,
|
2412
|
+
setAttribute(pickerBtn, ariaExpanded, 'false');
|
2413
|
+
setAttribute(pickerBtn, ariaHasPopup, 'true');
|
1792
2414
|
pickerBtn.append(createElement({
|
1793
2415
|
tagName: 'span',
|
1794
2416
|
className: vHidden,
|
1795
|
-
innerText:
|
2417
|
+
innerText: `${pickerLabel}. ${formatLabel}: ${formatString}`,
|
1796
2418
|
}));
|
1797
2419
|
|
1798
|
-
const
|
2420
|
+
const pickerDropdown = createElement({
|
1799
2421
|
tagName: 'div',
|
1800
2422
|
className: `color-dropdown picker${dropClass}`,
|
1801
2423
|
});
|
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
|
-
);
|
1838
|
-
|
1839
|
-
if (format !== 'hex') {
|
1840
|
-
colorControls.append(
|
1841
|
-
getColorControl(3, id, cv2w, cvh),
|
1842
|
-
);
|
1843
|
-
}
|
2424
|
+
setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
|
2425
|
+
setAttribute(pickerDropdown, 'role', 'group');
|
1844
2426
|
|
1845
|
-
|
2427
|
+
const colorControls = getColorControls(self);
|
1846
2428
|
const colorForm = getColorForm(self);
|
1847
|
-
colorPickerDropdown.append(colorControls, colorForm);
|
1848
|
-
parent.append(pickerBtn, colorPickerDropdown);
|
1849
2429
|
|
1850
|
-
|
1851
|
-
|
1852
|
-
|
2430
|
+
pickerDropdown.append(colorControls, colorForm);
|
2431
|
+
input.before(pickerBtn);
|
2432
|
+
parent.append(pickerDropdown);
|
2433
|
+
|
2434
|
+
// set colour key menu template
|
2435
|
+
if (colorKeywords || colorPresets) {
|
1853
2436
|
const presetsDropdown = createElement({
|
1854
2437
|
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);
|
2438
|
+
className: `color-dropdown scrollable menu${dropClass}`,
|
1881
2439
|
});
|
2440
|
+
|
2441
|
+
// color presets
|
2442
|
+
if ((colorPresets instanceof Array && colorPresets.length)
|
2443
|
+
|| (colorPresets instanceof ColorPalette && colorPresets.colors)) {
|
2444
|
+
const presetsMenu = getColorMenu(self, colorPresets, 'color-options');
|
2445
|
+
presetsDropdown.append(presetsMenu);
|
2446
|
+
}
|
2447
|
+
|
2448
|
+
// explicit defaults [reset, initial, inherit, transparent, currentColor]
|
2449
|
+
if (colorKeywords && colorKeywords.length) {
|
2450
|
+
const keywordsMenu = getColorMenu(self, colorKeywords, 'color-defaults');
|
2451
|
+
presetsDropdown.append(keywordsMenu);
|
2452
|
+
}
|
2453
|
+
|
1882
2454
|
const presetsBtn = createElement({
|
1883
2455
|
tagName: 'button',
|
1884
|
-
className: 'menu-toggle
|
1885
|
-
ariaExpanded: 'false',
|
1886
|
-
ariaHasPopup: 'true',
|
2456
|
+
className: 'menu-toggle btn-appearance',
|
1887
2457
|
});
|
2458
|
+
setAttribute(presetsBtn, 'tabindex', '-1');
|
2459
|
+
setAttribute(presetsBtn, ariaExpanded, 'false');
|
2460
|
+
setAttribute(presetsBtn, ariaHasPopup, 'true');
|
2461
|
+
|
1888
2462
|
const xmlns = encodeURI('http://www.w3.org/2000/svg');
|
1889
2463
|
const presetsIcon = createElementNS(xmlns, { tagName: 'svg' });
|
1890
2464
|
setAttribute(presetsIcon, 'xmlns', xmlns);
|
1891
|
-
setAttribute(presetsIcon, ariaHidden, 'true');
|
1892
2465
|
setAttribute(presetsIcon, 'viewBox', '0 0 512 512');
|
1893
|
-
|
1894
|
-
|
1895
|
-
|
1896
|
-
|
2466
|
+
setAttribute(presetsIcon, ariaHidden, 'true');
|
2467
|
+
|
2468
|
+
const path = createElementNS(xmlns, { tagName: 'path' });
|
2469
|
+
setAttribute(path, 'd', 'M98,158l157,156L411,158l27,27L255,368L71,185L98,158z');
|
2470
|
+
setAttribute(path, 'fill', '#fff');
|
2471
|
+
presetsIcon.append(path);
|
1897
2472
|
presetsBtn.append(createElement({
|
1898
2473
|
tagName: 'span',
|
1899
2474
|
className: vHidden,
|
@@ -1904,9 +2479,29 @@
|
|
1904
2479
|
}
|
1905
2480
|
|
1906
2481
|
// solve non-colors after settings save
|
1907
|
-
if (
|
2482
|
+
if (colorKeywords && nonColors.includes(colorValue)) {
|
1908
2483
|
self.value = colorValue;
|
1909
2484
|
}
|
2485
|
+
setAttribute(input, 'tabindex', '-1');
|
2486
|
+
}
|
2487
|
+
|
2488
|
+
/**
|
2489
|
+
* Add / remove `ColorPicker` main event listeners.
|
2490
|
+
* @param {ColorPicker} self
|
2491
|
+
* @param {boolean=} action
|
2492
|
+
*/
|
2493
|
+
function toggleEvents(self, action) {
|
2494
|
+
const fn = action ? addListener : removeListener;
|
2495
|
+
const { input, pickerToggle, menuToggle } = self;
|
2496
|
+
|
2497
|
+
fn(input, focusinEvent, self.showPicker);
|
2498
|
+
fn(pickerToggle, mouseclickEvent, self.togglePicker);
|
2499
|
+
|
2500
|
+
fn(input, keydownEvent, self.keyToggle);
|
2501
|
+
|
2502
|
+
if (menuToggle) {
|
2503
|
+
fn(menuToggle, mouseclickEvent, self.toggleMenu);
|
2504
|
+
}
|
1910
2505
|
}
|
1911
2506
|
|
1912
2507
|
/**
|
@@ -1916,26 +2511,33 @@
|
|
1916
2511
|
*/
|
1917
2512
|
function toggleEventsOnShown(self, action) {
|
1918
2513
|
const fn = action ? addListener : removeListener;
|
1919
|
-
const
|
1920
|
-
|
1921
|
-
|
2514
|
+
const { input, colorMenu, parent } = self;
|
2515
|
+
const doc = getDocument(input);
|
2516
|
+
const win = getWindow(input);
|
2517
|
+
const pointerEvents = `on${touchstartEvent}` in doc
|
2518
|
+
? { down: touchstartEvent, move: touchmoveEvent, up: touchendEvent }
|
2519
|
+
: { down: mousedownEvent, move: mousemoveEvent, up: mouseupEvent };
|
1922
2520
|
|
1923
2521
|
fn(self.controls, pointerEvents.down, self.pointerDown);
|
1924
|
-
self.controlKnobs.forEach((x) => fn(x,
|
2522
|
+
self.controlKnobs.forEach((x) => fn(x, keydownEvent, self.handleKnobs));
|
1925
2523
|
|
1926
|
-
|
2524
|
+
// @ts-ignore -- this is `Window`
|
2525
|
+
fn(win, scrollEvent, self.handleScroll);
|
2526
|
+
// @ts-ignore -- this is `Window`
|
2527
|
+
fn(win, resizeEvent, self.update);
|
1927
2528
|
|
1928
|
-
[
|
2529
|
+
[input, ...self.inputs].forEach((x) => fn(x, changeEvent, self.changeHandler));
|
1929
2530
|
|
1930
|
-
if (
|
1931
|
-
fn(
|
1932
|
-
fn(
|
2531
|
+
if (colorMenu) {
|
2532
|
+
fn(colorMenu, mouseclickEvent, self.menuClickHandler);
|
2533
|
+
fn(colorMenu, keydownEvent, self.menuKeyHandler);
|
1933
2534
|
}
|
1934
2535
|
|
1935
|
-
fn(
|
1936
|
-
fn(
|
1937
|
-
fn(
|
1938
|
-
|
2536
|
+
fn(doc, pointerEvents.move, self.pointerMove);
|
2537
|
+
fn(doc, pointerEvents.up, self.pointerUp);
|
2538
|
+
fn(parent, focusoutEvent, self.handleFocusOut);
|
2539
|
+
// @ts-ignore -- this is `Window`
|
2540
|
+
fn(win, keyupEvent, self.handleDismiss);
|
1939
2541
|
}
|
1940
2542
|
|
1941
2543
|
/**
|
@@ -1947,61 +2549,82 @@
|
|
1947
2549
|
}
|
1948
2550
|
|
1949
2551
|
/**
|
1950
|
-
*
|
2552
|
+
* Hides a visible dropdown.
|
1951
2553
|
* @param {HTMLElement} element
|
1952
|
-
* @
|
1953
|
-
* @returns {void | boolean}
|
2554
|
+
* @returns {void}
|
1954
2555
|
*/
|
1955
|
-
function
|
1956
|
-
const fn1 = !check ? 'forEach' : 'some';
|
1957
|
-
const fn2 = !check ? removeClass : hasClass;
|
1958
|
-
|
2556
|
+
function removePosition(element) {
|
1959
2557
|
if (element) {
|
1960
|
-
|
2558
|
+
['bottom', 'top'].forEach((x) => removeClass(element, x));
|
1961
2559
|
}
|
1962
|
-
|
1963
|
-
return false;
|
1964
2560
|
}
|
1965
2561
|
|
1966
2562
|
/**
|
1967
|
-
* Shows
|
2563
|
+
* Shows a `ColorPicker` dropdown and close the curent open dropdown.
|
1968
2564
|
* @param {ColorPicker} self
|
2565
|
+
* @param {HTMLElement | Element} dropdown
|
1969
2566
|
*/
|
1970
|
-
function
|
1971
|
-
|
1972
|
-
|
2567
|
+
function showDropdown(self, dropdown) {
|
2568
|
+
const {
|
2569
|
+
colorPicker, colorMenu, menuToggle, pickerToggle, parent,
|
2570
|
+
} = self;
|
2571
|
+
const isPicker = dropdown === colorPicker;
|
2572
|
+
const openDropdown = isPicker ? colorMenu : colorPicker;
|
2573
|
+
const activeBtn = isPicker ? menuToggle : pickerToggle;
|
2574
|
+
const nextBtn = !isPicker ? menuToggle : pickerToggle;
|
2575
|
+
|
2576
|
+
if (!hasClass(parent, 'open')) {
|
2577
|
+
addClass(parent, 'open');
|
2578
|
+
}
|
2579
|
+
if (openDropdown) {
|
2580
|
+
removeClass(openDropdown, 'show');
|
2581
|
+
removePosition(openDropdown);
|
2582
|
+
}
|
2583
|
+
addClass(dropdown, 'bottom');
|
2584
|
+
reflow(dropdown);
|
2585
|
+
addClass(dropdown, 'show');
|
2586
|
+
if (isPicker) self.update();
|
1973
2587
|
self.show();
|
1974
|
-
setAttribute(
|
2588
|
+
setAttribute(nextBtn, ariaExpanded, 'true');
|
2589
|
+
if (activeBtn) {
|
2590
|
+
setAttribute(activeBtn, ariaExpanded, 'false');
|
2591
|
+
}
|
1975
2592
|
}
|
1976
2593
|
|
1977
2594
|
/**
|
1978
|
-
* Color Picker
|
2595
|
+
* Color Picker Web Component
|
1979
2596
|
* @see http://thednp.github.io/color-picker
|
1980
2597
|
*/
|
1981
2598
|
class ColorPicker {
|
1982
2599
|
/**
|
1983
|
-
* Returns a new ColorPicker instance.
|
2600
|
+
* Returns a new `ColorPicker` instance. The target of this constructor
|
2601
|
+
* must be an `HTMLInputElement`.
|
2602
|
+
*
|
1984
2603
|
* @param {HTMLInputElement | string} target the target `<input>` element
|
2604
|
+
* @param {CP.ColorPickerOptions=} config instance options
|
1985
2605
|
*/
|
1986
|
-
constructor(target) {
|
2606
|
+
constructor(target, config) {
|
1987
2607
|
const self = this;
|
1988
2608
|
/** @type {HTMLInputElement} */
|
1989
2609
|
// @ts-ignore
|
1990
|
-
|
2610
|
+
const input = querySelector(target);
|
2611
|
+
|
1991
2612
|
// invalidate
|
1992
|
-
if (!
|
1993
|
-
|
2613
|
+
if (!input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
|
2614
|
+
self.input = input;
|
2615
|
+
|
2616
|
+
const parent = closest(input, colorPickerParentSelector);
|
2617
|
+
if (!parent) throw new TypeError('ColorPicker requires a specific markup to work.');
|
1994
2618
|
|
1995
2619
|
/** @type {HTMLElement} */
|
1996
2620
|
// @ts-ignore
|
1997
|
-
self.parent =
|
1998
|
-
if (!self.parent) throw new TypeError('ColorPicker requires a specific markup to work.');
|
2621
|
+
self.parent = parent;
|
1999
2622
|
|
2000
2623
|
/** @type {number} */
|
2001
2624
|
self.id = getUID(input, colorPickerString);
|
2002
2625
|
|
2003
2626
|
// set initial state
|
2004
|
-
/** @type {
|
2627
|
+
/** @type {HTMLElement?} */
|
2005
2628
|
self.dragElement = null;
|
2006
2629
|
/** @type {boolean} */
|
2007
2630
|
self.isOpen = false;
|
@@ -2011,26 +2634,59 @@
|
|
2011
2634
|
};
|
2012
2635
|
/** @type {Record<string, string>} */
|
2013
2636
|
self.colorLabels = {};
|
2014
|
-
/** @type {
|
2015
|
-
self.
|
2016
|
-
/** @type {
|
2017
|
-
self.
|
2637
|
+
/** @type {string[]=} */
|
2638
|
+
self.colorKeywords = undefined;
|
2639
|
+
/** @type {(ColorPalette | string[])=} */
|
2640
|
+
self.colorPresets = undefined;
|
2641
|
+
|
2642
|
+
// process options
|
2643
|
+
const {
|
2644
|
+
format, componentLabels, colorLabels, colorKeywords, colorPresets,
|
2645
|
+
} = normalizeOptions(this.isCE ? parent : input, colorPickerDefaults, config || {});
|
2646
|
+
|
2647
|
+
let translatedColorLabels = colorNames;
|
2648
|
+
if (colorLabels instanceof Array && colorLabels.length === 17) {
|
2649
|
+
translatedColorLabels = colorLabels;
|
2650
|
+
} else if (colorLabels && colorLabels.split(',').length === 17) {
|
2651
|
+
translatedColorLabels = colorLabels.split(',');
|
2652
|
+
}
|
2653
|
+
|
2654
|
+
// expose colour labels to all methods
|
2655
|
+
colorNames.forEach((c, i) => {
|
2656
|
+
self.colorLabels[c] = translatedColorLabels[i].trim();
|
2657
|
+
});
|
2658
|
+
|
2659
|
+
// update and expose component labels
|
2660
|
+
const tempLabels = ObjectAssign({}, colorPickerLabels);
|
2661
|
+
const jsonLabels = componentLabels && isValidJSON(componentLabels)
|
2662
|
+
? JSON.parse(componentLabels) : componentLabels || {};
|
2663
|
+
|
2018
2664
|
/** @type {Record<string, string>} */
|
2019
|
-
self.componentLabels = ObjectAssign(
|
2665
|
+
self.componentLabels = ObjectAssign(tempLabels, jsonLabels);
|
2020
2666
|
|
2021
|
-
|
2022
|
-
|
2023
|
-
self.componentLabels = ObjectAssign(self.componentLabels, temp);
|
2667
|
+
/** @type {Color} */
|
2668
|
+
self.color = new Color('white', format);
|
2024
2669
|
|
2025
|
-
|
2026
|
-
|
2670
|
+
/** @type {CP.ColorFormats} */
|
2671
|
+
self.format = format;
|
2027
2672
|
|
2028
|
-
//
|
2029
|
-
|
2673
|
+
// set colour defaults
|
2674
|
+
if (colorKeywords instanceof Array) {
|
2675
|
+
self.colorKeywords = colorKeywords;
|
2676
|
+
} else if (typeof colorKeywords === 'string' && colorKeywords.length) {
|
2677
|
+
self.colorKeywords = colorKeywords.split(',');
|
2678
|
+
}
|
2030
2679
|
|
2031
2680
|
// set colour presets
|
2032
|
-
if (
|
2033
|
-
self.
|
2681
|
+
if (colorPresets instanceof Array) {
|
2682
|
+
self.colorPresets = colorPresets;
|
2683
|
+
} else if (typeof colorPresets === 'string' && colorPresets.length) {
|
2684
|
+
if (isValidJSON(colorPresets)) {
|
2685
|
+
const { hue, hueSteps, lightSteps } = JSON.parse(colorPresets);
|
2686
|
+
self.colorPresets = new ColorPalette(hue, hueSteps, lightSteps);
|
2687
|
+
} else {
|
2688
|
+
self.colorPresets = colorPresets.split(',').map((x) => x.trim());
|
2689
|
+
}
|
2034
2690
|
}
|
2035
2691
|
|
2036
2692
|
// bind events
|
@@ -2042,17 +2698,18 @@
|
|
2042
2698
|
self.pointerDown = self.pointerDown.bind(self);
|
2043
2699
|
self.pointerMove = self.pointerMove.bind(self);
|
2044
2700
|
self.pointerUp = self.pointerUp.bind(self);
|
2701
|
+
self.update = self.update.bind(self);
|
2045
2702
|
self.handleScroll = self.handleScroll.bind(self);
|
2046
2703
|
self.handleFocusOut = self.handleFocusOut.bind(self);
|
2047
2704
|
self.changeHandler = self.changeHandler.bind(self);
|
2048
2705
|
self.handleDismiss = self.handleDismiss.bind(self);
|
2049
|
-
self.
|
2706
|
+
self.keyToggle = self.keyToggle.bind(self);
|
2050
2707
|
self.handleKnobs = self.handleKnobs.bind(self);
|
2051
2708
|
|
2052
2709
|
// generate markup
|
2053
2710
|
initCallback(self);
|
2054
2711
|
|
2055
|
-
const
|
2712
|
+
const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
|
2056
2713
|
// set main elements
|
2057
2714
|
/** @type {HTMLElement} */
|
2058
2715
|
// @ts-ignore
|
@@ -2062,68 +2719,24 @@
|
|
2062
2719
|
self.menuToggle = querySelector('.menu-toggle', parent);
|
2063
2720
|
/** @type {HTMLElement} */
|
2064
2721
|
// @ts-ignore
|
2065
|
-
self.
|
2066
|
-
/** @type {HTMLElement} */
|
2067
|
-
// @ts-ignore
|
2068
|
-
self.colorPicker = querySelector('.color-dropdown.picker', parent);
|
2722
|
+
self.colorPicker = colorPicker;
|
2069
2723
|
/** @type {HTMLElement} */
|
2070
2724
|
// @ts-ignore
|
2071
|
-
self.
|
2725
|
+
self.colorMenu = colorMenu;
|
2072
2726
|
/** @type {HTMLInputElement[]} */
|
2073
2727
|
// @ts-ignore
|
2074
|
-
self.inputs = [...
|
2728
|
+
self.inputs = [...getElementsByClassName('color-input', parent)];
|
2729
|
+
const [controls] = getElementsByClassName('color-controls', parent);
|
2730
|
+
self.controls = controls;
|
2731
|
+
/** @type {(HTMLElement | Element)[]} */
|
2732
|
+
self.controlKnobs = [...getElementsByClassName('knob', controls)];
|
2075
2733
|
/** @type {(HTMLElement)[]} */
|
2076
2734
|
// @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);
|
2735
|
+
self.visuals = [...getElementsByClassName('visual-control', controls)];
|
2105
2736
|
|
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
|
-
}
|
2737
|
+
// update colour picker controls, inputs and visuals
|
2738
|
+
self.update();
|
2119
2739
|
|
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
2740
|
// add main events listeners
|
2128
2741
|
toggleEvents(self, true);
|
2129
2742
|
|
@@ -2131,65 +2744,52 @@
|
|
2131
2744
|
Data.set(input, colorPickerString, self);
|
2132
2745
|
}
|
2133
2746
|
|
2134
|
-
/** Returns the current
|
2747
|
+
/** Returns the current colour value */
|
2135
2748
|
get value() { return this.input.value; }
|
2136
2749
|
|
2137
2750
|
/**
|
2138
|
-
* Sets a new
|
2139
|
-
* @param {string} v new
|
2751
|
+
* Sets a new colour value.
|
2752
|
+
* @param {string} v new colour value
|
2140
2753
|
*/
|
2141
2754
|
set value(v) { this.input.value = v; }
|
2142
2755
|
|
2143
|
-
/** Check if the
|
2144
|
-
get required() { return hasAttribute(this.input, 'required'); }
|
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. */
|
2756
|
+
/** Check if the colour presets include any non-colour. */
|
2163
2757
|
get includeNonColor() {
|
2164
|
-
return this.
|
2165
|
-
&& this.
|
2758
|
+
return this.colorKeywords instanceof Array
|
2759
|
+
&& this.colorKeywords.some((x) => nonColors.includes(x));
|
2166
2760
|
}
|
2167
2761
|
|
2168
|
-
/**
|
2169
|
-
get
|
2762
|
+
/** Check if the parent of the target is a `ColorPickerElement` instance. */
|
2763
|
+
get isCE() { return this.parent.localName === colorPickerString; }
|
2170
2764
|
|
2171
|
-
/** Returns
|
2765
|
+
/** Returns hexadecimal value of the current colour. */
|
2766
|
+
get hex() { return this.color.toHex(true); }
|
2767
|
+
|
2768
|
+
/** Returns the current colour value in {h,s,v,a} object format. */
|
2172
2769
|
get hsv() { return this.color.toHsv(); }
|
2173
2770
|
|
2174
|
-
/** Returns the current
|
2771
|
+
/** Returns the current colour value in {h,s,l,a} object format. */
|
2175
2772
|
get hsl() { return this.color.toHsl(); }
|
2176
2773
|
|
2177
|
-
/** Returns the current
|
2774
|
+
/** Returns the current colour value in {h,w,b,a} object format. */
|
2775
|
+
get hwb() { return this.color.toHwb(); }
|
2776
|
+
|
2777
|
+
/** Returns the current colour value in {r,g,b,a} object format. */
|
2178
2778
|
get rgb() { return this.color.toRgb(); }
|
2179
2779
|
|
2180
|
-
/** Returns the current
|
2780
|
+
/** Returns the current colour brightness. */
|
2181
2781
|
get brightness() { return this.color.brightness; }
|
2182
2782
|
|
2183
|
-
/** Returns the current
|
2783
|
+
/** Returns the current colour luminance. */
|
2184
2784
|
get luminance() { return this.color.luminance; }
|
2185
2785
|
|
2186
|
-
/** Checks if the current colour requires a light text
|
2786
|
+
/** Checks if the current colour requires a light text colour. */
|
2187
2787
|
get isDark() {
|
2188
|
-
const {
|
2189
|
-
return brightness < 120 &&
|
2788
|
+
const { color, brightness } = this;
|
2789
|
+
return brightness < 120 && color.a > 0.33;
|
2190
2790
|
}
|
2191
2791
|
|
2192
|
-
/** Checks if the current input value is a valid
|
2792
|
+
/** Checks if the current input value is a valid colour. */
|
2193
2793
|
get isValid() {
|
2194
2794
|
const inputValue = this.input.value;
|
2195
2795
|
return inputValue !== '' && new Color(inputValue).isValid;
|
@@ -2199,89 +2799,79 @@
|
|
2199
2799
|
updateVisuals() {
|
2200
2800
|
const self = this;
|
2201
2801
|
const {
|
2202
|
-
|
2203
|
-
width1, width2, width3,
|
2204
|
-
height1, height2, height3,
|
2205
|
-
ctx1, ctx2, ctx3,
|
2802
|
+
format, controlPositions, visuals,
|
2206
2803
|
} = self;
|
2207
|
-
const
|
2804
|
+
const [v1, v2, v3] = visuals;
|
2805
|
+
const { offsetWidth, offsetHeight } = v1;
|
2806
|
+
const hue = format === 'hsl'
|
2807
|
+
? controlPositions.c1x / offsetWidth
|
2808
|
+
: controlPositions.c2y / offsetHeight;
|
2809
|
+
// @ts-ignore - `hslToRgb` is assigned to `Color` as static method
|
2810
|
+
const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
|
2811
|
+
const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
|
2812
|
+
const alpha = 1 - controlPositions.c3y / offsetHeight;
|
2813
|
+
const roundA = Math.round((alpha * 100)) / 100;
|
2208
2814
|
|
2209
2815
|
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);
|
2816
|
+
const fill = new Color({
|
2817
|
+
h: hue, s: 1, l: 0.5, a: alpha,
|
2818
|
+
}).toRgbString();
|
2819
|
+
const hueGradient = `linear-gradient(
|
2820
|
+
rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
|
2821
|
+
rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
|
2822
|
+
rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
|
2823
|
+
rgb(255,0,0) 100%)`;
|
2824
|
+
setElementStyle(v1, {
|
2825
|
+
background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
|
2826
|
+
linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
|
2827
|
+
${whiteGrad}`,
|
2828
|
+
});
|
2829
|
+
setElementStyle(v2, { background: hueGradient });
|
2236
2830
|
} 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);
|
2831
|
+
const saturation = Math.round((controlPositions.c2y / offsetHeight) * 100);
|
2832
|
+
const fill0 = new Color({
|
2833
|
+
r: 255, g: 0, b: 0, a: alpha,
|
2834
|
+
}).saturate(-saturation).toRgbString();
|
2835
|
+
const fill1 = new Color({
|
2836
|
+
r: 255, g: 255, b: 0, a: alpha,
|
2837
|
+
}).saturate(-saturation).toRgbString();
|
2838
|
+
const fill2 = new Color({
|
2839
|
+
r: 0, g: 255, b: 0, a: alpha,
|
2840
|
+
}).saturate(-saturation).toRgbString();
|
2841
|
+
const fill3 = new Color({
|
2842
|
+
r: 0, g: 255, b: 255, a: alpha,
|
2843
|
+
}).saturate(-saturation).toRgbString();
|
2844
|
+
const fill4 = new Color({
|
2845
|
+
r: 0, g: 0, b: 255, a: alpha,
|
2846
|
+
}).saturate(-saturation).toRgbString();
|
2847
|
+
const fill5 = new Color({
|
2848
|
+
r: 255, g: 0, b: 255, a: alpha,
|
2849
|
+
}).saturate(-saturation).toRgbString();
|
2850
|
+
const fill6 = new Color({
|
2851
|
+
r: 255, g: 0, b: 0, a: alpha,
|
2852
|
+
}).saturate(-saturation).toRgbString();
|
2853
|
+
const fillGradient = `linear-gradient(to right,
|
2854
|
+
${fill0} 0%, ${fill1} 16.67%, ${fill2} 33.33%, ${fill3} 50%,
|
2855
|
+
${fill4} 66.67%, ${fill5} 83.33%, ${fill6} 100%)`;
|
2856
|
+
const lightGrad = `linear-gradient(rgba(255,255,255,${roundA}) 0%, rgba(255,255,255,0) 50%),
|
2857
|
+
linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,${roundA}) 100%)`;
|
2858
|
+
|
2859
|
+
setElementStyle(v1, { background: `${lightGrad},${fillGradient},${whiteGrad}` });
|
2860
|
+
const {
|
2861
|
+
r: gr, g: gg, b: gb,
|
2862
|
+
} = new Color({ r, g, b }).greyscale().toRgb();
|
2863
|
+
|
2864
|
+
setElementStyle(v2, {
|
2865
|
+
background: `linear-gradient(rgb(${r},${g},${b}) 0%, rgb(${gr},${gg},${gb}) 100%)`,
|
2866
|
+
});
|
2280
2867
|
}
|
2868
|
+
setElementStyle(v3, {
|
2869
|
+
background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
|
2870
|
+
});
|
2281
2871
|
}
|
2282
2872
|
|
2283
2873
|
/**
|
2284
|
-
*
|
2874
|
+
* The `ColorPicker` *focusout* event listener when open.
|
2285
2875
|
* @param {FocusEvent} e
|
2286
2876
|
* @this {ColorPicker}
|
2287
2877
|
*/
|
@@ -2293,7 +2883,7 @@
|
|
2293
2883
|
}
|
2294
2884
|
|
2295
2885
|
/**
|
2296
|
-
*
|
2886
|
+
* The `ColorPicker` *keyup* event listener when open.
|
2297
2887
|
* @param {KeyboardEvent} e
|
2298
2888
|
* @this {ColorPicker}
|
2299
2889
|
*/
|
@@ -2305,14 +2895,13 @@
|
|
2305
2895
|
}
|
2306
2896
|
|
2307
2897
|
/**
|
2308
|
-
*
|
2898
|
+
* The `ColorPicker` *scroll* event listener when open.
|
2309
2899
|
* @param {Event} e
|
2310
2900
|
* @this {ColorPicker}
|
2311
2901
|
*/
|
2312
2902
|
handleScroll(e) {
|
2313
2903
|
const self = this;
|
2314
|
-
|
2315
|
-
const { activeElement } = document;
|
2904
|
+
const { activeElement } = getDocument(self.input);
|
2316
2905
|
|
2317
2906
|
if ((isMobile && self.dragElement)
|
2318
2907
|
|| (activeElement && self.controlKnobs.includes(activeElement))) {
|
@@ -2324,22 +2913,51 @@
|
|
2324
2913
|
}
|
2325
2914
|
|
2326
2915
|
/**
|
2327
|
-
*
|
2916
|
+
* The `ColorPicker` keyboard event listener for menu navigation.
|
2328
2917
|
* @param {KeyboardEvent} e
|
2329
2918
|
* @this {ColorPicker}
|
2330
2919
|
*/
|
2331
2920
|
menuKeyHandler(e) {
|
2332
2921
|
const { target, code } = e;
|
2333
|
-
|
2334
|
-
|
2922
|
+
// @ts-ignore
|
2923
|
+
const { previousElementSibling, nextElementSibling, parentElement } = target;
|
2924
|
+
const isColorOptionsMenu = parentElement && hasClass(parentElement, 'color-options');
|
2925
|
+
const allSiblings = [...parentElement.children];
|
2926
|
+
const columnsCount = isColorOptionsMenu
|
2927
|
+
&& getElementStyle(parentElement, 'grid-template-columns').split(' ').length;
|
2928
|
+
const currentIndex = allSiblings.indexOf(target);
|
2929
|
+
const previousElement = currentIndex > -1
|
2930
|
+
&& columnsCount && allSiblings[currentIndex - columnsCount];
|
2931
|
+
const nextElement = currentIndex > -1
|
2932
|
+
&& columnsCount && allSiblings[currentIndex + columnsCount];
|
2933
|
+
|
2934
|
+
if ([keyArrowDown, keyArrowUp, keySpace].includes(code)) {
|
2935
|
+
// prevent scroll when navigating the menu via arrow keys / Space
|
2335
2936
|
e.preventDefault();
|
2336
|
-
}
|
2937
|
+
}
|
2938
|
+
if (isColorOptionsMenu) {
|
2939
|
+
if (previousElement && code === keyArrowUp) {
|
2940
|
+
focus(previousElement);
|
2941
|
+
} else if (nextElement && code === keyArrowDown) {
|
2942
|
+
focus(nextElement);
|
2943
|
+
} else if (previousElementSibling && code === keyArrowLeft) {
|
2944
|
+
focus(previousElementSibling);
|
2945
|
+
} else if (nextElementSibling && code === keyArrowRight) {
|
2946
|
+
focus(nextElementSibling);
|
2947
|
+
}
|
2948
|
+
} else if (previousElementSibling && [keyArrowLeft, keyArrowUp].includes(code)) {
|
2949
|
+
focus(previousElementSibling);
|
2950
|
+
} else if (nextElementSibling && [keyArrowRight, keyArrowDown].includes(code)) {
|
2951
|
+
focus(nextElementSibling);
|
2952
|
+
}
|
2953
|
+
|
2954
|
+
if ([keyEnter, keySpace].includes(code)) {
|
2337
2955
|
this.menuClickHandler({ target });
|
2338
2956
|
}
|
2339
2957
|
}
|
2340
2958
|
|
2341
2959
|
/**
|
2342
|
-
*
|
2960
|
+
* The `ColorPicker` click event listener for the colour menu presets / defaults.
|
2343
2961
|
* @param {Partial<Event>} e
|
2344
2962
|
* @this {ColorPicker}
|
2345
2963
|
*/
|
@@ -2347,16 +2965,23 @@
|
|
2347
2965
|
const self = this;
|
2348
2966
|
/** @type {*} */
|
2349
2967
|
const { target } = e;
|
2350
|
-
const {
|
2968
|
+
const { colorMenu } = self;
|
2351
2969
|
const newOption = (getAttribute(target, 'data-value') || '').trim();
|
2352
|
-
|
2353
|
-
|
2354
|
-
|
2355
|
-
|
2356
|
-
|
2357
|
-
|
2358
|
-
|
2359
|
-
|
2970
|
+
// invalidate for targets other than color options
|
2971
|
+
if (!newOption.length) return;
|
2972
|
+
const currentActive = querySelector('li.active', colorMenu);
|
2973
|
+
let newColor = nonColors.includes(newOption) ? 'white' : newOption;
|
2974
|
+
newColor = newOption === 'transparent' ? 'rgba(0,0,0,0)' : newOption;
|
2975
|
+
|
2976
|
+
const {
|
2977
|
+
r, g, b, a,
|
2978
|
+
} = new Color(newColor);
|
2979
|
+
|
2980
|
+
ObjectAssign(self.color, {
|
2981
|
+
r, g, b, a,
|
2982
|
+
});
|
2983
|
+
|
2984
|
+
self.update();
|
2360
2985
|
|
2361
2986
|
if (currentActive) {
|
2362
2987
|
removeClass(currentActive, 'active');
|
@@ -2369,29 +2994,28 @@
|
|
2369
2994
|
|
2370
2995
|
if (nonColors.includes(newOption)) {
|
2371
2996
|
self.value = newOption;
|
2372
|
-
firePickerChange(self);
|
2373
2997
|
}
|
2998
|
+
firePickerChange(self);
|
2374
2999
|
}
|
2375
3000
|
}
|
2376
3001
|
|
2377
3002
|
/**
|
2378
|
-
*
|
3003
|
+
* The `ColorPicker` *touchstart* / *mousedown* events listener for control knobs.
|
2379
3004
|
* @param {TouchEvent} e
|
2380
3005
|
* @this {ColorPicker}
|
2381
3006
|
*/
|
2382
3007
|
pointerDown(e) {
|
2383
3008
|
const self = this;
|
3009
|
+
/** @type {*} */
|
2384
3010
|
const {
|
2385
|
-
// @ts-ignore
|
2386
3011
|
type, target, touches, pageX, pageY,
|
2387
3012
|
} = e;
|
2388
|
-
const { visuals, controlKnobs
|
3013
|
+
const { colorMenu, visuals, controlKnobs } = self;
|
2389
3014
|
const [v1, v2, v3] = visuals;
|
2390
3015
|
const [c1, c2, c3] = controlKnobs;
|
2391
|
-
/** @type {
|
2392
|
-
|
2393
|
-
|
2394
|
-
? target : querySelector('canvas', target.parentElement);
|
3016
|
+
/** @type {HTMLElement} */
|
3017
|
+
const visual = hasClass(target, 'visual-control')
|
3018
|
+
? target : querySelector('.visual-control', target.parentElement);
|
2395
3019
|
const visualRect = getBoundingClientRect(visual);
|
2396
3020
|
const X = type === 'touchstart' ? touches[0].pageX : pageX;
|
2397
3021
|
const Y = type === 'touchstart' ? touches[0].pageY : pageY;
|
@@ -2400,42 +3024,53 @@
|
|
2400
3024
|
|
2401
3025
|
if (target === v1 || target === c1) {
|
2402
3026
|
self.dragElement = visual;
|
2403
|
-
self.changeControl1(
|
3027
|
+
self.changeControl1(offsetX, offsetY);
|
2404
3028
|
} else if (target === v2 || target === c2) {
|
2405
3029
|
self.dragElement = visual;
|
2406
|
-
self.changeControl2(
|
2407
|
-
} else if (
|
3030
|
+
self.changeControl2(offsetY);
|
3031
|
+
} else if (target === v3 || target === c3) {
|
2408
3032
|
self.dragElement = visual;
|
2409
|
-
self.changeAlpha(
|
3033
|
+
self.changeAlpha(offsetY);
|
3034
|
+
}
|
3035
|
+
|
3036
|
+
if (colorMenu) {
|
3037
|
+
const currentActive = querySelector('li.active', colorMenu);
|
3038
|
+
if (currentActive) {
|
3039
|
+
removeClass(currentActive, 'active');
|
3040
|
+
removeAttribute(currentActive, ariaSelected);
|
3041
|
+
}
|
2410
3042
|
}
|
2411
3043
|
e.preventDefault();
|
2412
3044
|
}
|
2413
3045
|
|
2414
3046
|
/**
|
2415
|
-
*
|
3047
|
+
* The `ColorPicker` *touchend* / *mouseup* events listener for control knobs.
|
2416
3048
|
* @param {TouchEvent} e
|
2417
3049
|
* @this {ColorPicker}
|
2418
3050
|
*/
|
2419
3051
|
pointerUp({ target }) {
|
2420
3052
|
const self = this;
|
2421
|
-
const
|
3053
|
+
const { parent } = self;
|
3054
|
+
const doc = getDocument(parent);
|
3055
|
+
const currentOpen = querySelector(`${colorPickerParentSelector}.open`, doc) !== null;
|
3056
|
+
const selection = doc.getSelection();
|
2422
3057
|
// @ts-ignore
|
2423
3058
|
if (!self.dragElement && !selection.toString().length
|
2424
3059
|
// @ts-ignore
|
2425
|
-
&& !
|
2426
|
-
self.hide();
|
3060
|
+
&& !parent.contains(target)) {
|
3061
|
+
self.hide(currentOpen);
|
2427
3062
|
}
|
2428
3063
|
|
2429
3064
|
self.dragElement = null;
|
2430
3065
|
}
|
2431
3066
|
|
2432
3067
|
/**
|
2433
|
-
*
|
3068
|
+
* The `ColorPicker` *touchmove* / *mousemove* events listener for control knobs.
|
2434
3069
|
* @param {TouchEvent} e
|
2435
3070
|
*/
|
2436
3071
|
pointerMove(e) {
|
2437
3072
|
const self = this;
|
2438
|
-
const { dragElement, visuals
|
3073
|
+
const { dragElement, visuals } = self;
|
2439
3074
|
const [v1, v2, v3] = visuals;
|
2440
3075
|
const {
|
2441
3076
|
// @ts-ignore
|
@@ -2451,20 +3086,20 @@
|
|
2451
3086
|
const offsetY = Y - window.pageYOffset - controlRect.top;
|
2452
3087
|
|
2453
3088
|
if (dragElement === v1) {
|
2454
|
-
self.changeControl1(
|
3089
|
+
self.changeControl1(offsetX, offsetY);
|
2455
3090
|
}
|
2456
3091
|
|
2457
3092
|
if (dragElement === v2) {
|
2458
|
-
self.changeControl2(
|
3093
|
+
self.changeControl2(offsetY);
|
2459
3094
|
}
|
2460
3095
|
|
2461
|
-
if (dragElement === v3
|
2462
|
-
self.changeAlpha(
|
3096
|
+
if (dragElement === v3) {
|
3097
|
+
self.changeAlpha(offsetY);
|
2463
3098
|
}
|
2464
3099
|
}
|
2465
3100
|
|
2466
3101
|
/**
|
2467
|
-
*
|
3102
|
+
* The `ColorPicker` *keydown* event listener for control knobs.
|
2468
3103
|
* @param {KeyboardEvent} e
|
2469
3104
|
*/
|
2470
3105
|
handleKnobs(e) {
|
@@ -2475,10 +3110,10 @@
|
|
2475
3110
|
if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
|
2476
3111
|
e.preventDefault();
|
2477
3112
|
|
2478
|
-
const { activeElement } = document;
|
2479
3113
|
const { controlKnobs } = self;
|
2480
|
-
const currentKnob = controlKnobs.find((x) => x === activeElement);
|
2481
3114
|
const [c1, c2, c3] = controlKnobs;
|
3115
|
+
const { activeElement } = getDocument(c1);
|
3116
|
+
const currentKnob = controlKnobs.find((x) => x === activeElement);
|
2482
3117
|
|
2483
3118
|
if (currentKnob) {
|
2484
3119
|
let offsetX = 0;
|
@@ -2492,37 +3127,36 @@
|
|
2492
3127
|
|
2493
3128
|
offsetX = self.controlPositions.c1x;
|
2494
3129
|
offsetY = self.controlPositions.c1y;
|
2495
|
-
self.changeControl1(
|
3130
|
+
self.changeControl1(offsetX, offsetY);
|
2496
3131
|
} else if (target === c2) {
|
2497
3132
|
self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
|
2498
3133
|
offsetY = self.controlPositions.c2y;
|
2499
|
-
self.changeControl2(
|
3134
|
+
self.changeControl2(offsetY);
|
2500
3135
|
} else if (target === c3) {
|
2501
3136
|
self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
|
2502
3137
|
offsetY = self.controlPositions.c3y;
|
2503
|
-
self.changeAlpha(
|
3138
|
+
self.changeAlpha(offsetY);
|
2504
3139
|
}
|
2505
|
-
|
2506
|
-
self.setColorAppearence();
|
2507
|
-
self.updateInputs();
|
2508
|
-
self.updateControls();
|
2509
|
-
self.updateVisuals();
|
2510
3140
|
self.handleScroll(e);
|
2511
3141
|
}
|
2512
3142
|
}
|
2513
3143
|
|
2514
|
-
/**
|
3144
|
+
/** The event listener of the colour form inputs. */
|
2515
3145
|
changeHandler() {
|
2516
3146
|
const self = this;
|
2517
3147
|
let colorSource;
|
2518
|
-
/** @type {HTMLInputElement} */
|
2519
|
-
// @ts-ignore
|
2520
|
-
const { activeElement } = document;
|
2521
3148
|
const {
|
2522
|
-
inputs, format, value: currentValue, input,
|
3149
|
+
inputs, format, value: currentValue, input, controlPositions, visuals,
|
2523
3150
|
} = self;
|
2524
|
-
|
3151
|
+
/** @type {*} */
|
3152
|
+
const { activeElement } = getDocument(input);
|
3153
|
+
const { offsetHeight } = visuals[0];
|
3154
|
+
const [i1,,, i4] = inputs;
|
3155
|
+
const [v1, v2, v3, v4] = format === 'rgb'
|
3156
|
+
? inputs.map((i) => parseFloat(i.value) / (i === i4 ? 100 : 1))
|
3157
|
+
: inputs.map((i) => parseFloat(i.value) / (i !== i1 ? 100 : 360));
|
2525
3158
|
const isNonColorValue = self.includeNonColor && nonColors.includes(currentValue);
|
3159
|
+
const alpha = i4 ? v4 : (1 - controlPositions.c3y / offsetHeight);
|
2526
3160
|
|
2527
3161
|
if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
|
2528
3162
|
if (activeElement === input) {
|
@@ -2534,14 +3168,28 @@
|
|
2534
3168
|
} else if (format === 'hex') {
|
2535
3169
|
colorSource = i1.value;
|
2536
3170
|
} else if (format === 'hsl') {
|
2537
|
-
colorSource =
|
3171
|
+
colorSource = {
|
3172
|
+
h: v1, s: v2, l: v3, a: alpha,
|
3173
|
+
};
|
3174
|
+
} else if (format === 'hwb') {
|
3175
|
+
colorSource = {
|
3176
|
+
h: v1, w: v2, b: v3, a: alpha,
|
3177
|
+
};
|
2538
3178
|
} else {
|
2539
|
-
colorSource =
|
3179
|
+
colorSource = {
|
3180
|
+
r: v1, g: v2, b: v3, a: alpha,
|
3181
|
+
};
|
2540
3182
|
}
|
2541
3183
|
|
2542
|
-
|
3184
|
+
const {
|
3185
|
+
r, g, b, a,
|
3186
|
+
} = new Color(colorSource);
|
3187
|
+
|
3188
|
+
ObjectAssign(self.color, {
|
3189
|
+
r, g, b, a,
|
3190
|
+
});
|
2543
3191
|
self.setControlPositions();
|
2544
|
-
self.
|
3192
|
+
self.updateAppearance();
|
2545
3193
|
self.updateInputs();
|
2546
3194
|
self.updateControls();
|
2547
3195
|
self.updateVisuals();
|
@@ -2558,49 +3206,57 @@
|
|
2558
3206
|
* * `lightness` and `saturation` for HEX/RGB;
|
2559
3207
|
* * `lightness` and `hue` for HSL.
|
2560
3208
|
*
|
2561
|
-
* @param {
|
3209
|
+
* @param {number} X the X component of the offset
|
3210
|
+
* @param {number} Y the Y component of the offset
|
2562
3211
|
*/
|
2563
|
-
changeControl1(
|
3212
|
+
changeControl1(X, Y) {
|
2564
3213
|
const self = this;
|
2565
3214
|
let [offsetX, offsetY] = [0, 0];
|
2566
|
-
const { offsetX: X, offsetY: Y } = offsets;
|
2567
3215
|
const {
|
2568
|
-
format, controlPositions,
|
2569
|
-
height1, height2, height3, width1,
|
3216
|
+
format, controlPositions, visuals,
|
2570
3217
|
} = self;
|
3218
|
+
const { offsetHeight, offsetWidth } = visuals[0];
|
2571
3219
|
|
2572
|
-
if (X >
|
2573
|
-
|
2574
|
-
} else if (X >= 0) {
|
2575
|
-
offsetX = X;
|
2576
|
-
}
|
3220
|
+
if (X > offsetWidth) offsetX = offsetWidth;
|
3221
|
+
else if (X >= 0) offsetX = X;
|
2577
3222
|
|
2578
|
-
if (Y >
|
2579
|
-
|
2580
|
-
} else if (Y >= 0) {
|
2581
|
-
offsetY = Y;
|
2582
|
-
}
|
3223
|
+
if (Y > offsetHeight) offsetY = offsetHeight;
|
3224
|
+
else if (Y >= 0) offsetY = Y;
|
2583
3225
|
|
2584
|
-
const hue = format
|
2585
|
-
?
|
2586
|
-
:
|
3226
|
+
const hue = format === 'hsl'
|
3227
|
+
? offsetX / offsetWidth
|
3228
|
+
: controlPositions.c2y / offsetHeight;
|
2587
3229
|
|
2588
|
-
const saturation = format
|
2589
|
-
?
|
2590
|
-
:
|
3230
|
+
const saturation = format === 'hsl'
|
3231
|
+
? 1 - controlPositions.c2y / offsetHeight
|
3232
|
+
: offsetX / offsetWidth;
|
2591
3233
|
|
2592
|
-
const lightness =
|
2593
|
-
const alpha =
|
2594
|
-
|
3234
|
+
const lightness = 1 - offsetY / offsetHeight;
|
3235
|
+
const alpha = 1 - controlPositions.c3y / offsetHeight;
|
3236
|
+
|
3237
|
+
const colorObject = format === 'hsl'
|
3238
|
+
? {
|
3239
|
+
h: hue, s: saturation, l: lightness, a: alpha,
|
3240
|
+
}
|
3241
|
+
: {
|
3242
|
+
h: hue, s: saturation, v: lightness, a: alpha,
|
3243
|
+
};
|
2595
3244
|
|
2596
3245
|
// new color
|
2597
|
-
|
3246
|
+
const {
|
3247
|
+
r, g, b, a,
|
3248
|
+
} = new Color(colorObject);
|
3249
|
+
|
3250
|
+
ObjectAssign(self.color, {
|
3251
|
+
r, g, b, a,
|
3252
|
+
});
|
3253
|
+
|
2598
3254
|
// new positions
|
2599
3255
|
self.controlPositions.c1x = offsetX;
|
2600
3256
|
self.controlPositions.c1y = offsetY;
|
2601
3257
|
|
2602
3258
|
// update color picker
|
2603
|
-
self.
|
3259
|
+
self.updateAppearance();
|
2604
3260
|
self.updateInputs();
|
2605
3261
|
self.updateControls();
|
2606
3262
|
self.updateVisuals();
|
@@ -2608,37 +3264,52 @@
|
|
2608
3264
|
|
2609
3265
|
/**
|
2610
3266
|
* Updates `ColorPicker` second control:
|
2611
|
-
* * `hue` for HEX/RGB;
|
3267
|
+
* * `hue` for HEX/RGB/HWB;
|
2612
3268
|
* * `saturation` for HSL.
|
2613
3269
|
*
|
2614
|
-
* @param {
|
3270
|
+
* @param {number} Y the Y offset
|
2615
3271
|
*/
|
2616
|
-
changeControl2(
|
3272
|
+
changeControl2(Y) {
|
2617
3273
|
const self = this;
|
2618
|
-
const { offsetY: Y } = offset;
|
2619
3274
|
const {
|
2620
|
-
format,
|
3275
|
+
format, controlPositions, visuals,
|
2621
3276
|
} = self;
|
2622
|
-
|
3277
|
+
const { offsetHeight, offsetWidth } = visuals[0];
|
2623
3278
|
|
2624
|
-
|
2625
|
-
offsetY = height2;
|
2626
|
-
} else if (Y >= 0) {
|
2627
|
-
offsetY = Y;
|
2628
|
-
}
|
3279
|
+
let offsetY = 0;
|
2629
3280
|
|
2630
|
-
|
2631
|
-
|
2632
|
-
|
2633
|
-
const
|
2634
|
-
|
3281
|
+
if (Y > offsetHeight) offsetY = offsetHeight;
|
3282
|
+
else if (Y >= 0) offsetY = Y;
|
3283
|
+
|
3284
|
+
const hue = format === 'hsl'
|
3285
|
+
? controlPositions.c1x / offsetWidth
|
3286
|
+
: offsetY / offsetHeight;
|
3287
|
+
const saturation = format === 'hsl'
|
3288
|
+
? 1 - offsetY / offsetHeight
|
3289
|
+
: controlPositions.c1x / offsetWidth;
|
3290
|
+
const lightness = 1 - controlPositions.c1y / offsetHeight;
|
3291
|
+
const alpha = 1 - controlPositions.c3y / offsetHeight;
|
3292
|
+
const colorObject = format === 'hsl'
|
3293
|
+
? {
|
3294
|
+
h: hue, s: saturation, l: lightness, a: alpha,
|
3295
|
+
}
|
3296
|
+
: {
|
3297
|
+
h: hue, s: saturation, v: lightness, a: alpha,
|
3298
|
+
};
|
2635
3299
|
|
2636
3300
|
// new color
|
2637
|
-
|
3301
|
+
const {
|
3302
|
+
r, g, b, a,
|
3303
|
+
} = new Color(colorObject);
|
3304
|
+
|
3305
|
+
ObjectAssign(self.color, {
|
3306
|
+
r, g, b, a,
|
3307
|
+
});
|
3308
|
+
|
2638
3309
|
// new position
|
2639
3310
|
self.controlPositions.c2y = offsetY;
|
2640
3311
|
// update color picker
|
2641
|
-
self.
|
3312
|
+
self.updateAppearance();
|
2642
3313
|
self.updateInputs();
|
2643
3314
|
self.updateControls();
|
2644
3315
|
self.updateVisuals();
|
@@ -2646,92 +3317,105 @@
|
|
2646
3317
|
|
2647
3318
|
/**
|
2648
3319
|
* Updates `ColorPicker` last control,
|
2649
|
-
* the `alpha` channel
|
3320
|
+
* the `alpha` channel.
|
2650
3321
|
*
|
2651
|
-
* @param {
|
3322
|
+
* @param {number} Y
|
2652
3323
|
*/
|
2653
|
-
changeAlpha(
|
3324
|
+
changeAlpha(Y) {
|
2654
3325
|
const self = this;
|
2655
|
-
const {
|
2656
|
-
const {
|
3326
|
+
const { visuals } = self;
|
3327
|
+
const { offsetHeight } = visuals[0];
|
2657
3328
|
let offsetY = 0;
|
2658
3329
|
|
2659
|
-
if (Y >
|
2660
|
-
|
2661
|
-
} else if (Y >= 0) {
|
2662
|
-
offsetY = Y;
|
2663
|
-
}
|
3330
|
+
if (Y > offsetHeight) offsetY = offsetHeight;
|
3331
|
+
else if (Y >= 0) offsetY = Y;
|
2664
3332
|
|
2665
3333
|
// update color alpha
|
2666
|
-
const alpha =
|
2667
|
-
self.color.setAlpha(alpha
|
3334
|
+
const alpha = 1 - offsetY / offsetHeight;
|
3335
|
+
self.color.setAlpha(alpha);
|
2668
3336
|
// update position
|
2669
3337
|
self.controlPositions.c3y = offsetY;
|
2670
3338
|
// update color picker
|
3339
|
+
self.updateAppearance();
|
2671
3340
|
self.updateInputs();
|
2672
3341
|
self.updateControls();
|
2673
|
-
// alpha?
|
2674
3342
|
self.updateVisuals();
|
2675
3343
|
}
|
2676
3344
|
|
2677
|
-
/**
|
3345
|
+
/**
|
3346
|
+
* Updates `ColorPicker` control positions on:
|
3347
|
+
* * initialization
|
3348
|
+
* * window resize
|
3349
|
+
*/
|
3350
|
+
update() {
|
3351
|
+
const self = this;
|
3352
|
+
self.updateDropdownPosition();
|
3353
|
+
self.updateAppearance();
|
3354
|
+
self.setControlPositions();
|
3355
|
+
self.updateInputs(true);
|
3356
|
+
self.updateControls();
|
3357
|
+
self.updateVisuals();
|
3358
|
+
}
|
3359
|
+
|
3360
|
+
/** Updates the open dropdown position on *scroll* event. */
|
2678
3361
|
updateDropdownPosition() {
|
2679
3362
|
const self = this;
|
2680
3363
|
const { input, colorPicker, colorMenu } = self;
|
2681
3364
|
const elRect = getBoundingClientRect(input);
|
3365
|
+
const { top, bottom } = elRect;
|
2682
3366
|
const { offsetHeight: elHeight } = input;
|
2683
|
-
const windowHeight =
|
2684
|
-
const isPicker =
|
3367
|
+
const windowHeight = getDocumentElement(input).clientHeight;
|
3368
|
+
const isPicker = hasClass(colorPicker, 'show');
|
2685
3369
|
const dropdown = isPicker ? colorPicker : colorMenu;
|
3370
|
+
if (!dropdown) return;
|
2686
3371
|
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');
|
3372
|
+
const distanceBottom = windowHeight - bottom;
|
3373
|
+
const distanceTop = top;
|
3374
|
+
const bottomExceed = top + dropHeight + elHeight > windowHeight; // show
|
3375
|
+
const topExceed = top - dropHeight < 0; // show-top
|
3376
|
+
|
3377
|
+
if ((hasClass(dropdown, 'bottom') || !topExceed) && distanceBottom < distanceTop && bottomExceed) {
|
3378
|
+
removeClass(dropdown, 'bottom');
|
3379
|
+
addClass(dropdown, 'top');
|
3380
|
+
} else {
|
3381
|
+
removeClass(dropdown, 'top');
|
3382
|
+
addClass(dropdown, 'bottom');
|
2699
3383
|
}
|
2700
3384
|
}
|
2701
3385
|
|
2702
|
-
/**
|
3386
|
+
/** Updates control knobs' positions. */
|
2703
3387
|
setControlPositions() {
|
2704
3388
|
const self = this;
|
2705
3389
|
const {
|
2706
|
-
|
3390
|
+
format, visuals, color, hsl, hsv,
|
2707
3391
|
} = self;
|
3392
|
+
const { offsetHeight, offsetWidth } = visuals[0];
|
3393
|
+
const alpha = color.a;
|
2708
3394
|
const hue = hsl.h;
|
3395
|
+
|
2709
3396
|
const saturation = format !== 'hsl' ? hsv.s : hsl.s;
|
2710
3397
|
const lightness = format !== 'hsl' ? hsv.v : hsl.l;
|
2711
|
-
const alpha = hsv.a;
|
2712
3398
|
|
2713
|
-
self.controlPositions.c1x = format !== 'hsl' ? saturation *
|
2714
|
-
self.controlPositions.c1y = (1 - lightness) *
|
2715
|
-
self.controlPositions.c2y = format !== 'hsl' ?
|
2716
|
-
|
2717
|
-
if (format !== 'hex') {
|
2718
|
-
self.controlPositions.c3y = (1 - alpha) * height3;
|
2719
|
-
}
|
3399
|
+
self.controlPositions.c1x = format !== 'hsl' ? saturation * offsetWidth : hue * offsetWidth;
|
3400
|
+
self.controlPositions.c1y = (1 - lightness) * offsetHeight;
|
3401
|
+
self.controlPositions.c2y = format !== 'hsl' ? hue * offsetHeight : (1 - saturation) * offsetHeight;
|
3402
|
+
self.controlPositions.c3y = (1 - alpha) * offsetHeight;
|
2720
3403
|
}
|
2721
3404
|
|
2722
|
-
/** Update the visual appearance label. */
|
2723
|
-
|
3405
|
+
/** Update the visual appearance label and control knob labels. */
|
3406
|
+
updateAppearance() {
|
2724
3407
|
const self = this;
|
2725
3408
|
const {
|
2726
|
-
componentLabels, colorLabels,
|
3409
|
+
componentLabels, colorLabels, color, parent,
|
3410
|
+
hsl, hsv, hex, format, controlKnobs,
|
2727
3411
|
} = self;
|
2728
3412
|
const {
|
2729
|
-
|
3413
|
+
appearanceLabel, hexLabel, valueLabel,
|
2730
3414
|
} = componentLabels;
|
2731
|
-
|
2732
|
-
const [
|
2733
|
-
const hue = Math.round(hsl.h);
|
2734
|
-
const alpha =
|
3415
|
+
const { r, g, b } = color.toRgb();
|
3416
|
+
const [knob1, knob2, knob3] = controlKnobs;
|
3417
|
+
const hue = Math.round(hsl.h * 360);
|
3418
|
+
const alpha = color.a;
|
2735
3419
|
const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
|
2736
3420
|
const saturation = Math.round(saturationSource * 100);
|
2737
3421
|
const lightness = Math.round(hsl.l * 100);
|
@@ -2770,99 +3454,111 @@
|
|
2770
3454
|
colorName = colorLabels.pink;
|
2771
3455
|
}
|
2772
3456
|
|
3457
|
+
let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
|
3458
|
+
|
2773
3459
|
if (format === 'hsl') {
|
2774
|
-
|
2775
|
-
|
3460
|
+
colorLabel = `HSL: ${hue}°, ${saturation}%, ${lightness}%`;
|
3461
|
+
setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
|
3462
|
+
setAttribute(knob1, ariaValueText, `${hue}° & ${lightness}%`);
|
3463
|
+
setAttribute(knob1, ariaValueNow, `${hue}`);
|
3464
|
+
setAttribute(knob2, ariaValueText, `${saturation}%`);
|
3465
|
+
setAttribute(knob2, ariaValueNow, `${saturation}`);
|
3466
|
+
} else if (format === 'hwb') {
|
3467
|
+
const { hwb } = self;
|
3468
|
+
const whiteness = Math.round(hwb.w * 100);
|
3469
|
+
const blackness = Math.round(hwb.b * 100);
|
3470
|
+
colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
|
3471
|
+
setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
|
3472
|
+
setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
|
3473
|
+
setAttribute(knob1, ariaValueNow, `${whiteness}`);
|
3474
|
+
setAttribute(knob2, ariaValueText, `${hue}%`);
|
3475
|
+
setAttribute(knob2, ariaValueNow, `${hue}`);
|
2776
3476
|
} else {
|
2777
|
-
|
2778
|
-
|
3477
|
+
colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
|
3478
|
+
setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
|
3479
|
+
setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
|
3480
|
+
setAttribute(knob1, ariaValueNow, `${lightness}`);
|
3481
|
+
setAttribute(knob2, ariaValueText, `${hue}°`);
|
3482
|
+
setAttribute(knob2, ariaValueNow, `${hue}`);
|
2779
3483
|
}
|
2780
3484
|
|
2781
|
-
|
2782
|
-
|
2783
|
-
|
2784
|
-
}
|
3485
|
+
const alphaValue = Math.round(alpha * 100);
|
3486
|
+
setAttribute(knob3, ariaValueText, `${alphaValue}%`);
|
3487
|
+
setAttribute(knob3, ariaValueNow, `${alphaValue}`);
|
2785
3488
|
|
2786
|
-
// update
|
2787
|
-
|
2788
|
-
|
2789
|
-
? `${hexLabel} ${hex.split('').join(' ')}.`
|
2790
|
-
: self.value.toUpperCase();
|
3489
|
+
// update the input backgroundColor
|
3490
|
+
const newColor = color.toString();
|
3491
|
+
setElementStyle(self.input, { backgroundColor: newColor });
|
2791
3492
|
|
2792
|
-
|
2793
|
-
|
2794
|
-
|
2795
|
-
|
2796
|
-
|
2797
|
-
|
2798
|
-
|
3493
|
+
// toggle dark/light classes will also style the placeholder
|
3494
|
+
// dark sets color white, light sets color black
|
3495
|
+
// isDark ? '#000' : '#fff'
|
3496
|
+
if (!self.isDark) {
|
3497
|
+
if (hasClass(parent, 'txt-dark')) removeClass(parent, 'txt-dark');
|
3498
|
+
if (!hasClass(parent, 'txt-light')) addClass(parent, 'txt-light');
|
3499
|
+
} else {
|
3500
|
+
if (hasClass(parent, 'txt-light')) removeClass(parent, 'txt-light');
|
3501
|
+
if (!hasClass(parent, 'txt-dark')) addClass(parent, 'txt-dark');
|
2799
3502
|
}
|
2800
3503
|
}
|
2801
3504
|
|
2802
|
-
/** Updates the control knobs positions. */
|
3505
|
+
/** Updates the control knobs actual positions. */
|
2803
3506
|
updateControls() {
|
2804
|
-
const {
|
3507
|
+
const { controlKnobs, controlPositions } = this;
|
2805
3508
|
const [control1, control2, control3] = controlKnobs;
|
2806
|
-
control1
|
2807
|
-
control2
|
2808
|
-
|
2809
|
-
if (format !== 'hex') {
|
2810
|
-
control3.style.transform = `translate3d(0,${controlPositions.c3y - 3}px,0)`;
|
2811
|
-
}
|
3509
|
+
setElementStyle(control1, { transform: `translate3d(${controlPositions.c1x - 4}px,${controlPositions.c1y - 4}px,0)` });
|
3510
|
+
setElementStyle(control2, { transform: `translate3d(0,${controlPositions.c2y - 4}px,0)` });
|
3511
|
+
setElementStyle(control3, { transform: `translate3d(0,${controlPositions.c3y - 4}px,0)` });
|
2812
3512
|
}
|
2813
3513
|
|
2814
3514
|
/**
|
2815
|
-
*
|
3515
|
+
* Updates all color form inputs.
|
2816
3516
|
* @param {boolean=} isPrevented when `true`, the component original event is prevented
|
2817
3517
|
*/
|
2818
3518
|
updateInputs(isPrevented) {
|
2819
3519
|
const self = this;
|
2820
3520
|
const {
|
2821
|
-
value: oldColor,
|
3521
|
+
value: oldColor, format, inputs, color, hsl,
|
2822
3522
|
} = self;
|
2823
3523
|
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);
|
3524
|
+
const alpha = Math.round(color.a * 100);
|
3525
|
+
const hue = Math.round(hsl.h * 360);
|
2830
3526
|
let newColor;
|
2831
3527
|
|
2832
3528
|
if (format === 'hex') {
|
2833
|
-
newColor = self.color.toHexString();
|
3529
|
+
newColor = self.color.toHexString(true);
|
2834
3530
|
i1.value = self.hex;
|
2835
3531
|
} else if (format === 'hsl') {
|
3532
|
+
const lightness = Math.round(hsl.l * 100);
|
3533
|
+
const saturation = Math.round(hsl.s * 100);
|
2836
3534
|
newColor = self.color.toHslString();
|
2837
3535
|
i1.value = `${hue}`;
|
2838
3536
|
i2.value = `${saturation}`;
|
2839
3537
|
i3.value = `${lightness}`;
|
2840
3538
|
i4.value = `${alpha}`;
|
3539
|
+
} else if (format === 'hwb') {
|
3540
|
+
const { w, b } = self.hwb;
|
3541
|
+
const whiteness = Math.round(w * 100);
|
3542
|
+
const blackness = Math.round(b * 100);
|
3543
|
+
|
3544
|
+
newColor = self.color.toHwbString();
|
3545
|
+
i1.value = `${hue}`;
|
3546
|
+
i2.value = `${whiteness}`;
|
3547
|
+
i3.value = `${blackness}`;
|
3548
|
+
i4.value = `${alpha}`;
|
2841
3549
|
} else if (format === 'rgb') {
|
3550
|
+
const { r, g, b } = self.rgb;
|
3551
|
+
|
2842
3552
|
newColor = self.color.toRgbString();
|
2843
|
-
i1.value = `${
|
2844
|
-
i2.value = `${
|
2845
|
-
i3.value = `${
|
3553
|
+
i1.value = `${r}`;
|
3554
|
+
i2.value = `${g}`;
|
3555
|
+
i3.value = `${b}`;
|
2846
3556
|
i4.value = `${alpha}`;
|
2847
3557
|
}
|
2848
3558
|
|
2849
3559
|
// update the color value
|
2850
3560
|
self.value = `${newColor}`;
|
2851
3561
|
|
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
3562
|
// don't trigger the custom event unless it's really changed
|
2867
3563
|
if (!isPrevented && newColor !== oldColor) {
|
2868
3564
|
firePickerChange(self);
|
@@ -2870,14 +3566,15 @@
|
|
2870
3566
|
}
|
2871
3567
|
|
2872
3568
|
/**
|
2873
|
-
*
|
3569
|
+
* The `Space` & `Enter` keys specific event listener.
|
3570
|
+
* Toggle visibility of the `ColorPicker` / the presets menu, showing one will hide the other.
|
2874
3571
|
* @param {KeyboardEvent} e
|
2875
3572
|
* @this {ColorPicker}
|
2876
3573
|
*/
|
2877
|
-
|
3574
|
+
keyToggle(e) {
|
2878
3575
|
const self = this;
|
2879
3576
|
const { menuToggle } = self;
|
2880
|
-
const { activeElement } =
|
3577
|
+
const { activeElement } = getDocument(menuToggle);
|
2881
3578
|
const { code } = e;
|
2882
3579
|
|
2883
3580
|
if ([keyEnter, keySpace].includes(code)) {
|
@@ -2900,80 +3597,94 @@
|
|
2900
3597
|
togglePicker(e) {
|
2901
3598
|
e.preventDefault();
|
2902
3599
|
const self = this;
|
2903
|
-
const
|
3600
|
+
const { colorPicker } = self;
|
2904
3601
|
|
2905
|
-
if (self.isOpen &&
|
3602
|
+
if (self.isOpen && hasClass(colorPicker, 'show')) {
|
2906
3603
|
self.hide(true);
|
2907
3604
|
} else {
|
2908
|
-
self
|
3605
|
+
showDropdown(self, colorPicker);
|
2909
3606
|
}
|
2910
3607
|
}
|
2911
3608
|
|
2912
3609
|
/** Shows the `ColorPicker` dropdown. */
|
2913
3610
|
showPicker() {
|
2914
3611
|
const self = this;
|
2915
|
-
|
2916
|
-
|
2917
|
-
|
2918
|
-
|
2919
|
-
|
3612
|
+
const { colorPicker } = self;
|
3613
|
+
|
3614
|
+
if (!hasClass(colorPicker, 'show')) {
|
3615
|
+
showDropdown(self, colorPicker);
|
3616
|
+
}
|
2920
3617
|
}
|
2921
3618
|
|
2922
3619
|
/** Toggles the visibility of the `ColorPicker` presets menu. */
|
2923
3620
|
toggleMenu() {
|
2924
3621
|
const self = this;
|
2925
|
-
const
|
3622
|
+
const { colorMenu } = self;
|
2926
3623
|
|
2927
|
-
if (self.isOpen &&
|
3624
|
+
if (self.isOpen && hasClass(colorMenu, 'show')) {
|
2928
3625
|
self.hide(true);
|
2929
3626
|
} else {
|
2930
|
-
|
3627
|
+
showDropdown(self, colorMenu);
|
2931
3628
|
}
|
2932
3629
|
}
|
2933
3630
|
|
2934
|
-
/**
|
3631
|
+
/** Shows the `ColorPicker` dropdown or the presets menu. */
|
2935
3632
|
show() {
|
2936
3633
|
const self = this;
|
3634
|
+
const { menuToggle } = self;
|
2937
3635
|
if (!self.isOpen) {
|
2938
|
-
addClass(self.parent, 'open');
|
2939
3636
|
toggleEventsOnShown(self, true);
|
2940
3637
|
self.updateDropdownPosition();
|
2941
3638
|
self.isOpen = true;
|
3639
|
+
setAttribute(self.input, 'tabindex', '0');
|
3640
|
+
if (menuToggle) {
|
3641
|
+
setAttribute(menuToggle, 'tabindex', '0');
|
3642
|
+
}
|
2942
3643
|
}
|
2943
3644
|
}
|
2944
3645
|
|
2945
3646
|
/**
|
2946
|
-
* Hides the currently
|
3647
|
+
* Hides the currently open `ColorPicker` dropdown.
|
2947
3648
|
* @param {boolean=} focusPrevented
|
2948
3649
|
*/
|
2949
3650
|
hide(focusPrevented) {
|
2950
3651
|
const self = this;
|
2951
3652
|
if (self.isOpen) {
|
2952
|
-
const {
|
2953
|
-
|
2954
|
-
|
2955
|
-
|
2956
|
-
|
2957
|
-
|
2958
|
-
|
2959
|
-
|
2960
|
-
if (
|
2961
|
-
|
2962
|
-
setAttribute(
|
3653
|
+
const {
|
3654
|
+
pickerToggle, menuToggle, colorPicker, colorMenu, parent, input,
|
3655
|
+
} = self;
|
3656
|
+
const openPicker = hasClass(colorPicker, 'show');
|
3657
|
+
const openDropdown = openPicker ? colorPicker : colorMenu;
|
3658
|
+
const relatedBtn = openPicker ? pickerToggle : menuToggle;
|
3659
|
+
const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
|
3660
|
+
|
3661
|
+
if (openDropdown) {
|
3662
|
+
removeClass(openDropdown, 'show');
|
3663
|
+
setAttribute(relatedBtn, ariaExpanded, 'false');
|
3664
|
+
setTimeout(() => {
|
3665
|
+
removePosition(openDropdown);
|
3666
|
+
if (!querySelector('.show', parent)) {
|
3667
|
+
removeClass(parent, 'open');
|
3668
|
+
toggleEventsOnShown(self);
|
3669
|
+
self.isOpen = false;
|
3670
|
+
}
|
3671
|
+
}, animationDuration);
|
2963
3672
|
}
|
2964
3673
|
|
2965
3674
|
if (!self.isValid) {
|
2966
3675
|
self.value = self.color.toString();
|
2967
3676
|
}
|
2968
|
-
|
2969
|
-
self.isOpen = false;
|
2970
|
-
|
2971
3677
|
if (!focusPrevented) {
|
2972
|
-
|
3678
|
+
focus(pickerToggle);
|
3679
|
+
}
|
3680
|
+
setAttribute(input, 'tabindex', '-1');
|
3681
|
+
if (menuToggle) {
|
3682
|
+
setAttribute(menuToggle, 'tabindex', '-1');
|
2973
3683
|
}
|
2974
3684
|
}
|
2975
3685
|
}
|
2976
3686
|
|
3687
|
+
/** Removes `ColorPicker` from target `<input>`. */
|
2977
3688
|
dispose() {
|
2978
3689
|
const self = this;
|
2979
3690
|
const { input, parent } = self;
|
@@ -2982,25 +3693,20 @@
|
|
2982
3693
|
[...parent.children].forEach((el) => {
|
2983
3694
|
if (el !== input) el.remove();
|
2984
3695
|
});
|
3696
|
+
setElementStyle(input, { backgroundColor: '' });
|
3697
|
+
['txt-light', 'txt-dark'].forEach((c) => removeClass(parent, c));
|
2985
3698
|
Data.remove(input, colorPickerString);
|
2986
3699
|
}
|
2987
3700
|
}
|
2988
3701
|
|
2989
3702
|
ObjectAssign(ColorPicker, {
|
2990
3703
|
Color,
|
3704
|
+
Version,
|
2991
3705
|
getInstance: getColorPickerInstance,
|
2992
3706
|
init: initColorPicker,
|
2993
3707
|
selector: colorPickerSelector,
|
2994
3708
|
});
|
2995
3709
|
|
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
3710
|
return ColorPicker;
|
3005
3711
|
|
3006
3712
|
})));
|