@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
@@ -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 };