@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
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * ColorPickerElement v0.0.1alpha2 (http://thednp.github.io/color-picker)
2
+ * ColorPickerElement v0.0.2alpha1 (http://thednp.github.io/color-picker)
3
3
  * Copyright 2022 © thednp
4
4
  * Licensed under MIT (https://github.com/thednp/color-picker/blob/master/LICENSE)
5
5
  */
@@ -82,14 +82,9 @@ const setAttribute = (element, attribute, value) => element.setAttribute(attribu
82
82
  const getAttribute = (element, attribute) => element.getAttribute(attribute);
83
83
 
84
84
  /**
85
- * Returns the `document.head` or the `<head>` element.
86
- *
87
- * @param {(Node | HTMLElement | Element | globalThis)=} node
88
- * @returns {HTMLElement | HTMLHeadElement}
85
+ * A global namespace for `document.head`.
89
86
  */
90
- function getDocumentHead(node) {
91
- return getDocument(node).head;
92
- }
87
+ const { head: documentHead } = document;
93
88
 
94
89
  /**
95
90
  * Shortcut for `window.getComputedStyle(element).propertyName`
@@ -118,13 +113,31 @@ function getElementStyle(element, property) {
118
113
  // @ts-ignore
119
114
  const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
120
115
 
116
+ /**
117
+ * Shortcut for `String.toLowerCase()`.
118
+ *
119
+ * @param {string} source input string
120
+ * @returns {string} lowercase output string
121
+ */
122
+ const toLowerCase = (source) => source.toLowerCase();
123
+
121
124
  /**
122
125
  * A list of explicit default non-color values.
123
126
  */
124
127
  const nonColors = ['transparent', 'currentColor', 'inherit', 'revert', 'initial'];
125
128
 
129
+ /**
130
+ * Round colour components, for all formats except HEX.
131
+ * @param {number} v one of the colour components
132
+ * @returns {number} the rounded number
133
+ */
134
+ function roundPart(v) {
135
+ const floor = Math.floor(v);
136
+ return v - floor < 0.5 ? floor : Math.round(v);
137
+ }
138
+
126
139
  // Color supported formats
127
- const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
140
+ const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsv', 'hwb'];
128
141
 
129
142
  // Hue angles
130
143
  const ANGLES = 'deg|rad|grad|turn';
@@ -146,10 +159,17 @@ const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
146
159
  // Add angles to the mix
147
160
  const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
148
161
 
162
+ // Start & end
163
+ const START_MATCH = '(?:[\\s|\\(\\s|\\s\\(\\s]+)?';
164
+ const END_MATCH = '(?:[\\s|\\)\\s]+)?';
165
+ // Components separation
166
+ const SEP = '(?:[,|\\s]+)';
167
+ const SEP2 = '(?:[,|\\/\\s]*)?';
168
+
149
169
  // Actual matching.
150
170
  // Parentheses and commas are optional, but not required.
151
171
  // Whitespace can take the place of commas or opening paren
152
- const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
172
+ const PERMISSIVE_MATCH = `${START_MATCH}(${CSS_UNIT2})${SEP}(${CSS_UNIT})${SEP}(${CSS_UNIT})${SEP2}(${CSS_UNIT})?${END_MATCH}`;
153
173
 
154
174
  const matchers = {
155
175
  CSS_UNIT: new RegExp(CSS_UNIT2),
@@ -182,23 +202,22 @@ function isPercentage(n) {
182
202
  return `${n}`.includes('%');
183
203
  }
184
204
 
185
- /**
186
- * Check to see if string passed in is an angle
187
- * @param {string} n testing string
188
- * @returns {boolean} the query result
189
- */
190
- function isAngle(n) {
191
- return ANGLES.split('|').some((a) => `${n}`.includes(a));
192
- }
193
-
194
205
  /**
195
206
  * Check to see if string passed is a web safe colour.
207
+ * @see https://stackoverflow.com/a/16994164
196
208
  * @param {string} color a colour name, EG: *red*
197
209
  * @returns {boolean} the query result
198
210
  */
199
211
  function isColorName(color) {
200
- return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
201
- && !/[0-9]/.test(color);
212
+ if (nonColors.includes(color)
213
+ || ['#', ...COLOR_FORMAT].some((f) => color.includes(f))) return false;
214
+
215
+ return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
216
+ setElementStyle(documentHead, { color });
217
+ const computedColor = getElementStyle(documentHead, 'color');
218
+ setElementStyle(documentHead, { color: '' });
219
+ return computedColor !== c;
220
+ });
202
221
  }
203
222
 
204
223
  /**
@@ -219,15 +238,15 @@ function isValidCSSUnit(color) {
219
238
  */
220
239
  function bound01(N, max) {
221
240
  let n = N;
222
- if (isOnePointZero(n)) n = '100%';
223
-
224
- n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
241
+ if (isOnePointZero(N)) n = '100%';
225
242
 
226
- // Handle hue angles
227
- if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
243
+ const processPercent = isPercentage(n);
244
+ n = max === 360
245
+ ? parseFloat(n)
246
+ : Math.min(max, Math.max(0, parseFloat(n)));
228
247
 
229
248
  // Automatically convert percentage into number
230
- if (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
249
+ if (processPercent) n = (n * max) / 100;
231
250
 
232
251
  // Handle floating point rounding errors
233
252
  if (Math.abs(n - max) < 0.000001) {
@@ -238,11 +257,11 @@ function bound01(N, max) {
238
257
  // If n is a hue given in degrees,
239
258
  // wrap around out-of-range values into [0, 360] range
240
259
  // then convert into [0, 1].
241
- n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max));
260
+ n = (n < 0 ? (n % max) + max : n % max) / max;
242
261
  } else {
243
262
  // If n not a hue given in degrees
244
263
  // Convert into [0, 1] range if it isn't already.
245
- n = (n % max) / parseFloat(String(max));
264
+ n = (n % max) / max;
246
265
  }
247
266
  return n;
248
267
  }
@@ -277,7 +296,6 @@ function clamp01(v) {
277
296
  * @returns {string}
278
297
  */
279
298
  function getRGBFromName(name) {
280
- const documentHead = getDocumentHead();
281
299
  setElementStyle(documentHead, { color: name });
282
300
  const colorName = getElementStyle(documentHead, 'color');
283
301
  setElementStyle(documentHead, { color: '' });
@@ -290,7 +308,7 @@ function getRGBFromName(name) {
290
308
  * @returns {string} - the hexadecimal value
291
309
  */
292
310
  function convertDecimalToHex(d) {
293
- return Math.round(d * 255).toString(16);
311
+ return roundPart(d * 255).toString(16);
294
312
  }
295
313
 
296
314
  /**
@@ -376,6 +394,36 @@ function hueToRgb(p, q, t) {
376
394
  return p;
377
395
  }
378
396
 
397
+ /**
398
+ * Converts an HSL colour value to RGB.
399
+ *
400
+ * @param {number} h Hue Angle [0, 1]
401
+ * @param {number} s Saturation [0, 1]
402
+ * @param {number} l Lightness Angle [0, 1]
403
+ * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
404
+ */
405
+ function hslToRgb(h, s, l) {
406
+ let r = 0;
407
+ let g = 0;
408
+ let b = 0;
409
+
410
+ if (s === 0) {
411
+ // achromatic
412
+ g = l;
413
+ b = l;
414
+ r = l;
415
+ } else {
416
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
417
+ const p = 2 * l - q;
418
+ r = hueToRgb(p, q, h + 1 / 3);
419
+ g = hueToRgb(p, q, h);
420
+ b = hueToRgb(p, q, h - 1 / 3);
421
+ }
422
+ [r, g, b] = [r, g, b].map((x) => x * 255);
423
+
424
+ return { r, g, b };
425
+ }
426
+
379
427
  /**
380
428
  * Returns an HWB colour object from an RGB colour object.
381
429
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
@@ -438,36 +486,6 @@ function hwbToRgb(H, W, B) {
438
486
  return { r, g, b };
439
487
  }
440
488
 
441
- /**
442
- * Converts an HSL colour value to RGB.
443
- *
444
- * @param {number} h Hue Angle [0, 1]
445
- * @param {number} s Saturation [0, 1]
446
- * @param {number} l Lightness Angle [0, 1]
447
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
448
- */
449
- function hslToRgb(h, s, l) {
450
- let r = 0;
451
- let g = 0;
452
- let b = 0;
453
-
454
- if (s === 0) {
455
- // achromatic
456
- g = l;
457
- b = l;
458
- r = l;
459
- } else {
460
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
461
- const p = 2 * l - q;
462
- r = hueToRgb(p, q, h + 1 / 3);
463
- g = hueToRgb(p, q, h);
464
- b = hueToRgb(p, q, h - 1 / 3);
465
- }
466
- [r, g, b] = [r, g, b].map((x) => x * 255);
467
-
468
- return { r, g, b };
469
- }
470
-
471
489
  /**
472
490
  * Converts an RGB colour value to HSV.
473
491
  *
@@ -523,10 +541,11 @@ function hsvToRgb(H, S, V) {
523
541
  const q = v * (1 - f * s);
524
542
  const t = v * (1 - (1 - f) * s);
525
543
  const mod = i % 6;
526
- const r = [v, q, p, p, t, v][mod];
527
- const g = [t, v, v, q, p, p][mod];
528
- const b = [p, p, t, v, v, q][mod];
529
- return { r: r * 255, g: g * 255, b: b * 255 };
544
+ let r = [v, q, p, p, t, v][mod];
545
+ let g = [t, v, v, q, p, p][mod];
546
+ let b = [p, p, t, v, v, q][mod];
547
+ [r, g, b] = [r, g, b].map((n) => n * 255);
548
+ return { r, g, b };
530
549
  }
531
550
 
532
551
  /**
@@ -542,15 +561,15 @@ function hsvToRgb(H, S, V) {
542
561
  */
543
562
  function rgbToHex(r, g, b, allow3Char) {
544
563
  const hex = [
545
- pad2(Math.round(r).toString(16)),
546
- pad2(Math.round(g).toString(16)),
547
- pad2(Math.round(b).toString(16)),
564
+ pad2(roundPart(r).toString(16)),
565
+ pad2(roundPart(g).toString(16)),
566
+ pad2(roundPart(b).toString(16)),
548
567
  ];
549
568
 
550
569
  // Return a 3 character hex if possible
551
570
  if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
552
571
  && hex[1].charAt(0) === hex[1].charAt(1)
553
- && hex[2].charAt(0) === hex[2].charAt(1)) {
572
+ && hex[2].charAt(0) === hex[2].charAt(1)) {
554
573
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
555
574
  }
556
575
 
@@ -569,48 +588,33 @@ function rgbToHex(r, g, b, allow3Char) {
569
588
  */
570
589
  function rgbaToHex(r, g, b, a, allow4Char) {
571
590
  const hex = [
572
- pad2(Math.round(r).toString(16)),
573
- pad2(Math.round(g).toString(16)),
574
- pad2(Math.round(b).toString(16)),
591
+ pad2(roundPart(r).toString(16)),
592
+ pad2(roundPart(g).toString(16)),
593
+ pad2(roundPart(b).toString(16)),
575
594
  pad2(convertDecimalToHex(a)),
576
595
  ];
577
596
 
578
597
  // Return a 4 character hex if possible
579
598
  if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
580
599
  && hex[1].charAt(0) === hex[1].charAt(1)
581
- && hex[2].charAt(0) === hex[2].charAt(1)
582
- && hex[3].charAt(0) === hex[3].charAt(1)) {
600
+ && hex[2].charAt(0) === hex[2].charAt(1)
601
+ && hex[3].charAt(0) === hex[3].charAt(1)) {
583
602
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
584
603
  }
585
604
  return hex.join('');
586
605
  }
587
606
 
588
- /**
589
- * Returns a colour object corresponding to a given number.
590
- * @param {number} color input number
591
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
592
- */
593
- function numberInputToObject(color) {
594
- /* eslint-disable no-bitwise */
595
- return {
596
- r: color >> 16,
597
- g: (color & 0xff00) >> 8,
598
- b: color & 0xff,
599
- };
600
- /* eslint-enable no-bitwise */
601
- }
602
-
603
607
  /**
604
608
  * Permissive string parsing. Take in a number of formats, and output an object
605
609
  * based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
606
610
  * @param {string} input colour value in any format
607
- * @returns {Record<string, (number | string)> | false} an object matching the RegExp
611
+ * @returns {Record<string, (number | string | boolean)> | false} an object matching the RegExp
608
612
  */
609
613
  function stringInputToObject(input) {
610
- let color = input.trim().toLowerCase();
614
+ let color = toLowerCase(input.trim());
611
615
  if (color.length === 0) {
612
616
  return {
613
- r: 0, g: 0, b: 0, a: 0,
617
+ r: 0, g: 0, b: 0, a: 1,
614
618
  };
615
619
  }
616
620
  let named = false;
@@ -618,11 +622,9 @@ function stringInputToObject(input) {
618
622
  color = getRGBFromName(color);
619
623
  named = true;
620
624
  } else if (nonColors.includes(color)) {
621
- const isTransparent = color === 'transparent';
622
- const rgb = isTransparent ? 0 : 255;
623
- const a = isTransparent ? 0 : 1;
625
+ const a = color === 'transparent' ? 0 : 1;
624
626
  return {
625
- r: rgb, g: rgb, b: rgb, a, format: 'rgb',
627
+ r: 0, g: 0, b: 0, a, format: 'rgb', ok: true,
626
628
  };
627
629
  }
628
630
 
@@ -662,7 +664,6 @@ function stringInputToObject(input) {
662
664
  g: parseIntFromHex(m2),
663
665
  b: parseIntFromHex(m3),
664
666
  a: convertHexToDecimal(m4),
665
- // format: named ? 'rgb' : 'hex8',
666
667
  format: named ? 'rgb' : 'hex',
667
668
  };
668
669
  }
@@ -726,6 +727,7 @@ function stringInputToObject(input) {
726
727
  function inputToRGB(input) {
727
728
  let rgb = { r: 0, g: 0, b: 0 };
728
729
  let color = input;
730
+ /** @type {string | number} */
729
731
  let a = 1;
730
732
  let s = null;
731
733
  let v = null;
@@ -733,8 +735,11 @@ function inputToRGB(input) {
733
735
  let w = null;
734
736
  let b = null;
735
737
  let h = null;
738
+ let r = null;
739
+ let g = null;
736
740
  let ok = false;
737
- let format = 'hex';
741
+ const inputFormat = typeof color === 'object' && color.format;
742
+ let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
738
743
 
739
744
  if (typeof input === 'string') {
740
745
  // @ts-ignore -- this now is converted to object
@@ -743,7 +748,10 @@ function inputToRGB(input) {
743
748
  }
744
749
  if (typeof color === 'object') {
745
750
  if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
746
- rgb = { r: color.r, g: color.g, b: color.b }; // RGB values in [0, 255] range
751
+ ({ r, g, b } = color);
752
+ // RGB values now are all in [0, 255] range
753
+ [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255);
754
+ rgb = { r, g, b };
747
755
  ok = true;
748
756
  format = 'rgb';
749
757
  } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
@@ -772,14 +780,17 @@ function inputToRGB(input) {
772
780
  format = 'hwb';
773
781
  }
774
782
  if (isValidCSSUnit(color.a)) {
775
- a = color.a;
776
- a = isPercentage(`${a}`) ? bound01(a, 100) : a;
783
+ a = color.a; // @ts-ignore -- `parseFloat` works with numbers too
784
+ a = isPercentage(`${a}`) || parseFloat(a) > 1 ? bound01(a, 100) : a;
777
785
  }
778
786
  }
787
+ if (typeof color === 'undefined') {
788
+ ok = true;
789
+ }
779
790
 
780
791
  return {
781
- ok, // @ts-ignore
782
- format: color.format || format,
792
+ ok,
793
+ format,
783
794
  r: Math.min(255, Math.max(rgb.r, 0)),
784
795
  g: Math.min(255, Math.max(rgb.g, 0)),
785
796
  b: Math.min(255, Math.max(rgb.b, 0)),
@@ -808,7 +819,8 @@ class Color {
808
819
  color = inputToRGB(color);
809
820
  }
810
821
  if (typeof color === 'number') {
811
- color = numberInputToObject(color);
822
+ const len = `${color}`.length;
823
+ color = `#${(len === 2 ? '0' : '00')}${color}`;
812
824
  }
813
825
  const {
814
826
  r, g, b, a, ok, format,
@@ -818,7 +830,7 @@ class Color {
818
830
  const self = this;
819
831
 
820
832
  /** @type {CP.ColorInput} */
821
- self.originalInput = color;
833
+ self.originalInput = input;
822
834
  /** @type {number} */
823
835
  self.r = r;
824
836
  /** @type {number} */
@@ -831,14 +843,6 @@ class Color {
831
843
  self.ok = ok;
832
844
  /** @type {CP.ColorFormats} */
833
845
  self.format = configFormat || format;
834
-
835
- // Don't let the range of [0,255] come back in [0,1].
836
- // Potentially lose a little bit of precision here, but will fix issues where
837
- // .5 gets interpreted as half of the total, instead of half of 1
838
- // If it was supposed to be 128, this was already taken care of by `inputToRgb`
839
- if (r < 1) self.r = Math.round(r);
840
- if (g < 1) self.g = Math.round(g);
841
- if (b < 1) self.b = Math.round(b);
842
846
  }
843
847
 
844
848
  /**
@@ -854,7 +858,7 @@ class Color {
854
858
  * @returns {boolean} the query result
855
859
  */
856
860
  get isDark() {
857
- return this.brightness < 128;
861
+ return this.brightness < 120;
858
862
  }
859
863
 
860
864
  /**
@@ -906,13 +910,9 @@ class Color {
906
910
  const {
907
911
  r, g, b, a,
908
912
  } = this;
909
- const [R, G, B] = [r, g, b].map((x) => Math.round(x));
910
913
 
911
914
  return {
912
- r: R,
913
- g: G,
914
- b: B,
915
- a: Math.round(a * 100) / 100,
915
+ r, g, b, a: roundPart(a * 100) / 100,
916
916
  };
917
917
  }
918
918
 
@@ -926,10 +926,11 @@ class Color {
926
926
  const {
927
927
  r, g, b, a,
928
928
  } = this.toRgb();
929
+ const [R, G, B] = [r, g, b].map(roundPart);
929
930
 
930
931
  return a === 1
931
- ? `rgb(${r}, ${g}, ${b})`
932
- : `rgba(${r}, ${g}, ${b}, ${a})`;
932
+ ? `rgb(${R}, ${G}, ${B})`
933
+ : `rgba(${R}, ${G}, ${B}, ${a})`;
933
934
  }
934
935
 
935
936
  /**
@@ -942,9 +943,10 @@ class Color {
942
943
  const {
943
944
  r, g, b, a,
944
945
  } = this.toRgb();
945
- const A = a === 1 ? '' : ` / ${Math.round(a * 100)}%`;
946
+ const [R, G, B] = [r, g, b].map(roundPart);
947
+ const A = a === 1 ? '' : ` / ${roundPart(a * 100)}%`;
946
948
 
947
- return `rgb(${r} ${g} ${b}${A})`;
949
+ return `rgb(${R} ${G} ${B}${A})`;
948
950
  }
949
951
 
950
952
  /**
@@ -1037,10 +1039,10 @@ class Color {
1037
1039
  let {
1038
1040
  h, s, l, a,
1039
1041
  } = this.toHsl();
1040
- h = Math.round(h * 360);
1041
- s = Math.round(s * 100);
1042
- l = Math.round(l * 100);
1043
- a = Math.round(a * 100) / 100;
1042
+ h = roundPart(h * 360);
1043
+ s = roundPart(s * 100);
1044
+ l = roundPart(l * 100);
1045
+ a = roundPart(a * 100) / 100;
1044
1046
 
1045
1047
  return a === 1
1046
1048
  ? `hsl(${h}, ${s}%, ${l}%)`
@@ -1057,11 +1059,11 @@ class Color {
1057
1059
  let {
1058
1060
  h, s, l, a,
1059
1061
  } = this.toHsl();
1060
- h = Math.round(h * 360);
1061
- s = Math.round(s * 100);
1062
- l = Math.round(l * 100);
1063
- a = Math.round(a * 100);
1064
- const A = a < 100 ? ` / ${Math.round(a)}%` : '';
1062
+ h = roundPart(h * 360);
1063
+ s = roundPart(s * 100);
1064
+ l = roundPart(l * 100);
1065
+ a = roundPart(a * 100);
1066
+ const A = a < 100 ? ` / ${roundPart(a)}%` : '';
1065
1067
 
1066
1068
  return `hsl(${h}deg ${s}% ${l}%${A})`;
1067
1069
  }
@@ -1088,11 +1090,11 @@ class Color {
1088
1090
  let {
1089
1091
  h, w, b, a,
1090
1092
  } = this.toHwb();
1091
- h = Math.round(h * 360);
1092
- w = Math.round(w * 100);
1093
- b = Math.round(b * 100);
1094
- a = Math.round(a * 100);
1095
- const A = a < 100 ? ` / ${Math.round(a)}%` : '';
1093
+ h = roundPart(h * 360);
1094
+ w = roundPart(w * 100);
1095
+ b = roundPart(b * 100);
1096
+ a = roundPart(a * 100);
1097
+ const A = a < 100 ? ` / ${roundPart(a)}%` : '';
1096
1098
 
1097
1099
  return `hwb(${h}deg ${w}% ${b}%${A})`;
1098
1100
  }
@@ -1218,6 +1220,7 @@ ObjectAssign(Color, {
1218
1220
  isOnePointZero,
1219
1221
  isPercentage,
1220
1222
  isValidCSSUnit,
1223
+ isColorName,
1221
1224
  pad2,
1222
1225
  clamp01,
1223
1226
  bound01,
@@ -1235,9 +1238,11 @@ ObjectAssign(Color, {
1235
1238
  hueToRgb,
1236
1239
  hwbToRgb,
1237
1240
  parseIntFromHex,
1238
- numberInputToObject,
1239
1241
  stringInputToObject,
1240
1242
  inputToRGB,
1243
+ roundPart,
1244
+ getElementStyle,
1245
+ setElementStyle,
1241
1246
  ObjectAssign,
1242
1247
  });
1243
1248
 
@@ -1367,24 +1372,6 @@ const ariaValueText = 'aria-valuetext';
1367
1372
  */
1368
1373
  const ariaValueNow = 'aria-valuenow';
1369
1374
 
1370
- /**
1371
- * A global namespace for aria-haspopup.
1372
- * @type {string}
1373
- */
1374
- const ariaHasPopup = 'aria-haspopup';
1375
-
1376
- /**
1377
- * A global namespace for aria-hidden.
1378
- * @type {string}
1379
- */
1380
- const ariaHidden = 'aria-hidden';
1381
-
1382
- /**
1383
- * A global namespace for aria-labelledby.
1384
- * @type {string}
1385
- */
1386
- const ariaLabelledBy = 'aria-labelledby';
1387
-
1388
1375
  /**
1389
1376
  * A global namespace for `ArrowDown` key.
1390
1377
  * @type {string} e.which = 40 equivalent
@@ -1511,37 +1498,6 @@ const resizeEvent = 'resize';
1511
1498
  */
1512
1499
  const focusoutEvent = 'focusout';
1513
1500
 
1514
- // @ts-ignore
1515
- const { userAgentData: uaDATA } = navigator;
1516
-
1517
- /**
1518
- * A global namespace for `userAgentData` object.
1519
- */
1520
- const userAgentData = uaDATA;
1521
-
1522
- const { userAgent: userAgentString } = navigator;
1523
-
1524
- /**
1525
- * A global namespace for `navigator.userAgent` string.
1526
- */
1527
- const userAgent = userAgentString;
1528
-
1529
- const mobileBrands = /iPhone|iPad|iPod|Android/i;
1530
- let isMobileCheck = false;
1531
-
1532
- if (userAgentData) {
1533
- isMobileCheck = userAgentData.brands
1534
- .some((/** @type {Record<String, any>} */x) => mobileBrands.test(x.brand));
1535
- } else {
1536
- isMobileCheck = mobileBrands.test(userAgent);
1537
- }
1538
-
1539
- /**
1540
- * A global `boolean` for mobile detection.
1541
- * @type {boolean}
1542
- */
1543
- const isMobile = isMobileCheck;
1544
-
1545
1501
  /**
1546
1502
  * Returns the `document.documentElement` or the `<html>` element.
1547
1503
  *
@@ -1726,30 +1682,6 @@ function getElementsByClassName(selector, parent) {
1726
1682
  return lookUp.getElementsByClassName(selector);
1727
1683
  }
1728
1684
 
1729
- /**
1730
- * This is a shortie for `document.createElementNS` method
1731
- * which allows you to create a new `HTMLElement` for a given `tagName`
1732
- * or based on an object with specific non-readonly attributes:
1733
- * `id`, `className`, `textContent`, `style`, etc.
1734
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
1735
- *
1736
- * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
1737
- * @param {Record<string, string> | string} param `tagName` or object
1738
- * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
1739
- */
1740
- function createElementNS(namespace, param) {
1741
- if (typeof param === 'string') {
1742
- return getDocument().createElementNS(namespace, param);
1743
- }
1744
-
1745
- const { tagName } = param;
1746
- const attr = { ...param };
1747
- const newElement = createElementNS(namespace, tagName);
1748
- delete attr.tagName;
1749
- ObjectAssign(newElement, attr);
1750
- return newElement;
1751
- }
1752
-
1753
1685
  /**
1754
1686
  * Shortcut for the `Element.dispatchEvent(Event)` method.
1755
1687
  *
@@ -1873,14 +1805,6 @@ function normalizeValue(value) {
1873
1805
  */
1874
1806
  const ObjectKeys = (obj) => Object.keys(obj);
1875
1807
 
1876
- /**
1877
- * Shortcut for `String.toLowerCase()`.
1878
- *
1879
- * @param {string} source input string
1880
- * @returns {string} lowercase output string
1881
- */
1882
- const toLowerCase = (source) => source.toLowerCase();
1883
-
1884
1808
  /**
1885
1809
  * Utility to normalize component options.
1886
1810
  *
@@ -1984,6 +1908,80 @@ function removeClass(element, classNAME) {
1984
1908
  */
1985
1909
  const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
1986
1910
 
1911
+ /**
1912
+ * @class
1913
+ * Returns a color palette with a given set of parameters.
1914
+ * @example
1915
+ * new ColorPalette(0, 12, 10);
1916
+ * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: Array<Color> }
1917
+ */
1918
+ class ColorPalette {
1919
+ /**
1920
+ * The `hue` parameter is optional, which would be set to 0.
1921
+ * @param {number[]} args represeinting hue, hueSteps, lightSteps
1922
+ * * `args.hue` the starting Hue [0, 360]
1923
+ * * `args.hueSteps` Hue Steps Count [5, 24]
1924
+ * * `args.lightSteps` Lightness Steps Count [5, 12]
1925
+ */
1926
+ constructor(...args) {
1927
+ let hue = 0;
1928
+ let hueSteps = 12;
1929
+ let lightSteps = 10;
1930
+ let lightnessArray = [0.5];
1931
+
1932
+ if (args.length === 3) {
1933
+ [hue, hueSteps, lightSteps] = args;
1934
+ } else if (args.length === 2) {
1935
+ [hueSteps, lightSteps] = args;
1936
+ if ([hueSteps, lightSteps].some((n) => n < 1)) {
1937
+ throw TypeError('ColorPalette: when 2 arguments used, both must be larger than 0.');
1938
+ }
1939
+ } else {
1940
+ throw TypeError('ColorPalette requires minimum 2 arguments');
1941
+ }
1942
+
1943
+ /** @type {Color[]} */
1944
+ const colors = [];
1945
+
1946
+ const hueStep = 360 / hueSteps;
1947
+ const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
1948
+ const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
1949
+
1950
+ let lightStep = 0.25;
1951
+ lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
1952
+ lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
1953
+ lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
1954
+ lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
1955
+ lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
1956
+ lightStep = lightSteps > 13 ? estimatedStep : lightStep;
1957
+
1958
+ // light tints
1959
+ for (let i = 1; i < half + 1; i += 1) {
1960
+ lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
1961
+ }
1962
+
1963
+ // dark tints
1964
+ for (let i = 1; i < lightSteps - half; i += 1) {
1965
+ lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
1966
+ }
1967
+
1968
+ // feed `colors` Array
1969
+ for (let i = 0; i < hueSteps; i += 1) {
1970
+ const currentHue = ((hue + i * hueStep) % 360) / 360;
1971
+ lightnessArray.forEach((l) => {
1972
+ colors.push(new Color({ h: currentHue, s: 1, l }));
1973
+ });
1974
+ }
1975
+
1976
+ this.hue = hue;
1977
+ this.hueSteps = hueSteps;
1978
+ this.lightSteps = lightSteps;
1979
+ this.colors = colors;
1980
+ }
1981
+ }
1982
+
1983
+ ObjectAssign(ColorPalette, { Color });
1984
+
1987
1985
  /** @type {Record<string, string>} */
1988
1986
  const colorPickerLabels = {
1989
1987
  pickerLabel: 'Colour Picker',
@@ -2011,6 +2009,22 @@ const colorPickerLabels = {
2011
2009
  */
2012
2010
  const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
2013
2011
 
2012
+ const tabIndex = 'tabindex';
2013
+
2014
+ /**
2015
+ * Check if a string is valid JSON string.
2016
+ * @param {string} str the string input
2017
+ * @returns {boolean} the query result
2018
+ */
2019
+ function isValidJSON(str) {
2020
+ try {
2021
+ JSON.parse(str);
2022
+ } catch (e) {
2023
+ return false;
2024
+ }
2025
+ return true;
2026
+ }
2027
+
2014
2028
  /**
2015
2029
  * Shortcut for `String.toUpperCase()`.
2016
2030
  *
@@ -2019,6 +2033,48 @@ const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold',
2019
2033
  */
2020
2034
  const toUpperCase = (source) => source.toUpperCase();
2021
2035
 
2036
+ /**
2037
+ * A global namespace for aria-haspopup.
2038
+ * @type {string}
2039
+ */
2040
+ const ariaHasPopup = 'aria-haspopup';
2041
+
2042
+ /**
2043
+ * A global namespace for aria-hidden.
2044
+ * @type {string}
2045
+ */
2046
+ const ariaHidden = 'aria-hidden';
2047
+
2048
+ /**
2049
+ * A global namespace for aria-labelledby.
2050
+ * @type {string}
2051
+ */
2052
+ const ariaLabelledBy = 'aria-labelledby';
2053
+
2054
+ /**
2055
+ * This is a shortie for `document.createElementNS` method
2056
+ * which allows you to create a new `HTMLElement` for a given `tagName`
2057
+ * or based on an object with specific non-readonly attributes:
2058
+ * `id`, `className`, `textContent`, `style`, etc.
2059
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
2060
+ *
2061
+ * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
2062
+ * @param {Record<string, string> | string} param `tagName` or object
2063
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
2064
+ */
2065
+ function createElementNS(namespace, param) {
2066
+ if (typeof param === 'string') {
2067
+ return getDocument().createElementNS(namespace, param);
2068
+ }
2069
+
2070
+ const { tagName } = param;
2071
+ const attr = { ...param };
2072
+ const newElement = createElementNS(namespace, tagName);
2073
+ delete attr.tagName;
2074
+ ObjectAssign(newElement, attr);
2075
+ return newElement;
2076
+ }
2077
+
2022
2078
  const vHidden = 'v-hidden';
2023
2079
 
2024
2080
  /**
@@ -2075,7 +2131,6 @@ function getColorForm(self) {
2075
2131
  max,
2076
2132
  step,
2077
2133
  });
2078
- // }
2079
2134
  colorForm.append(cInputLabel, cInput);
2080
2135
  });
2081
2136
  return colorForm;
@@ -2164,10 +2219,8 @@ function getColorControls(self) {
2164
2219
  const {
2165
2220
  i, c, l, min, max,
2166
2221
  } = template;
2167
- // const hidden = i === 2 && format === 'hwb' ? ' v-hidden' : '';
2168
2222
  const control = createElement({
2169
2223
  tagName: 'div',
2170
- // className: `color-control${hidden}`,
2171
2224
  className: 'color-control',
2172
2225
  });
2173
2226
  setAttribute(control, 'role', 'presentation');
@@ -2187,7 +2240,7 @@ function getColorControls(self) {
2187
2240
 
2188
2241
  setAttribute(knob, ariaLabel, l);
2189
2242
  setAttribute(knob, 'role', 'slider');
2190
- setAttribute(knob, 'tabindex', '0');
2243
+ setAttribute(knob, tabIndex, '0');
2191
2244
  setAttribute(knob, ariaValueMin, `${min}`);
2192
2245
  setAttribute(knob, ariaValueMax, `${max}`);
2193
2246
  control.append(knob);
@@ -2198,64 +2251,14 @@ function getColorControls(self) {
2198
2251
  }
2199
2252
 
2200
2253
  /**
2201
- * @class
2202
- * Returns a color palette with a given set of parameters.
2203
- * @example
2204
- * new ColorPalette(0, 12, 10);
2205
- * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
2254
+ * Helps setting CSS variables to the color-menu.
2255
+ * @param {HTMLElement} element
2256
+ * @param {Record<string,any>} props
2206
2257
  */
2207
- class ColorPalette {
2208
- /**
2209
- * The `hue` parameter is optional, which would be set to 0.
2210
- * @param {number[]} args represeinting hue, hueSteps, lightSteps
2211
- * * `args.hue` the starting Hue [0, 360]
2212
- * * `args.hueSteps` Hue Steps Count [5, 13]
2213
- * * `args.lightSteps` Lightness Steps Count [8, 10]
2214
- */
2215
- constructor(...args) {
2216
- let hue = 0;
2217
- let hueSteps = 12;
2218
- let lightSteps = 10;
2219
- let lightnessArray = [0.5];
2220
-
2221
- if (args.length === 3) {
2222
- [hue, hueSteps, lightSteps] = args;
2223
- } else if (args.length === 2) {
2224
- [hueSteps, lightSteps] = args;
2225
- } else {
2226
- throw TypeError('The ColorPalette requires minimum 2 arguments');
2227
- }
2228
-
2229
- /** @type {string[]} */
2230
- const colors = [];
2231
-
2232
- const hueStep = 360 / hueSteps;
2233
- const lightStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
2234
- const half = Math.round((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
2235
-
2236
- // light tints
2237
- for (let i = 0; i < half; i += 1) {
2238
- lightnessArray = [...lightnessArray, (0.5 + lightStep * (i + 1))];
2239
- }
2240
-
2241
- // dark tints
2242
- for (let i = 0; i < lightSteps - half - 1; i += 1) {
2243
- lightnessArray = [(0.5 - lightStep * (i + 1)), ...lightnessArray];
2244
- }
2245
-
2246
- // feed `colors` Array
2247
- for (let i = 0; i < hueSteps; i += 1) {
2248
- const currentHue = ((hue + i * hueStep) % 360) / 360;
2249
- lightnessArray.forEach((l) => {
2250
- colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
2251
- });
2252
- }
2253
-
2254
- this.hue = hue;
2255
- this.hueSteps = hueSteps;
2256
- this.lightSteps = lightSteps;
2257
- this.colors = colors;
2258
- }
2258
+ function setCSSProperties(element, props) {
2259
+ ObjectKeys(props).forEach((prop) => {
2260
+ element.style.setProperty(prop, props[prop]);
2261
+ });
2259
2262
  }
2260
2263
 
2261
2264
  /**
@@ -2275,68 +2278,64 @@ function getColorMenu(self, colorsSource, menuClass) {
2275
2278
  colorsArray = colorsArray instanceof Array ? colorsArray : [];
2276
2279
  const colorsCount = colorsArray.length;
2277
2280
  const { lightSteps } = isPalette ? colorsSource : { lightSteps: null };
2278
- let fit = lightSteps
2279
- || Math.max(...[5, 6, 7, 8, 9, 10].filter((x) => colorsCount > (x * 2) && !(colorsCount % x)));
2280
- fit = Number.isFinite(fit) ? fit : 5;
2281
+ const fit = lightSteps || [9, 10].find((x) => colorsCount > x * 2 && !(colorsCount % x)) || 5;
2281
2282
  const isMultiLine = isOptionsMenu && colorsCount > fit;
2282
- let rowCountHover = 1;
2283
- rowCountHover = isMultiLine && colorsCount < 27 ? 2 : rowCountHover;
2284
- rowCountHover = colorsCount >= 27 ? 3 : rowCountHover;
2285
- rowCountHover = colorsCount >= 36 ? 4 : rowCountHover;
2286
- rowCountHover = colorsCount >= 45 ? 5 : rowCountHover;
2287
- const rowCount = rowCountHover - (colorsCount < 27 ? 1 : 2);
2288
- const isScrollable = isMultiLine && colorsCount > rowCountHover * fit;
2283
+ let rowCountHover = 2;
2284
+ rowCountHover = isMultiLine && colorsCount >= fit * 2 ? 3 : rowCountHover;
2285
+ rowCountHover = colorsCount >= fit * 3 ? 4 : rowCountHover;
2286
+ rowCountHover = colorsCount >= fit * 4 ? 5 : rowCountHover;
2287
+ const rowCount = rowCountHover - (colorsCount < fit * 3 ? 1 : 2);
2288
+ const isScrollable = isMultiLine && colorsCount > rowCount * fit;
2289
2289
  let finalClass = menuClass;
2290
2290
  finalClass += isScrollable ? ' scrollable' : '';
2291
2291
  finalClass += isMultiLine ? ' multiline' : '';
2292
2292
  const gap = isMultiLine ? '1px' : '0.25rem';
2293
2293
  let optionSize = isMultiLine ? 1.75 : 2;
2294
- optionSize = !(colorsCount % 10) && isMultiLine ? 1.5 : optionSize;
2294
+ optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2295
2295
  const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2296
2296
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2297
- const gridTemplateColumns = `repeat(${fit}, ${optionSize}rem)`;
2298
- const gridTemplateRows = `repeat(auto-fill, ${optionSize}rem)`;
2299
-
2297
+ /** @type {HTMLUListElement} */
2298
+ // @ts-ignore -- <UL> is an `HTMLElement`
2300
2299
  const menu = createElement({
2301
2300
  tagName: 'ul',
2302
2301
  className: finalClass,
2303
2302
  });
2304
2303
  setAttribute(menu, 'role', 'listbox');
2305
- setAttribute(menu, ariaLabel, `${menuLabel}`);
2306
-
2307
- if (isOptionsMenu) {
2308
- if (isScrollable) {
2309
- const styleText = 'this.style.height=';
2310
- setAttribute(menu, 'onmouseout', `${styleText}'${menuHeight}'`);
2311
- setAttribute(menu, 'onmouseover', `${styleText}'${menuHeightHover}'`);
2312
- }
2313
- const menuStyle = {
2314
- height: isScrollable ? menuHeight : '', gridTemplateColumns, gridTemplateRows, gap,
2315
- };
2316
- setElementStyle(menu, menuStyle);
2304
+ setAttribute(menu, ariaLabel, menuLabel);
2305
+
2306
+ if (isScrollable) {
2307
+ setCSSProperties(menu, {
2308
+ '--grid-item-size': `${optionSize}rem`,
2309
+ '--grid-fit': fit,
2310
+ '--grid-gap': gap,
2311
+ '--grid-height': menuHeight,
2312
+ '--grid-hover-height': menuHeightHover,
2313
+ });
2317
2314
  }
2318
2315
 
2319
2316
  colorsArray.forEach((x) => {
2320
- const [value, label] = x.trim().split(':');
2321
- const xRealColor = new Color(value, format).toString();
2322
- const isActive = xRealColor === getAttribute(input, 'value');
2317
+ let [value, label] = typeof x === 'string' ? x.trim().split(':') : [];
2318
+ if (x instanceof Color) {
2319
+ value = x.toHexString();
2320
+ label = value;
2321
+ }
2322
+ const color = new Color(x instanceof Color ? x : value, format);
2323
+ const isActive = color.toString() === getAttribute(input, 'value');
2323
2324
  const active = isActive ? ' active' : '';
2324
2325
 
2325
2326
  const option = createElement({
2326
2327
  tagName: 'li',
2327
2328
  className: `color-option${active}`,
2328
- innerText: `${label || x}`,
2329
+ innerText: `${label || value}`,
2329
2330
  });
2330
2331
 
2331
- setAttribute(option, 'tabindex', '0');
2332
+ setAttribute(option, tabIndex, '0');
2332
2333
  setAttribute(option, 'data-value', `${value}`);
2333
2334
  setAttribute(option, 'role', 'option');
2334
2335
  setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2335
2336
 
2336
2337
  if (isOptionsMenu) {
2337
- setElementStyle(option, {
2338
- width: `${optionSize}rem`, height: `${optionSize}rem`, backgroundColor: x,
2339
- });
2338
+ setElementStyle(option, { backgroundColor: value });
2340
2339
  }
2341
2340
 
2342
2341
  menu.append(option);
@@ -2345,55 +2344,10 @@ function getColorMenu(self, colorsSource, menuClass) {
2345
2344
  }
2346
2345
 
2347
2346
  /**
2348
- * Check if a string is valid JSON string.
2349
- * @param {string} str the string input
2350
- * @returns {boolean} the query result
2351
- */
2352
- function isValidJSON(str) {
2353
- try {
2354
- JSON.parse(str);
2355
- } catch (e) {
2356
- return false;
2357
- }
2358
- return true;
2359
- }
2360
-
2361
- var version = "0.0.1alpha2";
2362
-
2363
- // @ts-ignore
2364
-
2365
- const Version = version;
2366
-
2367
- // ColorPicker GC
2368
- // ==============
2369
- const colorPickerString = 'color-picker';
2370
- const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2371
- const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2372
- const colorPickerDefaults = {
2373
- componentLabels: colorPickerLabels,
2374
- colorLabels: colorNames,
2375
- format: 'rgb',
2376
- colorPresets: undefined,
2377
- colorKeywords: nonColors,
2378
- };
2379
-
2380
- // ColorPicker Static Methods
2381
- // ==========================
2382
-
2383
- /** @type {CP.GetInstance<ColorPicker>} */
2384
- const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2385
-
2386
- /** @type {CP.InitCallback<ColorPicker>} */
2387
- const initColorPicker = (element) => new ColorPicker(element);
2388
-
2389
- // ColorPicker Private Methods
2390
- // ===========================
2391
-
2392
- /**
2393
- * Generate HTML markup and update instance properties.
2394
- * @param {ColorPicker} self
2395
- */
2396
- function initCallback(self) {
2347
+ * Generate HTML markup and update instance properties.
2348
+ * @param {CP.ColorPicker} self
2349
+ */
2350
+ function setMarkup(self) {
2397
2351
  const {
2398
2352
  input, parent, format, id, componentLabels, colorKeywords, colorPresets,
2399
2353
  } = self;
@@ -2408,9 +2362,7 @@ function initCallback(self) {
2408
2362
  self.color = new Color(color, format);
2409
2363
 
2410
2364
  // set initial controls dimensions
2411
- // make the controls smaller on mobile
2412
- const dropClass = isMobile ? ' mobile' : '';
2413
- const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
2365
+ const formatString = format === 'hex' ? hexLabel : toUpperCase(format);
2414
2366
 
2415
2367
  const pickerBtn = createElement({
2416
2368
  id: `picker-btn-${id}`,
@@ -2427,7 +2379,7 @@ function initCallback(self) {
2427
2379
 
2428
2380
  const pickerDropdown = createElement({
2429
2381
  tagName: 'div',
2430
- className: `color-dropdown picker${dropClass}`,
2382
+ className: 'color-dropdown picker',
2431
2383
  });
2432
2384
  setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
2433
2385
  setAttribute(pickerDropdown, 'role', 'group');
@@ -2443,7 +2395,7 @@ function initCallback(self) {
2443
2395
  if (colorKeywords || colorPresets) {
2444
2396
  const presetsDropdown = createElement({
2445
2397
  tagName: 'div',
2446
- className: `color-dropdown scrollable menu${dropClass}`,
2398
+ className: 'color-dropdown scrollable menu',
2447
2399
  });
2448
2400
 
2449
2401
  // color presets
@@ -2463,7 +2415,7 @@ function initCallback(self) {
2463
2415
  tagName: 'button',
2464
2416
  className: 'menu-toggle btn-appearance',
2465
2417
  });
2466
- setAttribute(presetsBtn, 'tabindex', '-1');
2418
+ setAttribute(presetsBtn, tabIndex, '-1');
2467
2419
  setAttribute(presetsBtn, ariaExpanded, 'false');
2468
2420
  setAttribute(presetsBtn, ariaHasPopup, 'true');
2469
2421
 
@@ -2490,9 +2442,40 @@ function initCallback(self) {
2490
2442
  if (colorKeywords && nonColors.includes(colorValue)) {
2491
2443
  self.value = colorValue;
2492
2444
  }
2493
- setAttribute(input, 'tabindex', '-1');
2445
+ setAttribute(input, tabIndex, '-1');
2494
2446
  }
2495
2447
 
2448
+ var version = "0.0.2alpha1";
2449
+
2450
+ // @ts-ignore
2451
+
2452
+ const Version = version;
2453
+
2454
+ // ColorPicker GC
2455
+ // ==============
2456
+ const colorPickerString = 'color-picker';
2457
+ const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2458
+ const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2459
+ const colorPickerDefaults = {
2460
+ componentLabels: colorPickerLabels,
2461
+ colorLabels: colorNames,
2462
+ format: 'rgb',
2463
+ colorPresets: false,
2464
+ colorKeywords: false,
2465
+ };
2466
+
2467
+ // ColorPicker Static Methods
2468
+ // ==========================
2469
+
2470
+ /** @type {CP.GetInstance<ColorPicker>} */
2471
+ const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2472
+
2473
+ /** @type {CP.InitCallback<ColorPicker>} */
2474
+ const initColorPicker = (element) => new ColorPicker(element);
2475
+
2476
+ // ColorPicker Private Methods
2477
+ // ===========================
2478
+
2496
2479
  /**
2497
2480
  * Add / remove `ColorPicker` main event listeners.
2498
2481
  * @param {ColorPicker} self
@@ -2591,8 +2574,19 @@ function showDropdown(self, dropdown) {
2591
2574
  addClass(dropdown, 'bottom');
2592
2575
  reflow(dropdown);
2593
2576
  addClass(dropdown, 'show');
2577
+
2594
2578
  if (isPicker) self.update();
2595
- self.show();
2579
+
2580
+ if (!self.isOpen) {
2581
+ toggleEventsOnShown(self, true);
2582
+ self.updateDropdownPosition();
2583
+ self.isOpen = true;
2584
+ setAttribute(self.input, tabIndex, '0');
2585
+ if (menuToggle) {
2586
+ setAttribute(menuToggle, tabIndex, '0');
2587
+ }
2588
+ }
2589
+
2596
2590
  setAttribute(nextBtn, ariaExpanded, 'true');
2597
2591
  if (activeBtn) {
2598
2592
  setAttribute(activeBtn, ariaExpanded, 'false');
@@ -2715,7 +2709,7 @@ class ColorPicker {
2715
2709
  self.handleKnobs = self.handleKnobs.bind(self);
2716
2710
 
2717
2711
  // generate markup
2718
- initCallback(self);
2712
+ setMarkup(self);
2719
2713
 
2720
2714
  const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
2721
2715
  // set main elements
@@ -2762,7 +2756,7 @@ class ColorPicker {
2762
2756
  set value(v) { this.input.value = v; }
2763
2757
 
2764
2758
  /** Check if the colour presets include any non-colour. */
2765
- get includeNonColor() {
2759
+ get hasNonColor() {
2766
2760
  return this.colorKeywords instanceof Array
2767
2761
  && this.colorKeywords.some((x) => nonColors.includes(x));
2768
2762
  }
@@ -2818,7 +2812,7 @@ class ColorPicker {
2818
2812
  const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
2819
2813
  const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
2820
2814
  const alpha = 1 - controlPositions.c3y / offsetHeight;
2821
- const roundA = Math.round((alpha * 100)) / 100;
2815
+ const roundA = roundPart((alpha * 100)) / 100;
2822
2816
 
2823
2817
  if (format !== 'hsl') {
2824
2818
  const fill = new Color({
@@ -2836,7 +2830,7 @@ class ColorPicker {
2836
2830
  });
2837
2831
  setElementStyle(v2, { background: hueGradient });
2838
2832
  } else {
2839
- const saturation = Math.round((controlPositions.c2y / offsetHeight) * 100);
2833
+ const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
2840
2834
  const fill0 = new Color({
2841
2835
  r: 255, g: 0, b: 0, a: alpha,
2842
2836
  }).saturate(-saturation).toRgbString();
@@ -2911,7 +2905,7 @@ class ColorPicker {
2911
2905
  const self = this;
2912
2906
  const { activeElement } = getDocument(self.input);
2913
2907
 
2914
- if ((isMobile && self.dragElement)
2908
+ if ((e.type === touchmoveEvent && self.dragElement)
2915
2909
  || (activeElement && self.controlKnobs.includes(activeElement))) {
2916
2910
  e.stopPropagation();
2917
2911
  e.preventDefault();
@@ -2991,12 +2985,12 @@ class ColorPicker {
2991
2985
 
2992
2986
  self.update();
2993
2987
 
2994
- if (currentActive) {
2995
- removeClass(currentActive, 'active');
2996
- removeAttribute(currentActive, ariaSelected);
2997
- }
2998
-
2999
2988
  if (currentActive !== target) {
2989
+ if (currentActive) {
2990
+ removeClass(currentActive, 'active');
2991
+ removeAttribute(currentActive, ariaSelected);
2992
+ }
2993
+
3000
2994
  addClass(target, 'active');
3001
2995
  setAttribute(target, ariaSelected, 'true');
3002
2996
 
@@ -3118,30 +3112,41 @@ class ColorPicker {
3118
3112
  if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
3119
3113
  e.preventDefault();
3120
3114
 
3121
- const { controlKnobs } = self;
3115
+ const { format, controlKnobs, visuals } = self;
3116
+ const { offsetWidth, offsetHeight } = visuals[0];
3122
3117
  const [c1, c2, c3] = controlKnobs;
3123
3118
  const { activeElement } = getDocument(c1);
3124
3119
  const currentKnob = controlKnobs.find((x) => x === activeElement);
3120
+ const yRatio = offsetHeight / (format === 'hsl' ? 100 : 360);
3125
3121
 
3126
3122
  if (currentKnob) {
3127
3123
  let offsetX = 0;
3128
3124
  let offsetY = 0;
3125
+
3129
3126
  if (target === c1) {
3127
+ const xRatio = offsetWidth / (format === 'hsl' ? 360 : 100);
3128
+
3130
3129
  if ([keyArrowLeft, keyArrowRight].includes(code)) {
3131
- self.controlPositions.c1x += code === keyArrowRight ? +1 : -1;
3130
+ self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
3132
3131
  } else if ([keyArrowUp, keyArrowDown].includes(code)) {
3133
- self.controlPositions.c1y += code === keyArrowDown ? +1 : -1;
3132
+ self.controlPositions.c1y += code === keyArrowDown ? yRatio : -yRatio;
3134
3133
  }
3135
3134
 
3136
3135
  offsetX = self.controlPositions.c1x;
3137
3136
  offsetY = self.controlPositions.c1y;
3138
3137
  self.changeControl1(offsetX, offsetY);
3139
3138
  } else if (target === c2) {
3140
- self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
3139
+ self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code)
3140
+ ? yRatio
3141
+ : -yRatio;
3142
+
3141
3143
  offsetY = self.controlPositions.c2y;
3142
3144
  self.changeControl2(offsetY);
3143
3145
  } else if (target === c3) {
3144
- self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
3146
+ self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code)
3147
+ ? yRatio
3148
+ : -yRatio;
3149
+
3145
3150
  offsetY = self.controlPositions.c3y;
3146
3151
  self.changeAlpha(offsetY);
3147
3152
  }
@@ -3163,7 +3168,7 @@ class ColorPicker {
3163
3168
  const [v1, v2, v3, v4] = format === 'rgb'
3164
3169
  ? inputs.map((i) => parseFloat(i.value) / (i === i4 ? 100 : 1))
3165
3170
  : inputs.map((i) => parseFloat(i.value) / (i !== i1 ? 100 : 360));
3166
- const isNonColorValue = self.includeNonColor && nonColors.includes(currentValue);
3171
+ const isNonColorValue = self.hasNonColor && nonColors.includes(currentValue);
3167
3172
  const alpha = i4 ? v4 : (1 - controlPositions.c3y / offsetHeight);
3168
3173
 
3169
3174
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
@@ -3422,11 +3427,11 @@ class ColorPicker {
3422
3427
  } = componentLabels;
3423
3428
  const { r, g, b } = color.toRgb();
3424
3429
  const [knob1, knob2, knob3] = controlKnobs;
3425
- const hue = Math.round(hsl.h * 360);
3430
+ const hue = roundPart(hsl.h * 360);
3426
3431
  const alpha = color.a;
3427
3432
  const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
3428
- const saturation = Math.round(saturationSource * 100);
3429
- const lightness = Math.round(hsl.l * 100);
3433
+ const saturation = roundPart(saturationSource * 100);
3434
+ const lightness = roundPart(hsl.l * 100);
3430
3435
  const hsvl = hsv.v * 100;
3431
3436
  let colorName;
3432
3437
 
@@ -3473,8 +3478,8 @@ class ColorPicker {
3473
3478
  setAttribute(knob2, ariaValueNow, `${saturation}`);
3474
3479
  } else if (format === 'hwb') {
3475
3480
  const { hwb } = self;
3476
- const whiteness = Math.round(hwb.w * 100);
3477
- const blackness = Math.round(hwb.b * 100);
3481
+ const whiteness = roundPart(hwb.w * 100);
3482
+ const blackness = roundPart(hwb.b * 100);
3478
3483
  colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
3479
3484
  setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3480
3485
  setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
@@ -3490,7 +3495,7 @@ class ColorPicker {
3490
3495
  setAttribute(knob2, ariaValueNow, `${hue}`);
3491
3496
  }
3492
3497
 
3493
- const alphaValue = Math.round(alpha * 100);
3498
+ const alphaValue = roundPart(alpha * 100);
3494
3499
  setAttribute(knob3, ariaValueText, `${alphaValue}%`);
3495
3500
  setAttribute(knob3, ariaValueNow, `${alphaValue}`);
3496
3501
 
@@ -3513,10 +3518,16 @@ class ColorPicker {
3513
3518
  /** Updates the control knobs actual positions. */
3514
3519
  updateControls() {
3515
3520
  const { controlKnobs, controlPositions } = this;
3521
+ let {
3522
+ c1x, c1y, c2y, c3y,
3523
+ } = controlPositions;
3516
3524
  const [control1, control2, control3] = controlKnobs;
3517
- setElementStyle(control1, { transform: `translate3d(${controlPositions.c1x - 4}px,${controlPositions.c1y - 4}px,0)` });
3518
- setElementStyle(control2, { transform: `translate3d(0,${controlPositions.c2y - 4}px,0)` });
3519
- setElementStyle(control3, { transform: `translate3d(0,${controlPositions.c3y - 4}px,0)` });
3525
+ // round control positions
3526
+ [c1x, c1y, c2y, c3y] = [c1x, c1y, c2y, c3y].map(roundPart);
3527
+
3528
+ setElementStyle(control1, { transform: `translate3d(${c1x - 4}px,${c1y - 4}px,0)` });
3529
+ setElementStyle(control2, { transform: `translate3d(0,${c2y - 4}px,0)` });
3530
+ setElementStyle(control3, { transform: `translate3d(0,${c3y - 4}px,0)` });
3520
3531
  }
3521
3532
 
3522
3533
  /**
@@ -3529,16 +3540,16 @@ class ColorPicker {
3529
3540
  value: oldColor, format, inputs, color, hsl,
3530
3541
  } = self;
3531
3542
  const [i1, i2, i3, i4] = inputs;
3532
- const alpha = Math.round(color.a * 100);
3533
- const hue = Math.round(hsl.h * 360);
3543
+ const alpha = roundPart(color.a * 100);
3544
+ const hue = roundPart(hsl.h * 360);
3534
3545
  let newColor;
3535
3546
 
3536
3547
  if (format === 'hex') {
3537
3548
  newColor = self.color.toHexString(true);
3538
3549
  i1.value = self.hex;
3539
3550
  } else if (format === 'hsl') {
3540
- const lightness = Math.round(hsl.l * 100);
3541
- const saturation = Math.round(hsl.s * 100);
3551
+ const lightness = roundPart(hsl.l * 100);
3552
+ const saturation = roundPart(hsl.s * 100);
3542
3553
  newColor = self.color.toHslString();
3543
3554
  i1.value = `${hue}`;
3544
3555
  i2.value = `${saturation}`;
@@ -3546,8 +3557,8 @@ class ColorPicker {
3546
3557
  i4.value = `${alpha}`;
3547
3558
  } else if (format === 'hwb') {
3548
3559
  const { w, b } = self.hwb;
3549
- const whiteness = Math.round(w * 100);
3550
- const blackness = Math.round(b * 100);
3560
+ const whiteness = roundPart(w * 100);
3561
+ const blackness = roundPart(b * 100);
3551
3562
 
3552
3563
  newColor = self.color.toHwbString();
3553
3564
  i1.value = `${hue}`;
@@ -3555,7 +3566,8 @@ class ColorPicker {
3555
3566
  i3.value = `${blackness}`;
3556
3567
  i4.value = `${alpha}`;
3557
3568
  } else if (format === 'rgb') {
3558
- const { r, g, b } = self.rgb;
3569
+ let { r, g, b } = self.rgb;
3570
+ [r, g, b] = [r, g, b].map(roundPart);
3559
3571
 
3560
3572
  newColor = self.color.toRgbString();
3561
3573
  i1.value = `${r}`;
@@ -3619,7 +3631,7 @@ class ColorPicker {
3619
3631
  const self = this;
3620
3632
  const { colorPicker } = self;
3621
3633
 
3622
- if (!hasClass(colorPicker, 'show')) {
3634
+ if (!['top', 'bottom'].some((c) => hasClass(colorPicker, c))) {
3623
3635
  showDropdown(self, colorPicker);
3624
3636
  }
3625
3637
  }
@@ -3636,21 +3648,6 @@ class ColorPicker {
3636
3648
  }
3637
3649
  }
3638
3650
 
3639
- /** Shows the `ColorPicker` dropdown or the presets menu. */
3640
- show() {
3641
- const self = this;
3642
- const { menuToggle } = self;
3643
- if (!self.isOpen) {
3644
- toggleEventsOnShown(self, true);
3645
- self.updateDropdownPosition();
3646
- self.isOpen = true;
3647
- setAttribute(self.input, 'tabindex', '0');
3648
- if (menuToggle) {
3649
- setAttribute(menuToggle, 'tabindex', '0');
3650
- }
3651
- }
3652
- }
3653
-
3654
3651
  /**
3655
3652
  * Hides the currently open `ColorPicker` dropdown.
3656
3653
  * @param {boolean=} focusPrevented
@@ -3685,9 +3682,9 @@ class ColorPicker {
3685
3682
  if (!focusPrevented) {
3686
3683
  focus(pickerToggle);
3687
3684
  }
3688
- setAttribute(input, 'tabindex', '-1');
3685
+ setAttribute(input, tabIndex, '-1');
3689
3686
  if (menuToggle) {
3690
- setAttribute(menuToggle, 'tabindex', '-1');
3687
+ setAttribute(menuToggle, tabIndex, '-1');
3691
3688
  }
3692
3689
  }
3693
3690
  }
@@ -3701,7 +3698,10 @@ class ColorPicker {
3701
3698
  [...parent.children].forEach((el) => {
3702
3699
  if (el !== input) el.remove();
3703
3700
  });
3701
+
3702
+ removeAttribute(input, tabIndex);
3704
3703
  setElementStyle(input, { backgroundColor: '' });
3704
+
3705
3705
  ['txt-light', 'txt-dark'].forEach((c) => removeClass(parent, c));
3706
3706
  Data.remove(input, colorPickerString);
3707
3707
  }
@@ -3709,10 +3709,16 @@ class ColorPicker {
3709
3709
 
3710
3710
  ObjectAssign(ColorPicker, {
3711
3711
  Color,
3712
+ ColorPalette,
3712
3713
  Version,
3713
3714
  getInstance: getColorPickerInstance,
3714
3715
  init: initColorPicker,
3715
3716
  selector: colorPickerSelector,
3717
+ // utils important for render
3718
+ roundPart,
3719
+ setElementStyle,
3720
+ setAttribute,
3721
+ getBoundingClientRect,
3716
3722
  });
3717
3723
 
3718
3724
  let CPID = 0;
@@ -3720,8 +3726,9 @@ let CPID = 0;
3720
3726
  /**
3721
3727
  * `ColorPickerElement` Web Component.
3722
3728
  * @example
3723
- * <color-picker>
3724
- * <input type="text">
3729
+ * <label for="UNIQUE_ID">Label</label>
3730
+ * <color-picker data-format="hex" data-value="#075">
3731
+ * <input id="UNIQUE_ID" type="text" class="color-preview btn-appearance">
3725
3732
  * </color-picker>
3726
3733
  */
3727
3734
  class ColorPickerElement extends HTMLElement {
@@ -3802,9 +3809,11 @@ class ColorPickerElement extends HTMLElement {
3802
3809
  ObjectAssign(ColorPickerElement, {
3803
3810
  Color,
3804
3811
  ColorPicker,
3812
+ ColorPalette,
3813
+ getInstance: getColorPickerInstance,
3805
3814
  Version,
3806
3815
  });
3807
3816
 
3808
3817
  customElements.define('color-picker', ColorPickerElement);
3809
3818
 
3810
- export default ColorPickerElement;
3819
+ export { ColorPickerElement as default };