@thednp/color-picker 0.0.1-alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/dist/css/color-picker.css +338 -0
- package/dist/js/color-picker-element-esm.js +2 -0
- package/dist/js/color-picker-element.js +3051 -0
- package/dist/js/color-picker-element.min.js +2 -0
- package/dist/js/color-picker.esm.js +2998 -0
- package/dist/js/color-picker.esm.min.js +2 -0
- package/dist/js/color-picker.js +3006 -0
- package/dist/js/color-picker.min.js +2 -0
- package/package.json +79 -0
- package/src/js/color-picker-element.js +61 -0
- package/src/js/color-picker.js +1333 -0
- package/src/js/color.js +860 -0
- package/src/js/index.js +12 -0
- package/src/js/util/colorNames.js +156 -0
- package/src/js/util/getColorControl.js +49 -0
- package/src/js/util/getColorForm.js +58 -0
- package/src/js/util/init.js +14 -0
- package/src/js/util/templates.js +9 -0
- package/src/js/util/vHidden.js +2 -0
- package/src/js/version.js +6 -0
- package/types/cp.d.ts +411 -0
- package/types/index.d.ts +41 -0
- package/types/source/source.ts +4 -0
- package/types/source/types.d.ts +67 -0
package/src/js/color.js
ADDED
@@ -0,0 +1,860 @@
|
|
1
|
+
import getDocumentHead from 'shorter-js/src/get/getDocumentHead';
|
2
|
+
import getElementStyle from 'shorter-js/src/get/getElementStyle';
|
3
|
+
import setElementStyle from 'shorter-js/src/misc/setElementStyle';
|
4
|
+
import ObjectAssign from 'shorter-js/src/misc/ObjectAssign';
|
5
|
+
|
6
|
+
import colorNames from './util/colorNames';
|
7
|
+
|
8
|
+
// <http://www.w3.org/TR/css3-values/#integers>
|
9
|
+
const CSS_INTEGER = '[-\\+]?\\d+%?';
|
10
|
+
|
11
|
+
// <http://www.w3.org/TR/css3-values/#number-value>
|
12
|
+
const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
|
13
|
+
|
14
|
+
// Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
|
15
|
+
const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
|
16
|
+
|
17
|
+
// Actual matching.
|
18
|
+
// Parentheses and commas are optional, but not required.
|
19
|
+
// Whitespace can take the place of commas or opening paren
|
20
|
+
const PERMISSIVE_MATCH3 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?`;
|
21
|
+
const PERMISSIVE_MATCH4 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?`;
|
22
|
+
|
23
|
+
const matchers = {
|
24
|
+
CSS_UNIT: new RegExp(CSS_UNIT),
|
25
|
+
rgb: new RegExp(`rgb${PERMISSIVE_MATCH3}`),
|
26
|
+
rgba: new RegExp(`rgba${PERMISSIVE_MATCH4}`),
|
27
|
+
hsl: new RegExp(`hsl${PERMISSIVE_MATCH3}`),
|
28
|
+
hsla: new RegExp(`hsla${PERMISSIVE_MATCH4}`),
|
29
|
+
hsv: new RegExp(`hsv${PERMISSIVE_MATCH3}`),
|
30
|
+
hsva: new RegExp(`hsva${PERMISSIVE_MATCH4}`),
|
31
|
+
hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
32
|
+
hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
|
33
|
+
hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
34
|
+
hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
|
35
|
+
};
|
36
|
+
|
37
|
+
/**
|
38
|
+
* Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
|
39
|
+
* <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
|
40
|
+
* @param {string} n
|
41
|
+
* @returns {boolean}
|
42
|
+
*/
|
43
|
+
function isOnePointZero(n) {
|
44
|
+
return typeof n === 'string' && n.includes('.') && parseFloat(n) === 1;
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Check to see if string passed in is a percentage
|
49
|
+
* @param {string} n
|
50
|
+
* @returns {boolean}
|
51
|
+
*/
|
52
|
+
function isPercentage(n) {
|
53
|
+
return typeof n === 'string' && n.includes('%');
|
54
|
+
}
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Check to see if it looks like a CSS unit
|
58
|
+
* (see `matchers` above for definition).
|
59
|
+
* @param {string | number} color
|
60
|
+
* @returns {boolean}
|
61
|
+
*/
|
62
|
+
function isValidCSSUnit(color) {
|
63
|
+
return Boolean(matchers.CSS_UNIT.exec(String(color)));
|
64
|
+
}
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Take input from [0, n] and return it as [0, 1]
|
68
|
+
* @param {*} n
|
69
|
+
* @param {number} max
|
70
|
+
* @returns {number}
|
71
|
+
*/
|
72
|
+
function bound01(n, max) {
|
73
|
+
let N = n;
|
74
|
+
if (isOnePointZero(n)) N = '100%';
|
75
|
+
|
76
|
+
N = max === 360 ? N : Math.min(max, Math.max(0, parseFloat(N)));
|
77
|
+
|
78
|
+
// Automatically convert percentage into number
|
79
|
+
if (isPercentage(N)) {
|
80
|
+
N = parseInt(String(N * max), 10) / 100;
|
81
|
+
}
|
82
|
+
// Handle floating point rounding errors
|
83
|
+
if (Math.abs(N - max) < 0.000001) {
|
84
|
+
return 1;
|
85
|
+
}
|
86
|
+
// Convert into [0, 1] range if it isn't already
|
87
|
+
if (max === 360) {
|
88
|
+
// If n is a hue given in degrees,
|
89
|
+
// wrap around out-of-range values into [0, 360] range
|
90
|
+
// then convert into [0, 1].
|
91
|
+
N = (N < 0 ? (N % max) + max : N % max) / parseFloat(String(max));
|
92
|
+
} else {
|
93
|
+
// If n not a hue given in degrees
|
94
|
+
// Convert into [0, 1] range if it isn't already.
|
95
|
+
N = (N % max) / parseFloat(String(max));
|
96
|
+
}
|
97
|
+
return N;
|
98
|
+
}
|
99
|
+
|
100
|
+
/**
|
101
|
+
* Return a valid alpha value [0,1] with all invalid values being set to 1.
|
102
|
+
* @param {string | number} a
|
103
|
+
* @returns {number}
|
104
|
+
*/
|
105
|
+
function boundAlpha(a) {
|
106
|
+
// @ts-ignore
|
107
|
+
let na = parseFloat(a);
|
108
|
+
|
109
|
+
if (Number.isNaN(na) || na < 0 || na > 1) {
|
110
|
+
na = 1;
|
111
|
+
}
|
112
|
+
|
113
|
+
return na;
|
114
|
+
}
|
115
|
+
|
116
|
+
/**
|
117
|
+
* Force a number between 0 and 1
|
118
|
+
* @param {number} val
|
119
|
+
* @returns {number}
|
120
|
+
*/
|
121
|
+
function clamp01(val) {
|
122
|
+
return Math.min(1, Math.max(0, val));
|
123
|
+
}
|
124
|
+
|
125
|
+
/**
|
126
|
+
* Returns the hexadecimal value of a web safe colour.
|
127
|
+
* @param {string} name
|
128
|
+
* @returns {string}
|
129
|
+
*/
|
130
|
+
function getHexFromColorName(name) {
|
131
|
+
const documentHead = getDocumentHead();
|
132
|
+
setElementStyle(documentHead, { color: name });
|
133
|
+
const colorName = getElementStyle(documentHead, 'color');
|
134
|
+
setElementStyle(documentHead, { color: '' });
|
135
|
+
return colorName;
|
136
|
+
}
|
137
|
+
|
138
|
+
/**
|
139
|
+
* Replace a decimal with it's percentage value
|
140
|
+
* @param {number | string} n
|
141
|
+
* @return {string | number}
|
142
|
+
*/
|
143
|
+
function convertToPercentage(n) {
|
144
|
+
if (n <= 1) {
|
145
|
+
return `${Number(n) * 100}%`;
|
146
|
+
}
|
147
|
+
return n;
|
148
|
+
}
|
149
|
+
|
150
|
+
/**
|
151
|
+
* Force a hex value to have 2 characters
|
152
|
+
* @param {string} c
|
153
|
+
* @returns {string}
|
154
|
+
*/
|
155
|
+
function pad2(c) {
|
156
|
+
return c.length === 1 ? `0${c}` : String(c);
|
157
|
+
}
|
158
|
+
|
159
|
+
// `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
|
160
|
+
// <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
|
161
|
+
/**
|
162
|
+
* Handle bounds / percentage checking to conform to CSS color spec
|
163
|
+
* * *Assumes:* r, g, b in [0, 255] or [0, 1]
|
164
|
+
* * *Returns:* { r, g, b } in [0, 255]
|
165
|
+
* @see http://www.w3.org/TR/css3-color/
|
166
|
+
* @param {number | string} r
|
167
|
+
* @param {number | string} g
|
168
|
+
* @param {number | string} b
|
169
|
+
* @returns {CP.RGB}
|
170
|
+
*/
|
171
|
+
function rgbToRgb(r, g, b) {
|
172
|
+
return {
|
173
|
+
r: bound01(r, 255) * 255,
|
174
|
+
g: bound01(g, 255) * 255,
|
175
|
+
b: bound01(b, 255) * 255,
|
176
|
+
};
|
177
|
+
}
|
178
|
+
|
179
|
+
/**
|
180
|
+
* Converts an RGB color value to HSL.
|
181
|
+
* *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
|
182
|
+
* *Returns:* { h, s, l } in [0,1]
|
183
|
+
* @param {number} R
|
184
|
+
* @param {number} G
|
185
|
+
* @param {number} B
|
186
|
+
* @returns {CP.HSL}
|
187
|
+
*/
|
188
|
+
function rgbToHsl(R, G, B) {
|
189
|
+
const r = bound01(R, 255);
|
190
|
+
const g = bound01(G, 255);
|
191
|
+
const b = bound01(B, 255);
|
192
|
+
const max = Math.max(r, g, b);
|
193
|
+
const min = Math.min(r, g, b);
|
194
|
+
let h = 0;
|
195
|
+
let s = 0;
|
196
|
+
const l = (max + min) / 2;
|
197
|
+
if (max === min) {
|
198
|
+
s = 0;
|
199
|
+
h = 0; // achromatic
|
200
|
+
} else {
|
201
|
+
const d = max - min;
|
202
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
203
|
+
switch (max) {
|
204
|
+
case r:
|
205
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
206
|
+
break;
|
207
|
+
case g:
|
208
|
+
h = (b - r) / d + 2;
|
209
|
+
break;
|
210
|
+
case b:
|
211
|
+
h = (r - g) / d + 4;
|
212
|
+
break;
|
213
|
+
default:
|
214
|
+
}
|
215
|
+
h /= 6;
|
216
|
+
}
|
217
|
+
return { h, s, l };
|
218
|
+
}
|
219
|
+
|
220
|
+
/**
|
221
|
+
* Returns a normalized RGB component value.
|
222
|
+
* @param {number} P
|
223
|
+
* @param {number} Q
|
224
|
+
* @param {number} T
|
225
|
+
* @returns {number}
|
226
|
+
*/
|
227
|
+
function hue2rgb(P, Q, T) {
|
228
|
+
const p = P;
|
229
|
+
const q = Q;
|
230
|
+
let t = T;
|
231
|
+
if (t < 0) {
|
232
|
+
t += 1;
|
233
|
+
}
|
234
|
+
if (t > 1) {
|
235
|
+
t -= 1;
|
236
|
+
}
|
237
|
+
if (t < 1 / 6) {
|
238
|
+
return p + (q - p) * (6 * t);
|
239
|
+
}
|
240
|
+
if (t < 1 / 2) {
|
241
|
+
return q;
|
242
|
+
}
|
243
|
+
if (t < 2 / 3) {
|
244
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
245
|
+
}
|
246
|
+
return p;
|
247
|
+
}
|
248
|
+
|
249
|
+
/**
|
250
|
+
* Converts an HSL colour value to RGB.
|
251
|
+
*
|
252
|
+
* * *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
|
253
|
+
* * *Returns:* { r, g, b } in the set [0, 255]
|
254
|
+
* @param {number | string} H
|
255
|
+
* @param {number | string} S
|
256
|
+
* @param {number | string} L
|
257
|
+
* @returns {CP.RGB}
|
258
|
+
*/
|
259
|
+
function hslToRgb(H, S, L) {
|
260
|
+
let r = 0;
|
261
|
+
let g = 0;
|
262
|
+
let b = 0;
|
263
|
+
const h = bound01(H, 360);
|
264
|
+
const s = bound01(S, 100);
|
265
|
+
const l = bound01(L, 100);
|
266
|
+
|
267
|
+
if (s === 0) {
|
268
|
+
// achromatic
|
269
|
+
g = l;
|
270
|
+
b = l;
|
271
|
+
r = l;
|
272
|
+
} else {
|
273
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
274
|
+
const p = 2 * l - q;
|
275
|
+
r = hue2rgb(p, q, h + 1 / 3);
|
276
|
+
g = hue2rgb(p, q, h);
|
277
|
+
b = hue2rgb(p, q, h - 1 / 3);
|
278
|
+
}
|
279
|
+
return { r: r * 255, g: g * 255, b: b * 255 };
|
280
|
+
}
|
281
|
+
|
282
|
+
/**
|
283
|
+
* Converts an RGB colour value to HSV.
|
284
|
+
*
|
285
|
+
* *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
|
286
|
+
* *Returns:* { h, s, v } in [0,1]
|
287
|
+
* @param {number | string} R
|
288
|
+
* @param {number | string} G
|
289
|
+
* @param {number | string} B
|
290
|
+
* @returns {CP.HSV}
|
291
|
+
*/
|
292
|
+
function rgbToHsv(R, G, B) {
|
293
|
+
const r = bound01(R, 255);
|
294
|
+
const g = bound01(G, 255);
|
295
|
+
const b = bound01(B, 255);
|
296
|
+
const max = Math.max(r, g, b);
|
297
|
+
const min = Math.min(r, g, b);
|
298
|
+
let h = 0;
|
299
|
+
const v = max;
|
300
|
+
const d = max - min;
|
301
|
+
const s = max === 0 ? 0 : d / max;
|
302
|
+
if (max === min) {
|
303
|
+
h = 0; // achromatic
|
304
|
+
} else {
|
305
|
+
switch (max) {
|
306
|
+
case r:
|
307
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
308
|
+
break;
|
309
|
+
case g:
|
310
|
+
h = (b - r) / d + 2;
|
311
|
+
break;
|
312
|
+
case b:
|
313
|
+
h = (r - g) / d + 4;
|
314
|
+
break;
|
315
|
+
default:
|
316
|
+
}
|
317
|
+
h /= 6;
|
318
|
+
}
|
319
|
+
return { h, s, v };
|
320
|
+
}
|
321
|
+
|
322
|
+
/**
|
323
|
+
* Converts an HSV color value to RGB.
|
324
|
+
*
|
325
|
+
* *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
|
326
|
+
* *Returns:* { r, g, b } in the set [0, 255]
|
327
|
+
* @param {number | string} H
|
328
|
+
* @param {number | string} S
|
329
|
+
* @param {number | string} V
|
330
|
+
* @returns {CP.RGB}
|
331
|
+
*/
|
332
|
+
function hsvToRgb(H, S, V) {
|
333
|
+
const h = bound01(H, 360) * 6;
|
334
|
+
const s = bound01(S, 100);
|
335
|
+
const v = bound01(V, 100);
|
336
|
+
const i = Math.floor(h);
|
337
|
+
const f = h - i;
|
338
|
+
const p = v * (1 - s);
|
339
|
+
const q = v * (1 - f * s);
|
340
|
+
const t = v * (1 - (1 - f) * s);
|
341
|
+
const mod = i % 6;
|
342
|
+
const r = [v, q, p, p, t, v][mod];
|
343
|
+
const g = [t, v, v, q, p, p][mod];
|
344
|
+
const b = [p, p, t, v, v, q][mod];
|
345
|
+
return { r: r * 255, g: g * 255, b: b * 255 };
|
346
|
+
}
|
347
|
+
|
348
|
+
/**
|
349
|
+
* Converts an RGB color to hex
|
350
|
+
*
|
351
|
+
* Assumes r, g, and b are contained in the set [0, 255]
|
352
|
+
* Returns a 3 or 6 character hex
|
353
|
+
* @param {number} r
|
354
|
+
* @param {number} g
|
355
|
+
* @param {number} b
|
356
|
+
* @returns {string}
|
357
|
+
*/
|
358
|
+
function rgbToHex(r, g, b) {
|
359
|
+
const hex = [
|
360
|
+
pad2(Math.round(r).toString(16)),
|
361
|
+
pad2(Math.round(g).toString(16)),
|
362
|
+
pad2(Math.round(b).toString(16)),
|
363
|
+
];
|
364
|
+
|
365
|
+
return hex.join('');
|
366
|
+
}
|
367
|
+
|
368
|
+
/**
|
369
|
+
* Converts a hex value to a decimal.
|
370
|
+
* @param {string} h
|
371
|
+
* @returns {number}
|
372
|
+
*/
|
373
|
+
function convertHexToDecimal(h) {
|
374
|
+
return parseIntFromHex(h) / 255;
|
375
|
+
}
|
376
|
+
|
377
|
+
/**
|
378
|
+
* Parse a base-16 hex value into a base-10 integer.
|
379
|
+
* @param {string} val
|
380
|
+
* @returns {number}
|
381
|
+
*/
|
382
|
+
function parseIntFromHex(val) {
|
383
|
+
return parseInt(val, 16);
|
384
|
+
}
|
385
|
+
|
386
|
+
/**
|
387
|
+
* Returns an `{r,g,b}` color object corresponding to a given number.
|
388
|
+
* @param {number} color
|
389
|
+
* @returns {CP.RGB}
|
390
|
+
*/
|
391
|
+
function numberInputToObject(color) {
|
392
|
+
/* eslint-disable no-bitwise */
|
393
|
+
return {
|
394
|
+
r: color >> 16,
|
395
|
+
g: (color & 0xff00) >> 8,
|
396
|
+
b: color & 0xff,
|
397
|
+
};
|
398
|
+
/* eslint-enable no-bitwise */
|
399
|
+
}
|
400
|
+
|
401
|
+
/**
|
402
|
+
* Permissive string parsing. Take in a number of formats, and output an object
|
403
|
+
* based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
|
404
|
+
* @param {string} input
|
405
|
+
* @returns {Record<string, (number | string)> | false}
|
406
|
+
*/
|
407
|
+
function stringInputToObject(input) {
|
408
|
+
let color = input.trim().toLowerCase();
|
409
|
+
if (color.length === 0) {
|
410
|
+
return {
|
411
|
+
r: 0, g: 0, b: 0, a: 0,
|
412
|
+
};
|
413
|
+
}
|
414
|
+
let named = false;
|
415
|
+
if (colorNames.includes(color)) {
|
416
|
+
color = getHexFromColorName(color);
|
417
|
+
named = true;
|
418
|
+
} else if (color === 'transparent') {
|
419
|
+
return {
|
420
|
+
r: 0, g: 0, b: 0, a: 0, format: 'name',
|
421
|
+
};
|
422
|
+
}
|
423
|
+
|
424
|
+
// Try to match string input using regular expressions.
|
425
|
+
// Keep most of the number bounding out of this function,
|
426
|
+
// don't worry about [0,1] or [0,100] or [0,360]
|
427
|
+
// Just return an object and let the conversion functions handle that.
|
428
|
+
// This way the result will be the same whether Color is initialized with string or object.
|
429
|
+
let match = matchers.rgb.exec(color);
|
430
|
+
if (match) {
|
431
|
+
return { r: match[1], g: match[2], b: match[3] };
|
432
|
+
}
|
433
|
+
match = matchers.rgba.exec(color);
|
434
|
+
if (match) {
|
435
|
+
return {
|
436
|
+
r: match[1], g: match[2], b: match[3], a: match[4],
|
437
|
+
};
|
438
|
+
}
|
439
|
+
match = matchers.hsl.exec(color);
|
440
|
+
if (match) {
|
441
|
+
return { h: match[1], s: match[2], l: match[3] };
|
442
|
+
}
|
443
|
+
match = matchers.hsla.exec(color);
|
444
|
+
if (match) {
|
445
|
+
return {
|
446
|
+
h: match[1], s: match[2], l: match[3], a: match[4],
|
447
|
+
};
|
448
|
+
}
|
449
|
+
match = matchers.hsv.exec(color);
|
450
|
+
if (match) {
|
451
|
+
return { h: match[1], s: match[2], v: match[3] };
|
452
|
+
}
|
453
|
+
match = matchers.hsva.exec(color);
|
454
|
+
if (match) {
|
455
|
+
return {
|
456
|
+
h: match[1], s: match[2], v: match[3], a: match[4],
|
457
|
+
};
|
458
|
+
}
|
459
|
+
match = matchers.hex8.exec(color);
|
460
|
+
if (match) {
|
461
|
+
return {
|
462
|
+
r: parseIntFromHex(match[1]),
|
463
|
+
g: parseIntFromHex(match[2]),
|
464
|
+
b: parseIntFromHex(match[3]),
|
465
|
+
a: convertHexToDecimal(match[4]),
|
466
|
+
format: named ? 'name' : 'hex8',
|
467
|
+
};
|
468
|
+
}
|
469
|
+
match = matchers.hex6.exec(color);
|
470
|
+
if (match) {
|
471
|
+
return {
|
472
|
+
r: parseIntFromHex(match[1]),
|
473
|
+
g: parseIntFromHex(match[2]),
|
474
|
+
b: parseIntFromHex(match[3]),
|
475
|
+
format: named ? 'name' : 'hex',
|
476
|
+
};
|
477
|
+
}
|
478
|
+
match = matchers.hex4.exec(color);
|
479
|
+
if (match) {
|
480
|
+
return {
|
481
|
+
r: parseIntFromHex(match[1] + match[1]),
|
482
|
+
g: parseIntFromHex(match[2] + match[2]),
|
483
|
+
b: parseIntFromHex(match[3] + match[3]),
|
484
|
+
a: convertHexToDecimal(match[4] + match[4]),
|
485
|
+
format: named ? 'name' : 'hex8',
|
486
|
+
};
|
487
|
+
}
|
488
|
+
match = matchers.hex3.exec(color);
|
489
|
+
if (match) {
|
490
|
+
return {
|
491
|
+
r: parseIntFromHex(match[1] + match[1]),
|
492
|
+
g: parseIntFromHex(match[2] + match[2]),
|
493
|
+
b: parseIntFromHex(match[3] + match[3]),
|
494
|
+
format: named ? 'name' : 'hex',
|
495
|
+
};
|
496
|
+
}
|
497
|
+
return false;
|
498
|
+
}
|
499
|
+
|
500
|
+
/**
|
501
|
+
* Given a string or object, convert that input to RGB
|
502
|
+
*
|
503
|
+
* Possible string inputs:
|
504
|
+
* ```
|
505
|
+
* "red"
|
506
|
+
* "#f00" or "f00"
|
507
|
+
* "#ff0000" or "ff0000"
|
508
|
+
* "#ff000000" or "ff000000"
|
509
|
+
* "rgb 255 0 0" or "rgb (255, 0, 0)"
|
510
|
+
* "rgb 1.0 0 0" or "rgb (1, 0, 0)"
|
511
|
+
* "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
|
512
|
+
* "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
|
513
|
+
* "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
|
514
|
+
* "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
|
515
|
+
* "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
|
516
|
+
* ```
|
517
|
+
* @param {string | Record<string, any>} input
|
518
|
+
* @returns {CP.ColorObject}
|
519
|
+
*/
|
520
|
+
function inputToRGB(input) {
|
521
|
+
/** @type {CP.RGB} */
|
522
|
+
let rgb = { r: 0, g: 0, b: 0 };
|
523
|
+
let color = input;
|
524
|
+
let a;
|
525
|
+
let s = null;
|
526
|
+
let v = null;
|
527
|
+
let l = null;
|
528
|
+
let ok = false;
|
529
|
+
let format = 'hex';
|
530
|
+
|
531
|
+
if (typeof input === 'string') {
|
532
|
+
// @ts-ignore -- this now is converted to object
|
533
|
+
color = stringInputToObject(input);
|
534
|
+
if (color) ok = true;
|
535
|
+
}
|
536
|
+
if (typeof color === 'object') {
|
537
|
+
if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
|
538
|
+
rgb = rgbToRgb(color.r, color.g, color.b);
|
539
|
+
ok = true;
|
540
|
+
format = `${color.r}`.slice(-1) === '%' ? 'prgb' : 'rgb';
|
541
|
+
} else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
|
542
|
+
s = convertToPercentage(color.s);
|
543
|
+
v = convertToPercentage(color.v);
|
544
|
+
rgb = hsvToRgb(color.h, s, v);
|
545
|
+
ok = true;
|
546
|
+
format = 'hsv';
|
547
|
+
} else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
|
548
|
+
s = convertToPercentage(color.s);
|
549
|
+
l = convertToPercentage(color.l);
|
550
|
+
rgb = hslToRgb(color.h, s, l);
|
551
|
+
ok = true;
|
552
|
+
format = 'hsl';
|
553
|
+
}
|
554
|
+
if ('a' in color) a = color.a;
|
555
|
+
}
|
556
|
+
|
557
|
+
return {
|
558
|
+
ok, // @ts-ignore
|
559
|
+
format: color.format || format,
|
560
|
+
r: Math.min(255, Math.max(rgb.r, 0)),
|
561
|
+
g: Math.min(255, Math.max(rgb.g, 0)),
|
562
|
+
b: Math.min(255, Math.max(rgb.b, 0)),
|
563
|
+
a: boundAlpha(a),
|
564
|
+
};
|
565
|
+
}
|
566
|
+
|
567
|
+
/** @type {CP.ColorOptions} */
|
568
|
+
const colorPickerDefaults = {
|
569
|
+
format: 'hex',
|
570
|
+
};
|
571
|
+
|
572
|
+
/**
|
573
|
+
* Returns a new `Color` instance.
|
574
|
+
* @see https://github.com/bgrins/TinyColor
|
575
|
+
* @class
|
576
|
+
*/
|
577
|
+
export default class Color {
|
578
|
+
/**
|
579
|
+
* @constructor
|
580
|
+
* @param {CP.ColorInput} input
|
581
|
+
* @param {CP.ColorOptions=} config
|
582
|
+
*/
|
583
|
+
constructor(input, config) {
|
584
|
+
let color = input;
|
585
|
+
const opts = typeof config === 'object'
|
586
|
+
? ObjectAssign(colorPickerDefaults, config)
|
587
|
+
: ObjectAssign({}, colorPickerDefaults);
|
588
|
+
|
589
|
+
// If input is already a `Color`, return itself
|
590
|
+
if (color instanceof Color) {
|
591
|
+
color = inputToRGB(color);
|
592
|
+
}
|
593
|
+
if (typeof color === 'number') {
|
594
|
+
color = numberInputToObject(color);
|
595
|
+
}
|
596
|
+
const {
|
597
|
+
r, g, b, a, ok, format,
|
598
|
+
} = inputToRGB(color);
|
599
|
+
|
600
|
+
/** @type {CP.ColorInput} */
|
601
|
+
this.originalInput = color;
|
602
|
+
/** @type {number} */
|
603
|
+
this.r = r;
|
604
|
+
/** @type {number} */
|
605
|
+
this.g = g;
|
606
|
+
/** @type {number} */
|
607
|
+
this.b = b;
|
608
|
+
/** @type {number} */
|
609
|
+
this.a = a;
|
610
|
+
/** @type {boolean} */
|
611
|
+
this.ok = ok;
|
612
|
+
/** @type {number} */
|
613
|
+
this.roundA = Math.round(100 * this.a) / 100;
|
614
|
+
/** @type {CP.ColorFormats} */
|
615
|
+
this.format = opts.format || format;
|
616
|
+
|
617
|
+
// Don't let the range of [0,255] come back in [0,1].
|
618
|
+
// Potentially lose a little bit of precision here, but will fix issues where
|
619
|
+
// .5 gets interpreted as half of the total, instead of half of 1
|
620
|
+
// If it was supposed to be 128, this was already taken care of by `inputToRgb`
|
621
|
+
if (this.r < 1) {
|
622
|
+
this.r = Math.round(this.r);
|
623
|
+
}
|
624
|
+
if (this.g < 1) {
|
625
|
+
this.g = Math.round(this.g);
|
626
|
+
}
|
627
|
+
if (this.b < 1) {
|
628
|
+
this.b = Math.round(this.b);
|
629
|
+
}
|
630
|
+
}
|
631
|
+
|
632
|
+
/**
|
633
|
+
* Checks if the current input value is a valid colour.
|
634
|
+
* @returns {boolean} the query result
|
635
|
+
*/
|
636
|
+
get isValid() {
|
637
|
+
return this.ok;
|
638
|
+
}
|
639
|
+
|
640
|
+
/**
|
641
|
+
* Checks if the current colour requires a light text colour.
|
642
|
+
* @returns {boolean} the query result
|
643
|
+
*/
|
644
|
+
get isDark() {
|
645
|
+
return this.brightness < 128;
|
646
|
+
}
|
647
|
+
|
648
|
+
/**
|
649
|
+
* Returns the perceived luminance of a color.
|
650
|
+
* @see http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
|
651
|
+
* @returns {number} a number in [0-1] range
|
652
|
+
*/
|
653
|
+
get luminance() {
|
654
|
+
const { r, g, b } = this;
|
655
|
+
let R = 0;
|
656
|
+
let G = 0;
|
657
|
+
let B = 0;
|
658
|
+
const RsRGB = r / 255;
|
659
|
+
const GsRGB = g / 255;
|
660
|
+
const BsRGB = b / 255;
|
661
|
+
|
662
|
+
if (RsRGB <= 0.03928) {
|
663
|
+
R = RsRGB / 12.92;
|
664
|
+
} else {
|
665
|
+
R = ((RsRGB + 0.055) / 1.055) ** 2.4;
|
666
|
+
}
|
667
|
+
if (GsRGB <= 0.03928) {
|
668
|
+
G = GsRGB / 12.92;
|
669
|
+
} else {
|
670
|
+
G = ((GsRGB + 0.055) / 1.055) ** 2.4;
|
671
|
+
}
|
672
|
+
if (BsRGB <= 0.03928) {
|
673
|
+
B = BsRGB / 12.92;
|
674
|
+
} else {
|
675
|
+
B = ((BsRGB + 0.055) / 1.055) ** 2.4;
|
676
|
+
}
|
677
|
+
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
|
678
|
+
}
|
679
|
+
|
680
|
+
/**
|
681
|
+
* Returns the perceived brightness of the color.
|
682
|
+
* @returns {number} a number in [0-255] range
|
683
|
+
*/
|
684
|
+
get brightness() {
|
685
|
+
const { r, g, b } = this;
|
686
|
+
return (r * 299 + g * 587 + b * 114) / 1000;
|
687
|
+
}
|
688
|
+
|
689
|
+
/**
|
690
|
+
* Returns the color as a RGBA object.
|
691
|
+
* @returns {CP.RGBA}
|
692
|
+
*/
|
693
|
+
toRgb() {
|
694
|
+
return {
|
695
|
+
r: Math.round(this.r),
|
696
|
+
g: Math.round(this.g),
|
697
|
+
b: Math.round(this.b),
|
698
|
+
a: this.a,
|
699
|
+
};
|
700
|
+
}
|
701
|
+
|
702
|
+
/**
|
703
|
+
* Returns the RGBA values concatenated into a string.
|
704
|
+
* @returns {string} the CSS valid color in RGB/RGBA format
|
705
|
+
*/
|
706
|
+
toRgbString() {
|
707
|
+
const r = Math.round(this.r);
|
708
|
+
const g = Math.round(this.g);
|
709
|
+
const b = Math.round(this.b);
|
710
|
+
return this.a === 1
|
711
|
+
? `rgb(${r},${g},${b})`
|
712
|
+
: `rgba(${r},${g},${b},${this.roundA})`;
|
713
|
+
}
|
714
|
+
|
715
|
+
/**
|
716
|
+
* Returns the HEX value of the color.
|
717
|
+
* @returns {string} the hexadecimal color format
|
718
|
+
*/
|
719
|
+
toHex() {
|
720
|
+
return rgbToHex(this.r, this.g, this.b);
|
721
|
+
}
|
722
|
+
|
723
|
+
/**
|
724
|
+
* Returns the HEX value of the color.
|
725
|
+
* @returns {string} the CSS valid color in hexadecimal format
|
726
|
+
*/
|
727
|
+
toHexString() {
|
728
|
+
return `#${this.toHex()}`;
|
729
|
+
}
|
730
|
+
|
731
|
+
/**
|
732
|
+
* Returns the color as a HSVA object.
|
733
|
+
* @returns {CP.HSVA} the `{h,s,v,a}` object
|
734
|
+
*/
|
735
|
+
toHsv() {
|
736
|
+
const { h, s, v } = rgbToHsv(this.r, this.g, this.b);
|
737
|
+
return {
|
738
|
+
h: h * 360, s, v, a: this.a,
|
739
|
+
};
|
740
|
+
}
|
741
|
+
|
742
|
+
/**
|
743
|
+
* Returns the color as a HSLA object.
|
744
|
+
* @returns {CP.HSLA}
|
745
|
+
*/
|
746
|
+
toHsl() {
|
747
|
+
const { h, s, l } = rgbToHsl(this.r, this.g, this.b);
|
748
|
+
return {
|
749
|
+
h: h * 360, s, l, a: this.a,
|
750
|
+
};
|
751
|
+
}
|
752
|
+
|
753
|
+
/**
|
754
|
+
* Returns the HSLA values concatenated into a string.
|
755
|
+
* @returns {string} the CSS valid color in HSL/HSLA format
|
756
|
+
*/
|
757
|
+
toHslString() {
|
758
|
+
const hsl = this.toHsl();
|
759
|
+
const h = Math.round(hsl.h);
|
760
|
+
const s = Math.round(hsl.s * 100);
|
761
|
+
const l = Math.round(hsl.l * 100);
|
762
|
+
return this.a === 1
|
763
|
+
? `hsl(${h},${s}%,${l}%)`
|
764
|
+
: `hsla(${h},${s}%,${l}%,${this.roundA})`;
|
765
|
+
}
|
766
|
+
|
767
|
+
/**
|
768
|
+
* Sets the alpha value on the current color.
|
769
|
+
* @param {number} alpha a new alpha value in [0-1] range.
|
770
|
+
* @returns {Color} a new `Color` instance
|
771
|
+
*/
|
772
|
+
setAlpha(alpha) {
|
773
|
+
this.a = boundAlpha(alpha);
|
774
|
+
this.roundA = Math.round(100 * this.a) / 100;
|
775
|
+
return this;
|
776
|
+
}
|
777
|
+
|
778
|
+
/**
|
779
|
+
* Saturate the color with a given amount.
|
780
|
+
* @param {number=} amount a value in [0-100] range
|
781
|
+
* @returns {Color} a new `Color` instance
|
782
|
+
*/
|
783
|
+
saturate(amount) {
|
784
|
+
if (!amount) return this;
|
785
|
+
const hsl = this.toHsl();
|
786
|
+
hsl.s += amount / 100;
|
787
|
+
hsl.s = clamp01(hsl.s);
|
788
|
+
return new Color(hsl);
|
789
|
+
}
|
790
|
+
|
791
|
+
/**
|
792
|
+
* Desaturate the color with a given amount.
|
793
|
+
* @param {number=} amount a value in [0-100] range
|
794
|
+
* @returns {Color} a new `Color` instance
|
795
|
+
*/
|
796
|
+
desaturate(amount) {
|
797
|
+
return amount ? this.saturate(-amount) : this;
|
798
|
+
}
|
799
|
+
|
800
|
+
/**
|
801
|
+
* Completely desaturates a color into greyscale.
|
802
|
+
* Same as calling `desaturate(100)`
|
803
|
+
* @returns {Color} a new `Color` instance
|
804
|
+
*/
|
805
|
+
greyscale() {
|
806
|
+
return this.desaturate(100);
|
807
|
+
}
|
808
|
+
|
809
|
+
/** Returns a clone of the current `Color` instance. */
|
810
|
+
clone() {
|
811
|
+
return new Color(this);
|
812
|
+
}
|
813
|
+
|
814
|
+
/**
|
815
|
+
* Returns the color value in CSS valid string format.
|
816
|
+
* @returns {string}
|
817
|
+
*/
|
818
|
+
toString() {
|
819
|
+
const { format } = this;
|
820
|
+
|
821
|
+
if (format === 'rgb') {
|
822
|
+
return this.toRgbString();
|
823
|
+
}
|
824
|
+
if (format === 'hsl') {
|
825
|
+
return this.toHslString();
|
826
|
+
}
|
827
|
+
return this.toHexString();
|
828
|
+
}
|
829
|
+
}
|
830
|
+
|
831
|
+
ObjectAssign(Color, {
|
832
|
+
colorNames,
|
833
|
+
CSS_INTEGER,
|
834
|
+
CSS_NUMBER,
|
835
|
+
CSS_UNIT,
|
836
|
+
PERMISSIVE_MATCH3,
|
837
|
+
PERMISSIVE_MATCH4,
|
838
|
+
matchers,
|
839
|
+
isOnePointZero,
|
840
|
+
isPercentage,
|
841
|
+
isValidCSSUnit,
|
842
|
+
bound01,
|
843
|
+
boundAlpha,
|
844
|
+
clamp01,
|
845
|
+
getHexFromColorName,
|
846
|
+
convertToPercentage,
|
847
|
+
convertHexToDecimal,
|
848
|
+
pad2,
|
849
|
+
rgbToRgb,
|
850
|
+
rgbToHsl,
|
851
|
+
rgbToHex,
|
852
|
+
rgbToHsv,
|
853
|
+
hslToRgb,
|
854
|
+
hsvToRgb,
|
855
|
+
hue2rgb,
|
856
|
+
parseIntFromHex,
|
857
|
+
numberInputToObject,
|
858
|
+
stringInputToObject,
|
859
|
+
inputToRGB,
|
860
|
+
});
|