@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.
Files changed (37) hide show
  1. package/README.md +32 -15
  2. package/dist/css/color-picker.css +38 -15
  3. package/dist/css/color-picker.min.css +2 -2
  4. package/dist/css/color-picker.rtl.css +38 -15
  5. package/dist/css/color-picker.rtl.min.css +2 -2
  6. package/dist/js/color-esm.js +1178 -0
  7. package/dist/js/color-esm.min.js +2 -0
  8. package/dist/js/color-palette-esm.js +1252 -0
  9. package/dist/js/color-palette-esm.min.js +2 -0
  10. package/dist/js/color-palette.js +1260 -0
  11. package/dist/js/color-palette.min.js +2 -0
  12. package/dist/js/color-picker-element-esm.js +433 -424
  13. package/dist/js/color-picker-element-esm.min.js +2 -2
  14. package/dist/js/color-picker-element.js +435 -426
  15. package/dist/js/color-picker-element.min.js +2 -2
  16. package/dist/js/color-picker-esm.js +745 -739
  17. package/dist/js/color-picker-esm.min.js +2 -2
  18. package/dist/js/color-picker.js +747 -741
  19. package/dist/js/color-picker.min.js +2 -2
  20. package/dist/js/color.js +1186 -0
  21. package/dist/js/color.min.js +2 -0
  22. package/package.json +19 -3
  23. package/src/js/color-palette.js +28 -12
  24. package/src/js/color-picker-element.js +8 -4
  25. package/src/js/color-picker.js +84 -172
  26. package/src/js/color.js +125 -131
  27. package/src/js/util/getColorControls.js +3 -3
  28. package/src/js/util/getColorForm.js +0 -1
  29. package/src/js/util/getColorMenu.js +31 -33
  30. package/src/js/util/roundPart.js +9 -0
  31. package/src/js/util/setCSSProperties.js +12 -0
  32. package/src/js/util/setMarkup.js +122 -0
  33. package/src/js/util/tabindex.js +3 -0
  34. package/src/js/util/version.js +6 -0
  35. package/src/scss/color-picker.scss +35 -16
  36. package/types/cp.d.ts +48 -20
  37. package/src/js/util/templates.js +0 -10
package/src/js/color.js CHANGED
@@ -1,12 +1,14 @@
1
- import getDocumentHead from 'shorter-js/src/get/getDocumentHead';
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', 'hsb', 'hwb'];
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 = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
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
- return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
83
- && !/[0-9]/.test(color);
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(n)) n = '100%';
105
-
106
- n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
112
+ if (isOnePointZero(N)) n = '100%';
107
113
 
108
- // Handle hue angles
109
- if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
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 (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
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) / parseFloat(String(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) / parseFloat(String(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 Math.round(d * 255).toString(16);
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
- const r = [v, q, p, p, t, v][mod];
411
- const g = [t, v, v, q, p, p][mod];
412
- const b = [p, p, t, v, v, q][mod];
413
- return { r: r * 255, g: g * 255, b: b * 255 };
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(Math.round(r).toString(16)),
430
- pad2(Math.round(g).toString(16)),
431
- pad2(Math.round(b).toString(16)),
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
- && hex[2].charAt(0) === hex[2].charAt(1)) {
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(Math.round(r).toString(16)),
457
- pad2(Math.round(g).toString(16)),
458
- pad2(Math.round(b).toString(16)),
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
- && hex[2].charAt(0) === hex[2].charAt(1)
466
- && hex[3].charAt(0) === hex[3].charAt(1)) {
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().toLowerCase();
487
+ let color = toLowerCase(input.trim());
495
488
  if (color.length === 0) {
496
489
  return {
497
- r: 0, g: 0, b: 0, a: 0,
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 isTransparent = color === 'transparent';
506
- const rgb = isTransparent ? 0 : 255;
507
- const a = isTransparent ? 0 : 1;
498
+ const a = color === 'transparent' ? 0 : 1;
508
499
  return {
509
- r: rgb, g: rgb, b: rgb, a, format: 'rgb',
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
- let format = 'hex';
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
- rgb = { r: color.r, g: color.g, b: color.b }; // RGB values in [0, 255] range
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, // @ts-ignore
666
- format: color.format || 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
- color = numberInputToObject(color);
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 = color;
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 < 128;
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: 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(${r}, ${g}, ${b})`
816
- : `rgba(${r}, ${g}, ${b}, ${a})`;
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 A = a === 1 ? '' : ` / ${Math.round(a * 100)}%`;
819
+ const [R, G, B] = [r, g, b].map(roundPart);
820
+ const A = a === 1 ? '' : ` / ${roundPart(a * 100)}%`;
830
821
 
831
- return `rgb(${r} ${g} ${b}${A})`;
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 = Math.round(h * 360);
925
- s = Math.round(s * 100);
926
- l = Math.round(l * 100);
927
- a = Math.round(a * 100) / 100;
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 = Math.round(h * 360);
945
- s = Math.round(s * 100);
946
- l = Math.round(l * 100);
947
- a = Math.round(a * 100);
948
- const A = a < 100 ? ` / ${Math.round(a)}%` : '';
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 = Math.round(h * 360);
976
- w = Math.round(w * 100);
977
- b = Math.round(b * 100);
978
- a = Math.round(a * 100);
979
- const A = a < 100 ? ` / ${Math.round(a)}%` : '';
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, 'tabindex', '0');
95
+ setAttribute(knob, tabIndex, '0');
96
96
  setAttribute(knob, ariaValueMin, `${min}`);
97
97
  setAttribute(knob, ariaValueMax, `${max}`);
98
98
  control.append(knob);
@@ -59,7 +59,6 @@ export default function getColorForm(self) {
59
59
  max,
60
60
  step,
61
61
  });
62
- // }
63
62
  colorForm.append(cInputLabel, cInput);
64
63
  });
65
64
  return colorForm;
@@ -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
- let fit = lightSteps
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 = 1;
33
- rowCountHover = isMultiLine && colorsCount < 27 ? 2 : rowCountHover;
34
- rowCountHover = colorsCount >= 27 ? 3 : rowCountHover;
35
- rowCountHover = colorsCount >= 36 ? 4 : rowCountHover;
36
- rowCountHover = colorsCount >= 45 ? 5 : rowCountHover;
37
- const rowCount = rowCountHover - (colorsCount < 27 ? 1 : 2);
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 = !(colorsCount % 10) && isMultiLine ? 1.5 : 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
- const gridTemplateColumns = `repeat(${fit}, ${optionSize}rem)`;
48
- const gridTemplateRows = `repeat(auto-fill, ${optionSize}rem)`;
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, `${menuLabel}`);
53
+ setAttribute(menu, ariaLabel, menuLabel);
56
54
 
57
- if (isOptionsMenu) {
58
- if (isScrollable) {
59
- const styleText = 'this.style.height=';
60
- setAttribute(menu, 'onmouseout', `${styleText}'${menuHeight}'`);
61
- setAttribute(menu, 'onmouseover', `${styleText}'${menuHeightHover}'`);
62
- }
63
- const menuStyle = {
64
- height: isScrollable ? menuHeight : '', gridTemplateColumns, gridTemplateRows, gap,
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
- const [value, label] = x.trim().split(':');
71
- const xRealColor = new Color(value, format).toString();
72
- const isActive = xRealColor === getAttribute(input, 'value');
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 || x}`,
78
+ innerText: `${label || value}`,
79
79
  });
80
80
 
81
- setAttribute(option, 'tabindex', '0');
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
+ }