@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,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
+ }));