@thednp/color-picker 0.0.1-alpha2 → 0.0.2-alpha1
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +32 -15
- package/dist/css/color-picker.css +38 -15
- package/dist/css/color-picker.min.css +2 -2
- package/dist/css/color-picker.rtl.css +38 -15
- package/dist/css/color-picker.rtl.min.css +2 -2
- package/dist/js/color-esm.js +1178 -0
- package/dist/js/color-esm.min.js +2 -0
- package/dist/js/color-palette-esm.js +1252 -0
- package/dist/js/color-palette-esm.min.js +2 -0
- package/dist/js/color-palette.js +1260 -0
- package/dist/js/color-palette.min.js +2 -0
- package/dist/js/color-picker-element-esm.js +433 -424
- package/dist/js/color-picker-element-esm.min.js +2 -2
- package/dist/js/color-picker-element.js +435 -426
- package/dist/js/color-picker-element.min.js +2 -2
- package/dist/js/color-picker-esm.js +745 -739
- package/dist/js/color-picker-esm.min.js +2 -2
- package/dist/js/color-picker.js +747 -741
- package/dist/js/color-picker.min.js +2 -2
- package/dist/js/color.js +1186 -0
- package/dist/js/color.min.js +2 -0
- package/package.json +19 -3
- package/src/js/color-palette.js +28 -12
- package/src/js/color-picker-element.js +8 -4
- package/src/js/color-picker.js +84 -172
- package/src/js/color.js +125 -131
- package/src/js/util/getColorControls.js +3 -3
- package/src/js/util/getColorForm.js +0 -1
- package/src/js/util/getColorMenu.js +31 -33
- package/src/js/util/roundPart.js +9 -0
- package/src/js/util/setCSSProperties.js +12 -0
- package/src/js/util/setMarkup.js +122 -0
- package/src/js/util/tabindex.js +3 -0
- package/src/js/util/version.js +6 -0
- package/src/scss/color-picker.scss +35 -16
- package/types/cp.d.ts +48 -20
- package/src/js/util/templates.js +0 -10
package/src/js/color.js
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
import
|
1
|
+
import documentHead from 'shorter-js/src/blocks/documentHead';
|
2
2
|
import getElementStyle from 'shorter-js/src/get/getElementStyle';
|
3
3
|
import setElementStyle from 'shorter-js/src/misc/setElementStyle';
|
4
4
|
import ObjectAssign from 'shorter-js/src/misc/ObjectAssign';
|
5
|
+
import toLowerCase from 'shorter-js/src/misc/toLowerCase';
|
5
6
|
|
6
7
|
import nonColors from './util/nonColors';
|
8
|
+
import roundPart from './util/roundPart';
|
7
9
|
|
8
10
|
// Color supported formats
|
9
|
-
const COLOR_FORMAT = ['rgb', 'hex', 'hsl', '
|
11
|
+
const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsv', 'hwb'];
|
10
12
|
|
11
13
|
// Hue angles
|
12
14
|
const ANGLES = 'deg|rad|grad|turn';
|
@@ -28,10 +30,17 @@ const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
|
|
28
30
|
// Add angles to the mix
|
29
31
|
const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
|
30
32
|
|
33
|
+
// Start & end
|
34
|
+
const START_MATCH = '(?:[\\s|\\(\\s|\\s\\(\\s]+)?';
|
35
|
+
const END_MATCH = '(?:[\\s|\\)\\s]+)?';
|
36
|
+
// Components separation
|
37
|
+
const SEP = '(?:[,|\\s]+)';
|
38
|
+
const SEP2 = '(?:[,|\\/\\s]*)?';
|
39
|
+
|
31
40
|
// Actual matching.
|
32
41
|
// Parentheses and commas are optional, but not required.
|
33
42
|
// Whitespace can take the place of commas or opening paren
|
34
|
-
const PERMISSIVE_MATCH =
|
43
|
+
const PERMISSIVE_MATCH = `${START_MATCH}(${CSS_UNIT2})${SEP}(${CSS_UNIT})${SEP}(${CSS_UNIT})${SEP2}(${CSS_UNIT})?${END_MATCH}`;
|
35
44
|
|
36
45
|
const matchers = {
|
37
46
|
CSS_UNIT: new RegExp(CSS_UNIT2),
|
@@ -64,23 +73,22 @@ function isPercentage(n) {
|
|
64
73
|
return `${n}`.includes('%');
|
65
74
|
}
|
66
75
|
|
67
|
-
/**
|
68
|
-
* Check to see if string passed in is an angle
|
69
|
-
* @param {string} n testing string
|
70
|
-
* @returns {boolean} the query result
|
71
|
-
*/
|
72
|
-
function isAngle(n) {
|
73
|
-
return ANGLES.split('|').some((a) => `${n}`.includes(a));
|
74
|
-
}
|
75
|
-
|
76
76
|
/**
|
77
77
|
* Check to see if string passed is a web safe colour.
|
78
|
+
* @see https://stackoverflow.com/a/16994164
|
78
79
|
* @param {string} color a colour name, EG: *red*
|
79
80
|
* @returns {boolean} the query result
|
80
81
|
*/
|
81
82
|
function isColorName(color) {
|
82
|
-
|
83
|
-
|
83
|
+
if (nonColors.includes(color)
|
84
|
+
|| ['#', ...COLOR_FORMAT].some((f) => color.includes(f))) return false;
|
85
|
+
|
86
|
+
return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
|
87
|
+
setElementStyle(documentHead, { color });
|
88
|
+
const computedColor = getElementStyle(documentHead, 'color');
|
89
|
+
setElementStyle(documentHead, { color: '' });
|
90
|
+
return computedColor !== c;
|
91
|
+
});
|
84
92
|
}
|
85
93
|
|
86
94
|
/**
|
@@ -101,15 +109,15 @@ function isValidCSSUnit(color) {
|
|
101
109
|
*/
|
102
110
|
function bound01(N, max) {
|
103
111
|
let n = N;
|
104
|
-
if (isOnePointZero(
|
105
|
-
|
106
|
-
n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
|
112
|
+
if (isOnePointZero(N)) n = '100%';
|
107
113
|
|
108
|
-
|
109
|
-
|
114
|
+
const processPercent = isPercentage(n);
|
115
|
+
n = max === 360
|
116
|
+
? parseFloat(n)
|
117
|
+
: Math.min(max, Math.max(0, parseFloat(n)));
|
110
118
|
|
111
119
|
// Automatically convert percentage into number
|
112
|
-
if (
|
120
|
+
if (processPercent) n = (n * max) / 100;
|
113
121
|
|
114
122
|
// Handle floating point rounding errors
|
115
123
|
if (Math.abs(n - max) < 0.000001) {
|
@@ -120,11 +128,11 @@ function bound01(N, max) {
|
|
120
128
|
// If n is a hue given in degrees,
|
121
129
|
// wrap around out-of-range values into [0, 360] range
|
122
130
|
// then convert into [0, 1].
|
123
|
-
n = (n < 0 ? (n % max) + max : n % max) /
|
131
|
+
n = (n < 0 ? (n % max) + max : n % max) / max;
|
124
132
|
} else {
|
125
133
|
// If n not a hue given in degrees
|
126
134
|
// Convert into [0, 1] range if it isn't already.
|
127
|
-
n = (n % max) /
|
135
|
+
n = (n % max) / max;
|
128
136
|
}
|
129
137
|
return n;
|
130
138
|
}
|
@@ -159,7 +167,6 @@ function clamp01(v) {
|
|
159
167
|
* @returns {string}
|
160
168
|
*/
|
161
169
|
function getRGBFromName(name) {
|
162
|
-
const documentHead = getDocumentHead();
|
163
170
|
setElementStyle(documentHead, { color: name });
|
164
171
|
const colorName = getElementStyle(documentHead, 'color');
|
165
172
|
setElementStyle(documentHead, { color: '' });
|
@@ -172,7 +179,7 @@ function getRGBFromName(name) {
|
|
172
179
|
* @returns {string} - the hexadecimal value
|
173
180
|
*/
|
174
181
|
function convertDecimalToHex(d) {
|
175
|
-
return
|
182
|
+
return roundPart(d * 255).toString(16);
|
176
183
|
}
|
177
184
|
|
178
185
|
/**
|
@@ -259,6 +266,36 @@ function hueToRgb(p, q, t) {
|
|
259
266
|
return p;
|
260
267
|
}
|
261
268
|
|
269
|
+
/**
|
270
|
+
* Converts an HSL colour value to RGB.
|
271
|
+
*
|
272
|
+
* @param {number} h Hue Angle [0, 1]
|
273
|
+
* @param {number} s Saturation [0, 1]
|
274
|
+
* @param {number} l Lightness Angle [0, 1]
|
275
|
+
* @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
276
|
+
*/
|
277
|
+
function hslToRgb(h, s, l) {
|
278
|
+
let r = 0;
|
279
|
+
let g = 0;
|
280
|
+
let b = 0;
|
281
|
+
|
282
|
+
if (s === 0) {
|
283
|
+
// achromatic
|
284
|
+
g = l;
|
285
|
+
b = l;
|
286
|
+
r = l;
|
287
|
+
} else {
|
288
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
289
|
+
const p = 2 * l - q;
|
290
|
+
r = hueToRgb(p, q, h + 1 / 3);
|
291
|
+
g = hueToRgb(p, q, h);
|
292
|
+
b = hueToRgb(p, q, h - 1 / 3);
|
293
|
+
}
|
294
|
+
[r, g, b] = [r, g, b].map((x) => x * 255);
|
295
|
+
|
296
|
+
return { r, g, b };
|
297
|
+
}
|
298
|
+
|
262
299
|
/**
|
263
300
|
* Returns an HWB colour object from an RGB colour object.
|
264
301
|
* @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
|
@@ -321,36 +358,6 @@ function hwbToRgb(H, W, B) {
|
|
321
358
|
return { r, g, b };
|
322
359
|
}
|
323
360
|
|
324
|
-
/**
|
325
|
-
* Converts an HSL colour value to RGB.
|
326
|
-
*
|
327
|
-
* @param {number} h Hue Angle [0, 1]
|
328
|
-
* @param {number} s Saturation [0, 1]
|
329
|
-
* @param {number} l Lightness Angle [0, 1]
|
330
|
-
* @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
331
|
-
*/
|
332
|
-
function hslToRgb(h, s, l) {
|
333
|
-
let r = 0;
|
334
|
-
let g = 0;
|
335
|
-
let b = 0;
|
336
|
-
|
337
|
-
if (s === 0) {
|
338
|
-
// achromatic
|
339
|
-
g = l;
|
340
|
-
b = l;
|
341
|
-
r = l;
|
342
|
-
} else {
|
343
|
-
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
344
|
-
const p = 2 * l - q;
|
345
|
-
r = hueToRgb(p, q, h + 1 / 3);
|
346
|
-
g = hueToRgb(p, q, h);
|
347
|
-
b = hueToRgb(p, q, h - 1 / 3);
|
348
|
-
}
|
349
|
-
[r, g, b] = [r, g, b].map((x) => x * 255);
|
350
|
-
|
351
|
-
return { r, g, b };
|
352
|
-
}
|
353
|
-
|
354
361
|
/**
|
355
362
|
* Converts an RGB colour value to HSV.
|
356
363
|
*
|
@@ -407,10 +414,11 @@ function hsvToRgb(H, S, V) {
|
|
407
414
|
const q = v * (1 - f * s);
|
408
415
|
const t = v * (1 - (1 - f) * s);
|
409
416
|
const mod = i % 6;
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
417
|
+
let r = [v, q, p, p, t, v][mod];
|
418
|
+
let g = [t, v, v, q, p, p][mod];
|
419
|
+
let b = [p, p, t, v, v, q][mod];
|
420
|
+
[r, g, b] = [r, g, b].map((n) => n * 255);
|
421
|
+
return { r, g, b };
|
414
422
|
}
|
415
423
|
|
416
424
|
/**
|
@@ -426,15 +434,15 @@ function hsvToRgb(H, S, V) {
|
|
426
434
|
*/
|
427
435
|
function rgbToHex(r, g, b, allow3Char) {
|
428
436
|
const hex = [
|
429
|
-
pad2(
|
430
|
-
pad2(
|
431
|
-
pad2(
|
437
|
+
pad2(roundPart(r).toString(16)),
|
438
|
+
pad2(roundPart(g).toString(16)),
|
439
|
+
pad2(roundPart(b).toString(16)),
|
432
440
|
];
|
433
441
|
|
434
442
|
// Return a 3 character hex if possible
|
435
443
|
if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
|
436
444
|
&& hex[1].charAt(0) === hex[1].charAt(1)
|
437
|
-
|
445
|
+
&& hex[2].charAt(0) === hex[2].charAt(1)) {
|
438
446
|
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
|
439
447
|
}
|
440
448
|
|
@@ -453,48 +461,33 @@ function rgbToHex(r, g, b, allow3Char) {
|
|
453
461
|
*/
|
454
462
|
function rgbaToHex(r, g, b, a, allow4Char) {
|
455
463
|
const hex = [
|
456
|
-
pad2(
|
457
|
-
pad2(
|
458
|
-
pad2(
|
464
|
+
pad2(roundPart(r).toString(16)),
|
465
|
+
pad2(roundPart(g).toString(16)),
|
466
|
+
pad2(roundPart(b).toString(16)),
|
459
467
|
pad2(convertDecimalToHex(a)),
|
460
468
|
];
|
461
469
|
|
462
470
|
// Return a 4 character hex if possible
|
463
471
|
if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
|
464
472
|
&& hex[1].charAt(0) === hex[1].charAt(1)
|
465
|
-
|
466
|
-
|
473
|
+
&& hex[2].charAt(0) === hex[2].charAt(1)
|
474
|
+
&& hex[3].charAt(0) === hex[3].charAt(1)) {
|
467
475
|
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
|
468
476
|
}
|
469
477
|
return hex.join('');
|
470
478
|
}
|
471
479
|
|
472
|
-
/**
|
473
|
-
* Returns a colour object corresponding to a given number.
|
474
|
-
* @param {number} color input number
|
475
|
-
* @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
|
476
|
-
*/
|
477
|
-
function numberInputToObject(color) {
|
478
|
-
/* eslint-disable no-bitwise */
|
479
|
-
return {
|
480
|
-
r: color >> 16,
|
481
|
-
g: (color & 0xff00) >> 8,
|
482
|
-
b: color & 0xff,
|
483
|
-
};
|
484
|
-
/* eslint-enable no-bitwise */
|
485
|
-
}
|
486
|
-
|
487
480
|
/**
|
488
481
|
* Permissive string parsing. Take in a number of formats, and output an object
|
489
482
|
* based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
|
490
483
|
* @param {string} input colour value in any format
|
491
|
-
* @returns {Record<string, (number | string)> | false} an object matching the RegExp
|
484
|
+
* @returns {Record<string, (number | string | boolean)> | false} an object matching the RegExp
|
492
485
|
*/
|
493
486
|
function stringInputToObject(input) {
|
494
|
-
let color = input.trim()
|
487
|
+
let color = toLowerCase(input.trim());
|
495
488
|
if (color.length === 0) {
|
496
489
|
return {
|
497
|
-
r: 0, g: 0, b: 0, a:
|
490
|
+
r: 0, g: 0, b: 0, a: 1,
|
498
491
|
};
|
499
492
|
}
|
500
493
|
let named = false;
|
@@ -502,11 +495,9 @@ function stringInputToObject(input) {
|
|
502
495
|
color = getRGBFromName(color);
|
503
496
|
named = true;
|
504
497
|
} else if (nonColors.includes(color)) {
|
505
|
-
const
|
506
|
-
const rgb = isTransparent ? 0 : 255;
|
507
|
-
const a = isTransparent ? 0 : 1;
|
498
|
+
const a = color === 'transparent' ? 0 : 1;
|
508
499
|
return {
|
509
|
-
r:
|
500
|
+
r: 0, g: 0, b: 0, a, format: 'rgb', ok: true,
|
510
501
|
};
|
511
502
|
}
|
512
503
|
|
@@ -546,7 +537,6 @@ function stringInputToObject(input) {
|
|
546
537
|
g: parseIntFromHex(m2),
|
547
538
|
b: parseIntFromHex(m3),
|
548
539
|
a: convertHexToDecimal(m4),
|
549
|
-
// format: named ? 'rgb' : 'hex8',
|
550
540
|
format: named ? 'rgb' : 'hex',
|
551
541
|
};
|
552
542
|
}
|
@@ -610,6 +600,7 @@ function stringInputToObject(input) {
|
|
610
600
|
function inputToRGB(input) {
|
611
601
|
let rgb = { r: 0, g: 0, b: 0 };
|
612
602
|
let color = input;
|
603
|
+
/** @type {string | number} */
|
613
604
|
let a = 1;
|
614
605
|
let s = null;
|
615
606
|
let v = null;
|
@@ -617,8 +608,11 @@ function inputToRGB(input) {
|
|
617
608
|
let w = null;
|
618
609
|
let b = null;
|
619
610
|
let h = null;
|
611
|
+
let r = null;
|
612
|
+
let g = null;
|
620
613
|
let ok = false;
|
621
|
-
|
614
|
+
const inputFormat = typeof color === 'object' && color.format;
|
615
|
+
let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
|
622
616
|
|
623
617
|
if (typeof input === 'string') {
|
624
618
|
// @ts-ignore -- this now is converted to object
|
@@ -627,7 +621,10 @@ function inputToRGB(input) {
|
|
627
621
|
}
|
628
622
|
if (typeof color === 'object') {
|
629
623
|
if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
|
630
|
-
|
624
|
+
({ r, g, b } = color);
|
625
|
+
// RGB values now are all in [0, 255] range
|
626
|
+
[r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255);
|
627
|
+
rgb = { r, g, b };
|
631
628
|
ok = true;
|
632
629
|
format = 'rgb';
|
633
630
|
} else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
|
@@ -656,14 +653,17 @@ function inputToRGB(input) {
|
|
656
653
|
format = 'hwb';
|
657
654
|
}
|
658
655
|
if (isValidCSSUnit(color.a)) {
|
659
|
-
a = color.a;
|
660
|
-
a = isPercentage(`${a}`) ? bound01(a, 100) : a;
|
656
|
+
a = color.a; // @ts-ignore -- `parseFloat` works with numbers too
|
657
|
+
a = isPercentage(`${a}`) || parseFloat(a) > 1 ? bound01(a, 100) : a;
|
661
658
|
}
|
662
659
|
}
|
660
|
+
if (typeof color === 'undefined') {
|
661
|
+
ok = true;
|
662
|
+
}
|
663
663
|
|
664
664
|
return {
|
665
|
-
ok,
|
666
|
-
format
|
665
|
+
ok,
|
666
|
+
format,
|
667
667
|
r: Math.min(255, Math.max(rgb.r, 0)),
|
668
668
|
g: Math.min(255, Math.max(rgb.g, 0)),
|
669
669
|
b: Math.min(255, Math.max(rgb.b, 0)),
|
@@ -692,7 +692,8 @@ export default class Color {
|
|
692
692
|
color = inputToRGB(color);
|
693
693
|
}
|
694
694
|
if (typeof color === 'number') {
|
695
|
-
|
695
|
+
const len = `${color}`.length;
|
696
|
+
color = `#${(len === 2 ? '0' : '00')}${color}`;
|
696
697
|
}
|
697
698
|
const {
|
698
699
|
r, g, b, a, ok, format,
|
@@ -702,7 +703,7 @@ export default class Color {
|
|
702
703
|
const self = this;
|
703
704
|
|
704
705
|
/** @type {CP.ColorInput} */
|
705
|
-
self.originalInput =
|
706
|
+
self.originalInput = input;
|
706
707
|
/** @type {number} */
|
707
708
|
self.r = r;
|
708
709
|
/** @type {number} */
|
@@ -715,14 +716,6 @@ export default class Color {
|
|
715
716
|
self.ok = ok;
|
716
717
|
/** @type {CP.ColorFormats} */
|
717
718
|
self.format = configFormat || format;
|
718
|
-
|
719
|
-
// Don't let the range of [0,255] come back in [0,1].
|
720
|
-
// Potentially lose a little bit of precision here, but will fix issues where
|
721
|
-
// .5 gets interpreted as half of the total, instead of half of 1
|
722
|
-
// If it was supposed to be 128, this was already taken care of by `inputToRgb`
|
723
|
-
if (r < 1) self.r = Math.round(r);
|
724
|
-
if (g < 1) self.g = Math.round(g);
|
725
|
-
if (b < 1) self.b = Math.round(b);
|
726
719
|
}
|
727
720
|
|
728
721
|
/**
|
@@ -738,7 +731,7 @@ export default class Color {
|
|
738
731
|
* @returns {boolean} the query result
|
739
732
|
*/
|
740
733
|
get isDark() {
|
741
|
-
return this.brightness <
|
734
|
+
return this.brightness < 120;
|
742
735
|
}
|
743
736
|
|
744
737
|
/**
|
@@ -790,13 +783,9 @@ export default class Color {
|
|
790
783
|
const {
|
791
784
|
r, g, b, a,
|
792
785
|
} = this;
|
793
|
-
const [R, G, B] = [r, g, b].map((x) => Math.round(x));
|
794
786
|
|
795
787
|
return {
|
796
|
-
r:
|
797
|
-
g: G,
|
798
|
-
b: B,
|
799
|
-
a: Math.round(a * 100) / 100,
|
788
|
+
r, g, b, a: roundPart(a * 100) / 100,
|
800
789
|
};
|
801
790
|
}
|
802
791
|
|
@@ -810,10 +799,11 @@ export default class Color {
|
|
810
799
|
const {
|
811
800
|
r, g, b, a,
|
812
801
|
} = this.toRgb();
|
802
|
+
const [R, G, B] = [r, g, b].map(roundPart);
|
813
803
|
|
814
804
|
return a === 1
|
815
|
-
? `rgb(${
|
816
|
-
: `rgba(${
|
805
|
+
? `rgb(${R}, ${G}, ${B})`
|
806
|
+
: `rgba(${R}, ${G}, ${B}, ${a})`;
|
817
807
|
}
|
818
808
|
|
819
809
|
/**
|
@@ -826,9 +816,10 @@ export default class Color {
|
|
826
816
|
const {
|
827
817
|
r, g, b, a,
|
828
818
|
} = this.toRgb();
|
829
|
-
const
|
819
|
+
const [R, G, B] = [r, g, b].map(roundPart);
|
820
|
+
const A = a === 1 ? '' : ` / ${roundPart(a * 100)}%`;
|
830
821
|
|
831
|
-
return `rgb(${
|
822
|
+
return `rgb(${R} ${G} ${B}${A})`;
|
832
823
|
}
|
833
824
|
|
834
825
|
/**
|
@@ -921,10 +912,10 @@ export default class Color {
|
|
921
912
|
let {
|
922
913
|
h, s, l, a,
|
923
914
|
} = this.toHsl();
|
924
|
-
h =
|
925
|
-
s =
|
926
|
-
l =
|
927
|
-
a =
|
915
|
+
h = roundPart(h * 360);
|
916
|
+
s = roundPart(s * 100);
|
917
|
+
l = roundPart(l * 100);
|
918
|
+
a = roundPart(a * 100) / 100;
|
928
919
|
|
929
920
|
return a === 1
|
930
921
|
? `hsl(${h}, ${s}%, ${l}%)`
|
@@ -941,11 +932,11 @@ export default class Color {
|
|
941
932
|
let {
|
942
933
|
h, s, l, a,
|
943
934
|
} = this.toHsl();
|
944
|
-
h =
|
945
|
-
s =
|
946
|
-
l =
|
947
|
-
a =
|
948
|
-
const A = a < 100 ? ` / ${
|
935
|
+
h = roundPart(h * 360);
|
936
|
+
s = roundPart(s * 100);
|
937
|
+
l = roundPart(l * 100);
|
938
|
+
a = roundPart(a * 100);
|
939
|
+
const A = a < 100 ? ` / ${roundPart(a)}%` : '';
|
949
940
|
|
950
941
|
return `hsl(${h}deg ${s}% ${l}%${A})`;
|
951
942
|
}
|
@@ -972,11 +963,11 @@ export default class Color {
|
|
972
963
|
let {
|
973
964
|
h, w, b, a,
|
974
965
|
} = this.toHwb();
|
975
|
-
h =
|
976
|
-
w =
|
977
|
-
b =
|
978
|
-
a =
|
979
|
-
const A = a < 100 ? ` / ${
|
966
|
+
h = roundPart(h * 360);
|
967
|
+
w = roundPart(w * 100);
|
968
|
+
b = roundPart(b * 100);
|
969
|
+
a = roundPart(a * 100);
|
970
|
+
const A = a < 100 ? ` / ${roundPart(a)}%` : '';
|
980
971
|
|
981
972
|
return `hwb(${h}deg ${w}% ${b}%${A})`;
|
982
973
|
}
|
@@ -1102,6 +1093,7 @@ ObjectAssign(Color, {
|
|
1102
1093
|
isOnePointZero,
|
1103
1094
|
isPercentage,
|
1104
1095
|
isValidCSSUnit,
|
1096
|
+
isColorName,
|
1105
1097
|
pad2,
|
1106
1098
|
clamp01,
|
1107
1099
|
bound01,
|
@@ -1119,8 +1111,10 @@ ObjectAssign(Color, {
|
|
1119
1111
|
hueToRgb,
|
1120
1112
|
hwbToRgb,
|
1121
1113
|
parseIntFromHex,
|
1122
|
-
numberInputToObject,
|
1123
1114
|
stringInputToObject,
|
1124
1115
|
inputToRGB,
|
1116
|
+
roundPart,
|
1117
|
+
getElementStyle,
|
1118
|
+
setElementStyle,
|
1125
1119
|
ObjectAssign,
|
1126
1120
|
});
|
@@ -4,6 +4,8 @@ import ariaValueMax from 'shorter-js/src/strings/ariaValueMax';
|
|
4
4
|
import createElement from 'shorter-js/src/misc/createElement';
|
5
5
|
import setAttribute from 'shorter-js/src/attr/setAttribute';
|
6
6
|
|
7
|
+
import tabIndex from './tabindex';
|
8
|
+
|
7
9
|
/**
|
8
10
|
* Returns all color controls for `ColorPicker`.
|
9
11
|
*
|
@@ -69,10 +71,8 @@ export default function getColorControls(self) {
|
|
69
71
|
const {
|
70
72
|
i, c, l, min, max,
|
71
73
|
} = template;
|
72
|
-
// const hidden = i === 2 && format === 'hwb' ? ' v-hidden' : '';
|
73
74
|
const control = createElement({
|
74
75
|
tagName: 'div',
|
75
|
-
// className: `color-control${hidden}`,
|
76
76
|
className: 'color-control',
|
77
77
|
});
|
78
78
|
setAttribute(control, 'role', 'presentation');
|
@@ -92,7 +92,7 @@ export default function getColorControls(self) {
|
|
92
92
|
|
93
93
|
setAttribute(knob, ariaLabel, l);
|
94
94
|
setAttribute(knob, 'role', 'slider');
|
95
|
-
setAttribute(knob,
|
95
|
+
setAttribute(knob, tabIndex, '0');
|
96
96
|
setAttribute(knob, ariaValueMin, `${min}`);
|
97
97
|
setAttribute(knob, ariaValueMax, `${max}`);
|
98
98
|
control.append(knob);
|
@@ -5,6 +5,8 @@ import getAttribute from 'shorter-js/src/attr/getAttribute';
|
|
5
5
|
import createElement from 'shorter-js/src/misc/createElement';
|
6
6
|
import setElementStyle from 'shorter-js/src/misc/setElementStyle';
|
7
7
|
|
8
|
+
import setCSSProperties from './setCSSProperties';
|
9
|
+
import tabIndex from './tabindex';
|
8
10
|
import Color from '../color';
|
9
11
|
import ColorPalette from '../color-palette';
|
10
12
|
|
@@ -25,68 +27,64 @@ export default function getColorMenu(self, colorsSource, menuClass) {
|
|
25
27
|
colorsArray = colorsArray instanceof Array ? colorsArray : [];
|
26
28
|
const colorsCount = colorsArray.length;
|
27
29
|
const { lightSteps } = isPalette ? colorsSource : { lightSteps: null };
|
28
|
-
|
29
|
-
|| Math.max(...[5, 6, 7, 8, 9, 10].filter((x) => colorsCount > (x * 2) && !(colorsCount % x)));
|
30
|
-
fit = Number.isFinite(fit) ? fit : 5;
|
30
|
+
const fit = lightSteps || [9, 10].find((x) => colorsCount > x * 2 && !(colorsCount % x)) || 5;
|
31
31
|
const isMultiLine = isOptionsMenu && colorsCount > fit;
|
32
|
-
let rowCountHover =
|
33
|
-
rowCountHover = isMultiLine && colorsCount
|
34
|
-
rowCountHover = colorsCount >=
|
35
|
-
rowCountHover = colorsCount >=
|
36
|
-
|
37
|
-
const
|
38
|
-
const isScrollable = isMultiLine && colorsCount > rowCountHover * fit;
|
32
|
+
let rowCountHover = 2;
|
33
|
+
rowCountHover = isMultiLine && colorsCount >= fit * 2 ? 3 : rowCountHover;
|
34
|
+
rowCountHover = colorsCount >= fit * 3 ? 4 : rowCountHover;
|
35
|
+
rowCountHover = colorsCount >= fit * 4 ? 5 : rowCountHover;
|
36
|
+
const rowCount = rowCountHover - (colorsCount < fit * 3 ? 1 : 2);
|
37
|
+
const isScrollable = isMultiLine && colorsCount > rowCount * fit;
|
39
38
|
let finalClass = menuClass;
|
40
39
|
finalClass += isScrollable ? ' scrollable' : '';
|
41
40
|
finalClass += isMultiLine ? ' multiline' : '';
|
42
41
|
const gap = isMultiLine ? '1px' : '0.25rem';
|
43
42
|
let optionSize = isMultiLine ? 1.75 : 2;
|
44
|
-
optionSize =
|
43
|
+
optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
|
45
44
|
const menuHeight = `${(rowCount || 1) * optionSize}rem`;
|
46
45
|
const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
/** @type {HTMLUListElement} */
|
47
|
+
// @ts-ignore -- <UL> is an `HTMLElement`
|
50
48
|
const menu = createElement({
|
51
49
|
tagName: 'ul',
|
52
50
|
className: finalClass,
|
53
51
|
});
|
54
52
|
setAttribute(menu, 'role', 'listbox');
|
55
|
-
setAttribute(menu, ariaLabel,
|
53
|
+
setAttribute(menu, ariaLabel, menuLabel);
|
56
54
|
|
57
|
-
if (
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
};
|
66
|
-
setElementStyle(menu, menuStyle);
|
55
|
+
if (isScrollable) {
|
56
|
+
setCSSProperties(menu, {
|
57
|
+
'--grid-item-size': `${optionSize}rem`,
|
58
|
+
'--grid-fit': fit,
|
59
|
+
'--grid-gap': gap,
|
60
|
+
'--grid-height': menuHeight,
|
61
|
+
'--grid-hover-height': menuHeightHover,
|
62
|
+
});
|
67
63
|
}
|
68
64
|
|
69
65
|
colorsArray.forEach((x) => {
|
70
|
-
|
71
|
-
|
72
|
-
|
66
|
+
let [value, label] = typeof x === 'string' ? x.trim().split(':') : [];
|
67
|
+
if (x instanceof Color) {
|
68
|
+
value = x.toHexString();
|
69
|
+
label = value;
|
70
|
+
}
|
71
|
+
const color = new Color(x instanceof Color ? x : value, format);
|
72
|
+
const isActive = color.toString() === getAttribute(input, 'value');
|
73
73
|
const active = isActive ? ' active' : '';
|
74
74
|
|
75
75
|
const option = createElement({
|
76
76
|
tagName: 'li',
|
77
77
|
className: `color-option${active}`,
|
78
|
-
innerText: `${label ||
|
78
|
+
innerText: `${label || value}`,
|
79
79
|
});
|
80
80
|
|
81
|
-
setAttribute(option,
|
81
|
+
setAttribute(option, tabIndex, '0');
|
82
82
|
setAttribute(option, 'data-value', `${value}`);
|
83
83
|
setAttribute(option, 'role', 'option');
|
84
84
|
setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
|
85
85
|
|
86
86
|
if (isOptionsMenu) {
|
87
|
-
setElementStyle(option, {
|
88
|
-
width: `${optionSize}rem`, height: `${optionSize}rem`, backgroundColor: x,
|
89
|
-
});
|
87
|
+
setElementStyle(option, { backgroundColor: value });
|
90
88
|
}
|
91
89
|
|
92
90
|
menu.append(option);
|
@@ -0,0 +1,9 @@
|
|
1
|
+
/**
|
2
|
+
* Round colour components, for all formats except HEX.
|
3
|
+
* @param {number} v one of the colour components
|
4
|
+
* @returns {number} the rounded number
|
5
|
+
*/
|
6
|
+
export default function roundPart(v) {
|
7
|
+
const floor = Math.floor(v);
|
8
|
+
return v - floor < 0.5 ? floor : Math.round(v);
|
9
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import ObjectKeys from 'shorter-js/src/misc/ObjectKeys';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Helps setting CSS variables to the color-menu.
|
5
|
+
* @param {HTMLElement} element
|
6
|
+
* @param {Record<string,any>} props
|
7
|
+
*/
|
8
|
+
export default function setCSSProperties(element, props) {
|
9
|
+
ObjectKeys(props).forEach((prop) => {
|
10
|
+
element.style.setProperty(prop, props[prop]);
|
11
|
+
});
|
12
|
+
}
|