@thednp/color-picker 0.0.1-alpha2 → 0.0.2-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.
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
+ }