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