@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,13 +1,13 @@
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
  */
6
6
  (function (global, factory) {
7
7
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
8
8
  typeof define === 'function' && define.amd ? define(factory) :
9
- (global = global || self, global.ColorPickerElement = factory());
10
- }(this, (function () { 'use strict';
9
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ColorPickerElement = factory());
10
+ })(this, (function () { 'use strict';
11
11
 
12
12
  /**
13
13
  * Returns the `document` or the `#document` element.
@@ -88,14 +88,9 @@
88
88
  const getAttribute = (element, attribute) => element.getAttribute(attribute);
89
89
 
90
90
  /**
91
- * Returns the `document.head` or the `<head>` element.
92
- *
93
- * @param {(Node | HTMLElement | Element | globalThis)=} node
94
- * @returns {HTMLElement | HTMLHeadElement}
91
+ * A global namespace for `document.head`.
95
92
  */
96
- function getDocumentHead(node) {
97
- return getDocument(node).head;
98
- }
93
+ const { head: documentHead } = document;
99
94
 
100
95
  /**
101
96
  * Shortcut for `window.getComputedStyle(element).propertyName`
@@ -124,13 +119,31 @@
124
119
  // @ts-ignore
125
120
  const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
126
121
 
122
+ /**
123
+ * Shortcut for `String.toLowerCase()`.
124
+ *
125
+ * @param {string} source input string
126
+ * @returns {string} lowercase output string
127
+ */
128
+ const toLowerCase = (source) => source.toLowerCase();
129
+
127
130
  /**
128
131
  * A list of explicit default non-color values.
129
132
  */
130
133
  const nonColors = ['transparent', 'currentColor', 'inherit', 'revert', 'initial'];
131
134
 
135
+ /**
136
+ * Round colour components, for all formats except HEX.
137
+ * @param {number} v one of the colour components
138
+ * @returns {number} the rounded number
139
+ */
140
+ function roundPart(v) {
141
+ const floor = Math.floor(v);
142
+ return v - floor < 0.5 ? floor : Math.round(v);
143
+ }
144
+
132
145
  // Color supported formats
133
- const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
146
+ const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsv', 'hwb'];
134
147
 
135
148
  // Hue angles
136
149
  const ANGLES = 'deg|rad|grad|turn';
@@ -152,10 +165,17 @@
152
165
  // Add angles to the mix
153
166
  const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
154
167
 
168
+ // Start & end
169
+ const START_MATCH = '(?:[\\s|\\(\\s|\\s\\(\\s]+)?';
170
+ const END_MATCH = '(?:[\\s|\\)\\s]+)?';
171
+ // Components separation
172
+ const SEP = '(?:[,|\\s]+)';
173
+ const SEP2 = '(?:[,|\\/\\s]*)?';
174
+
155
175
  // Actual matching.
156
176
  // Parentheses and commas are optional, but not required.
157
177
  // Whitespace can take the place of commas or opening paren
158
- const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
178
+ const PERMISSIVE_MATCH = `${START_MATCH}(${CSS_UNIT2})${SEP}(${CSS_UNIT})${SEP}(${CSS_UNIT})${SEP2}(${CSS_UNIT})?${END_MATCH}`;
159
179
 
160
180
  const matchers = {
161
181
  CSS_UNIT: new RegExp(CSS_UNIT2),
@@ -188,23 +208,22 @@
188
208
  return `${n}`.includes('%');
189
209
  }
190
210
 
191
- /**
192
- * Check to see if string passed in is an angle
193
- * @param {string} n testing string
194
- * @returns {boolean} the query result
195
- */
196
- function isAngle(n) {
197
- return ANGLES.split('|').some((a) => `${n}`.includes(a));
198
- }
199
-
200
211
  /**
201
212
  * Check to see if string passed is a web safe colour.
213
+ * @see https://stackoverflow.com/a/16994164
202
214
  * @param {string} color a colour name, EG: *red*
203
215
  * @returns {boolean} the query result
204
216
  */
205
217
  function isColorName(color) {
206
- return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
207
- && !/[0-9]/.test(color);
218
+ if (nonColors.includes(color)
219
+ || ['#', ...COLOR_FORMAT].some((f) => color.includes(f))) return false;
220
+
221
+ return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
222
+ setElementStyle(documentHead, { color });
223
+ const computedColor = getElementStyle(documentHead, 'color');
224
+ setElementStyle(documentHead, { color: '' });
225
+ return computedColor !== c;
226
+ });
208
227
  }
209
228
 
210
229
  /**
@@ -225,15 +244,15 @@
225
244
  */
226
245
  function bound01(N, max) {
227
246
  let n = N;
228
- if (isOnePointZero(n)) n = '100%';
229
-
230
- n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
247
+ if (isOnePointZero(N)) n = '100%';
231
248
 
232
- // Handle hue angles
233
- if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
249
+ const processPercent = isPercentage(n);
250
+ n = max === 360
251
+ ? parseFloat(n)
252
+ : Math.min(max, Math.max(0, parseFloat(n)));
234
253
 
235
254
  // Automatically convert percentage into number
236
- if (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
255
+ if (processPercent) n = (n * max) / 100;
237
256
 
238
257
  // Handle floating point rounding errors
239
258
  if (Math.abs(n - max) < 0.000001) {
@@ -244,11 +263,11 @@
244
263
  // If n is a hue given in degrees,
245
264
  // wrap around out-of-range values into [0, 360] range
246
265
  // then convert into [0, 1].
247
- n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max));
266
+ n = (n < 0 ? (n % max) + max : n % max) / max;
248
267
  } else {
249
268
  // If n not a hue given in degrees
250
269
  // Convert into [0, 1] range if it isn't already.
251
- n = (n % max) / parseFloat(String(max));
270
+ n = (n % max) / max;
252
271
  }
253
272
  return n;
254
273
  }
@@ -283,7 +302,6 @@
283
302
  * @returns {string}
284
303
  */
285
304
  function getRGBFromName(name) {
286
- const documentHead = getDocumentHead();
287
305
  setElementStyle(documentHead, { color: name });
288
306
  const colorName = getElementStyle(documentHead, 'color');
289
307
  setElementStyle(documentHead, { color: '' });
@@ -296,7 +314,7 @@
296
314
  * @returns {string} - the hexadecimal value
297
315
  */
298
316
  function convertDecimalToHex(d) {
299
- return Math.round(d * 255).toString(16);
317
+ return roundPart(d * 255).toString(16);
300
318
  }
301
319
 
302
320
  /**
@@ -382,6 +400,36 @@
382
400
  return p;
383
401
  }
384
402
 
403
+ /**
404
+ * Converts an HSL colour value to RGB.
405
+ *
406
+ * @param {number} h Hue Angle [0, 1]
407
+ * @param {number} s Saturation [0, 1]
408
+ * @param {number} l Lightness Angle [0, 1]
409
+ * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
410
+ */
411
+ function hslToRgb(h, s, l) {
412
+ let r = 0;
413
+ let g = 0;
414
+ let b = 0;
415
+
416
+ if (s === 0) {
417
+ // achromatic
418
+ g = l;
419
+ b = l;
420
+ r = l;
421
+ } else {
422
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
423
+ const p = 2 * l - q;
424
+ r = hueToRgb(p, q, h + 1 / 3);
425
+ g = hueToRgb(p, q, h);
426
+ b = hueToRgb(p, q, h - 1 / 3);
427
+ }
428
+ [r, g, b] = [r, g, b].map((x) => x * 255);
429
+
430
+ return { r, g, b };
431
+ }
432
+
385
433
  /**
386
434
  * Returns an HWB colour object from an RGB colour object.
387
435
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
@@ -444,36 +492,6 @@
444
492
  return { r, g, b };
445
493
  }
446
494
 
447
- /**
448
- * Converts an HSL colour value to RGB.
449
- *
450
- * @param {number} h Hue Angle [0, 1]
451
- * @param {number} s Saturation [0, 1]
452
- * @param {number} l Lightness Angle [0, 1]
453
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
454
- */
455
- function hslToRgb(h, s, l) {
456
- let r = 0;
457
- let g = 0;
458
- let b = 0;
459
-
460
- if (s === 0) {
461
- // achromatic
462
- g = l;
463
- b = l;
464
- r = l;
465
- } else {
466
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
467
- const p = 2 * l - q;
468
- r = hueToRgb(p, q, h + 1 / 3);
469
- g = hueToRgb(p, q, h);
470
- b = hueToRgb(p, q, h - 1 / 3);
471
- }
472
- [r, g, b] = [r, g, b].map((x) => x * 255);
473
-
474
- return { r, g, b };
475
- }
476
-
477
495
  /**
478
496
  * Converts an RGB colour value to HSV.
479
497
  *
@@ -529,10 +547,11 @@
529
547
  const q = v * (1 - f * s);
530
548
  const t = v * (1 - (1 - f) * s);
531
549
  const mod = i % 6;
532
- const r = [v, q, p, p, t, v][mod];
533
- const g = [t, v, v, q, p, p][mod];
534
- const b = [p, p, t, v, v, q][mod];
535
- return { r: r * 255, g: g * 255, b: b * 255 };
550
+ let r = [v, q, p, p, t, v][mod];
551
+ let g = [t, v, v, q, p, p][mod];
552
+ let b = [p, p, t, v, v, q][mod];
553
+ [r, g, b] = [r, g, b].map((n) => n * 255);
554
+ return { r, g, b };
536
555
  }
537
556
 
538
557
  /**
@@ -548,15 +567,15 @@
548
567
  */
549
568
  function rgbToHex(r, g, b, allow3Char) {
550
569
  const hex = [
551
- pad2(Math.round(r).toString(16)),
552
- pad2(Math.round(g).toString(16)),
553
- pad2(Math.round(b).toString(16)),
570
+ pad2(roundPart(r).toString(16)),
571
+ pad2(roundPart(g).toString(16)),
572
+ pad2(roundPart(b).toString(16)),
554
573
  ];
555
574
 
556
575
  // Return a 3 character hex if possible
557
576
  if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
558
577
  && hex[1].charAt(0) === hex[1].charAt(1)
559
- && hex[2].charAt(0) === hex[2].charAt(1)) {
578
+ && hex[2].charAt(0) === hex[2].charAt(1)) {
560
579
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
561
580
  }
562
581
 
@@ -575,48 +594,33 @@
575
594
  */
576
595
  function rgbaToHex(r, g, b, a, allow4Char) {
577
596
  const hex = [
578
- pad2(Math.round(r).toString(16)),
579
- pad2(Math.round(g).toString(16)),
580
- pad2(Math.round(b).toString(16)),
597
+ pad2(roundPart(r).toString(16)),
598
+ pad2(roundPart(g).toString(16)),
599
+ pad2(roundPart(b).toString(16)),
581
600
  pad2(convertDecimalToHex(a)),
582
601
  ];
583
602
 
584
603
  // Return a 4 character hex if possible
585
604
  if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
586
605
  && hex[1].charAt(0) === hex[1].charAt(1)
587
- && hex[2].charAt(0) === hex[2].charAt(1)
588
- && hex[3].charAt(0) === hex[3].charAt(1)) {
606
+ && hex[2].charAt(0) === hex[2].charAt(1)
607
+ && hex[3].charAt(0) === hex[3].charAt(1)) {
589
608
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
590
609
  }
591
610
  return hex.join('');
592
611
  }
593
612
 
594
- /**
595
- * Returns a colour object corresponding to a given number.
596
- * @param {number} color input number
597
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
598
- */
599
- function numberInputToObject(color) {
600
- /* eslint-disable no-bitwise */
601
- return {
602
- r: color >> 16,
603
- g: (color & 0xff00) >> 8,
604
- b: color & 0xff,
605
- };
606
- /* eslint-enable no-bitwise */
607
- }
608
-
609
613
  /**
610
614
  * Permissive string parsing. Take in a number of formats, and output an object
611
615
  * based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
612
616
  * @param {string} input colour value in any format
613
- * @returns {Record<string, (number | string)> | false} an object matching the RegExp
617
+ * @returns {Record<string, (number | string | boolean)> | false} an object matching the RegExp
614
618
  */
615
619
  function stringInputToObject(input) {
616
- let color = input.trim().toLowerCase();
620
+ let color = toLowerCase(input.trim());
617
621
  if (color.length === 0) {
618
622
  return {
619
- r: 0, g: 0, b: 0, a: 0,
623
+ r: 0, g: 0, b: 0, a: 1,
620
624
  };
621
625
  }
622
626
  let named = false;
@@ -624,11 +628,9 @@
624
628
  color = getRGBFromName(color);
625
629
  named = true;
626
630
  } else if (nonColors.includes(color)) {
627
- const isTransparent = color === 'transparent';
628
- const rgb = isTransparent ? 0 : 255;
629
- const a = isTransparent ? 0 : 1;
631
+ const a = color === 'transparent' ? 0 : 1;
630
632
  return {
631
- r: rgb, g: rgb, b: rgb, a, format: 'rgb',
633
+ r: 0, g: 0, b: 0, a, format: 'rgb', ok: true,
632
634
  };
633
635
  }
634
636
 
@@ -668,7 +670,6 @@
668
670
  g: parseIntFromHex(m2),
669
671
  b: parseIntFromHex(m3),
670
672
  a: convertHexToDecimal(m4),
671
- // format: named ? 'rgb' : 'hex8',
672
673
  format: named ? 'rgb' : 'hex',
673
674
  };
674
675
  }
@@ -732,6 +733,7 @@
732
733
  function inputToRGB(input) {
733
734
  let rgb = { r: 0, g: 0, b: 0 };
734
735
  let color = input;
736
+ /** @type {string | number} */
735
737
  let a = 1;
736
738
  let s = null;
737
739
  let v = null;
@@ -739,8 +741,11 @@
739
741
  let w = null;
740
742
  let b = null;
741
743
  let h = null;
744
+ let r = null;
745
+ let g = null;
742
746
  let ok = false;
743
- let format = 'hex';
747
+ const inputFormat = typeof color === 'object' && color.format;
748
+ let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
744
749
 
745
750
  if (typeof input === 'string') {
746
751
  // @ts-ignore -- this now is converted to object
@@ -749,7 +754,10 @@
749
754
  }
750
755
  if (typeof color === 'object') {
751
756
  if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
752
- rgb = { r: color.r, g: color.g, b: color.b }; // RGB values in [0, 255] range
757
+ ({ r, g, b } = color);
758
+ // RGB values now are all in [0, 255] range
759
+ [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255);
760
+ rgb = { r, g, b };
753
761
  ok = true;
754
762
  format = 'rgb';
755
763
  } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
@@ -778,14 +786,17 @@
778
786
  format = 'hwb';
779
787
  }
780
788
  if (isValidCSSUnit(color.a)) {
781
- a = color.a;
782
- a = isPercentage(`${a}`) ? bound01(a, 100) : a;
789
+ a = color.a; // @ts-ignore -- `parseFloat` works with numbers too
790
+ a = isPercentage(`${a}`) || parseFloat(a) > 1 ? bound01(a, 100) : a;
783
791
  }
784
792
  }
793
+ if (typeof color === 'undefined') {
794
+ ok = true;
795
+ }
785
796
 
786
797
  return {
787
- ok, // @ts-ignore
788
- format: color.format || format,
798
+ ok,
799
+ format,
789
800
  r: Math.min(255, Math.max(rgb.r, 0)),
790
801
  g: Math.min(255, Math.max(rgb.g, 0)),
791
802
  b: Math.min(255, Math.max(rgb.b, 0)),
@@ -814,7 +825,8 @@
814
825
  color = inputToRGB(color);
815
826
  }
816
827
  if (typeof color === 'number') {
817
- color = numberInputToObject(color);
828
+ const len = `${color}`.length;
829
+ color = `#${(len === 2 ? '0' : '00')}${color}`;
818
830
  }
819
831
  const {
820
832
  r, g, b, a, ok, format,
@@ -824,7 +836,7 @@
824
836
  const self = this;
825
837
 
826
838
  /** @type {CP.ColorInput} */
827
- self.originalInput = color;
839
+ self.originalInput = input;
828
840
  /** @type {number} */
829
841
  self.r = r;
830
842
  /** @type {number} */
@@ -837,14 +849,6 @@
837
849
  self.ok = ok;
838
850
  /** @type {CP.ColorFormats} */
839
851
  self.format = configFormat || format;
840
-
841
- // Don't let the range of [0,255] come back in [0,1].
842
- // Potentially lose a little bit of precision here, but will fix issues where
843
- // .5 gets interpreted as half of the total, instead of half of 1
844
- // If it was supposed to be 128, this was already taken care of by `inputToRgb`
845
- if (r < 1) self.r = Math.round(r);
846
- if (g < 1) self.g = Math.round(g);
847
- if (b < 1) self.b = Math.round(b);
848
852
  }
849
853
 
850
854
  /**
@@ -860,7 +864,7 @@
860
864
  * @returns {boolean} the query result
861
865
  */
862
866
  get isDark() {
863
- return this.brightness < 128;
867
+ return this.brightness < 120;
864
868
  }
865
869
 
866
870
  /**
@@ -912,13 +916,9 @@
912
916
  const {
913
917
  r, g, b, a,
914
918
  } = this;
915
- const [R, G, B] = [r, g, b].map((x) => Math.round(x));
916
919
 
917
920
  return {
918
- r: R,
919
- g: G,
920
- b: B,
921
- a: Math.round(a * 100) / 100,
921
+ r, g, b, a: roundPart(a * 100) / 100,
922
922
  };
923
923
  }
924
924
 
@@ -932,10 +932,11 @@
932
932
  const {
933
933
  r, g, b, a,
934
934
  } = this.toRgb();
935
+ const [R, G, B] = [r, g, b].map(roundPart);
935
936
 
936
937
  return a === 1
937
- ? `rgb(${r}, ${g}, ${b})`
938
- : `rgba(${r}, ${g}, ${b}, ${a})`;
938
+ ? `rgb(${R}, ${G}, ${B})`
939
+ : `rgba(${R}, ${G}, ${B}, ${a})`;
939
940
  }
940
941
 
941
942
  /**
@@ -948,9 +949,10 @@
948
949
  const {
949
950
  r, g, b, a,
950
951
  } = this.toRgb();
951
- const A = a === 1 ? '' : ` / ${Math.round(a * 100)}%`;
952
+ const [R, G, B] = [r, g, b].map(roundPart);
953
+ const A = a === 1 ? '' : ` / ${roundPart(a * 100)}%`;
952
954
 
953
- return `rgb(${r} ${g} ${b}${A})`;
955
+ return `rgb(${R} ${G} ${B}${A})`;
954
956
  }
955
957
 
956
958
  /**
@@ -1043,10 +1045,10 @@
1043
1045
  let {
1044
1046
  h, s, l, a,
1045
1047
  } = this.toHsl();
1046
- h = Math.round(h * 360);
1047
- s = Math.round(s * 100);
1048
- l = Math.round(l * 100);
1049
- a = Math.round(a * 100) / 100;
1048
+ h = roundPart(h * 360);
1049
+ s = roundPart(s * 100);
1050
+ l = roundPart(l * 100);
1051
+ a = roundPart(a * 100) / 100;
1050
1052
 
1051
1053
  return a === 1
1052
1054
  ? `hsl(${h}, ${s}%, ${l}%)`
@@ -1063,11 +1065,11 @@
1063
1065
  let {
1064
1066
  h, s, l, a,
1065
1067
  } = this.toHsl();
1066
- h = Math.round(h * 360);
1067
- s = Math.round(s * 100);
1068
- l = Math.round(l * 100);
1069
- a = Math.round(a * 100);
1070
- const A = a < 100 ? ` / ${Math.round(a)}%` : '';
1068
+ h = roundPart(h * 360);
1069
+ s = roundPart(s * 100);
1070
+ l = roundPart(l * 100);
1071
+ a = roundPart(a * 100);
1072
+ const A = a < 100 ? ` / ${roundPart(a)}%` : '';
1071
1073
 
1072
1074
  return `hsl(${h}deg ${s}% ${l}%${A})`;
1073
1075
  }
@@ -1094,11 +1096,11 @@
1094
1096
  let {
1095
1097
  h, w, b, a,
1096
1098
  } = this.toHwb();
1097
- h = Math.round(h * 360);
1098
- w = Math.round(w * 100);
1099
- b = Math.round(b * 100);
1100
- a = Math.round(a * 100);
1101
- const A = a < 100 ? ` / ${Math.round(a)}%` : '';
1099
+ h = roundPart(h * 360);
1100
+ w = roundPart(w * 100);
1101
+ b = roundPart(b * 100);
1102
+ a = roundPart(a * 100);
1103
+ const A = a < 100 ? ` / ${roundPart(a)}%` : '';
1102
1104
 
1103
1105
  return `hwb(${h}deg ${w}% ${b}%${A})`;
1104
1106
  }
@@ -1224,6 +1226,7 @@
1224
1226
  isOnePointZero,
1225
1227
  isPercentage,
1226
1228
  isValidCSSUnit,
1229
+ isColorName,
1227
1230
  pad2,
1228
1231
  clamp01,
1229
1232
  bound01,
@@ -1241,9 +1244,11 @@
1241
1244
  hueToRgb,
1242
1245
  hwbToRgb,
1243
1246
  parseIntFromHex,
1244
- numberInputToObject,
1245
1247
  stringInputToObject,
1246
1248
  inputToRGB,
1249
+ roundPart,
1250
+ getElementStyle,
1251
+ setElementStyle,
1247
1252
  ObjectAssign,
1248
1253
  });
1249
1254
 
@@ -1373,24 +1378,6 @@
1373
1378
  */
1374
1379
  const ariaValueNow = 'aria-valuenow';
1375
1380
 
1376
- /**
1377
- * A global namespace for aria-haspopup.
1378
- * @type {string}
1379
- */
1380
- const ariaHasPopup = 'aria-haspopup';
1381
-
1382
- /**
1383
- * A global namespace for aria-hidden.
1384
- * @type {string}
1385
- */
1386
- const ariaHidden = 'aria-hidden';
1387
-
1388
- /**
1389
- * A global namespace for aria-labelledby.
1390
- * @type {string}
1391
- */
1392
- const ariaLabelledBy = 'aria-labelledby';
1393
-
1394
1381
  /**
1395
1382
  * A global namespace for `ArrowDown` key.
1396
1383
  * @type {string} e.which = 40 equivalent
@@ -1517,37 +1504,6 @@
1517
1504
  */
1518
1505
  const focusoutEvent = 'focusout';
1519
1506
 
1520
- // @ts-ignore
1521
- const { userAgentData: uaDATA } = navigator;
1522
-
1523
- /**
1524
- * A global namespace for `userAgentData` object.
1525
- */
1526
- const userAgentData = uaDATA;
1527
-
1528
- const { userAgent: userAgentString } = navigator;
1529
-
1530
- /**
1531
- * A global namespace for `navigator.userAgent` string.
1532
- */
1533
- const userAgent = userAgentString;
1534
-
1535
- const mobileBrands = /iPhone|iPad|iPod|Android/i;
1536
- let isMobileCheck = false;
1537
-
1538
- if (userAgentData) {
1539
- isMobileCheck = userAgentData.brands
1540
- .some((/** @type {Record<String, any>} */x) => mobileBrands.test(x.brand));
1541
- } else {
1542
- isMobileCheck = mobileBrands.test(userAgent);
1543
- }
1544
-
1545
- /**
1546
- * A global `boolean` for mobile detection.
1547
- * @type {boolean}
1548
- */
1549
- const isMobile = isMobileCheck;
1550
-
1551
1507
  /**
1552
1508
  * Returns the `document.documentElement` or the `<html>` element.
1553
1509
  *
@@ -1732,30 +1688,6 @@
1732
1688
  return lookUp.getElementsByClassName(selector);
1733
1689
  }
1734
1690
 
1735
- /**
1736
- * This is a shortie for `document.createElementNS` method
1737
- * which allows you to create a new `HTMLElement` for a given `tagName`
1738
- * or based on an object with specific non-readonly attributes:
1739
- * `id`, `className`, `textContent`, `style`, etc.
1740
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
1741
- *
1742
- * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
1743
- * @param {Record<string, string> | string} param `tagName` or object
1744
- * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
1745
- */
1746
- function createElementNS(namespace, param) {
1747
- if (typeof param === 'string') {
1748
- return getDocument().createElementNS(namespace, param);
1749
- }
1750
-
1751
- const { tagName } = param;
1752
- const attr = { ...param };
1753
- const newElement = createElementNS(namespace, tagName);
1754
- delete attr.tagName;
1755
- ObjectAssign(newElement, attr);
1756
- return newElement;
1757
- }
1758
-
1759
1691
  /**
1760
1692
  * Shortcut for the `Element.dispatchEvent(Event)` method.
1761
1693
  *
@@ -1879,14 +1811,6 @@
1879
1811
  */
1880
1812
  const ObjectKeys = (obj) => Object.keys(obj);
1881
1813
 
1882
- /**
1883
- * Shortcut for `String.toLowerCase()`.
1884
- *
1885
- * @param {string} source input string
1886
- * @returns {string} lowercase output string
1887
- */
1888
- const toLowerCase = (source) => source.toLowerCase();
1889
-
1890
1814
  /**
1891
1815
  * Utility to normalize component options.
1892
1816
  *
@@ -1990,6 +1914,80 @@
1990
1914
  */
1991
1915
  const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
1992
1916
 
1917
+ /**
1918
+ * @class
1919
+ * Returns a color palette with a given set of parameters.
1920
+ * @example
1921
+ * new ColorPalette(0, 12, 10);
1922
+ * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: Array<Color> }
1923
+ */
1924
+ class ColorPalette {
1925
+ /**
1926
+ * The `hue` parameter is optional, which would be set to 0.
1927
+ * @param {number[]} args represeinting hue, hueSteps, lightSteps
1928
+ * * `args.hue` the starting Hue [0, 360]
1929
+ * * `args.hueSteps` Hue Steps Count [5, 24]
1930
+ * * `args.lightSteps` Lightness Steps Count [5, 12]
1931
+ */
1932
+ constructor(...args) {
1933
+ let hue = 0;
1934
+ let hueSteps = 12;
1935
+ let lightSteps = 10;
1936
+ let lightnessArray = [0.5];
1937
+
1938
+ if (args.length === 3) {
1939
+ [hue, hueSteps, lightSteps] = args;
1940
+ } else if (args.length === 2) {
1941
+ [hueSteps, lightSteps] = args;
1942
+ if ([hueSteps, lightSteps].some((n) => n < 1)) {
1943
+ throw TypeError('ColorPalette: when 2 arguments used, both must be larger than 0.');
1944
+ }
1945
+ } else {
1946
+ throw TypeError('ColorPalette requires minimum 2 arguments');
1947
+ }
1948
+
1949
+ /** @type {Color[]} */
1950
+ const colors = [];
1951
+
1952
+ const hueStep = 360 / hueSteps;
1953
+ const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
1954
+ const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
1955
+
1956
+ let lightStep = 0.25;
1957
+ lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
1958
+ lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
1959
+ lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
1960
+ lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
1961
+ lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
1962
+ lightStep = lightSteps > 13 ? estimatedStep : lightStep;
1963
+
1964
+ // light tints
1965
+ for (let i = 1; i < half + 1; i += 1) {
1966
+ lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
1967
+ }
1968
+
1969
+ // dark tints
1970
+ for (let i = 1; i < lightSteps - half; i += 1) {
1971
+ lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
1972
+ }
1973
+
1974
+ // feed `colors` Array
1975
+ for (let i = 0; i < hueSteps; i += 1) {
1976
+ const currentHue = ((hue + i * hueStep) % 360) / 360;
1977
+ lightnessArray.forEach((l) => {
1978
+ colors.push(new Color({ h: currentHue, s: 1, l }));
1979
+ });
1980
+ }
1981
+
1982
+ this.hue = hue;
1983
+ this.hueSteps = hueSteps;
1984
+ this.lightSteps = lightSteps;
1985
+ this.colors = colors;
1986
+ }
1987
+ }
1988
+
1989
+ ObjectAssign(ColorPalette, { Color });
1990
+
1993
1991
  /** @type {Record<string, string>} */
1994
1992
  const colorPickerLabels = {
1995
1993
  pickerLabel: 'Colour Picker',
@@ -2017,6 +2015,22 @@
2017
2015
  */
2018
2016
  const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
2019
2017
 
2018
+ const tabIndex = 'tabindex';
2019
+
2020
+ /**
2021
+ * Check if a string is valid JSON string.
2022
+ * @param {string} str the string input
2023
+ * @returns {boolean} the query result
2024
+ */
2025
+ function isValidJSON(str) {
2026
+ try {
2027
+ JSON.parse(str);
2028
+ } catch (e) {
2029
+ return false;
2030
+ }
2031
+ return true;
2032
+ }
2033
+
2020
2034
  /**
2021
2035
  * Shortcut for `String.toUpperCase()`.
2022
2036
  *
@@ -2025,6 +2039,48 @@
2025
2039
  */
2026
2040
  const toUpperCase = (source) => source.toUpperCase();
2027
2041
 
2042
+ /**
2043
+ * A global namespace for aria-haspopup.
2044
+ * @type {string}
2045
+ */
2046
+ const ariaHasPopup = 'aria-haspopup';
2047
+
2048
+ /**
2049
+ * A global namespace for aria-hidden.
2050
+ * @type {string}
2051
+ */
2052
+ const ariaHidden = 'aria-hidden';
2053
+
2054
+ /**
2055
+ * A global namespace for aria-labelledby.
2056
+ * @type {string}
2057
+ */
2058
+ const ariaLabelledBy = 'aria-labelledby';
2059
+
2060
+ /**
2061
+ * This is a shortie for `document.createElementNS` method
2062
+ * which allows you to create a new `HTMLElement` for a given `tagName`
2063
+ * or based on an object with specific non-readonly attributes:
2064
+ * `id`, `className`, `textContent`, `style`, etc.
2065
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
2066
+ *
2067
+ * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
2068
+ * @param {Record<string, string> | string} param `tagName` or object
2069
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
2070
+ */
2071
+ function createElementNS(namespace, param) {
2072
+ if (typeof param === 'string') {
2073
+ return getDocument().createElementNS(namespace, param);
2074
+ }
2075
+
2076
+ const { tagName } = param;
2077
+ const attr = { ...param };
2078
+ const newElement = createElementNS(namespace, tagName);
2079
+ delete attr.tagName;
2080
+ ObjectAssign(newElement, attr);
2081
+ return newElement;
2082
+ }
2083
+
2028
2084
  const vHidden = 'v-hidden';
2029
2085
 
2030
2086
  /**
@@ -2081,7 +2137,6 @@
2081
2137
  max,
2082
2138
  step,
2083
2139
  });
2084
- // }
2085
2140
  colorForm.append(cInputLabel, cInput);
2086
2141
  });
2087
2142
  return colorForm;
@@ -2170,10 +2225,8 @@
2170
2225
  const {
2171
2226
  i, c, l, min, max,
2172
2227
  } = template;
2173
- // const hidden = i === 2 && format === 'hwb' ? ' v-hidden' : '';
2174
2228
  const control = createElement({
2175
2229
  tagName: 'div',
2176
- // className: `color-control${hidden}`,
2177
2230
  className: 'color-control',
2178
2231
  });
2179
2232
  setAttribute(control, 'role', 'presentation');
@@ -2193,7 +2246,7 @@
2193
2246
 
2194
2247
  setAttribute(knob, ariaLabel, l);
2195
2248
  setAttribute(knob, 'role', 'slider');
2196
- setAttribute(knob, 'tabindex', '0');
2249
+ setAttribute(knob, tabIndex, '0');
2197
2250
  setAttribute(knob, ariaValueMin, `${min}`);
2198
2251
  setAttribute(knob, ariaValueMax, `${max}`);
2199
2252
  control.append(knob);
@@ -2204,64 +2257,14 @@
2204
2257
  }
2205
2258
 
2206
2259
  /**
2207
- * @class
2208
- * Returns a color palette with a given set of parameters.
2209
- * @example
2210
- * new ColorPalette(0, 12, 10);
2211
- * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
2260
+ * Helps setting CSS variables to the color-menu.
2261
+ * @param {HTMLElement} element
2262
+ * @param {Record<string,any>} props
2212
2263
  */
2213
- class ColorPalette {
2214
- /**
2215
- * The `hue` parameter is optional, which would be set to 0.
2216
- * @param {number[]} args represeinting hue, hueSteps, lightSteps
2217
- * * `args.hue` the starting Hue [0, 360]
2218
- * * `args.hueSteps` Hue Steps Count [5, 13]
2219
- * * `args.lightSteps` Lightness Steps Count [8, 10]
2220
- */
2221
- constructor(...args) {
2222
- let hue = 0;
2223
- let hueSteps = 12;
2224
- let lightSteps = 10;
2225
- let lightnessArray = [0.5];
2226
-
2227
- if (args.length === 3) {
2228
- [hue, hueSteps, lightSteps] = args;
2229
- } else if (args.length === 2) {
2230
- [hueSteps, lightSteps] = args;
2231
- } else {
2232
- throw TypeError('The ColorPalette requires minimum 2 arguments');
2233
- }
2234
-
2235
- /** @type {string[]} */
2236
- const colors = [];
2237
-
2238
- const hueStep = 360 / hueSteps;
2239
- const lightStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
2240
- const half = Math.round((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
2241
-
2242
- // light tints
2243
- for (let i = 0; i < half; i += 1) {
2244
- lightnessArray = [...lightnessArray, (0.5 + lightStep * (i + 1))];
2245
- }
2246
-
2247
- // dark tints
2248
- for (let i = 0; i < lightSteps - half - 1; i += 1) {
2249
- lightnessArray = [(0.5 - lightStep * (i + 1)), ...lightnessArray];
2250
- }
2251
-
2252
- // feed `colors` Array
2253
- for (let i = 0; i < hueSteps; i += 1) {
2254
- const currentHue = ((hue + i * hueStep) % 360) / 360;
2255
- lightnessArray.forEach((l) => {
2256
- colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
2257
- });
2258
- }
2259
-
2260
- this.hue = hue;
2261
- this.hueSteps = hueSteps;
2262
- this.lightSteps = lightSteps;
2263
- this.colors = colors;
2264
- }
2264
+ function setCSSProperties(element, props) {
2265
+ ObjectKeys(props).forEach((prop) => {
2266
+ element.style.setProperty(prop, props[prop]);
2267
+ });
2265
2268
  }
2266
2269
 
2267
2270
  /**
@@ -2281,68 +2284,64 @@
2281
2284
  colorsArray = colorsArray instanceof Array ? colorsArray : [];
2282
2285
  const colorsCount = colorsArray.length;
2283
2286
  const { lightSteps } = isPalette ? colorsSource : { lightSteps: null };
2284
- let fit = lightSteps
2285
- || Math.max(...[5, 6, 7, 8, 9, 10].filter((x) => colorsCount > (x * 2) && !(colorsCount % x)));
2286
- fit = Number.isFinite(fit) ? fit : 5;
2287
+ const fit = lightSteps || [9, 10].find((x) => colorsCount > x * 2 && !(colorsCount % x)) || 5;
2287
2288
  const isMultiLine = isOptionsMenu && colorsCount > fit;
2288
- let rowCountHover = 1;
2289
- rowCountHover = isMultiLine && colorsCount < 27 ? 2 : rowCountHover;
2290
- rowCountHover = colorsCount >= 27 ? 3 : rowCountHover;
2291
- rowCountHover = colorsCount >= 36 ? 4 : rowCountHover;
2292
- rowCountHover = colorsCount >= 45 ? 5 : rowCountHover;
2293
- const rowCount = rowCountHover - (colorsCount < 27 ? 1 : 2);
2294
- const isScrollable = isMultiLine && colorsCount > rowCountHover * fit;
2289
+ let rowCountHover = 2;
2290
+ rowCountHover = isMultiLine && colorsCount >= fit * 2 ? 3 : rowCountHover;
2291
+ rowCountHover = colorsCount >= fit * 3 ? 4 : rowCountHover;
2292
+ rowCountHover = colorsCount >= fit * 4 ? 5 : rowCountHover;
2293
+ const rowCount = rowCountHover - (colorsCount < fit * 3 ? 1 : 2);
2294
+ const isScrollable = isMultiLine && colorsCount > rowCount * fit;
2295
2295
  let finalClass = menuClass;
2296
2296
  finalClass += isScrollable ? ' scrollable' : '';
2297
2297
  finalClass += isMultiLine ? ' multiline' : '';
2298
2298
  const gap = isMultiLine ? '1px' : '0.25rem';
2299
2299
  let optionSize = isMultiLine ? 1.75 : 2;
2300
- optionSize = !(colorsCount % 10) && isMultiLine ? 1.5 : optionSize;
2300
+ optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2301
2301
  const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2302
2302
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2303
- const gridTemplateColumns = `repeat(${fit}, ${optionSize}rem)`;
2304
- const gridTemplateRows = `repeat(auto-fill, ${optionSize}rem)`;
2305
-
2303
+ /** @type {HTMLUListElement} */
2304
+ // @ts-ignore -- <UL> is an `HTMLElement`
2306
2305
  const menu = createElement({
2307
2306
  tagName: 'ul',
2308
2307
  className: finalClass,
2309
2308
  });
2310
2309
  setAttribute(menu, 'role', 'listbox');
2311
- setAttribute(menu, ariaLabel, `${menuLabel}`);
2312
-
2313
- if (isOptionsMenu) {
2314
- if (isScrollable) {
2315
- const styleText = 'this.style.height=';
2316
- setAttribute(menu, 'onmouseout', `${styleText}'${menuHeight}'`);
2317
- setAttribute(menu, 'onmouseover', `${styleText}'${menuHeightHover}'`);
2318
- }
2319
- const menuStyle = {
2320
- height: isScrollable ? menuHeight : '', gridTemplateColumns, gridTemplateRows, gap,
2321
- };
2322
- setElementStyle(menu, menuStyle);
2310
+ setAttribute(menu, ariaLabel, menuLabel);
2311
+
2312
+ if (isScrollable) {
2313
+ setCSSProperties(menu, {
2314
+ '--grid-item-size': `${optionSize}rem`,
2315
+ '--grid-fit': fit,
2316
+ '--grid-gap': gap,
2317
+ '--grid-height': menuHeight,
2318
+ '--grid-hover-height': menuHeightHover,
2319
+ });
2323
2320
  }
2324
2321
 
2325
2322
  colorsArray.forEach((x) => {
2326
- const [value, label] = x.trim().split(':');
2327
- const xRealColor = new Color(value, format).toString();
2328
- const isActive = xRealColor === getAttribute(input, 'value');
2323
+ let [value, label] = typeof x === 'string' ? x.trim().split(':') : [];
2324
+ if (x instanceof Color) {
2325
+ value = x.toHexString();
2326
+ label = value;
2327
+ }
2328
+ const color = new Color(x instanceof Color ? x : value, format);
2329
+ const isActive = color.toString() === getAttribute(input, 'value');
2329
2330
  const active = isActive ? ' active' : '';
2330
2331
 
2331
2332
  const option = createElement({
2332
2333
  tagName: 'li',
2333
2334
  className: `color-option${active}`,
2334
- innerText: `${label || x}`,
2335
+ innerText: `${label || value}`,
2335
2336
  });
2336
2337
 
2337
- setAttribute(option, 'tabindex', '0');
2338
+ setAttribute(option, tabIndex, '0');
2338
2339
  setAttribute(option, 'data-value', `${value}`);
2339
2340
  setAttribute(option, 'role', 'option');
2340
2341
  setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2341
2342
 
2342
2343
  if (isOptionsMenu) {
2343
- setElementStyle(option, {
2344
- width: `${optionSize}rem`, height: `${optionSize}rem`, backgroundColor: x,
2345
- });
2344
+ setElementStyle(option, { backgroundColor: value });
2346
2345
  }
2347
2346
 
2348
2347
  menu.append(option);
@@ -2351,55 +2350,10 @@
2351
2350
  }
2352
2351
 
2353
2352
  /**
2354
- * Check if a string is valid JSON string.
2355
- * @param {string} str the string input
2356
- * @returns {boolean} the query result
2357
- */
2358
- function isValidJSON(str) {
2359
- try {
2360
- JSON.parse(str);
2361
- } catch (e) {
2362
- return false;
2363
- }
2364
- return true;
2365
- }
2366
-
2367
- var version = "0.0.1alpha2";
2368
-
2369
- // @ts-ignore
2370
-
2371
- const Version = version;
2372
-
2373
- // ColorPicker GC
2374
- // ==============
2375
- const colorPickerString = 'color-picker';
2376
- const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2377
- const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2378
- const colorPickerDefaults = {
2379
- componentLabels: colorPickerLabels,
2380
- colorLabels: colorNames,
2381
- format: 'rgb',
2382
- colorPresets: undefined,
2383
- colorKeywords: nonColors,
2384
- };
2385
-
2386
- // ColorPicker Static Methods
2387
- // ==========================
2388
-
2389
- /** @type {CP.GetInstance<ColorPicker>} */
2390
- const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2391
-
2392
- /** @type {CP.InitCallback<ColorPicker>} */
2393
- const initColorPicker = (element) => new ColorPicker(element);
2394
-
2395
- // ColorPicker Private Methods
2396
- // ===========================
2397
-
2398
- /**
2399
- * Generate HTML markup and update instance properties.
2400
- * @param {ColorPicker} self
2401
- */
2402
- function initCallback(self) {
2353
+ * Generate HTML markup and update instance properties.
2354
+ * @param {CP.ColorPicker} self
2355
+ */
2356
+ function setMarkup(self) {
2403
2357
  const {
2404
2358
  input, parent, format, id, componentLabels, colorKeywords, colorPresets,
2405
2359
  } = self;
@@ -2414,9 +2368,7 @@
2414
2368
  self.color = new Color(color, format);
2415
2369
 
2416
2370
  // set initial controls dimensions
2417
- // make the controls smaller on mobile
2418
- const dropClass = isMobile ? ' mobile' : '';
2419
- const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
2371
+ const formatString = format === 'hex' ? hexLabel : toUpperCase(format);
2420
2372
 
2421
2373
  const pickerBtn = createElement({
2422
2374
  id: `picker-btn-${id}`,
@@ -2433,7 +2385,7 @@
2433
2385
 
2434
2386
  const pickerDropdown = createElement({
2435
2387
  tagName: 'div',
2436
- className: `color-dropdown picker${dropClass}`,
2388
+ className: 'color-dropdown picker',
2437
2389
  });
2438
2390
  setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
2439
2391
  setAttribute(pickerDropdown, 'role', 'group');
@@ -2449,7 +2401,7 @@
2449
2401
  if (colorKeywords || colorPresets) {
2450
2402
  const presetsDropdown = createElement({
2451
2403
  tagName: 'div',
2452
- className: `color-dropdown scrollable menu${dropClass}`,
2404
+ className: 'color-dropdown scrollable menu',
2453
2405
  });
2454
2406
 
2455
2407
  // color presets
@@ -2469,7 +2421,7 @@
2469
2421
  tagName: 'button',
2470
2422
  className: 'menu-toggle btn-appearance',
2471
2423
  });
2472
- setAttribute(presetsBtn, 'tabindex', '-1');
2424
+ setAttribute(presetsBtn, tabIndex, '-1');
2473
2425
  setAttribute(presetsBtn, ariaExpanded, 'false');
2474
2426
  setAttribute(presetsBtn, ariaHasPopup, 'true');
2475
2427
 
@@ -2496,9 +2448,40 @@
2496
2448
  if (colorKeywords && nonColors.includes(colorValue)) {
2497
2449
  self.value = colorValue;
2498
2450
  }
2499
- setAttribute(input, 'tabindex', '-1');
2451
+ setAttribute(input, tabIndex, '-1');
2500
2452
  }
2501
2453
 
2454
+ var version = "0.0.2alpha1";
2455
+
2456
+ // @ts-ignore
2457
+
2458
+ const Version = version;
2459
+
2460
+ // ColorPicker GC
2461
+ // ==============
2462
+ const colorPickerString = 'color-picker';
2463
+ const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2464
+ const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2465
+ const colorPickerDefaults = {
2466
+ componentLabels: colorPickerLabels,
2467
+ colorLabels: colorNames,
2468
+ format: 'rgb',
2469
+ colorPresets: false,
2470
+ colorKeywords: false,
2471
+ };
2472
+
2473
+ // ColorPicker Static Methods
2474
+ // ==========================
2475
+
2476
+ /** @type {CP.GetInstance<ColorPicker>} */
2477
+ const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2478
+
2479
+ /** @type {CP.InitCallback<ColorPicker>} */
2480
+ const initColorPicker = (element) => new ColorPicker(element);
2481
+
2482
+ // ColorPicker Private Methods
2483
+ // ===========================
2484
+
2502
2485
  /**
2503
2486
  * Add / remove `ColorPicker` main event listeners.
2504
2487
  * @param {ColorPicker} self
@@ -2597,8 +2580,19 @@
2597
2580
  addClass(dropdown, 'bottom');
2598
2581
  reflow(dropdown);
2599
2582
  addClass(dropdown, 'show');
2583
+
2600
2584
  if (isPicker) self.update();
2601
- self.show();
2585
+
2586
+ if (!self.isOpen) {
2587
+ toggleEventsOnShown(self, true);
2588
+ self.updateDropdownPosition();
2589
+ self.isOpen = true;
2590
+ setAttribute(self.input, tabIndex, '0');
2591
+ if (menuToggle) {
2592
+ setAttribute(menuToggle, tabIndex, '0');
2593
+ }
2594
+ }
2595
+
2602
2596
  setAttribute(nextBtn, ariaExpanded, 'true');
2603
2597
  if (activeBtn) {
2604
2598
  setAttribute(activeBtn, ariaExpanded, 'false');
@@ -2721,7 +2715,7 @@
2721
2715
  self.handleKnobs = self.handleKnobs.bind(self);
2722
2716
 
2723
2717
  // generate markup
2724
- initCallback(self);
2718
+ setMarkup(self);
2725
2719
 
2726
2720
  const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
2727
2721
  // set main elements
@@ -2768,7 +2762,7 @@
2768
2762
  set value(v) { this.input.value = v; }
2769
2763
 
2770
2764
  /** Check if the colour presets include any non-colour. */
2771
- get includeNonColor() {
2765
+ get hasNonColor() {
2772
2766
  return this.colorKeywords instanceof Array
2773
2767
  && this.colorKeywords.some((x) => nonColors.includes(x));
2774
2768
  }
@@ -2824,7 +2818,7 @@
2824
2818
  const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
2825
2819
  const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
2826
2820
  const alpha = 1 - controlPositions.c3y / offsetHeight;
2827
- const roundA = Math.round((alpha * 100)) / 100;
2821
+ const roundA = roundPart((alpha * 100)) / 100;
2828
2822
 
2829
2823
  if (format !== 'hsl') {
2830
2824
  const fill = new Color({
@@ -2842,7 +2836,7 @@
2842
2836
  });
2843
2837
  setElementStyle(v2, { background: hueGradient });
2844
2838
  } else {
2845
- const saturation = Math.round((controlPositions.c2y / offsetHeight) * 100);
2839
+ const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
2846
2840
  const fill0 = new Color({
2847
2841
  r: 255, g: 0, b: 0, a: alpha,
2848
2842
  }).saturate(-saturation).toRgbString();
@@ -2917,7 +2911,7 @@
2917
2911
  const self = this;
2918
2912
  const { activeElement } = getDocument(self.input);
2919
2913
 
2920
- if ((isMobile && self.dragElement)
2914
+ if ((e.type === touchmoveEvent && self.dragElement)
2921
2915
  || (activeElement && self.controlKnobs.includes(activeElement))) {
2922
2916
  e.stopPropagation();
2923
2917
  e.preventDefault();
@@ -2997,12 +2991,12 @@
2997
2991
 
2998
2992
  self.update();
2999
2993
 
3000
- if (currentActive) {
3001
- removeClass(currentActive, 'active');
3002
- removeAttribute(currentActive, ariaSelected);
3003
- }
3004
-
3005
2994
  if (currentActive !== target) {
2995
+ if (currentActive) {
2996
+ removeClass(currentActive, 'active');
2997
+ removeAttribute(currentActive, ariaSelected);
2998
+ }
2999
+
3006
3000
  addClass(target, 'active');
3007
3001
  setAttribute(target, ariaSelected, 'true');
3008
3002
 
@@ -3124,30 +3118,41 @@
3124
3118
  if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
3125
3119
  e.preventDefault();
3126
3120
 
3127
- const { controlKnobs } = self;
3121
+ const { format, controlKnobs, visuals } = self;
3122
+ const { offsetWidth, offsetHeight } = visuals[0];
3128
3123
  const [c1, c2, c3] = controlKnobs;
3129
3124
  const { activeElement } = getDocument(c1);
3130
3125
  const currentKnob = controlKnobs.find((x) => x === activeElement);
3126
+ const yRatio = offsetHeight / (format === 'hsl' ? 100 : 360);
3131
3127
 
3132
3128
  if (currentKnob) {
3133
3129
  let offsetX = 0;
3134
3130
  let offsetY = 0;
3131
+
3135
3132
  if (target === c1) {
3133
+ const xRatio = offsetWidth / (format === 'hsl' ? 360 : 100);
3134
+
3136
3135
  if ([keyArrowLeft, keyArrowRight].includes(code)) {
3137
- self.controlPositions.c1x += code === keyArrowRight ? +1 : -1;
3136
+ self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
3138
3137
  } else if ([keyArrowUp, keyArrowDown].includes(code)) {
3139
- self.controlPositions.c1y += code === keyArrowDown ? +1 : -1;
3138
+ self.controlPositions.c1y += code === keyArrowDown ? yRatio : -yRatio;
3140
3139
  }
3141
3140
 
3142
3141
  offsetX = self.controlPositions.c1x;
3143
3142
  offsetY = self.controlPositions.c1y;
3144
3143
  self.changeControl1(offsetX, offsetY);
3145
3144
  } else if (target === c2) {
3146
- self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
3145
+ self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code)
3146
+ ? yRatio
3147
+ : -yRatio;
3148
+
3147
3149
  offsetY = self.controlPositions.c2y;
3148
3150
  self.changeControl2(offsetY);
3149
3151
  } else if (target === c3) {
3150
- self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
3152
+ self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code)
3153
+ ? yRatio
3154
+ : -yRatio;
3155
+
3151
3156
  offsetY = self.controlPositions.c3y;
3152
3157
  self.changeAlpha(offsetY);
3153
3158
  }
@@ -3169,7 +3174,7 @@
3169
3174
  const [v1, v2, v3, v4] = format === 'rgb'
3170
3175
  ? inputs.map((i) => parseFloat(i.value) / (i === i4 ? 100 : 1))
3171
3176
  : inputs.map((i) => parseFloat(i.value) / (i !== i1 ? 100 : 360));
3172
- const isNonColorValue = self.includeNonColor && nonColors.includes(currentValue);
3177
+ const isNonColorValue = self.hasNonColor && nonColors.includes(currentValue);
3173
3178
  const alpha = i4 ? v4 : (1 - controlPositions.c3y / offsetHeight);
3174
3179
 
3175
3180
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
@@ -3428,11 +3433,11 @@
3428
3433
  } = componentLabels;
3429
3434
  const { r, g, b } = color.toRgb();
3430
3435
  const [knob1, knob2, knob3] = controlKnobs;
3431
- const hue = Math.round(hsl.h * 360);
3436
+ const hue = roundPart(hsl.h * 360);
3432
3437
  const alpha = color.a;
3433
3438
  const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
3434
- const saturation = Math.round(saturationSource * 100);
3435
- const lightness = Math.round(hsl.l * 100);
3439
+ const saturation = roundPart(saturationSource * 100);
3440
+ const lightness = roundPart(hsl.l * 100);
3436
3441
  const hsvl = hsv.v * 100;
3437
3442
  let colorName;
3438
3443
 
@@ -3479,8 +3484,8 @@
3479
3484
  setAttribute(knob2, ariaValueNow, `${saturation}`);
3480
3485
  } else if (format === 'hwb') {
3481
3486
  const { hwb } = self;
3482
- const whiteness = Math.round(hwb.w * 100);
3483
- const blackness = Math.round(hwb.b * 100);
3487
+ const whiteness = roundPart(hwb.w * 100);
3488
+ const blackness = roundPart(hwb.b * 100);
3484
3489
  colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
3485
3490
  setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3486
3491
  setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
@@ -3496,7 +3501,7 @@
3496
3501
  setAttribute(knob2, ariaValueNow, `${hue}`);
3497
3502
  }
3498
3503
 
3499
- const alphaValue = Math.round(alpha * 100);
3504
+ const alphaValue = roundPart(alpha * 100);
3500
3505
  setAttribute(knob3, ariaValueText, `${alphaValue}%`);
3501
3506
  setAttribute(knob3, ariaValueNow, `${alphaValue}`);
3502
3507
 
@@ -3519,10 +3524,16 @@
3519
3524
  /** Updates the control knobs actual positions. */
3520
3525
  updateControls() {
3521
3526
  const { controlKnobs, controlPositions } = this;
3527
+ let {
3528
+ c1x, c1y, c2y, c3y,
3529
+ } = controlPositions;
3522
3530
  const [control1, control2, control3] = controlKnobs;
3523
- setElementStyle(control1, { transform: `translate3d(${controlPositions.c1x - 4}px,${controlPositions.c1y - 4}px,0)` });
3524
- setElementStyle(control2, { transform: `translate3d(0,${controlPositions.c2y - 4}px,0)` });
3525
- setElementStyle(control3, { transform: `translate3d(0,${controlPositions.c3y - 4}px,0)` });
3531
+ // round control positions
3532
+ [c1x, c1y, c2y, c3y] = [c1x, c1y, c2y, c3y].map(roundPart);
3533
+
3534
+ setElementStyle(control1, { transform: `translate3d(${c1x - 4}px,${c1y - 4}px,0)` });
3535
+ setElementStyle(control2, { transform: `translate3d(0,${c2y - 4}px,0)` });
3536
+ setElementStyle(control3, { transform: `translate3d(0,${c3y - 4}px,0)` });
3526
3537
  }
3527
3538
 
3528
3539
  /**
@@ -3535,16 +3546,16 @@
3535
3546
  value: oldColor, format, inputs, color, hsl,
3536
3547
  } = self;
3537
3548
  const [i1, i2, i3, i4] = inputs;
3538
- const alpha = Math.round(color.a * 100);
3539
- const hue = Math.round(hsl.h * 360);
3549
+ const alpha = roundPart(color.a * 100);
3550
+ const hue = roundPart(hsl.h * 360);
3540
3551
  let newColor;
3541
3552
 
3542
3553
  if (format === 'hex') {
3543
3554
  newColor = self.color.toHexString(true);
3544
3555
  i1.value = self.hex;
3545
3556
  } else if (format === 'hsl') {
3546
- const lightness = Math.round(hsl.l * 100);
3547
- const saturation = Math.round(hsl.s * 100);
3557
+ const lightness = roundPart(hsl.l * 100);
3558
+ const saturation = roundPart(hsl.s * 100);
3548
3559
  newColor = self.color.toHslString();
3549
3560
  i1.value = `${hue}`;
3550
3561
  i2.value = `${saturation}`;
@@ -3552,8 +3563,8 @@
3552
3563
  i4.value = `${alpha}`;
3553
3564
  } else if (format === 'hwb') {
3554
3565
  const { w, b } = self.hwb;
3555
- const whiteness = Math.round(w * 100);
3556
- const blackness = Math.round(b * 100);
3566
+ const whiteness = roundPart(w * 100);
3567
+ const blackness = roundPart(b * 100);
3557
3568
 
3558
3569
  newColor = self.color.toHwbString();
3559
3570
  i1.value = `${hue}`;
@@ -3561,7 +3572,8 @@
3561
3572
  i3.value = `${blackness}`;
3562
3573
  i4.value = `${alpha}`;
3563
3574
  } else if (format === 'rgb') {
3564
- const { r, g, b } = self.rgb;
3575
+ let { r, g, b } = self.rgb;
3576
+ [r, g, b] = [r, g, b].map(roundPart);
3565
3577
 
3566
3578
  newColor = self.color.toRgbString();
3567
3579
  i1.value = `${r}`;
@@ -3625,7 +3637,7 @@
3625
3637
  const self = this;
3626
3638
  const { colorPicker } = self;
3627
3639
 
3628
- if (!hasClass(colorPicker, 'show')) {
3640
+ if (!['top', 'bottom'].some((c) => hasClass(colorPicker, c))) {
3629
3641
  showDropdown(self, colorPicker);
3630
3642
  }
3631
3643
  }
@@ -3642,21 +3654,6 @@
3642
3654
  }
3643
3655
  }
3644
3656
 
3645
- /** Shows the `ColorPicker` dropdown or the presets menu. */
3646
- show() {
3647
- const self = this;
3648
- const { menuToggle } = self;
3649
- if (!self.isOpen) {
3650
- toggleEventsOnShown(self, true);
3651
- self.updateDropdownPosition();
3652
- self.isOpen = true;
3653
- setAttribute(self.input, 'tabindex', '0');
3654
- if (menuToggle) {
3655
- setAttribute(menuToggle, 'tabindex', '0');
3656
- }
3657
- }
3658
- }
3659
-
3660
3657
  /**
3661
3658
  * Hides the currently open `ColorPicker` dropdown.
3662
3659
  * @param {boolean=} focusPrevented
@@ -3691,9 +3688,9 @@
3691
3688
  if (!focusPrevented) {
3692
3689
  focus(pickerToggle);
3693
3690
  }
3694
- setAttribute(input, 'tabindex', '-1');
3691
+ setAttribute(input, tabIndex, '-1');
3695
3692
  if (menuToggle) {
3696
- setAttribute(menuToggle, 'tabindex', '-1');
3693
+ setAttribute(menuToggle, tabIndex, '-1');
3697
3694
  }
3698
3695
  }
3699
3696
  }
@@ -3707,7 +3704,10 @@
3707
3704
  [...parent.children].forEach((el) => {
3708
3705
  if (el !== input) el.remove();
3709
3706
  });
3707
+
3708
+ removeAttribute(input, tabIndex);
3710
3709
  setElementStyle(input, { backgroundColor: '' });
3710
+
3711
3711
  ['txt-light', 'txt-dark'].forEach((c) => removeClass(parent, c));
3712
3712
  Data.remove(input, colorPickerString);
3713
3713
  }
@@ -3715,10 +3715,16 @@
3715
3715
 
3716
3716
  ObjectAssign(ColorPicker, {
3717
3717
  Color,
3718
+ ColorPalette,
3718
3719
  Version,
3719
3720
  getInstance: getColorPickerInstance,
3720
3721
  init: initColorPicker,
3721
3722
  selector: colorPickerSelector,
3723
+ // utils important for render
3724
+ roundPart,
3725
+ setElementStyle,
3726
+ setAttribute,
3727
+ getBoundingClientRect,
3722
3728
  });
3723
3729
 
3724
3730
  let CPID = 0;
@@ -3726,8 +3732,9 @@
3726
3732
  /**
3727
3733
  * `ColorPickerElement` Web Component.
3728
3734
  * @example
3729
- * <color-picker>
3730
- * <input type="text">
3735
+ * <label for="UNIQUE_ID">Label</label>
3736
+ * <color-picker data-format="hex" data-value="#075">
3737
+ * <input id="UNIQUE_ID" type="text" class="color-preview btn-appearance">
3731
3738
  * </color-picker>
3732
3739
  */
3733
3740
  class ColorPickerElement extends HTMLElement {
@@ -3808,6 +3815,8 @@
3808
3815
  ObjectAssign(ColorPickerElement, {
3809
3816
  Color,
3810
3817
  ColorPicker,
3818
+ ColorPalette,
3819
+ getInstance: getColorPickerInstance,
3811
3820
  Version,
3812
3821
  });
3813
3822
 
@@ -3815,4 +3824,4 @@
3815
3824
 
3816
3825
  return ColorPickerElement;
3817
3826
 
3818
- })));
3827
+ }));