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