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

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