@thednp/color-picker 0.0.1-alpha3 → 0.0.2-alpha2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * ColorPickerElement v0.0.1alpha3 (http://thednp.github.io/color-picker)
2
+ * ColorPickerElement v0.0.2alpha2 (http://thednp.github.io/color-picker)
3
3
  * Copyright 2022 © thednp
4
4
  * Licensed under MIT (https://github.com/thednp/color-picker/blob/master/LICENSE)
5
5
  */
@@ -82,14 +82,9 @@ const setAttribute = (element, attribute, value) => element.setAttribute(attribu
82
82
  const getAttribute = (element, attribute) => element.getAttribute(attribute);
83
83
 
84
84
  /**
85
- * Returns the `document.head` or the `<head>` element.
86
- *
87
- * @param {(Node | HTMLElement | Element | globalThis)=} node
88
- * @returns {HTMLElement | HTMLHeadElement}
85
+ * A global namespace for `document.head`.
89
86
  */
90
- function getDocumentHead(node) {
91
- return getDocument(node).head;
92
- }
87
+ const { head: documentHead } = document;
93
88
 
94
89
  /**
95
90
  * Shortcut for `window.getComputedStyle(element).propertyName`
@@ -110,20 +105,21 @@ function getElementStyle(element, property) {
110
105
  return property in computedStyle ? computedStyle[property] : '';
111
106
  }
112
107
 
113
- /**
114
- * Shortcut for `Object.keys()` static method.
115
- * @param {Record<string, any>} obj a target object
116
- * @returns {string[]}
117
- */
118
- const ObjectKeys = (obj) => Object.keys(obj);
119
-
120
108
  /**
121
109
  * Shortcut for multiple uses of `HTMLElement.style.propertyName` method.
122
110
  * @param {HTMLElement | Element} element target element
123
111
  * @param {Partial<CSSStyleDeclaration>} styles attribute value
124
112
  */
125
113
  // @ts-ignore
126
- const setElementStyle = (element, styles) => ObjectAssign(element.style, styles);
114
+ const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
115
+
116
+ /**
117
+ * Shortcut for `String.toLowerCase()`.
118
+ *
119
+ * @param {string} source input string
120
+ * @returns {string} lowercase output string
121
+ */
122
+ const toLowerCase = (source) => source.toLowerCase();
127
123
 
128
124
  /**
129
125
  * A list of explicit default non-color values.
@@ -141,7 +137,7 @@ function roundPart(v) {
141
137
  }
142
138
 
143
139
  // Color supported formats
144
- const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
140
+ const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsv', 'hwb'];
145
141
 
146
142
  // Hue angles
147
143
  const ANGLES = 'deg|rad|grad|turn';
@@ -163,10 +159,17 @@ const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
163
159
  // Add angles to the mix
164
160
  const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
165
161
 
162
+ // Start & end
163
+ const START_MATCH = '(?:[\\s|\\(\\s|\\s\\(\\s]+)?';
164
+ const END_MATCH = '(?:[\\s|\\)\\s]+)?';
165
+ // Components separation
166
+ const SEP = '(?:[,|\\s]+)';
167
+ const SEP2 = '(?:[,|\\/\\s]*)?';
168
+
166
169
  // Actual matching.
167
170
  // Parentheses and commas are optional, but not required.
168
171
  // Whitespace can take the place of commas or opening paren
169
- const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
172
+ const PERMISSIVE_MATCH = `${START_MATCH}(${CSS_UNIT2})${SEP}(${CSS_UNIT})${SEP}(${CSS_UNIT})${SEP2}(${CSS_UNIT})?${END_MATCH}`;
170
173
 
171
174
  const matchers = {
172
175
  CSS_UNIT: new RegExp(CSS_UNIT2),
@@ -199,23 +202,24 @@ function isPercentage(n) {
199
202
  return `${n}`.includes('%');
200
203
  }
201
204
 
202
- /**
203
- * Check to see if string passed in is an angle
204
- * @param {string} n testing string
205
- * @returns {boolean} the query result
206
- */
207
- function isAngle(n) {
208
- return ANGLES.split('|').some((a) => `${n}`.includes(a));
209
- }
210
-
211
205
  /**
212
206
  * Check to see if string passed is a web safe colour.
207
+ * @see https://stackoverflow.com/a/16994164
213
208
  * @param {string} color a colour name, EG: *red*
214
209
  * @returns {boolean} the query result
215
210
  */
216
211
  function isColorName(color) {
217
- return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
218
- && !/[0-9]/.test(color);
212
+ if (nonColors.includes(color)
213
+ || ['#', ...COLOR_FORMAT].some((f) => color.includes(f))) return false;
214
+
215
+ if (['black', 'white'].includes(color)) return true;
216
+
217
+ return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
218
+ setElementStyle(documentHead, { color });
219
+ const computedColor = getElementStyle(documentHead, 'color');
220
+ setElementStyle(documentHead, { color: '' });
221
+ return computedColor !== c;
222
+ });
219
223
  }
220
224
 
221
225
  /**
@@ -236,15 +240,20 @@ function isValidCSSUnit(color) {
236
240
  */
237
241
  function bound01(N, max) {
238
242
  let n = N;
239
- if (isOnePointZero(n)) n = '100%';
240
243
 
241
- n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
244
+ if (typeof N === 'number'
245
+ && Math.min(N, 0) === 0 // round values to 6 decimals Math.round(N * (10 ** 6)) / 10 ** 6
246
+ && Math.max(N, 1) === 1) return N;
247
+
248
+ if (isOnePointZero(N)) n = '100%';
242
249
 
243
- // Handle hue angles
244
- if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
250
+ const processPercent = isPercentage(n);
251
+ n = max === 360
252
+ ? parseFloat(n)
253
+ : Math.min(max, Math.max(0, parseFloat(n)));
245
254
 
246
255
  // Automatically convert percentage into number
247
- if (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
256
+ if (processPercent) n = (n * max) / 100;
248
257
 
249
258
  // Handle floating point rounding errors
250
259
  if (Math.abs(n - max) < 0.000001) {
@@ -255,11 +264,11 @@ function bound01(N, max) {
255
264
  // If n is a hue given in degrees,
256
265
  // wrap around out-of-range values into [0, 360] range
257
266
  // then convert into [0, 1].
258
- n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max));
267
+ n = (n < 0 ? (n % max) + max : n % max) / max;
259
268
  } else {
260
269
  // If n not a hue given in degrees
261
270
  // Convert into [0, 1] range if it isn't already.
262
- n = (n % max) / parseFloat(String(max));
271
+ n = (n % max) / max;
263
272
  }
264
273
  return n;
265
274
  }
@@ -294,7 +303,6 @@ function clamp01(v) {
294
303
  * @returns {string}
295
304
  */
296
305
  function getRGBFromName(name) {
297
- const documentHead = getDocumentHead();
298
306
  setElementStyle(documentHead, { color: name });
299
307
  const colorName = getElementStyle(documentHead, 'color');
300
308
  setElementStyle(documentHead, { color: '' });
@@ -340,15 +348,12 @@ function pad2(c) {
340
348
  /**
341
349
  * Converts an RGB colour value to HSL.
342
350
  *
343
- * @param {number} R Red component [0, 255]
344
- * @param {number} G Green component [0, 255]
345
- * @param {number} B Blue component [0, 255]
351
+ * @param {number} r Red component [0, 1]
352
+ * @param {number} g Green component [0, 1]
353
+ * @param {number} b Blue component [0, 1]
346
354
  * @returns {CP.HSL} {h,s,l} object with [0, 1] ranged values
347
355
  */
348
- function rgbToHsl(R, G, B) {
349
- const r = R / 255;
350
- const g = G / 255;
351
- const b = B / 255;
356
+ function rgbToHsl(r, g, b) {
352
357
  const max = Math.max(r, g, b);
353
358
  const min = Math.min(r, g, b);
354
359
  let h = 0;
@@ -360,17 +365,10 @@ function rgbToHsl(R, G, B) {
360
365
  } else {
361
366
  const d = max - min;
362
367
  s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
363
- switch (max) {
364
- case r:
365
- h = (g - b) / d + (g < b ? 6 : 0);
366
- break;
367
- case g:
368
- h = (b - r) / d + 2;
369
- break;
370
- case b:
371
- h = (r - g) / d + 4;
372
- break;
373
- }
368
+ if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
369
+ if (max === g) h = (b - r) / d + 2;
370
+ if (max === b) h = (r - g) / d + 4;
371
+
374
372
  h /= 6;
375
373
  }
376
374
  return { h, s, l };
@@ -393,21 +391,46 @@ function hueToRgb(p, q, t) {
393
391
  return p;
394
392
  }
395
393
 
394
+ /**
395
+ * Converts an HSL colour value to RGB.
396
+ *
397
+ * @param {number} h Hue Angle [0, 1]
398
+ * @param {number} s Saturation [0, 1]
399
+ * @param {number} l Lightness Angle [0, 1]
400
+ * @returns {CP.RGB} {r,g,b} object with [0, 1] ranged values
401
+ */
402
+ function hslToRgb(h, s, l) {
403
+ let r = 0;
404
+ let g = 0;
405
+ let b = 0;
406
+
407
+ if (s === 0) {
408
+ // achromatic
409
+ g = l;
410
+ b = l;
411
+ r = l;
412
+ } else {
413
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
414
+ const p = 2 * l - q;
415
+ r = hueToRgb(p, q, h + 1 / 3);
416
+ g = hueToRgb(p, q, h);
417
+ b = hueToRgb(p, q, h - 1 / 3);
418
+ }
419
+
420
+ return { r, g, b };
421
+ }
422
+
396
423
  /**
397
424
  * Returns an HWB colour object from an RGB colour object.
398
425
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
399
426
  * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
400
427
  *
401
- * @param {number} R Red component [0, 255]
402
- * @param {number} G Green [0, 255]
403
- * @param {number} B Blue [0, 255]
428
+ * @param {number} r Red component [0, 1]
429
+ * @param {number} g Green [0, 1]
430
+ * @param {number} b Blue [0, 1]
404
431
  * @return {CP.HWB} {h,w,b} object with [0, 1] ranged values
405
432
  */
406
- function rgbToHwb(R, G, B) {
407
- const r = R / 255;
408
- const g = G / 255;
409
- const b = B / 255;
410
-
433
+ function rgbToHwb(r, g, b) {
411
434
  let f = 0;
412
435
  let i = 0;
413
436
  const whiteness = Math.min(r, g, b);
@@ -437,50 +460,18 @@ function rgbToHwb(R, G, B) {
437
460
  * @param {number} H Hue Angle [0, 1]
438
461
  * @param {number} W Whiteness [0, 1]
439
462
  * @param {number} B Blackness [0, 1]
440
- * @return {CP.RGB} {r,g,b} object with [0, 255] ranged values
463
+ * @return {CP.RGB} {r,g,b} object with [0, 1] ranged values
441
464
  *
442
465
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
443
466
  * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
444
467
  */
445
468
  function hwbToRgb(H, W, B) {
446
469
  if (W + B >= 1) {
447
- const gray = (W / (W + B)) * 255;
470
+ const gray = W / (W + B);
448
471
  return { r: gray, g: gray, b: gray };
449
472
  }
450
473
  let { r, g, b } = hslToRgb(H, 1, 0.5);
451
- [r, g, b] = [r, g, b]
452
- .map((v) => (v / 255) * (1 - W - B) + W)
453
- .map((v) => v * 255);
454
-
455
- return { r, g, b };
456
- }
457
-
458
- /**
459
- * Converts an HSL colour value to RGB.
460
- *
461
- * @param {number} h Hue Angle [0, 1]
462
- * @param {number} s Saturation [0, 1]
463
- * @param {number} l Lightness Angle [0, 1]
464
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
465
- */
466
- function hslToRgb(h, s, l) {
467
- let r = 0;
468
- let g = 0;
469
- let b = 0;
470
-
471
- if (s === 0) {
472
- // achromatic
473
- g = l;
474
- b = l;
475
- r = l;
476
- } else {
477
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
478
- const p = 2 * l - q;
479
- r = hueToRgb(p, q, h + 1 / 3);
480
- g = hueToRgb(p, q, h);
481
- b = hueToRgb(p, q, h - 1 / 3);
482
- }
483
- [r, g, b] = [r, g, b].map((x) => x * 255);
474
+ [r, g, b] = [r, g, b].map((v) => v * (1 - W - B) + W);
484
475
 
485
476
  return { r, g, b };
486
477
  }
@@ -488,15 +479,12 @@ function hslToRgb(h, s, l) {
488
479
  /**
489
480
  * Converts an RGB colour value to HSV.
490
481
  *
491
- * @param {number} R Red component [0, 255]
492
- * @param {number} G Green [0, 255]
493
- * @param {number} B Blue [0, 255]
482
+ * @param {number} r Red component [0, 1]
483
+ * @param {number} g Green [0, 1]
484
+ * @param {number} b Blue [0, 1]
494
485
  * @returns {CP.HSV} {h,s,v} object with [0, 1] ranged values
495
486
  */
496
- function rgbToHsv(R, G, B) {
497
- const r = R / 255;
498
- const g = G / 255;
499
- const b = B / 255;
487
+ function rgbToHsv(r, g, b) {
500
488
  const max = Math.max(r, g, b);
501
489
  const min = Math.min(r, g, b);
502
490
  let h = 0;
@@ -506,17 +494,10 @@ function rgbToHsv(R, G, B) {
506
494
  if (max === min) {
507
495
  h = 0; // achromatic
508
496
  } else {
509
- switch (max) {
510
- case r:
511
- h = (g - b) / d + (g < b ? 6 : 0);
512
- break;
513
- case g:
514
- h = (b - r) / d + 2;
515
- break;
516
- case b:
517
- h = (r - g) / d + 4;
518
- break;
519
- }
497
+ if (r === max) h = (g - b) / d + (g < b ? 6 : 0);
498
+ if (g === max) h = (b - r) / d + 2;
499
+ if (b === max) h = (r - g) / d + 4;
500
+
520
501
  h /= 6;
521
502
  }
522
503
  return { h, s, v };
@@ -543,7 +524,7 @@ function hsvToRgb(H, S, V) {
543
524
  const r = [v, q, p, p, t, v][mod];
544
525
  const g = [t, v, v, q, p, p][mod];
545
526
  const b = [p, p, t, v, v, q][mod];
546
- return { r: r * 255, g: g * 255, b: b * 255 };
527
+ return { r, g, b };
547
528
  }
548
529
 
549
530
  /**
@@ -567,7 +548,7 @@ function rgbToHex(r, g, b, allow3Char) {
567
548
  // Return a 3 character hex if possible
568
549
  if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
569
550
  && hex[1].charAt(0) === hex[1].charAt(1)
570
- && hex[2].charAt(0) === hex[2].charAt(1)) {
551
+ && hex[2].charAt(0) === hex[2].charAt(1)) {
571
552
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
572
553
  }
573
554
 
@@ -595,51 +576,34 @@ function rgbaToHex(r, g, b, a, allow4Char) {
595
576
  // Return a 4 character hex if possible
596
577
  if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
597
578
  && hex[1].charAt(0) === hex[1].charAt(1)
598
- && hex[2].charAt(0) === hex[2].charAt(1)
599
- && hex[3].charAt(0) === hex[3].charAt(1)) {
579
+ && hex[2].charAt(0) === hex[2].charAt(1)
580
+ && hex[3].charAt(0) === hex[3].charAt(1)) {
600
581
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
601
582
  }
602
583
  return hex.join('');
603
584
  }
604
585
 
605
- /**
606
- * Returns a colour object corresponding to a given number.
607
- * @param {number} color input number
608
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
609
- */
610
- function numberInputToObject(color) {
611
- /* eslint-disable no-bitwise */
612
- return {
613
- r: color >> 16,
614
- g: (color & 0xff00) >> 8,
615
- b: color & 0xff,
616
- };
617
- /* eslint-enable no-bitwise */
618
- }
619
-
620
586
  /**
621
587
  * Permissive string parsing. Take in a number of formats, and output an object
622
588
  * based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
623
589
  * @param {string} input colour value in any format
624
- * @returns {Record<string, (number | string)> | false} an object matching the RegExp
590
+ * @returns {Record<string, (number | string | boolean)> | false} an object matching the RegExp
625
591
  */
626
592
  function stringInputToObject(input) {
627
- let color = input.trim().toLowerCase();
593
+ let color = toLowerCase(input.trim());
594
+
628
595
  if (color.length === 0) {
629
596
  return {
630
- r: 0, g: 0, b: 0, a: 0,
597
+ r: 0, g: 0, b: 0, a: 1,
631
598
  };
632
599
  }
633
- let named = false;
600
+
634
601
  if (isColorName(color)) {
635
602
  color = getRGBFromName(color);
636
- named = true;
637
603
  } else if (nonColors.includes(color)) {
638
- const isTransparent = color === 'transparent';
639
- const rgb = isTransparent ? 0 : 255;
640
- const a = isTransparent ? 0 : 1;
604
+ const a = color === 'transparent' ? 0 : 1;
641
605
  return {
642
- r: rgb, g: rgb, b: rgb, a, format: 'rgb',
606
+ r: 0, g: 0, b: 0, a, format: 'rgb', ok: true,
643
607
  };
644
608
  }
645
609
 
@@ -654,24 +618,28 @@ function stringInputToObject(input) {
654
618
  r: m1, g: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'rgb',
655
619
  };
656
620
  }
621
+
657
622
  [, m1, m2, m3, m4] = matchers.hsl.exec(color) || [];
658
623
  if (m1 && m2 && m3/* && m4 */) {
659
624
  return {
660
625
  h: m1, s: m2, l: m3, a: m4 !== undefined ? m4 : 1, format: 'hsl',
661
626
  };
662
627
  }
628
+
663
629
  [, m1, m2, m3, m4] = matchers.hsv.exec(color) || [];
664
630
  if (m1 && m2 && m3/* && m4 */) {
665
631
  return {
666
632
  h: m1, s: m2, v: m3, a: m4 !== undefined ? m4 : 1, format: 'hsv',
667
633
  };
668
634
  }
635
+
669
636
  [, m1, m2, m3, m4] = matchers.hwb.exec(color) || [];
670
637
  if (m1 && m2 && m3) {
671
638
  return {
672
639
  h: m1, w: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'hwb',
673
640
  };
674
641
  }
642
+
675
643
  [, m1, m2, m3, m4] = matchers.hex8.exec(color) || [];
676
644
  if (m1 && m2 && m3 && m4) {
677
645
  return {
@@ -679,19 +647,20 @@ function stringInputToObject(input) {
679
647
  g: parseIntFromHex(m2),
680
648
  b: parseIntFromHex(m3),
681
649
  a: convertHexToDecimal(m4),
682
- // format: named ? 'rgb' : 'hex8',
683
- format: named ? 'rgb' : 'hex',
650
+ format: 'hex',
684
651
  };
685
652
  }
653
+
686
654
  [, m1, m2, m3] = matchers.hex6.exec(color) || [];
687
655
  if (m1 && m2 && m3) {
688
656
  return {
689
657
  r: parseIntFromHex(m1),
690
658
  g: parseIntFromHex(m2),
691
659
  b: parseIntFromHex(m3),
692
- format: named ? 'rgb' : 'hex',
660
+ format: 'hex',
693
661
  };
694
662
  }
663
+
695
664
  [, m1, m2, m3, m4] = matchers.hex4.exec(color) || [];
696
665
  if (m1 && m2 && m3 && m4) {
697
666
  return {
@@ -699,19 +668,20 @@ function stringInputToObject(input) {
699
668
  g: parseIntFromHex(m2 + m2),
700
669
  b: parseIntFromHex(m3 + m3),
701
670
  a: convertHexToDecimal(m4 + m4),
702
- // format: named ? 'rgb' : 'hex8',
703
- format: named ? 'rgb' : 'hex',
671
+ format: 'hex',
704
672
  };
705
673
  }
674
+
706
675
  [, m1, m2, m3] = matchers.hex3.exec(color) || [];
707
676
  if (m1 && m2 && m3) {
708
677
  return {
709
678
  r: parseIntFromHex(m1 + m1),
710
679
  g: parseIntFromHex(m2 + m2),
711
680
  b: parseIntFromHex(m3 + m3),
712
- format: named ? 'rgb' : 'hex',
681
+ format: 'hex',
713
682
  };
714
683
  }
684
+
715
685
  return false;
716
686
  }
717
687
 
@@ -742,7 +712,9 @@ function stringInputToObject(input) {
742
712
  */
743
713
  function inputToRGB(input) {
744
714
  let rgb = { r: 0, g: 0, b: 0 };
715
+ /** @type {*} */
745
716
  let color = input;
717
+ /** @type {string | number} */
746
718
  let a = 1;
747
719
  let s = null;
748
720
  let v = null;
@@ -753,58 +725,67 @@ function inputToRGB(input) {
753
725
  let r = null;
754
726
  let g = null;
755
727
  let ok = false;
756
- let format = 'hex';
728
+ const inputFormat = typeof color === 'object' && color.format;
729
+ let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
757
730
 
758
731
  if (typeof input === 'string') {
759
- // @ts-ignore -- this now is converted to object
760
732
  color = stringInputToObject(input);
761
733
  if (color) ok = true;
762
734
  }
763
735
  if (typeof color === 'object') {
764
736
  if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
765
737
  ({ r, g, b } = color);
766
- [r, g, b] = [...[r, g, b]]
767
- .map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255).map(roundPart);
768
- rgb = { r, g, b }; // RGB values now are all in [0, 255] range
738
+ // RGB values now are all in [0, 1] range
739
+ [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255));
740
+ rgb = { r, g, b };
769
741
  ok = true;
770
- format = 'rgb';
771
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
742
+ format = color.format || 'rgb';
743
+ }
744
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
772
745
  ({ h, s, v } = color);
773
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
774
- s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
775
- v = typeof v === 'number' ? v : bound01(v, 100); // brightness can be `5%` or a [0, 1] value
746
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
747
+ s = bound01(s, 100); // saturation can be `5%` or a [0, 1] value
748
+ v = bound01(v, 100); // brightness can be `5%` or a [0, 1] value
776
749
  rgb = hsvToRgb(h, s, v);
777
750
  ok = true;
778
751
  format = 'hsv';
779
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
752
+ }
753
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
780
754
  ({ h, s, l } = color);
781
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
782
- s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
783
- l = typeof l === 'number' ? l : bound01(l, 100); // lightness can be `5%` or a [0, 1] value
755
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
756
+ s = bound01(s, 100); // saturation can be `5%` or a [0, 1] value
757
+ l = bound01(l, 100); // lightness can be `5%` or a [0, 1] value
784
758
  rgb = hslToRgb(h, s, l);
785
759
  ok = true;
786
760
  format = 'hsl';
787
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
761
+ }
762
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
788
763
  ({ h, w, b } = color);
789
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
790
- w = typeof w === 'number' ? w : bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
791
- b = typeof b === 'number' ? b : bound01(b, 100); // blackness can be `5%` or a [0, 1] value
764
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
765
+ w = bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
766
+ b = bound01(b, 100); // blackness can be `5%` or a [0, 1] value
792
767
  rgb = hwbToRgb(h, w, b);
793
768
  ok = true;
794
769
  format = 'hwb';
795
770
  }
796
771
  if (isValidCSSUnit(color.a)) {
797
- a = color.a;
798
- a = isPercentage(`${a}`) ? bound01(a, 100) : a;
772
+ a = color.a; // @ts-ignore -- `parseFloat` works with numbers too
773
+ a = isPercentage(`${a}`) || parseFloat(a) > 1 ? bound01(a, 100) : a;
799
774
  }
800
775
  }
776
+ if (typeof color === 'undefined') {
777
+ ok = true;
778
+ }
801
779
 
802
780
  return {
803
- ok, // @ts-ignore
804
- format: color.format || format,
805
- r: Math.min(255, Math.max(rgb.r, 0)),
806
- g: Math.min(255, Math.max(rgb.g, 0)),
807
- b: Math.min(255, Math.max(rgb.b, 0)),
781
+ ok,
782
+ format,
783
+ // r: Math.min(255, Math.max(rgb.r, 0)),
784
+ // g: Math.min(255, Math.max(rgb.g, 0)),
785
+ // b: Math.min(255, Math.max(rgb.b, 0)),
786
+ r: rgb.r,
787
+ g: rgb.g,
788
+ b: rgb.b,
808
789
  a: boundAlpha(a),
809
790
  };
810
791
  }
@@ -823,15 +804,13 @@ class Color {
823
804
  constructor(input, config) {
824
805
  let color = input;
825
806
  const configFormat = config && COLOR_FORMAT.includes(config)
826
- ? config : 'rgb';
807
+ ? config : '';
827
808
 
828
- // If input is already a `Color`, return itself
809
+ // If input is already a `Color`, clone its values
829
810
  if (color instanceof Color) {
830
811
  color = inputToRGB(color);
831
812
  }
832
- if (typeof color === 'number') {
833
- color = numberInputToObject(color);
834
- }
813
+
835
814
  const {
836
815
  r, g, b, a, ok, format,
837
816
  } = inputToRGB(color);
@@ -840,7 +819,7 @@ class Color {
840
819
  const self = this;
841
820
 
842
821
  /** @type {CP.ColorInput} */
843
- self.originalInput = color;
822
+ self.originalInput = input;
844
823
  /** @type {number} */
845
824
  self.r = r;
846
825
  /** @type {number} */
@@ -881,24 +860,21 @@ class Color {
881
860
  let R = 0;
882
861
  let G = 0;
883
862
  let B = 0;
884
- const rp = r / 255;
885
- const rg = g / 255;
886
- const rb = b / 255;
887
863
 
888
- if (rp <= 0.03928) {
889
- R = rp / 12.92;
864
+ if (r <= 0.03928) {
865
+ R = r / 12.92;
890
866
  } else {
891
- R = ((rp + 0.055) / 1.055) ** 2.4;
867
+ R = ((r + 0.055) / 1.055) ** 2.4;
892
868
  }
893
- if (rg <= 0.03928) {
894
- G = rg / 12.92;
869
+ if (g <= 0.03928) {
870
+ G = g / 12.92;
895
871
  } else {
896
- G = ((rg + 0.055) / 1.055) ** 2.4;
872
+ G = ((g + 0.055) / 1.055) ** 2.4;
897
873
  }
898
- if (rb <= 0.03928) {
899
- B = rb / 12.92;
874
+ if (b <= 0.03928) {
875
+ B = b / 12.92;
900
876
  } else {
901
- B = ((rb + 0.055) / 1.055) ** 2.4;
877
+ B = ((b + 0.055) / 1.055) ** 2.4;
902
878
  }
903
879
  return 0.2126 * R + 0.7152 * G + 0.0722 * B;
904
880
  }
@@ -908,7 +884,7 @@ class Color {
908
884
  * @returns {number} a number in the [0, 255] range
909
885
  */
910
886
  get brightness() {
911
- const { r, g, b } = this;
887
+ const { r, g, b } = this.toRgb();
912
888
  return (r * 299 + g * 587 + b * 114) / 1000;
913
889
  }
914
890
 
@@ -917,16 +893,14 @@ class Color {
917
893
  * @returns {CP.RGBA} an {r,g,b,a} object with [0, 255] ranged values
918
894
  */
919
895
  toRgb() {
920
- const {
896
+ let {
921
897
  r, g, b, a,
922
898
  } = this;
923
- const [R, G, B] = [r, g, b].map((x) => roundPart(x));
924
899
 
900
+ [r, g, b] = [r, g, b].map((n) => roundPart(n * 255 * 100) / 100);
901
+ a = roundPart(a * 100) / 100;
925
902
  return {
926
- r: R,
927
- g: G,
928
- b: B,
929
- a: roundPart(a * 100) / 100,
903
+ r, g, b, a,
930
904
  };
931
905
  }
932
906
 
@@ -940,10 +914,11 @@ class Color {
940
914
  const {
941
915
  r, g, b, a,
942
916
  } = this.toRgb();
917
+ const [R, G, B] = [r, g, b].map(roundPart);
943
918
 
944
919
  return a === 1
945
- ? `rgb(${r}, ${g}, ${b})`
946
- : `rgba(${r}, ${g}, ${b}, ${a})`;
920
+ ? `rgb(${R}, ${G}, ${B})`
921
+ : `rgba(${R}, ${G}, ${B}, ${a})`;
947
922
  }
948
923
 
949
924
  /**
@@ -956,9 +931,10 @@ class Color {
956
931
  const {
957
932
  r, g, b, a,
958
933
  } = this.toRgb();
934
+ const [R, G, B] = [r, g, b].map(roundPart);
959
935
  const A = a === 1 ? '' : ` / ${roundPart(a * 100)}%`;
960
936
 
961
- return `rgb(${r} ${g} ${b}${A})`;
937
+ return `rgb(${R} ${G} ${B}${A})`;
962
938
  }
963
939
 
964
940
  /**
@@ -1018,7 +994,7 @@ class Color {
1018
994
  toHsv() {
1019
995
  const {
1020
996
  r, g, b, a,
1021
- } = this.toRgb();
997
+ } = this;
1022
998
  const { h, s, v } = rgbToHsv(r, g, b);
1023
999
 
1024
1000
  return {
@@ -1033,7 +1009,7 @@ class Color {
1033
1009
  toHsl() {
1034
1010
  const {
1035
1011
  r, g, b, a,
1036
- } = this.toRgb();
1012
+ } = this;
1037
1013
  const { h, s, l } = rgbToHsl(r, g, b);
1038
1014
 
1039
1015
  return {
@@ -1118,6 +1094,7 @@ class Color {
1118
1094
  */
1119
1095
  setAlpha(alpha) {
1120
1096
  const self = this;
1097
+ if (typeof alpha !== 'number') return self;
1121
1098
  self.a = boundAlpha(alpha);
1122
1099
  return self;
1123
1100
  }
@@ -1232,6 +1209,7 @@ ObjectAssign(Color, {
1232
1209
  isOnePointZero,
1233
1210
  isPercentage,
1234
1211
  isValidCSSUnit,
1212
+ isColorName,
1235
1213
  pad2,
1236
1214
  clamp01,
1237
1215
  bound01,
@@ -1249,10 +1227,11 @@ ObjectAssign(Color, {
1249
1227
  hueToRgb,
1250
1228
  hwbToRgb,
1251
1229
  parseIntFromHex,
1252
- numberInputToObject,
1253
1230
  stringInputToObject,
1254
1231
  inputToRGB,
1255
1232
  roundPart,
1233
+ getElementStyle,
1234
+ setElementStyle,
1256
1235
  ObjectAssign,
1257
1236
  });
1258
1237
 
@@ -1382,24 +1361,6 @@ const ariaValueText = 'aria-valuetext';
1382
1361
  */
1383
1362
  const ariaValueNow = 'aria-valuenow';
1384
1363
 
1385
- /**
1386
- * A global namespace for aria-haspopup.
1387
- * @type {string}
1388
- */
1389
- const ariaHasPopup = 'aria-haspopup';
1390
-
1391
- /**
1392
- * A global namespace for aria-hidden.
1393
- * @type {string}
1394
- */
1395
- const ariaHidden = 'aria-hidden';
1396
-
1397
- /**
1398
- * A global namespace for aria-labelledby.
1399
- * @type {string}
1400
- */
1401
- const ariaLabelledBy = 'aria-labelledby';
1402
-
1403
1364
  /**
1404
1365
  * A global namespace for `ArrowDown` key.
1405
1366
  * @type {string} e.which = 40 equivalent
@@ -1526,37 +1487,6 @@ const resizeEvent = 'resize';
1526
1487
  */
1527
1488
  const focusoutEvent = 'focusout';
1528
1489
 
1529
- // @ts-ignore
1530
- const { userAgentData: uaDATA } = navigator;
1531
-
1532
- /**
1533
- * A global namespace for `userAgentData` object.
1534
- */
1535
- const userAgentData = uaDATA;
1536
-
1537
- const { userAgent: userAgentString } = navigator;
1538
-
1539
- /**
1540
- * A global namespace for `navigator.userAgent` string.
1541
- */
1542
- const userAgent = userAgentString;
1543
-
1544
- const mobileBrands = /iPhone|iPad|iPod|Android/i;
1545
- let isMobileCheck = false;
1546
-
1547
- if (userAgentData) {
1548
- isMobileCheck = userAgentData.brands
1549
- .some((/** @type {Record<String, any>} */x) => mobileBrands.test(x.brand));
1550
- } else {
1551
- isMobileCheck = mobileBrands.test(userAgent);
1552
- }
1553
-
1554
- /**
1555
- * A global `boolean` for mobile detection.
1556
- * @type {boolean}
1557
- */
1558
- const isMobile = isMobileCheck;
1559
-
1560
1490
  /**
1561
1491
  * Returns the `document.documentElement` or the `<html>` element.
1562
1492
  *
@@ -1741,30 +1671,6 @@ function getElementsByClassName(selector, parent) {
1741
1671
  return lookUp.getElementsByClassName(selector);
1742
1672
  }
1743
1673
 
1744
- /**
1745
- * This is a shortie for `document.createElementNS` method
1746
- * which allows you to create a new `HTMLElement` for a given `tagName`
1747
- * or based on an object with specific non-readonly attributes:
1748
- * `id`, `className`, `textContent`, `style`, etc.
1749
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
1750
- *
1751
- * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
1752
- * @param {Record<string, string> | string} param `tagName` or object
1753
- * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
1754
- */
1755
- function createElementNS(namespace, param) {
1756
- if (typeof param === 'string') {
1757
- return getDocument().createElementNS(namespace, param);
1758
- }
1759
-
1760
- const { tagName } = param;
1761
- const attr = { ...param };
1762
- const newElement = createElementNS(namespace, tagName);
1763
- delete attr.tagName;
1764
- ObjectAssign(newElement, attr);
1765
- return newElement;
1766
- }
1767
-
1768
1674
  /**
1769
1675
  * Shortcut for the `Element.dispatchEvent(Event)` method.
1770
1676
  *
@@ -1882,12 +1788,11 @@ function normalizeValue(value) {
1882
1788
  }
1883
1789
 
1884
1790
  /**
1885
- * Shortcut for `String.toLowerCase()`.
1886
- *
1887
- * @param {string} source input string
1888
- * @returns {string} lowercase output string
1791
+ * Shortcut for `Object.keys()` static method.
1792
+ * @param {Record<string, any>} obj a target object
1793
+ * @returns {string[]}
1889
1794
  */
1890
- const toLowerCase = (source) => source.toLowerCase();
1795
+ const ObjectKeys = (obj) => Object.keys(obj);
1891
1796
 
1892
1797
  /**
1893
1798
  * Utility to normalize component options.
@@ -1992,6 +1897,77 @@ function removeClass(element, classNAME) {
1992
1897
  */
1993
1898
  const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
1994
1899
 
1900
+ /**
1901
+ * @class
1902
+ * Returns a color palette with a given set of parameters.
1903
+ * @example
1904
+ * new ColorPalette(0, 12, 10);
1905
+ * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: Array<Color> }
1906
+ */
1907
+ class ColorPalette {
1908
+ /**
1909
+ * The `hue` parameter is optional, which would be set to 0.
1910
+ * @param {number[]} args represeinting hue, hueSteps, lightSteps
1911
+ * * `args.hue` the starting Hue [0, 360]
1912
+ * * `args.hueSteps` Hue Steps Count [5, 24]
1913
+ * * `args.lightSteps` Lightness Steps Count [5, 12]
1914
+ */
1915
+ constructor(...args) {
1916
+ let hue = 0;
1917
+ let hueSteps = 12;
1918
+ let lightSteps = 10;
1919
+ let lightnessArray = [0.5];
1920
+
1921
+ if (args.length === 3) {
1922
+ [hue, hueSteps, lightSteps] = args;
1923
+ } else if (args.length === 2) {
1924
+ [hueSteps, lightSteps] = args;
1925
+ if ([hueSteps, lightSteps].some((n) => n < 1)) {
1926
+ throw TypeError('ColorPalette: both arguments must be higher than 0.');
1927
+ }
1928
+ }
1929
+
1930
+ /** @type {*} */
1931
+ const colors = [];
1932
+ const hueStep = 360 / hueSteps;
1933
+ const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
1934
+ const steps1To13 = [0.25, 0.2, 0.15, 0.11, 0.09, 0.075];
1935
+ const lightSets = [[1, 2, 3], [4, 5], [6, 7], [8, 9], [10, 11], [12, 13]];
1936
+ const closestSet = lightSets.find((set) => set.includes(lightSteps));
1937
+
1938
+ // find a lightStep that won't go beyond black and white
1939
+ // something within the [10-90] range of lightness
1940
+ const lightStep = closestSet
1941
+ ? steps1To13[lightSets.indexOf(closestSet)]
1942
+ : (100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100);
1943
+
1944
+ // light tints
1945
+ for (let i = 1; i < half + 1; i += 1) {
1946
+ lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
1947
+ }
1948
+
1949
+ // dark tints
1950
+ for (let i = 1; i < lightSteps - half; i += 1) {
1951
+ lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
1952
+ }
1953
+
1954
+ // feed `colors` Array
1955
+ for (let i = 0; i < hueSteps; i += 1) {
1956
+ const currentHue = ((hue + i * hueStep) % 360) / 360;
1957
+ lightnessArray.forEach((l) => {
1958
+ colors.push(new Color({ h: currentHue, s: 1, l }));
1959
+ });
1960
+ }
1961
+
1962
+ this.hue = hue;
1963
+ this.hueSteps = hueSteps;
1964
+ this.lightSteps = lightSteps;
1965
+ this.colors = colors;
1966
+ }
1967
+ }
1968
+
1969
+ ObjectAssign(ColorPalette, { Color });
1970
+
1995
1971
  /** @type {Record<string, string>} */
1996
1972
  const colorPickerLabels = {
1997
1973
  pickerLabel: 'Colour Picker',
@@ -2019,14 +1995,72 @@ const colorPickerLabels = {
2019
1995
  */
2020
1996
  const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
2021
1997
 
1998
+ const tabIndex = 'tabindex';
1999
+
2022
2000
  /**
2023
- * Shortcut for `String.toUpperCase()`.
2024
- *
2025
- * @param {string} source input string
2026
- * @returns {string} uppercase output string
2001
+ * Check if a string is valid JSON string.
2002
+ * @param {string} str the string input
2003
+ * @returns {boolean} the query result
2004
+ */
2005
+ function isValidJSON(str) {
2006
+ try {
2007
+ JSON.parse(str);
2008
+ } catch (e) {
2009
+ return false;
2010
+ }
2011
+ return true;
2012
+ }
2013
+
2014
+ /**
2015
+ * Shortcut for `String.toUpperCase()`.
2016
+ *
2017
+ * @param {string} source input string
2018
+ * @returns {string} uppercase output string
2027
2019
  */
2028
2020
  const toUpperCase = (source) => source.toUpperCase();
2029
2021
 
2022
+ /**
2023
+ * A global namespace for aria-haspopup.
2024
+ * @type {string}
2025
+ */
2026
+ const ariaHasPopup = 'aria-haspopup';
2027
+
2028
+ /**
2029
+ * A global namespace for aria-hidden.
2030
+ * @type {string}
2031
+ */
2032
+ const ariaHidden = 'aria-hidden';
2033
+
2034
+ /**
2035
+ * A global namespace for aria-labelledby.
2036
+ * @type {string}
2037
+ */
2038
+ const ariaLabelledBy = 'aria-labelledby';
2039
+
2040
+ /**
2041
+ * This is a shortie for `document.createElementNS` method
2042
+ * which allows you to create a new `HTMLElement` for a given `tagName`
2043
+ * or based on an object with specific non-readonly attributes:
2044
+ * `id`, `className`, `textContent`, `style`, etc.
2045
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
2046
+ *
2047
+ * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
2048
+ * @param {Record<string, string> | string} param `tagName` or object
2049
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
2050
+ */
2051
+ function createElementNS(namespace, param) {
2052
+ if (typeof param === 'string') {
2053
+ return getDocument().createElementNS(namespace, param);
2054
+ }
2055
+
2056
+ const { tagName } = param;
2057
+ const attr = { ...param };
2058
+ const newElement = createElementNS(namespace, tagName);
2059
+ delete attr.tagName;
2060
+ ObjectAssign(newElement, attr);
2061
+ return newElement;
2062
+ }
2063
+
2030
2064
  const vHidden = 'v-hidden';
2031
2065
 
2032
2066
  /**
@@ -2106,8 +2140,6 @@ const ariaValueMin = 'aria-valuemin';
2106
2140
  */
2107
2141
  const ariaValueMax = 'aria-valuemax';
2108
2142
 
2109
- const tabIndex = 'tabindex';
2110
-
2111
2143
  /**
2112
2144
  * Returns all color controls for `ColorPicker`.
2113
2145
  *
@@ -2215,75 +2247,6 @@ function setCSSProperties(element, props) {
2215
2247
  });
2216
2248
  }
2217
2249
 
2218
- /**
2219
- * @class
2220
- * Returns a color palette with a given set of parameters.
2221
- * @example
2222
- * new ColorPalette(0, 12, 10);
2223
- * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
2224
- */
2225
- class ColorPalette {
2226
- /**
2227
- * The `hue` parameter is optional, which would be set to 0.
2228
- * @param {number[]} args represeinting hue, hueSteps, lightSteps
2229
- * * `args.hue` the starting Hue [0, 360]
2230
- * * `args.hueSteps` Hue Steps Count [5, 24]
2231
- * * `args.lightSteps` Lightness Steps Count [5, 12]
2232
- */
2233
- constructor(...args) {
2234
- let hue = 0;
2235
- let hueSteps = 12;
2236
- let lightSteps = 10;
2237
- let lightnessArray = [0.5];
2238
-
2239
- if (args.length === 3) {
2240
- [hue, hueSteps, lightSteps] = args;
2241
- } else if (args.length === 2) {
2242
- [hueSteps, lightSteps] = args;
2243
- } else {
2244
- throw TypeError('ColorPalette requires minimum 2 arguments');
2245
- }
2246
-
2247
- /** @type {string[]} */
2248
- const colors = [];
2249
-
2250
- const hueStep = 360 / hueSteps;
2251
- const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
2252
- const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
2253
-
2254
- let lightStep = 0.25;
2255
- lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
2256
- lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
2257
- lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
2258
- lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
2259
- lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
2260
- lightStep = lightSteps > 13 ? estimatedStep : lightStep;
2261
-
2262
- // light tints
2263
- for (let i = 1; i < half + 1; i += 1) {
2264
- lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
2265
- }
2266
-
2267
- // dark tints
2268
- for (let i = 1; i < lightSteps - half; i += 1) {
2269
- lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
2270
- }
2271
-
2272
- // feed `colors` Array
2273
- for (let i = 0; i < hueSteps; i += 1) {
2274
- const currentHue = ((hue + i * hueStep) % 360) / 360;
2275
- lightnessArray.forEach((l) => {
2276
- colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
2277
- });
2278
- }
2279
-
2280
- this.hue = hue;
2281
- this.hueSteps = hueSteps;
2282
- this.lightSteps = lightSteps;
2283
- this.colors = colors;
2284
- }
2285
- }
2286
-
2287
2250
  /**
2288
2251
  * Returns a color-defaults with given values and class.
2289
2252
  * @param {CP.ColorPicker} self
@@ -2317,7 +2280,8 @@ function getColorMenu(self, colorsSource, menuClass) {
2317
2280
  optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2318
2281
  const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2319
2282
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2320
-
2283
+ /** @type {HTMLUListElement} */
2284
+ // @ts-ignore -- <UL> is an `HTMLElement`
2321
2285
  const menu = createElement({
2322
2286
  tagName: 'ul',
2323
2287
  className: finalClass,
@@ -2325,7 +2289,7 @@ function getColorMenu(self, colorsSource, menuClass) {
2325
2289
  setAttribute(menu, 'role', 'listbox');
2326
2290
  setAttribute(menu, ariaLabel, menuLabel);
2327
2291
 
2328
- if (isScrollable) { // @ts-ignore
2292
+ if (isScrollable) {
2329
2293
  setCSSProperties(menu, {
2330
2294
  '--grid-item-size': `${optionSize}rem`,
2331
2295
  '--grid-fit': fit,
@@ -2336,15 +2300,19 @@ function getColorMenu(self, colorsSource, menuClass) {
2336
2300
  }
2337
2301
 
2338
2302
  colorsArray.forEach((x) => {
2339
- const [value, label] = x.trim().split(':');
2340
- const xRealColor = new Color(value, format).toString();
2341
- const isActive = xRealColor === getAttribute(input, 'value');
2303
+ let [value, label] = typeof x === 'string' ? x.trim().split(':') : [];
2304
+ if (x instanceof Color) {
2305
+ value = x.toHexString();
2306
+ label = value;
2307
+ }
2308
+ const color = new Color(x instanceof Color ? x : value, format);
2309
+ const isActive = color.toString() === getAttribute(input, 'value');
2342
2310
  const active = isActive ? ' active' : '';
2343
2311
 
2344
2312
  const option = createElement({
2345
2313
  tagName: 'li',
2346
2314
  className: `color-option${active}`,
2347
- innerText: `${label || x}`,
2315
+ innerText: `${label || value}`,
2348
2316
  });
2349
2317
 
2350
2318
  setAttribute(option, tabIndex, '0');
@@ -2353,7 +2321,7 @@ function getColorMenu(self, colorsSource, menuClass) {
2353
2321
  setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2354
2322
 
2355
2323
  if (isOptionsMenu) {
2356
- setElementStyle(option, { backgroundColor: x });
2324
+ setElementStyle(option, { backgroundColor: value });
2357
2325
  }
2358
2326
 
2359
2327
  menu.append(option);
@@ -2362,55 +2330,10 @@ function getColorMenu(self, colorsSource, menuClass) {
2362
2330
  }
2363
2331
 
2364
2332
  /**
2365
- * Check if a string is valid JSON string.
2366
- * @param {string} str the string input
2367
- * @returns {boolean} the query result
2368
- */
2369
- function isValidJSON(str) {
2370
- try {
2371
- JSON.parse(str);
2372
- } catch (e) {
2373
- return false;
2374
- }
2375
- return true;
2376
- }
2377
-
2378
- var version = "0.0.1alpha3";
2379
-
2380
- // @ts-ignore
2381
-
2382
- const Version = version;
2383
-
2384
- // ColorPicker GC
2385
- // ==============
2386
- const colorPickerString = 'color-picker';
2387
- const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2388
- const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2389
- const colorPickerDefaults = {
2390
- componentLabels: colorPickerLabels,
2391
- colorLabels: colorNames,
2392
- format: 'rgb',
2393
- colorPresets: false,
2394
- colorKeywords: false,
2395
- };
2396
-
2397
- // ColorPicker Static Methods
2398
- // ==========================
2399
-
2400
- /** @type {CP.GetInstance<ColorPicker>} */
2401
- const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2402
-
2403
- /** @type {CP.InitCallback<ColorPicker>} */
2404
- const initColorPicker = (element) => new ColorPicker(element);
2405
-
2406
- // ColorPicker Private Methods
2407
- // ===========================
2408
-
2409
- /**
2410
- * Generate HTML markup and update instance properties.
2411
- * @param {ColorPicker} self
2412
- */
2413
- function initCallback(self) {
2333
+ * Generate HTML markup and update instance properties.
2334
+ * @param {CP.ColorPicker} self
2335
+ */
2336
+ function setMarkup(self) {
2414
2337
  const {
2415
2338
  input, parent, format, id, componentLabels, colorKeywords, colorPresets,
2416
2339
  } = self;
@@ -2425,9 +2348,7 @@ function initCallback(self) {
2425
2348
  self.color = new Color(color, format);
2426
2349
 
2427
2350
  // set initial controls dimensions
2428
- // make the controls smaller on mobile
2429
- const dropClass = isMobile ? ' mobile' : '';
2430
- const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
2351
+ const formatString = format === 'hex' ? hexLabel : toUpperCase(format);
2431
2352
 
2432
2353
  const pickerBtn = createElement({
2433
2354
  id: `picker-btn-${id}`,
@@ -2444,7 +2365,7 @@ function initCallback(self) {
2444
2365
 
2445
2366
  const pickerDropdown = createElement({
2446
2367
  tagName: 'div',
2447
- className: `color-dropdown picker${dropClass}`,
2368
+ className: 'color-dropdown picker',
2448
2369
  });
2449
2370
  setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
2450
2371
  setAttribute(pickerDropdown, 'role', 'group');
@@ -2460,7 +2381,7 @@ function initCallback(self) {
2460
2381
  if (colorKeywords || colorPresets) {
2461
2382
  const presetsDropdown = createElement({
2462
2383
  tagName: 'div',
2463
- className: `color-dropdown scrollable menu${dropClass}`,
2384
+ className: 'color-dropdown scrollable menu',
2464
2385
  });
2465
2386
 
2466
2387
  // color presets
@@ -2510,6 +2431,37 @@ function initCallback(self) {
2510
2431
  setAttribute(input, tabIndex, '-1');
2511
2432
  }
2512
2433
 
2434
+ var version = "0.0.2alpha2";
2435
+
2436
+ // @ts-ignore
2437
+
2438
+ const Version = version;
2439
+
2440
+ // ColorPicker GC
2441
+ // ==============
2442
+ const colorPickerString = 'color-picker';
2443
+ const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2444
+ const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2445
+ const colorPickerDefaults = {
2446
+ componentLabels: colorPickerLabels,
2447
+ colorLabels: colorNames,
2448
+ format: 'rgb',
2449
+ colorPresets: false,
2450
+ colorKeywords: false,
2451
+ };
2452
+
2453
+ // ColorPicker Static Methods
2454
+ // ==========================
2455
+
2456
+ /** @type {CP.GetInstance<ColorPicker>} */
2457
+ const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2458
+
2459
+ /** @type {CP.InitCallback<ColorPicker>} */
2460
+ const initColorPicker = (element) => new ColorPicker(element);
2461
+
2462
+ // ColorPicker Private Methods
2463
+ // ===========================
2464
+
2513
2465
  /**
2514
2466
  * Add / remove `ColorPicker` main event listeners.
2515
2467
  * @param {ColorPicker} self
@@ -2522,8 +2474,6 @@ function toggleEvents(self, action) {
2522
2474
  fn(input, focusinEvent, self.showPicker);
2523
2475
  fn(pickerToggle, mouseclickEvent, self.togglePicker);
2524
2476
 
2525
- fn(input, keydownEvent, self.keyToggle);
2526
-
2527
2477
  if (menuToggle) {
2528
2478
  fn(menuToggle, mouseclickEvent, self.toggleMenu);
2529
2479
  }
@@ -2561,8 +2511,7 @@ function toggleEventsOnShown(self, action) {
2561
2511
  fn(doc, pointerEvents.move, self.pointerMove);
2562
2512
  fn(doc, pointerEvents.up, self.pointerUp);
2563
2513
  fn(parent, focusoutEvent, self.handleFocusOut);
2564
- // @ts-ignore -- this is `Window`
2565
- fn(win, keyupEvent, self.handleDismiss);
2514
+ fn(doc, keyupEvent, self.handleDismiss);
2566
2515
  }
2567
2516
 
2568
2517
  /**
@@ -2646,7 +2595,7 @@ class ColorPicker {
2646
2595
  const input = querySelector(target);
2647
2596
 
2648
2597
  // invalidate
2649
- if (!input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
2598
+ if (!input) throw new TypeError(`ColorPicker target "${target}" cannot be found.`);
2650
2599
  self.input = input;
2651
2600
 
2652
2601
  const parent = closest(input, colorPickerParentSelector);
@@ -2693,15 +2642,14 @@ class ColorPicker {
2693
2642
  });
2694
2643
 
2695
2644
  // update and expose component labels
2696
- const tempLabels = ObjectAssign({}, colorPickerLabels);
2697
- const jsonLabels = componentLabels && isValidJSON(componentLabels)
2698
- ? JSON.parse(componentLabels) : componentLabels || {};
2645
+ const tempComponentLabels = componentLabels && isValidJSON(componentLabels)
2646
+ ? JSON.parse(componentLabels) : componentLabels;
2699
2647
 
2700
2648
  /** @type {Record<string, string>} */
2701
- self.componentLabels = ObjectAssign(tempLabels, jsonLabels);
2649
+ self.componentLabels = ObjectAssign(colorPickerLabels, tempComponentLabels);
2702
2650
 
2703
2651
  /** @type {Color} */
2704
- self.color = new Color('white', format);
2652
+ self.color = new Color(input.value || '#fff', format);
2705
2653
 
2706
2654
  /** @type {CP.ColorFormats} */
2707
2655
  self.format = format;
@@ -2710,7 +2658,7 @@ class ColorPicker {
2710
2658
  if (colorKeywords instanceof Array) {
2711
2659
  self.colorKeywords = colorKeywords;
2712
2660
  } else if (typeof colorKeywords === 'string' && colorKeywords.length) {
2713
- self.colorKeywords = colorKeywords.split(',');
2661
+ self.colorKeywords = colorKeywords.split(',').map((x) => x.trim());
2714
2662
  }
2715
2663
 
2716
2664
  // set colour presets
@@ -2739,11 +2687,10 @@ class ColorPicker {
2739
2687
  self.handleFocusOut = self.handleFocusOut.bind(self);
2740
2688
  self.changeHandler = self.changeHandler.bind(self);
2741
2689
  self.handleDismiss = self.handleDismiss.bind(self);
2742
- self.keyToggle = self.keyToggle.bind(self);
2743
2690
  self.handleKnobs = self.handleKnobs.bind(self);
2744
2691
 
2745
2692
  // generate markup
2746
- initCallback(self);
2693
+ setMarkup(self);
2747
2694
 
2748
2695
  const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
2749
2696
  // set main elements
@@ -2831,76 +2778,83 @@ class ColorPicker {
2831
2778
  return inputValue !== '' && new Color(inputValue).isValid;
2832
2779
  }
2833
2780
 
2781
+ /** Returns the colour appearance, usually the closest colour name for the current value. */
2782
+ get appearance() {
2783
+ const {
2784
+ colorLabels, hsl, hsv, format,
2785
+ } = this;
2786
+
2787
+ const hue = roundPart(hsl.h * 360);
2788
+ const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
2789
+ const saturation = roundPart(saturationSource * 100);
2790
+ const lightness = roundPart(hsl.l * 100);
2791
+ const hsvl = hsv.v * 100;
2792
+
2793
+ let colorName;
2794
+
2795
+ // determine color appearance
2796
+ if (lightness === 100 && saturation === 0) {
2797
+ colorName = colorLabels.white;
2798
+ } else if (lightness === 0) {
2799
+ colorName = colorLabels.black;
2800
+ } else if (saturation === 0) {
2801
+ colorName = colorLabels.grey;
2802
+ } else if (hue < 15 || hue >= 345) {
2803
+ colorName = colorLabels.red;
2804
+ } else if (hue >= 15 && hue < 45) {
2805
+ colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
2806
+ } else if (hue >= 45 && hue < 75) {
2807
+ const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
2808
+ const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
2809
+ colorName = isGold ? colorLabels.gold : colorLabels.yellow;
2810
+ colorName = isOlive ? colorLabels.olive : colorName;
2811
+ } else if (hue >= 75 && hue < 155) {
2812
+ colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
2813
+ } else if (hue >= 155 && hue < 175) {
2814
+ colorName = colorLabels.teal;
2815
+ } else if (hue >= 175 && hue < 195) {
2816
+ colorName = colorLabels.cyan;
2817
+ } else if (hue >= 195 && hue < 255) {
2818
+ colorName = colorLabels.blue;
2819
+ } else if (hue >= 255 && hue < 270) {
2820
+ colorName = colorLabels.violet;
2821
+ } else if (hue >= 270 && hue < 295) {
2822
+ colorName = colorLabels.magenta;
2823
+ } else if (hue >= 295 && hue < 345) {
2824
+ colorName = colorLabels.pink;
2825
+ }
2826
+ return colorName;
2827
+ }
2828
+
2834
2829
  /** Updates `ColorPicker` visuals. */
2835
2830
  updateVisuals() {
2836
2831
  const self = this;
2837
2832
  const {
2838
- format, controlPositions, visuals,
2833
+ controlPositions, visuals,
2839
2834
  } = self;
2840
2835
  const [v1, v2, v3] = visuals;
2841
- const { offsetWidth, offsetHeight } = v1;
2842
- const hue = format === 'hsl'
2843
- ? controlPositions.c1x / offsetWidth
2844
- : controlPositions.c2y / offsetHeight;
2845
- // @ts-ignore - `hslToRgb` is assigned to `Color` as static method
2846
- const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
2836
+ const { offsetHeight } = v1;
2837
+ const hue = controlPositions.c2y / offsetHeight;
2838
+ const { r, g, b } = new Color({ h: hue, s: 1, l: 0.5 }).toRgb();
2847
2839
  const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
2848
2840
  const alpha = 1 - controlPositions.c3y / offsetHeight;
2849
2841
  const roundA = roundPart((alpha * 100)) / 100;
2850
2842
 
2851
- if (format !== 'hsl') {
2852
- const fill = new Color({
2853
- h: hue, s: 1, l: 0.5, a: alpha,
2854
- }).toRgbString();
2855
- const hueGradient = `linear-gradient(
2856
- rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2857
- rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2858
- rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2859
- rgb(255,0,0) 100%)`;
2860
- setElementStyle(v1, {
2861
- background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2862
- linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2863
- ${whiteGrad}`,
2864
- });
2865
- setElementStyle(v2, { background: hueGradient });
2866
- } else {
2867
- const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
2868
- const fill0 = new Color({
2869
- r: 255, g: 0, b: 0, a: alpha,
2870
- }).saturate(-saturation).toRgbString();
2871
- const fill1 = new Color({
2872
- r: 255, g: 255, b: 0, a: alpha,
2873
- }).saturate(-saturation).toRgbString();
2874
- const fill2 = new Color({
2875
- r: 0, g: 255, b: 0, a: alpha,
2876
- }).saturate(-saturation).toRgbString();
2877
- const fill3 = new Color({
2878
- r: 0, g: 255, b: 255, a: alpha,
2879
- }).saturate(-saturation).toRgbString();
2880
- const fill4 = new Color({
2881
- r: 0, g: 0, b: 255, a: alpha,
2882
- }).saturate(-saturation).toRgbString();
2883
- const fill5 = new Color({
2884
- r: 255, g: 0, b: 255, a: alpha,
2885
- }).saturate(-saturation).toRgbString();
2886
- const fill6 = new Color({
2887
- r: 255, g: 0, b: 0, a: alpha,
2888
- }).saturate(-saturation).toRgbString();
2889
- const fillGradient = `linear-gradient(to right,
2890
- ${fill0} 0%, ${fill1} 16.67%, ${fill2} 33.33%, ${fill3} 50%,
2891
- ${fill4} 66.67%, ${fill5} 83.33%, ${fill6} 100%)`;
2892
- const lightGrad = `linear-gradient(rgba(255,255,255,${roundA}) 0%, rgba(255,255,255,0) 50%),
2893
- linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,${roundA}) 100%)`;
2894
-
2895
- setElementStyle(v1, { background: `${lightGrad},${fillGradient},${whiteGrad}` });
2896
- const {
2897
- r: gr, g: gg, b: gb,
2898
- } = new Color({ r, g, b }).greyscale().toRgb();
2843
+ const fill = new Color({
2844
+ h: hue, s: 1, l: 0.5, a: alpha,
2845
+ }).toRgbString();
2846
+ const hueGradient = `linear-gradient(
2847
+ rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2848
+ rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2849
+ rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2850
+ rgb(255,0,0) 100%)`;
2851
+ setElementStyle(v1, {
2852
+ background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2853
+ linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2854
+ ${whiteGrad}`,
2855
+ });
2856
+ setElementStyle(v2, { background: hueGradient });
2899
2857
 
2900
- setElementStyle(v2, {
2901
- background: `linear-gradient(rgb(${r},${g},${b}) 0%, rgb(${gr},${gg},${gb}) 100%)`,
2902
- });
2903
- }
2904
2858
  setElementStyle(v3, {
2905
2859
  background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
2906
2860
  });
@@ -2939,7 +2893,7 @@ class ColorPicker {
2939
2893
  const self = this;
2940
2894
  const { activeElement } = getDocument(self.input);
2941
2895
 
2942
- if ((isMobile && self.dragElement)
2896
+ if ((e.type === touchmoveEvent && self.dragElement)
2943
2897
  || (activeElement && self.controlKnobs.includes(activeElement))) {
2944
2898
  e.stopPropagation();
2945
2899
  e.preventDefault();
@@ -3050,13 +3004,13 @@ class ColorPicker {
3050
3004
  const [v1, v2, v3] = visuals;
3051
3005
  const [c1, c2, c3] = controlKnobs;
3052
3006
  /** @type {HTMLElement} */
3053
- const visual = hasClass(target, 'visual-control')
3054
- ? target : querySelector('.visual-control', target.parentElement);
3007
+ const visual = controlKnobs.includes(target) ? target.previousElementSibling : target;
3055
3008
  const visualRect = getBoundingClientRect(visual);
3009
+ const html = getDocumentElement(v1);
3056
3010
  const X = type === 'touchstart' ? touches[0].pageX : pageX;
3057
3011
  const Y = type === 'touchstart' ? touches[0].pageY : pageY;
3058
- const offsetX = X - window.pageXOffset - visualRect.left;
3059
- const offsetY = Y - window.pageYOffset - visualRect.top;
3012
+ const offsetX = X - html.scrollLeft - visualRect.left;
3013
+ const offsetY = Y - html.scrollTop - visualRect.top;
3060
3014
 
3061
3015
  if (target === v1 || target === c1) {
3062
3016
  self.dragElement = visual;
@@ -3116,10 +3070,11 @@ class ColorPicker {
3116
3070
  if (!dragElement) return;
3117
3071
 
3118
3072
  const controlRect = getBoundingClientRect(dragElement);
3119
- const X = type === 'touchmove' ? touches[0].pageX : pageX;
3120
- const Y = type === 'touchmove' ? touches[0].pageY : pageY;
3121
- const offsetX = X - window.pageXOffset - controlRect.left;
3122
- const offsetY = Y - window.pageYOffset - controlRect.top;
3073
+ const win = getDocumentElement(v1);
3074
+ const X = type === touchmoveEvent ? touches[0].pageX : pageX;
3075
+ const Y = type === touchmoveEvent ? touches[0].pageY : pageY;
3076
+ const offsetX = X - win.scrollLeft - controlRect.left;
3077
+ const offsetY = Y - win.scrollTop - controlRect.top;
3123
3078
 
3124
3079
  if (dragElement === v1) {
3125
3080
  self.changeControl1(offsetX, offsetY);
@@ -3146,30 +3101,41 @@ class ColorPicker {
3146
3101
  if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
3147
3102
  e.preventDefault();
3148
3103
 
3149
- const { controlKnobs } = self;
3104
+ const { controlKnobs, visuals } = self;
3105
+ const { offsetWidth, offsetHeight } = visuals[0];
3150
3106
  const [c1, c2, c3] = controlKnobs;
3151
3107
  const { activeElement } = getDocument(c1);
3152
3108
  const currentKnob = controlKnobs.find((x) => x === activeElement);
3109
+ const yRatio = offsetHeight / 360;
3153
3110
 
3154
3111
  if (currentKnob) {
3155
3112
  let offsetX = 0;
3156
3113
  let offsetY = 0;
3114
+
3157
3115
  if (target === c1) {
3116
+ const xRatio = offsetWidth / 100;
3117
+
3158
3118
  if ([keyArrowLeft, keyArrowRight].includes(code)) {
3159
- self.controlPositions.c1x += code === keyArrowRight ? +1 : -1;
3119
+ self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
3160
3120
  } else if ([keyArrowUp, keyArrowDown].includes(code)) {
3161
- self.controlPositions.c1y += code === keyArrowDown ? +1 : -1;
3121
+ self.controlPositions.c1y += code === keyArrowDown ? yRatio : -yRatio;
3162
3122
  }
3163
3123
 
3164
3124
  offsetX = self.controlPositions.c1x;
3165
3125
  offsetY = self.controlPositions.c1y;
3166
3126
  self.changeControl1(offsetX, offsetY);
3167
3127
  } else if (target === c2) {
3168
- self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
3128
+ self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code)
3129
+ ? yRatio
3130
+ : -yRatio;
3131
+
3169
3132
  offsetY = self.controlPositions.c2y;
3170
3133
  self.changeControl2(offsetY);
3171
3134
  } else if (target === c3) {
3172
- self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
3135
+ self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code)
3136
+ ? yRatio
3137
+ : -yRatio;
3138
+
3173
3139
  offsetY = self.controlPositions.c3y;
3174
3140
  self.changeAlpha(offsetY);
3175
3141
  }
@@ -3197,7 +3163,7 @@ class ColorPicker {
3197
3163
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
3198
3164
  if (activeElement === input) {
3199
3165
  if (isNonColorValue) {
3200
- colorSource = 'white';
3166
+ colorSource = currentValue === 'transparent' ? 'rgba(0,0,0,0)' : 'rgb(0,0,0)';
3201
3167
  } else {
3202
3168
  colorSource = currentValue;
3203
3169
  }
@@ -3248,9 +3214,7 @@ class ColorPicker {
3248
3214
  changeControl1(X, Y) {
3249
3215
  const self = this;
3250
3216
  let [offsetX, offsetY] = [0, 0];
3251
- const {
3252
- format, controlPositions, visuals,
3253
- } = self;
3217
+ const { controlPositions, visuals } = self;
3254
3218
  const { offsetHeight, offsetWidth } = visuals[0];
3255
3219
 
3256
3220
  if (X > offsetWidth) offsetX = offsetWidth;
@@ -3259,29 +3223,19 @@ class ColorPicker {
3259
3223
  if (Y > offsetHeight) offsetY = offsetHeight;
3260
3224
  else if (Y >= 0) offsetY = Y;
3261
3225
 
3262
- const hue = format === 'hsl'
3263
- ? offsetX / offsetWidth
3264
- : controlPositions.c2y / offsetHeight;
3226
+ const hue = controlPositions.c2y / offsetHeight;
3265
3227
 
3266
- const saturation = format === 'hsl'
3267
- ? 1 - controlPositions.c2y / offsetHeight
3268
- : offsetX / offsetWidth;
3228
+ const saturation = offsetX / offsetWidth;
3269
3229
 
3270
3230
  const lightness = 1 - offsetY / offsetHeight;
3271
3231
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3272
3232
 
3273
- const colorObject = format === 'hsl'
3274
- ? {
3275
- h: hue, s: saturation, l: lightness, a: alpha,
3276
- }
3277
- : {
3278
- h: hue, s: saturation, v: lightness, a: alpha,
3279
- };
3280
-
3281
3233
  // new color
3282
3234
  const {
3283
3235
  r, g, b, a,
3284
- } = new Color(colorObject);
3236
+ } = new Color({
3237
+ h: hue, s: saturation, v: lightness, a: alpha,
3238
+ });
3285
3239
 
3286
3240
  ObjectAssign(self.color, {
3287
3241
  r, g, b, a,
@@ -3308,7 +3262,7 @@ class ColorPicker {
3308
3262
  changeControl2(Y) {
3309
3263
  const self = this;
3310
3264
  const {
3311
- format, controlPositions, visuals,
3265
+ controlPositions, visuals,
3312
3266
  } = self;
3313
3267
  const { offsetHeight, offsetWidth } = visuals[0];
3314
3268
 
@@ -3317,26 +3271,17 @@ class ColorPicker {
3317
3271
  if (Y > offsetHeight) offsetY = offsetHeight;
3318
3272
  else if (Y >= 0) offsetY = Y;
3319
3273
 
3320
- const hue = format === 'hsl'
3321
- ? controlPositions.c1x / offsetWidth
3322
- : offsetY / offsetHeight;
3323
- const saturation = format === 'hsl'
3324
- ? 1 - offsetY / offsetHeight
3325
- : controlPositions.c1x / offsetWidth;
3274
+ const hue = offsetY / offsetHeight;
3275
+ const saturation = controlPositions.c1x / offsetWidth;
3326
3276
  const lightness = 1 - controlPositions.c1y / offsetHeight;
3327
3277
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3328
- const colorObject = format === 'hsl'
3329
- ? {
3330
- h: hue, s: saturation, l: lightness, a: alpha,
3331
- }
3332
- : {
3333
- h: hue, s: saturation, v: lightness, a: alpha,
3334
- };
3335
3278
 
3336
3279
  // new color
3337
3280
  const {
3338
3281
  r, g, b, a,
3339
- } = new Color(colorObject);
3282
+ } = new Color({
3283
+ h: hue, s: saturation, v: lightness, a: alpha,
3284
+ });
3340
3285
 
3341
3286
  ObjectAssign(self.color, {
3342
3287
  r, g, b, a,
@@ -3423,18 +3368,18 @@ class ColorPicker {
3423
3368
  setControlPositions() {
3424
3369
  const self = this;
3425
3370
  const {
3426
- format, visuals, color, hsl, hsv,
3371
+ visuals, color, hsv,
3427
3372
  } = self;
3428
3373
  const { offsetHeight, offsetWidth } = visuals[0];
3429
3374
  const alpha = color.a;
3430
- const hue = hsl.h;
3375
+ const hue = hsv.h;
3431
3376
 
3432
- const saturation = format !== 'hsl' ? hsv.s : hsl.s;
3433
- const lightness = format !== 'hsl' ? hsv.v : hsl.l;
3377
+ const saturation = hsv.s;
3378
+ const lightness = hsv.v;
3434
3379
 
3435
- self.controlPositions.c1x = format !== 'hsl' ? saturation * offsetWidth : hue * offsetWidth;
3380
+ self.controlPositions.c1x = saturation * offsetWidth;
3436
3381
  self.controlPositions.c1y = (1 - lightness) * offsetHeight;
3437
- self.controlPositions.c2y = format !== 'hsl' ? hue * offsetHeight : (1 - saturation) * offsetHeight;
3382
+ self.controlPositions.c2y = hue * offsetHeight;
3438
3383
  self.controlPositions.c3y = (1 - alpha) * offsetHeight;
3439
3384
  }
3440
3385
 
@@ -3442,78 +3387,40 @@ class ColorPicker {
3442
3387
  updateAppearance() {
3443
3388
  const self = this;
3444
3389
  const {
3445
- componentLabels, colorLabels, color, parent,
3446
- hsl, hsv, hex, format, controlKnobs,
3390
+ componentLabels, color, parent,
3391
+ hsv, hex, format, controlKnobs,
3447
3392
  } = self;
3448
3393
  const {
3449
3394
  appearanceLabel, hexLabel, valueLabel,
3450
3395
  } = componentLabels;
3451
- const { r, g, b } = color.toRgb();
3396
+ let { r, g, b } = color.toRgb();
3452
3397
  const [knob1, knob2, knob3] = controlKnobs;
3453
- const hue = roundPart(hsl.h * 360);
3398
+ const hue = roundPart(hsv.h * 360);
3454
3399
  const alpha = color.a;
3455
- const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
3456
- const saturation = roundPart(saturationSource * 100);
3457
- const lightness = roundPart(hsl.l * 100);
3458
- const hsvl = hsv.v * 100;
3459
- let colorName;
3460
-
3461
- // determine color appearance
3462
- if (lightness === 100 && saturation === 0) {
3463
- colorName = colorLabels.white;
3464
- } else if (lightness === 0) {
3465
- colorName = colorLabels.black;
3466
- } else if (saturation === 0) {
3467
- colorName = colorLabels.grey;
3468
- } else if (hue < 15 || hue >= 345) {
3469
- colorName = colorLabels.red;
3470
- } else if (hue >= 15 && hue < 45) {
3471
- colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
3472
- } else if (hue >= 45 && hue < 75) {
3473
- const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
3474
- const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
3475
- colorName = isGold ? colorLabels.gold : colorLabels.yellow;
3476
- colorName = isOlive ? colorLabels.olive : colorName;
3477
- } else if (hue >= 75 && hue < 155) {
3478
- colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
3479
- } else if (hue >= 155 && hue < 175) {
3480
- colorName = colorLabels.teal;
3481
- } else if (hue >= 175 && hue < 195) {
3482
- colorName = colorLabels.cyan;
3483
- } else if (hue >= 195 && hue < 255) {
3484
- colorName = colorLabels.blue;
3485
- } else if (hue >= 255 && hue < 270) {
3486
- colorName = colorLabels.violet;
3487
- } else if (hue >= 270 && hue < 295) {
3488
- colorName = colorLabels.magenta;
3489
- } else if (hue >= 295 && hue < 345) {
3490
- colorName = colorLabels.pink;
3491
- }
3400
+ const saturation = roundPart(hsv.s * 100);
3401
+ const lightness = roundPart(hsv.v * 100);
3402
+ const colorName = self.appearance;
3492
3403
 
3493
3404
  let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
3494
3405
 
3495
- if (format === 'hsl') {
3496
- colorLabel = `HSL: ${hue}°, ${saturation}%, ${lightness}%`;
3497
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3498
- setAttribute(knob1, ariaValueText, `${hue}° & ${lightness}%`);
3499
- setAttribute(knob1, ariaValueNow, `${hue}`);
3500
- setAttribute(knob2, ariaValueText, `${saturation}%`);
3501
- setAttribute(knob2, ariaValueNow, `${saturation}`);
3502
- } else if (format === 'hwb') {
3406
+ if (format === 'hwb') {
3503
3407
  const { hwb } = self;
3504
3408
  const whiteness = roundPart(hwb.w * 100);
3505
3409
  const blackness = roundPart(hwb.b * 100);
3506
3410
  colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
3507
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3508
3411
  setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
3509
3412
  setAttribute(knob1, ariaValueNow, `${whiteness}`);
3413
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3510
3414
  setAttribute(knob2, ariaValueText, `${hue}%`);
3511
3415
  setAttribute(knob2, ariaValueNow, `${hue}`);
3512
3416
  } else {
3417
+ [r, g, b] = [r, g, b].map(roundPart);
3418
+ colorLabel = format === 'hsl' ? `HSL: ${hue}°, ${saturation}%, ${lightness}%` : colorLabel;
3513
3419
  colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
3514
- setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3420
+
3515
3421
  setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
3516
3422
  setAttribute(knob1, ariaValueNow, `${lightness}`);
3423
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3517
3424
  setAttribute(knob2, ariaValueText, `${hue}°`);
3518
3425
  setAttribute(knob2, ariaValueNow, `${hue}`);
3519
3426
  }
@@ -3541,10 +3448,12 @@ class ColorPicker {
3541
3448
  /** Updates the control knobs actual positions. */
3542
3449
  updateControls() {
3543
3450
  const { controlKnobs, controlPositions } = this;
3544
- const {
3451
+ let {
3545
3452
  c1x, c1y, c2y, c3y,
3546
3453
  } = controlPositions;
3547
3454
  const [control1, control2, control3] = controlKnobs;
3455
+ // round control positions
3456
+ [c1x, c1y, c2y, c3y] = [c1x, c1y, c2y, c3y].map(roundPart);
3548
3457
 
3549
3458
  setElementStyle(control1, { transform: `translate3d(${c1x - 4}px,${c1y - 4}px,0)` });
3550
3459
  setElementStyle(control2, { transform: `translate3d(0,${c2y - 4}px,0)` });
@@ -3587,7 +3496,8 @@ class ColorPicker {
3587
3496
  i3.value = `${blackness}`;
3588
3497
  i4.value = `${alpha}`;
3589
3498
  } else if (format === 'rgb') {
3590
- const { r, g, b } = self.rgb;
3499
+ let { r, g, b } = self.rgb;
3500
+ [r, g, b] = [r, g, b].map(roundPart);
3591
3501
 
3592
3502
  newColor = self.color.toRgbString();
3593
3503
  i1.value = `${r}`;
@@ -3605,37 +3515,13 @@ class ColorPicker {
3605
3515
  }
3606
3516
  }
3607
3517
 
3608
- /**
3609
- * The `Space` & `Enter` keys specific event listener.
3610
- * Toggle visibility of the `ColorPicker` / the presets menu, showing one will hide the other.
3611
- * @param {KeyboardEvent} e
3612
- * @this {ColorPicker}
3613
- */
3614
- keyToggle(e) {
3615
- const self = this;
3616
- const { menuToggle } = self;
3617
- const { activeElement } = getDocument(menuToggle);
3618
- const { code } = e;
3619
-
3620
- if ([keyEnter, keySpace].includes(code)) {
3621
- if ((menuToggle && activeElement === menuToggle) || !activeElement) {
3622
- e.preventDefault();
3623
- if (!activeElement) {
3624
- self.togglePicker(e);
3625
- } else {
3626
- self.toggleMenu();
3627
- }
3628
- }
3629
- }
3630
- }
3631
-
3632
3518
  /**
3633
3519
  * Toggle the `ColorPicker` dropdown visibility.
3634
- * @param {Event} e
3520
+ * @param {Event=} e
3635
3521
  * @this {ColorPicker}
3636
3522
  */
3637
3523
  togglePicker(e) {
3638
- e.preventDefault();
3524
+ if (e) e.preventDefault();
3639
3525
  const self = this;
3640
3526
  const { colorPicker } = self;
3641
3527
 
@@ -3656,8 +3542,13 @@ class ColorPicker {
3656
3542
  }
3657
3543
  }
3658
3544
 
3659
- /** Toggles the visibility of the `ColorPicker` presets menu. */
3660
- toggleMenu() {
3545
+ /**
3546
+ * Toggles the visibility of the `ColorPicker` presets menu.
3547
+ * @param {Event=} e
3548
+ * @this {ColorPicker}
3549
+ */
3550
+ toggleMenu(e) {
3551
+ if (e) e.preventDefault();
3661
3552
  const self = this;
3662
3553
  const { colorMenu } = self;
3663
3554
 
@@ -3683,6 +3574,10 @@ class ColorPicker {
3683
3574
  const relatedBtn = openPicker ? pickerToggle : menuToggle;
3684
3575
  const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
3685
3576
 
3577
+ // if (!self.isValid) {
3578
+ self.value = self.color.toString(true);
3579
+ // }
3580
+
3686
3581
  if (openDropdown) {
3687
3582
  removeClass(openDropdown, 'show');
3688
3583
  setAttribute(relatedBtn, ariaExpanded, 'false');
@@ -3696,9 +3591,6 @@ class ColorPicker {
3696
3591
  }, animationDuration);
3697
3592
  }
3698
3593
 
3699
- if (!self.isValid) {
3700
- self.value = self.color.toString();
3701
- }
3702
3594
  if (!focusPrevented) {
3703
3595
  focus(pickerToggle);
3704
3596
  }
@@ -3747,93 +3639,85 @@ let CPID = 0;
3747
3639
  * `ColorPickerElement` Web Component.
3748
3640
  * @example
3749
3641
  * <label for="UNIQUE_ID">Label</label>
3750
- * <color-picker data-format="hex" data-value="#075">
3751
- * <input id="UNIQUE_ID" type="text" class="color-preview btn-appearance">
3642
+ * <color-picker>
3643
+ * <input id="UNIQUE_ID" value="red" format="hex" class="color-preview btn-appearance">
3752
3644
  * </color-picker>
3645
+ * // or
3646
+ * <label for="UNIQUE_ID">Label</label>
3647
+ * <color-picker data-id="UNIQUE_ID" data-value="red" data-format="hex"></color-picker>
3753
3648
  */
3754
3649
  class ColorPickerElement extends HTMLElement {
3755
3650
  constructor() {
3756
3651
  super();
3757
- /** @type {boolean} */
3758
- this.isDisconnected = true;
3759
3652
  this.attachShadow({ mode: 'open' });
3760
3653
  }
3761
3654
 
3762
3655
  /**
3763
3656
  * Returns the current color value.
3764
- * @returns {string?}
3657
+ * @returns {string | undefined}
3765
3658
  */
3766
- get value() { return this.input ? this.input.value : null; }
3659
+ get value() { return this.input && this.input.value; }
3767
3660
 
3768
3661
  connectedCallback() {
3769
- if (this.colorPicker) {
3770
- if (this.isDisconnected) {
3771
- this.isDisconnected = false;
3772
- }
3773
- return;
3774
- }
3662
+ if (this.input) return;
3775
3663
 
3776
- const inputs = getElementsByTagName('input', this);
3664
+ let [input] = getElementsByTagName('input', this);
3665
+ const value = (input && getAttribute(input, 'value')) || getAttribute(this, 'data-value') || '#fff';
3666
+ const format = (input && getAttribute(input, 'format')) || getAttribute(this, 'data-format') || 'rgb';
3667
+ let id = (input && getAttribute(input, 'id')) || getAttribute(this, 'data-id');
3668
+
3669
+ if (!id) {
3670
+ id = `color-picker-${format}-${CPID}`;
3671
+ CPID += 1;
3672
+ }
3777
3673
 
3778
- if (!inputs.length) {
3779
- const label = getAttribute(this, 'data-label');
3780
- const value = getAttribute(this, 'data-value') || '#069';
3781
- const format = getAttribute(this, 'data-format') || 'rgb';
3782
- const newInput = createElement({
3674
+ if (!input) {
3675
+ input = createElement({
3783
3676
  tagName: 'input',
3784
3677
  type: 'text',
3785
3678
  className: 'color-preview btn-appearance',
3786
3679
  });
3787
- let id = getAttribute(this, 'data-id');
3788
- if (!id) {
3789
- id = `color-picker-${format}-${CPID}`;
3790
- CPID += 1;
3791
- }
3792
3680
 
3793
- const labelElement = createElement({ tagName: 'label', innerText: label || 'Color Picker' });
3794
- this.before(labelElement);
3795
- setAttribute(labelElement, 'for', id);
3796
- setAttribute(newInput, 'id', id);
3797
- setAttribute(newInput, 'name', id);
3798
- setAttribute(newInput, 'autocomplete', 'off');
3799
- setAttribute(newInput, 'spellcheck', 'false');
3800
- setAttribute(newInput, 'value', value);
3801
- this.append(newInput);
3681
+ setAttribute(input, 'id', id);
3682
+ setAttribute(input, 'name', id);
3683
+ setAttribute(input, 'autocomplete', 'off');
3684
+ setAttribute(input, 'spellcheck', 'false');
3685
+ setAttribute(input, 'value', value);
3686
+ this.append(input);
3802
3687
  }
3688
+ /** @type {HTMLInputElement} */
3689
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3690
+ this.input = input;
3803
3691
 
3804
- const [input] = inputs;
3805
-
3806
- if (input) {
3807
- /** @type {HTMLInputElement} */
3808
- // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3809
- this.input = input;
3810
-
3811
- // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3812
- this.colorPicker = new ColorPicker(input);
3813
- this.color = this.colorPicker.color;
3814
-
3815
- if (this.shadowRoot) {
3816
- this.shadowRoot.append(createElement('slot'));
3817
- }
3692
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3693
+ this.colorPicker = new ColorPicker(input);
3818
3694
 
3819
- this.isDisconnected = false;
3820
- }
3695
+ // @ts-ignore - `shadowRoot` is defined in the constructor
3696
+ this.shadowRoot.append(createElement('slot'));
3821
3697
  }
3822
3698
 
3699
+ /** @this {ColorPickerElement} */
3823
3700
  disconnectedCallback() {
3824
- if (this.colorPicker) this.colorPicker.dispose();
3825
- this.isDisconnected = true;
3701
+ const { input, colorPicker, shadowRoot } = this;
3702
+ if (colorPicker) colorPicker.dispose();
3703
+ if (input) input.remove();
3704
+ if (shadowRoot) shadowRoot.innerHTML = '';
3705
+
3706
+ ObjectAssign(this, {
3707
+ colorPicker: undefined,
3708
+ input: undefined,
3709
+ });
3826
3710
  }
3827
3711
  }
3828
3712
 
3829
3713
  ObjectAssign(ColorPickerElement, {
3830
3714
  Color,
3831
3715
  ColorPicker,
3832
- ColorPalette,
3833
- getInstance: getColorPickerInstance,
3716
+ ColorPalette, // @ts-ignore
3717
+ getInstance: ColorPicker.getInstance,
3834
3718
  Version,
3835
3719
  });
3836
3720
 
3837
3721
  customElements.define('color-picker', ColorPickerElement);
3838
3722
 
3839
- export default ColorPickerElement;
3723
+ export { ColorPickerElement as default };