@thednp/color-picker 0.0.1 → 0.0.2-alpha3

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.1 (http://thednp.github.io/color-picker)
2
+ * ColorPickerElement v0.0.2alpha3 (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
- // RGB values now are all in [0, 255] range
767
- [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255);
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));
768
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,12 +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
899
 
900
+ [r, g, b] = [r, g, b].map((n) => roundPart(n * 255 * 100) / 100);
901
+ a = roundPart(a * 100) / 100;
924
902
  return {
925
- r, g, b, a: roundPart(a * 100) / 100,
903
+ r, g, b, a,
926
904
  };
927
905
  }
928
906
 
@@ -1016,7 +994,7 @@ class Color {
1016
994
  toHsv() {
1017
995
  const {
1018
996
  r, g, b, a,
1019
- } = this.toRgb();
997
+ } = this;
1020
998
  const { h, s, v } = rgbToHsv(r, g, b);
1021
999
 
1022
1000
  return {
@@ -1031,7 +1009,7 @@ class Color {
1031
1009
  toHsl() {
1032
1010
  const {
1033
1011
  r, g, b, a,
1034
- } = this.toRgb();
1012
+ } = this;
1035
1013
  const { h, s, l } = rgbToHsl(r, g, b);
1036
1014
 
1037
1015
  return {
@@ -1116,6 +1094,7 @@ class Color {
1116
1094
  */
1117
1095
  setAlpha(alpha) {
1118
1096
  const self = this;
1097
+ if (typeof alpha !== 'number') return self;
1119
1098
  self.a = boundAlpha(alpha);
1120
1099
  return self;
1121
1100
  }
@@ -1230,6 +1209,7 @@ ObjectAssign(Color, {
1230
1209
  isOnePointZero,
1231
1210
  isPercentage,
1232
1211
  isValidCSSUnit,
1212
+ isColorName,
1233
1213
  pad2,
1234
1214
  clamp01,
1235
1215
  bound01,
@@ -1247,10 +1227,11 @@ ObjectAssign(Color, {
1247
1227
  hueToRgb,
1248
1228
  hwbToRgb,
1249
1229
  parseIntFromHex,
1250
- numberInputToObject,
1251
1230
  stringInputToObject,
1252
1231
  inputToRGB,
1253
1232
  roundPart,
1233
+ getElementStyle,
1234
+ setElementStyle,
1254
1235
  ObjectAssign,
1255
1236
  });
1256
1237
 
@@ -1380,24 +1361,6 @@ const ariaValueText = 'aria-valuetext';
1380
1361
  */
1381
1362
  const ariaValueNow = 'aria-valuenow';
1382
1363
 
1383
- /**
1384
- * A global namespace for aria-haspopup.
1385
- * @type {string}
1386
- */
1387
- const ariaHasPopup = 'aria-haspopup';
1388
-
1389
- /**
1390
- * A global namespace for aria-hidden.
1391
- * @type {string}
1392
- */
1393
- const ariaHidden = 'aria-hidden';
1394
-
1395
- /**
1396
- * A global namespace for aria-labelledby.
1397
- * @type {string}
1398
- */
1399
- const ariaLabelledBy = 'aria-labelledby';
1400
-
1401
1364
  /**
1402
1365
  * A global namespace for `ArrowDown` key.
1403
1366
  * @type {string} e.which = 40 equivalent
@@ -1524,37 +1487,6 @@ const resizeEvent = 'resize';
1524
1487
  */
1525
1488
  const focusoutEvent = 'focusout';
1526
1489
 
1527
- // @ts-ignore
1528
- const { userAgentData: uaDATA } = navigator;
1529
-
1530
- /**
1531
- * A global namespace for `userAgentData` object.
1532
- */
1533
- const userAgentData = uaDATA;
1534
-
1535
- const { userAgent: userAgentString } = navigator;
1536
-
1537
- /**
1538
- * A global namespace for `navigator.userAgent` string.
1539
- */
1540
- const userAgent = userAgentString;
1541
-
1542
- const mobileBrands = /iPhone|iPad|iPod|Android/i;
1543
- let isMobileCheck = false;
1544
-
1545
- if (userAgentData) {
1546
- isMobileCheck = userAgentData.brands
1547
- .some((/** @type {Record<String, any>} */x) => mobileBrands.test(x.brand));
1548
- } else {
1549
- isMobileCheck = mobileBrands.test(userAgent);
1550
- }
1551
-
1552
- /**
1553
- * A global `boolean` for mobile detection.
1554
- * @type {boolean}
1555
- */
1556
- const isMobile = isMobileCheck;
1557
-
1558
1490
  /**
1559
1491
  * Returns the `document.documentElement` or the `<html>` element.
1560
1492
  *
@@ -1739,30 +1671,6 @@ function getElementsByClassName(selector, parent) {
1739
1671
  return lookUp.getElementsByClassName(selector);
1740
1672
  }
1741
1673
 
1742
- /**
1743
- * This is a shortie for `document.createElementNS` method
1744
- * which allows you to create a new `HTMLElement` for a given `tagName`
1745
- * or based on an object with specific non-readonly attributes:
1746
- * `id`, `className`, `textContent`, `style`, etc.
1747
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
1748
- *
1749
- * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
1750
- * @param {Record<string, string> | string} param `tagName` or object
1751
- * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
1752
- */
1753
- function createElementNS(namespace, param) {
1754
- if (typeof param === 'string') {
1755
- return getDocument().createElementNS(namespace, param);
1756
- }
1757
-
1758
- const { tagName } = param;
1759
- const attr = { ...param };
1760
- const newElement = createElementNS(namespace, tagName);
1761
- delete attr.tagName;
1762
- ObjectAssign(newElement, attr);
1763
- return newElement;
1764
- }
1765
-
1766
1674
  /**
1767
1675
  * Shortcut for the `Element.dispatchEvent(Event)` method.
1768
1676
  *
@@ -1880,12 +1788,11 @@ function normalizeValue(value) {
1880
1788
  }
1881
1789
 
1882
1790
  /**
1883
- * Shortcut for `String.toLowerCase()`.
1884
- *
1885
- * @param {string} source input string
1886
- * @returns {string} lowercase output string
1791
+ * Shortcut for `Object.keys()` static method.
1792
+ * @param {Record<string, any>} obj a target object
1793
+ * @returns {string[]}
1887
1794
  */
1888
- const toLowerCase = (source) => source.toLowerCase();
1795
+ const ObjectKeys = (obj) => Object.keys(obj);
1889
1796
 
1890
1797
  /**
1891
1798
  * Utility to normalize component options.
@@ -1990,6 +1897,77 @@ function removeClass(element, classNAME) {
1990
1897
  */
1991
1898
  const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
1992
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
+
1993
1971
  /** @type {Record<string, string>} */
1994
1972
  const colorPickerLabels = {
1995
1973
  pickerLabel: 'Colour Picker',
@@ -2017,14 +1995,72 @@ const colorPickerLabels = {
2017
1995
  */
2018
1996
  const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
2019
1997
 
1998
+ const tabIndex = 'tabindex';
1999
+
2020
2000
  /**
2021
- * Shortcut for `String.toUpperCase()`.
2022
- *
2023
- * @param {string} source input string
2024
- * @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
2025
2019
  */
2026
2020
  const toUpperCase = (source) => source.toUpperCase();
2027
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
+
2028
2064
  const vHidden = 'v-hidden';
2029
2065
 
2030
2066
  /**
@@ -2104,8 +2140,6 @@ const ariaValueMin = 'aria-valuemin';
2104
2140
  */
2105
2141
  const ariaValueMax = 'aria-valuemax';
2106
2142
 
2107
- const tabIndex = 'tabindex';
2108
-
2109
2143
  /**
2110
2144
  * Returns all color controls for `ColorPicker`.
2111
2145
  *
@@ -2213,75 +2247,6 @@ function setCSSProperties(element, props) {
2213
2247
  });
2214
2248
  }
2215
2249
 
2216
- /**
2217
- * @class
2218
- * Returns a color palette with a given set of parameters.
2219
- * @example
2220
- * new ColorPalette(0, 12, 10);
2221
- * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
2222
- */
2223
- class ColorPalette {
2224
- /**
2225
- * The `hue` parameter is optional, which would be set to 0.
2226
- * @param {number[]} args represeinting hue, hueSteps, lightSteps
2227
- * * `args.hue` the starting Hue [0, 360]
2228
- * * `args.hueSteps` Hue Steps Count [5, 24]
2229
- * * `args.lightSteps` Lightness Steps Count [5, 12]
2230
- */
2231
- constructor(...args) {
2232
- let hue = 0;
2233
- let hueSteps = 12;
2234
- let lightSteps = 10;
2235
- let lightnessArray = [0.5];
2236
-
2237
- if (args.length === 3) {
2238
- [hue, hueSteps, lightSteps] = args;
2239
- } else if (args.length === 2) {
2240
- [hueSteps, lightSteps] = args;
2241
- } else {
2242
- throw TypeError('ColorPalette requires minimum 2 arguments');
2243
- }
2244
-
2245
- /** @type {string[]} */
2246
- const colors = [];
2247
-
2248
- const hueStep = 360 / hueSteps;
2249
- const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
2250
- const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
2251
-
2252
- let lightStep = 0.25;
2253
- lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
2254
- lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
2255
- lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
2256
- lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
2257
- lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
2258
- lightStep = lightSteps > 13 ? estimatedStep : lightStep;
2259
-
2260
- // light tints
2261
- for (let i = 1; i < half + 1; i += 1) {
2262
- lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
2263
- }
2264
-
2265
- // dark tints
2266
- for (let i = 1; i < lightSteps - half; i += 1) {
2267
- lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
2268
- }
2269
-
2270
- // feed `colors` Array
2271
- for (let i = 0; i < hueSteps; i += 1) {
2272
- const currentHue = ((hue + i * hueStep) % 360) / 360;
2273
- lightnessArray.forEach((l) => {
2274
- colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
2275
- });
2276
- }
2277
-
2278
- this.hue = hue;
2279
- this.hueSteps = hueSteps;
2280
- this.lightSteps = lightSteps;
2281
- this.colors = colors;
2282
- }
2283
- }
2284
-
2285
2250
  /**
2286
2251
  * Returns a color-defaults with given values and class.
2287
2252
  * @param {CP.ColorPicker} self
@@ -2315,7 +2280,8 @@ function getColorMenu(self, colorsSource, menuClass) {
2315
2280
  optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2316
2281
  const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2317
2282
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2318
-
2283
+ /** @type {HTMLUListElement} */
2284
+ // @ts-ignore -- <UL> is an `HTMLElement`
2319
2285
  const menu = createElement({
2320
2286
  tagName: 'ul',
2321
2287
  className: finalClass,
@@ -2323,7 +2289,7 @@ function getColorMenu(self, colorsSource, menuClass) {
2323
2289
  setAttribute(menu, 'role', 'listbox');
2324
2290
  setAttribute(menu, ariaLabel, menuLabel);
2325
2291
 
2326
- if (isScrollable) { // @ts-ignore
2292
+ if (isScrollable) {
2327
2293
  setCSSProperties(menu, {
2328
2294
  '--grid-item-size': `${optionSize}rem`,
2329
2295
  '--grid-fit': fit,
@@ -2334,15 +2300,19 @@ function getColorMenu(self, colorsSource, menuClass) {
2334
2300
  }
2335
2301
 
2336
2302
  colorsArray.forEach((x) => {
2337
- const [value, label] = x.trim().split(':');
2338
- const xRealColor = new Color(value, format).toString();
2339
- 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');
2340
2310
  const active = isActive ? ' active' : '';
2341
2311
 
2342
2312
  const option = createElement({
2343
2313
  tagName: 'li',
2344
2314
  className: `color-option${active}`,
2345
- innerText: `${label || x}`,
2315
+ innerText: `${label || value}`,
2346
2316
  });
2347
2317
 
2348
2318
  setAttribute(option, tabIndex, '0');
@@ -2351,7 +2321,7 @@ function getColorMenu(self, colorsSource, menuClass) {
2351
2321
  setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2352
2322
 
2353
2323
  if (isOptionsMenu) {
2354
- setElementStyle(option, { backgroundColor: x });
2324
+ setElementStyle(option, { backgroundColor: value });
2355
2325
  }
2356
2326
 
2357
2327
  menu.append(option);
@@ -2360,55 +2330,10 @@ function getColorMenu(self, colorsSource, menuClass) {
2360
2330
  }
2361
2331
 
2362
2332
  /**
2363
- * Check if a string is valid JSON string.
2364
- * @param {string} str the string input
2365
- * @returns {boolean} the query result
2366
- */
2367
- function isValidJSON(str) {
2368
- try {
2369
- JSON.parse(str);
2370
- } catch (e) {
2371
- return false;
2372
- }
2373
- return true;
2374
- }
2375
-
2376
- var version = "0.0.1";
2377
-
2378
- // @ts-ignore
2379
-
2380
- const Version = version;
2381
-
2382
- // ColorPicker GC
2383
- // ==============
2384
- const colorPickerString = 'color-picker';
2385
- const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2386
- const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2387
- const colorPickerDefaults = {
2388
- componentLabels: colorPickerLabels,
2389
- colorLabels: colorNames,
2390
- format: 'rgb',
2391
- colorPresets: false,
2392
- colorKeywords: false,
2393
- };
2394
-
2395
- // ColorPicker Static Methods
2396
- // ==========================
2397
-
2398
- /** @type {CP.GetInstance<ColorPicker>} */
2399
- const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2400
-
2401
- /** @type {CP.InitCallback<ColorPicker>} */
2402
- const initColorPicker = (element) => new ColorPicker(element);
2403
-
2404
- // ColorPicker Private Methods
2405
- // ===========================
2406
-
2407
- /**
2408
- * Generate HTML markup and update instance properties.
2409
- * @param {ColorPicker} self
2410
- */
2411
- function initCallback(self) {
2333
+ * Generate HTML markup and update instance properties.
2334
+ * @param {CP.ColorPicker} self
2335
+ */
2336
+ function setMarkup(self) {
2412
2337
  const {
2413
2338
  input, parent, format, id, componentLabels, colorKeywords, colorPresets,
2414
2339
  } = self;
@@ -2423,9 +2348,7 @@ function initCallback(self) {
2423
2348
  self.color = new Color(color, format);
2424
2349
 
2425
2350
  // set initial controls dimensions
2426
- // make the controls smaller on mobile
2427
- const dropClass = isMobile ? ' mobile' : '';
2428
- const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
2351
+ const formatString = format === 'hex' ? hexLabel : toUpperCase(format);
2429
2352
 
2430
2353
  const pickerBtn = createElement({
2431
2354
  id: `picker-btn-${id}`,
@@ -2442,7 +2365,7 @@ function initCallback(self) {
2442
2365
 
2443
2366
  const pickerDropdown = createElement({
2444
2367
  tagName: 'div',
2445
- className: `color-dropdown picker${dropClass}`,
2368
+ className: 'color-dropdown picker',
2446
2369
  });
2447
2370
  setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
2448
2371
  setAttribute(pickerDropdown, 'role', 'group');
@@ -2458,7 +2381,7 @@ function initCallback(self) {
2458
2381
  if (colorKeywords || colorPresets) {
2459
2382
  const presetsDropdown = createElement({
2460
2383
  tagName: 'div',
2461
- className: `color-dropdown scrollable menu${dropClass}`,
2384
+ className: 'color-dropdown scrollable menu',
2462
2385
  });
2463
2386
 
2464
2387
  // color presets
@@ -2508,6 +2431,37 @@ function initCallback(self) {
2508
2431
  setAttribute(input, tabIndex, '-1');
2509
2432
  }
2510
2433
 
2434
+ var version = "0.0.2alpha3";
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
+
2511
2465
  /**
2512
2466
  * Add / remove `ColorPicker` main event listeners.
2513
2467
  * @param {ColorPicker} self
@@ -2520,8 +2474,6 @@ function toggleEvents(self, action) {
2520
2474
  fn(input, focusinEvent, self.showPicker);
2521
2475
  fn(pickerToggle, mouseclickEvent, self.togglePicker);
2522
2476
 
2523
- fn(input, keydownEvent, self.keyToggle);
2524
-
2525
2477
  if (menuToggle) {
2526
2478
  fn(menuToggle, mouseclickEvent, self.toggleMenu);
2527
2479
  }
@@ -2559,8 +2511,7 @@ function toggleEventsOnShown(self, action) {
2559
2511
  fn(doc, pointerEvents.move, self.pointerMove);
2560
2512
  fn(doc, pointerEvents.up, self.pointerUp);
2561
2513
  fn(parent, focusoutEvent, self.handleFocusOut);
2562
- // @ts-ignore -- this is `Window`
2563
- fn(win, keyupEvent, self.handleDismiss);
2514
+ fn(doc, keyupEvent, self.handleDismiss);
2564
2515
  }
2565
2516
 
2566
2517
  /**
@@ -2644,7 +2595,7 @@ class ColorPicker {
2644
2595
  const input = querySelector(target);
2645
2596
 
2646
2597
  // invalidate
2647
- if (!input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
2598
+ if (!input) throw new TypeError(`ColorPicker target "${target}" cannot be found.`);
2648
2599
  self.input = input;
2649
2600
 
2650
2601
  const parent = closest(input, colorPickerParentSelector);
@@ -2691,15 +2642,14 @@ class ColorPicker {
2691
2642
  });
2692
2643
 
2693
2644
  // update and expose component labels
2694
- const tempLabels = ObjectAssign({}, colorPickerLabels);
2695
- const jsonLabels = componentLabels && isValidJSON(componentLabels)
2696
- ? JSON.parse(componentLabels) : componentLabels || {};
2645
+ const tempComponentLabels = componentLabels && isValidJSON(componentLabels)
2646
+ ? JSON.parse(componentLabels) : componentLabels;
2697
2647
 
2698
2648
  /** @type {Record<string, string>} */
2699
- self.componentLabels = ObjectAssign(tempLabels, jsonLabels);
2649
+ self.componentLabels = ObjectAssign(colorPickerLabels, tempComponentLabels);
2700
2650
 
2701
2651
  /** @type {Color} */
2702
- self.color = new Color('white', format);
2652
+ self.color = new Color(input.value || '#fff', format);
2703
2653
 
2704
2654
  /** @type {CP.ColorFormats} */
2705
2655
  self.format = format;
@@ -2708,7 +2658,7 @@ class ColorPicker {
2708
2658
  if (colorKeywords instanceof Array) {
2709
2659
  self.colorKeywords = colorKeywords;
2710
2660
  } else if (typeof colorKeywords === 'string' && colorKeywords.length) {
2711
- self.colorKeywords = colorKeywords.split(',');
2661
+ self.colorKeywords = colorKeywords.split(',').map((x) => x.trim());
2712
2662
  }
2713
2663
 
2714
2664
  // set colour presets
@@ -2737,11 +2687,10 @@ class ColorPicker {
2737
2687
  self.handleFocusOut = self.handleFocusOut.bind(self);
2738
2688
  self.changeHandler = self.changeHandler.bind(self);
2739
2689
  self.handleDismiss = self.handleDismiss.bind(self);
2740
- self.keyToggle = self.keyToggle.bind(self);
2741
2690
  self.handleKnobs = self.handleKnobs.bind(self);
2742
2691
 
2743
2692
  // generate markup
2744
- initCallback(self);
2693
+ setMarkup(self);
2745
2694
 
2746
2695
  const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
2747
2696
  // set main elements
@@ -2829,76 +2778,83 @@ class ColorPicker {
2829
2778
  return inputValue !== '' && new Color(inputValue).isValid;
2830
2779
  }
2831
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
+
2832
2829
  /** Updates `ColorPicker` visuals. */
2833
2830
  updateVisuals() {
2834
2831
  const self = this;
2835
2832
  const {
2836
- format, controlPositions, visuals,
2833
+ controlPositions, visuals,
2837
2834
  } = self;
2838
2835
  const [v1, v2, v3] = visuals;
2839
- const { offsetWidth, offsetHeight } = v1;
2840
- const hue = format === 'hsl'
2841
- ? controlPositions.c1x / offsetWidth
2842
- : controlPositions.c2y / offsetHeight;
2843
- // @ts-ignore - `hslToRgb` is assigned to `Color` as static method
2844
- 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();
2845
2839
  const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
2846
2840
  const alpha = 1 - controlPositions.c3y / offsetHeight;
2847
2841
  const roundA = roundPart((alpha * 100)) / 100;
2848
2842
 
2849
- if (format !== 'hsl') {
2850
- const fill = new Color({
2851
- h: hue, s: 1, l: 0.5, a: alpha,
2852
- }).toRgbString();
2853
- const hueGradient = `linear-gradient(
2854
- rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2855
- rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2856
- rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2857
- rgb(255,0,0) 100%)`;
2858
- setElementStyle(v1, {
2859
- background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2860
- linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2861
- ${whiteGrad}`,
2862
- });
2863
- setElementStyle(v2, { background: hueGradient });
2864
- } else {
2865
- const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
2866
- const fill0 = new Color({
2867
- r: 255, g: 0, b: 0, a: alpha,
2868
- }).saturate(-saturation).toRgbString();
2869
- const fill1 = new Color({
2870
- r: 255, g: 255, b: 0, a: alpha,
2871
- }).saturate(-saturation).toRgbString();
2872
- const fill2 = new Color({
2873
- r: 0, g: 255, b: 0, a: alpha,
2874
- }).saturate(-saturation).toRgbString();
2875
- const fill3 = new Color({
2876
- r: 0, g: 255, b: 255, a: alpha,
2877
- }).saturate(-saturation).toRgbString();
2878
- const fill4 = new Color({
2879
- r: 0, g: 0, b: 255, a: alpha,
2880
- }).saturate(-saturation).toRgbString();
2881
- const fill5 = new Color({
2882
- r: 255, g: 0, b: 255, a: alpha,
2883
- }).saturate(-saturation).toRgbString();
2884
- const fill6 = new Color({
2885
- r: 255, g: 0, b: 0, a: alpha,
2886
- }).saturate(-saturation).toRgbString();
2887
- const fillGradient = `linear-gradient(to right,
2888
- ${fill0} 0%, ${fill1} 16.67%, ${fill2} 33.33%, ${fill3} 50%,
2889
- ${fill4} 66.67%, ${fill5} 83.33%, ${fill6} 100%)`;
2890
- const lightGrad = `linear-gradient(rgba(255,255,255,${roundA}) 0%, rgba(255,255,255,0) 50%),
2891
- linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,${roundA}) 100%)`;
2892
-
2893
- setElementStyle(v1, { background: `${lightGrad},${fillGradient},${whiteGrad}` });
2894
- const {
2895
- r: gr, g: gg, b: gb,
2896
- } = 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 });
2897
2857
 
2898
- setElementStyle(v2, {
2899
- background: `linear-gradient(rgb(${r},${g},${b}) 0%, rgb(${gr},${gg},${gb}) 100%)`,
2900
- });
2901
- }
2902
2858
  setElementStyle(v3, {
2903
2859
  background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
2904
2860
  });
@@ -2937,7 +2893,7 @@ class ColorPicker {
2937
2893
  const self = this;
2938
2894
  const { activeElement } = getDocument(self.input);
2939
2895
 
2940
- if ((isMobile && self.dragElement)
2896
+ if ((e.type === touchmoveEvent && self.dragElement)
2941
2897
  || (activeElement && self.controlKnobs.includes(activeElement))) {
2942
2898
  e.stopPropagation();
2943
2899
  e.preventDefault();
@@ -3048,13 +3004,13 @@ class ColorPicker {
3048
3004
  const [v1, v2, v3] = visuals;
3049
3005
  const [c1, c2, c3] = controlKnobs;
3050
3006
  /** @type {HTMLElement} */
3051
- const visual = hasClass(target, 'visual-control')
3052
- ? target : querySelector('.visual-control', target.parentElement);
3007
+ const visual = controlKnobs.includes(target) ? target.previousElementSibling : target;
3053
3008
  const visualRect = getBoundingClientRect(visual);
3009
+ const html = getDocumentElement(v1);
3054
3010
  const X = type === 'touchstart' ? touches[0].pageX : pageX;
3055
3011
  const Y = type === 'touchstart' ? touches[0].pageY : pageY;
3056
- const offsetX = X - window.pageXOffset - visualRect.left;
3057
- const offsetY = Y - window.pageYOffset - visualRect.top;
3012
+ const offsetX = X - html.scrollLeft - visualRect.left;
3013
+ const offsetY = Y - html.scrollTop - visualRect.top;
3058
3014
 
3059
3015
  if (target === v1 || target === c1) {
3060
3016
  self.dragElement = visual;
@@ -3114,10 +3070,11 @@ class ColorPicker {
3114
3070
  if (!dragElement) return;
3115
3071
 
3116
3072
  const controlRect = getBoundingClientRect(dragElement);
3117
- const X = type === 'touchmove' ? touches[0].pageX : pageX;
3118
- const Y = type === 'touchmove' ? touches[0].pageY : pageY;
3119
- const offsetX = X - window.pageXOffset - controlRect.left;
3120
- 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;
3121
3078
 
3122
3079
  if (dragElement === v1) {
3123
3080
  self.changeControl1(offsetX, offsetY);
@@ -3144,19 +3101,19 @@ class ColorPicker {
3144
3101
  if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
3145
3102
  e.preventDefault();
3146
3103
 
3147
- const { format, controlKnobs, visuals } = self;
3104
+ const { controlKnobs, visuals } = self;
3148
3105
  const { offsetWidth, offsetHeight } = visuals[0];
3149
3106
  const [c1, c2, c3] = controlKnobs;
3150
3107
  const { activeElement } = getDocument(c1);
3151
3108
  const currentKnob = controlKnobs.find((x) => x === activeElement);
3152
- const yRatio = offsetHeight / (format === 'hsl' ? 100 : 360);
3109
+ const yRatio = offsetHeight / 360;
3153
3110
 
3154
3111
  if (currentKnob) {
3155
3112
  let offsetX = 0;
3156
3113
  let offsetY = 0;
3157
3114
 
3158
3115
  if (target === c1) {
3159
- const xRatio = offsetWidth / (format === 'hsl' ? 360 : 100);
3116
+ const xRatio = offsetWidth / 100;
3160
3117
 
3161
3118
  if ([keyArrowLeft, keyArrowRight].includes(code)) {
3162
3119
  self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
@@ -3206,7 +3163,7 @@ class ColorPicker {
3206
3163
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
3207
3164
  if (activeElement === input) {
3208
3165
  if (isNonColorValue) {
3209
- colorSource = 'white';
3166
+ colorSource = currentValue === 'transparent' ? 'rgba(0,0,0,0)' : 'rgb(0,0,0)';
3210
3167
  } else {
3211
3168
  colorSource = currentValue;
3212
3169
  }
@@ -3257,9 +3214,7 @@ class ColorPicker {
3257
3214
  changeControl1(X, Y) {
3258
3215
  const self = this;
3259
3216
  let [offsetX, offsetY] = [0, 0];
3260
- const {
3261
- format, controlPositions, visuals,
3262
- } = self;
3217
+ const { controlPositions, visuals } = self;
3263
3218
  const { offsetHeight, offsetWidth } = visuals[0];
3264
3219
 
3265
3220
  if (X > offsetWidth) offsetX = offsetWidth;
@@ -3268,29 +3223,19 @@ class ColorPicker {
3268
3223
  if (Y > offsetHeight) offsetY = offsetHeight;
3269
3224
  else if (Y >= 0) offsetY = Y;
3270
3225
 
3271
- const hue = format === 'hsl'
3272
- ? offsetX / offsetWidth
3273
- : controlPositions.c2y / offsetHeight;
3226
+ const hue = controlPositions.c2y / offsetHeight;
3274
3227
 
3275
- const saturation = format === 'hsl'
3276
- ? 1 - controlPositions.c2y / offsetHeight
3277
- : offsetX / offsetWidth;
3228
+ const saturation = offsetX / offsetWidth;
3278
3229
 
3279
3230
  const lightness = 1 - offsetY / offsetHeight;
3280
3231
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3281
3232
 
3282
- const colorObject = format === 'hsl'
3283
- ? {
3284
- h: hue, s: saturation, l: lightness, a: alpha,
3285
- }
3286
- : {
3287
- h: hue, s: saturation, v: lightness, a: alpha,
3288
- };
3289
-
3290
3233
  // new color
3291
3234
  const {
3292
3235
  r, g, b, a,
3293
- } = new Color(colorObject);
3236
+ } = new Color({
3237
+ h: hue, s: saturation, v: lightness, a: alpha,
3238
+ });
3294
3239
 
3295
3240
  ObjectAssign(self.color, {
3296
3241
  r, g, b, a,
@@ -3317,7 +3262,7 @@ class ColorPicker {
3317
3262
  changeControl2(Y) {
3318
3263
  const self = this;
3319
3264
  const {
3320
- format, controlPositions, visuals,
3265
+ controlPositions, visuals,
3321
3266
  } = self;
3322
3267
  const { offsetHeight, offsetWidth } = visuals[0];
3323
3268
 
@@ -3326,26 +3271,17 @@ class ColorPicker {
3326
3271
  if (Y > offsetHeight) offsetY = offsetHeight;
3327
3272
  else if (Y >= 0) offsetY = Y;
3328
3273
 
3329
- const hue = format === 'hsl'
3330
- ? controlPositions.c1x / offsetWidth
3331
- : offsetY / offsetHeight;
3332
- const saturation = format === 'hsl'
3333
- ? 1 - offsetY / offsetHeight
3334
- : controlPositions.c1x / offsetWidth;
3274
+ const hue = offsetY / offsetHeight;
3275
+ const saturation = controlPositions.c1x / offsetWidth;
3335
3276
  const lightness = 1 - controlPositions.c1y / offsetHeight;
3336
3277
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3337
- const colorObject = format === 'hsl'
3338
- ? {
3339
- h: hue, s: saturation, l: lightness, a: alpha,
3340
- }
3341
- : {
3342
- h: hue, s: saturation, v: lightness, a: alpha,
3343
- };
3344
3278
 
3345
3279
  // new color
3346
3280
  const {
3347
3281
  r, g, b, a,
3348
- } = new Color(colorObject);
3282
+ } = new Color({
3283
+ h: hue, s: saturation, v: lightness, a: alpha,
3284
+ });
3349
3285
 
3350
3286
  ObjectAssign(self.color, {
3351
3287
  r, g, b, a,
@@ -3432,18 +3368,18 @@ class ColorPicker {
3432
3368
  setControlPositions() {
3433
3369
  const self = this;
3434
3370
  const {
3435
- format, visuals, color, hsl, hsv,
3371
+ visuals, color, hsv,
3436
3372
  } = self;
3437
3373
  const { offsetHeight, offsetWidth } = visuals[0];
3438
3374
  const alpha = color.a;
3439
- const hue = hsl.h;
3375
+ const hue = hsv.h;
3440
3376
 
3441
- const saturation = format !== 'hsl' ? hsv.s : hsl.s;
3442
- const lightness = format !== 'hsl' ? hsv.v : hsl.l;
3377
+ const saturation = hsv.s;
3378
+ const lightness = hsv.v;
3443
3379
 
3444
- self.controlPositions.c1x = format !== 'hsl' ? saturation * offsetWidth : hue * offsetWidth;
3380
+ self.controlPositions.c1x = saturation * offsetWidth;
3445
3381
  self.controlPositions.c1y = (1 - lightness) * offsetHeight;
3446
- self.controlPositions.c2y = format !== 'hsl' ? hue * offsetHeight : (1 - saturation) * offsetHeight;
3382
+ self.controlPositions.c2y = hue * offsetHeight;
3447
3383
  self.controlPositions.c3y = (1 - alpha) * offsetHeight;
3448
3384
  }
3449
3385
 
@@ -3451,78 +3387,40 @@ class ColorPicker {
3451
3387
  updateAppearance() {
3452
3388
  const self = this;
3453
3389
  const {
3454
- componentLabels, colorLabels, color, parent,
3455
- hsl, hsv, hex, format, controlKnobs,
3390
+ componentLabels, color, parent,
3391
+ hsv, hex, format, controlKnobs,
3456
3392
  } = self;
3457
3393
  const {
3458
3394
  appearanceLabel, hexLabel, valueLabel,
3459
3395
  } = componentLabels;
3460
- const { r, g, b } = color.toRgb();
3396
+ let { r, g, b } = color.toRgb();
3461
3397
  const [knob1, knob2, knob3] = controlKnobs;
3462
- const hue = roundPart(hsl.h * 360);
3398
+ const hue = roundPart(hsv.h * 360);
3463
3399
  const alpha = color.a;
3464
- const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
3465
- const saturation = roundPart(saturationSource * 100);
3466
- const lightness = roundPart(hsl.l * 100);
3467
- const hsvl = hsv.v * 100;
3468
- let colorName;
3469
-
3470
- // determine color appearance
3471
- if (lightness === 100 && saturation === 0) {
3472
- colorName = colorLabels.white;
3473
- } else if (lightness === 0) {
3474
- colorName = colorLabels.black;
3475
- } else if (saturation === 0) {
3476
- colorName = colorLabels.grey;
3477
- } else if (hue < 15 || hue >= 345) {
3478
- colorName = colorLabels.red;
3479
- } else if (hue >= 15 && hue < 45) {
3480
- colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
3481
- } else if (hue >= 45 && hue < 75) {
3482
- const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
3483
- const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
3484
- colorName = isGold ? colorLabels.gold : colorLabels.yellow;
3485
- colorName = isOlive ? colorLabels.olive : colorName;
3486
- } else if (hue >= 75 && hue < 155) {
3487
- colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
3488
- } else if (hue >= 155 && hue < 175) {
3489
- colorName = colorLabels.teal;
3490
- } else if (hue >= 175 && hue < 195) {
3491
- colorName = colorLabels.cyan;
3492
- } else if (hue >= 195 && hue < 255) {
3493
- colorName = colorLabels.blue;
3494
- } else if (hue >= 255 && hue < 270) {
3495
- colorName = colorLabels.violet;
3496
- } else if (hue >= 270 && hue < 295) {
3497
- colorName = colorLabels.magenta;
3498
- } else if (hue >= 295 && hue < 345) {
3499
- colorName = colorLabels.pink;
3500
- }
3400
+ const saturation = roundPart(hsv.s * 100);
3401
+ const lightness = roundPart(hsv.v * 100);
3402
+ const colorName = self.appearance;
3501
3403
 
3502
3404
  let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
3503
3405
 
3504
- if (format === 'hsl') {
3505
- colorLabel = `HSL: ${hue}°, ${saturation}%, ${lightness}%`;
3506
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3507
- setAttribute(knob1, ariaValueText, `${hue}° & ${lightness}%`);
3508
- setAttribute(knob1, ariaValueNow, `${hue}`);
3509
- setAttribute(knob2, ariaValueText, `${saturation}%`);
3510
- setAttribute(knob2, ariaValueNow, `${saturation}`);
3511
- } else if (format === 'hwb') {
3406
+ if (format === 'hwb') {
3512
3407
  const { hwb } = self;
3513
3408
  const whiteness = roundPart(hwb.w * 100);
3514
3409
  const blackness = roundPart(hwb.b * 100);
3515
3410
  colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
3516
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3517
3411
  setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
3518
3412
  setAttribute(knob1, ariaValueNow, `${whiteness}`);
3413
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3519
3414
  setAttribute(knob2, ariaValueText, `${hue}%`);
3520
3415
  setAttribute(knob2, ariaValueNow, `${hue}`);
3521
3416
  } else {
3417
+ [r, g, b] = [r, g, b].map(roundPart);
3418
+ colorLabel = format === 'hsl' ? `HSL: ${hue}°, ${saturation}%, ${lightness}%` : colorLabel;
3522
3419
  colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
3523
- setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3420
+
3524
3421
  setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
3525
3422
  setAttribute(knob1, ariaValueNow, `${lightness}`);
3423
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3526
3424
  setAttribute(knob2, ariaValueText, `${hue}°`);
3527
3425
  setAttribute(knob2, ariaValueNow, `${hue}`);
3528
3426
  }
@@ -3617,37 +3515,13 @@ class ColorPicker {
3617
3515
  }
3618
3516
  }
3619
3517
 
3620
- /**
3621
- * The `Space` & `Enter` keys specific event listener.
3622
- * Toggle visibility of the `ColorPicker` / the presets menu, showing one will hide the other.
3623
- * @param {KeyboardEvent} e
3624
- * @this {ColorPicker}
3625
- */
3626
- keyToggle(e) {
3627
- const self = this;
3628
- const { menuToggle } = self;
3629
- const { activeElement } = getDocument(menuToggle);
3630
- const { code } = e;
3631
-
3632
- if ([keyEnter, keySpace].includes(code)) {
3633
- if ((menuToggle && activeElement === menuToggle) || !activeElement) {
3634
- e.preventDefault();
3635
- if (!activeElement) {
3636
- self.togglePicker(e);
3637
- } else {
3638
- self.toggleMenu();
3639
- }
3640
- }
3641
- }
3642
- }
3643
-
3644
3518
  /**
3645
3519
  * Toggle the `ColorPicker` dropdown visibility.
3646
- * @param {Event} e
3520
+ * @param {Event=} e
3647
3521
  * @this {ColorPicker}
3648
3522
  */
3649
3523
  togglePicker(e) {
3650
- e.preventDefault();
3524
+ if (e) e.preventDefault();
3651
3525
  const self = this;
3652
3526
  const { colorPicker } = self;
3653
3527
 
@@ -3668,8 +3542,13 @@ class ColorPicker {
3668
3542
  }
3669
3543
  }
3670
3544
 
3671
- /** Toggles the visibility of the `ColorPicker` presets menu. */
3672
- 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();
3673
3552
  const self = this;
3674
3553
  const { colorMenu } = self;
3675
3554
 
@@ -3695,6 +3574,10 @@ class ColorPicker {
3695
3574
  const relatedBtn = openPicker ? pickerToggle : menuToggle;
3696
3575
  const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
3697
3576
 
3577
+ // if (!self.isValid) {
3578
+ self.value = self.color.toString(true);
3579
+ // }
3580
+
3698
3581
  if (openDropdown) {
3699
3582
  removeClass(openDropdown, 'show');
3700
3583
  setAttribute(relatedBtn, ariaExpanded, 'false');
@@ -3708,9 +3591,6 @@ class ColorPicker {
3708
3591
  }, animationDuration);
3709
3592
  }
3710
3593
 
3711
- if (!self.isValid) {
3712
- self.value = self.color.toString();
3713
- }
3714
3594
  if (!focusPrevented) {
3715
3595
  focus(pickerToggle);
3716
3596
  }
@@ -3759,93 +3639,85 @@ let CPID = 0;
3759
3639
  * `ColorPickerElement` Web Component.
3760
3640
  * @example
3761
3641
  * <label for="UNIQUE_ID">Label</label>
3762
- * <color-picker data-format="hex" data-value="#075">
3763
- * <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">
3764
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>
3765
3648
  */
3766
3649
  class ColorPickerElement extends HTMLElement {
3767
3650
  constructor() {
3768
3651
  super();
3769
- /** @type {boolean} */
3770
- this.isDisconnected = true;
3771
3652
  this.attachShadow({ mode: 'open' });
3772
3653
  }
3773
3654
 
3774
3655
  /**
3775
3656
  * Returns the current color value.
3776
- * @returns {string?}
3657
+ * @returns {string | undefined}
3777
3658
  */
3778
- get value() { return this.input ? this.input.value : null; }
3659
+ get value() { return this.input && this.input.value; }
3779
3660
 
3780
3661
  connectedCallback() {
3781
- if (this.colorPicker) {
3782
- if (this.isDisconnected) {
3783
- this.isDisconnected = false;
3784
- }
3785
- return;
3786
- }
3662
+ if (this.input) return;
3787
3663
 
3788
- 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
+ }
3789
3673
 
3790
- if (!inputs.length) {
3791
- const label = getAttribute(this, 'data-label');
3792
- const value = getAttribute(this, 'data-value') || '#069';
3793
- const format = getAttribute(this, 'data-format') || 'rgb';
3794
- const newInput = createElement({
3674
+ if (!input) {
3675
+ input = createElement({
3795
3676
  tagName: 'input',
3796
3677
  type: 'text',
3797
3678
  className: 'color-preview btn-appearance',
3798
3679
  });
3799
- let id = getAttribute(this, 'data-id');
3800
- if (!id) {
3801
- id = `color-picker-${format}-${CPID}`;
3802
- CPID += 1;
3803
- }
3804
3680
 
3805
- const labelElement = createElement({ tagName: 'label', innerText: label || 'Color Picker' });
3806
- this.before(labelElement);
3807
- setAttribute(labelElement, 'for', id);
3808
- setAttribute(newInput, 'id', id);
3809
- setAttribute(newInput, 'name', id);
3810
- setAttribute(newInput, 'autocomplete', 'off');
3811
- setAttribute(newInput, 'spellcheck', 'false');
3812
- setAttribute(newInput, 'value', value);
3813
- 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);
3814
3687
  }
3688
+ /** @type {HTMLInputElement} */
3689
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3690
+ this.input = input;
3815
3691
 
3816
- const [input] = inputs;
3817
-
3818
- if (input) {
3819
- /** @type {HTMLInputElement} */
3820
- // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3821
- this.input = input;
3822
-
3823
- // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3824
- this.colorPicker = new ColorPicker(input);
3825
- this.color = this.colorPicker.color;
3826
-
3827
- if (this.shadowRoot) {
3828
- this.shadowRoot.append(createElement('slot'));
3829
- }
3692
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3693
+ this.colorPicker = new ColorPicker(input);
3830
3694
 
3831
- this.isDisconnected = false;
3832
- }
3695
+ // @ts-ignore - `shadowRoot` is defined in the constructor
3696
+ this.shadowRoot.append(createElement('slot'));
3833
3697
  }
3834
3698
 
3699
+ /** @this {ColorPickerElement} */
3835
3700
  disconnectedCallback() {
3836
- if (this.colorPicker) this.colorPicker.dispose();
3837
- 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
+ });
3838
3710
  }
3839
3711
  }
3840
3712
 
3841
3713
  ObjectAssign(ColorPickerElement, {
3842
3714
  Color,
3843
3715
  ColorPicker,
3844
- ColorPalette,
3845
- getInstance: getColorPickerInstance,
3716
+ ColorPalette, // @ts-ignore
3717
+ getInstance: ColorPicker.getInstance,
3846
3718
  Version,
3847
3719
  });
3848
3720
 
3849
3721
  customElements.define('color-picker', ColorPickerElement);
3850
3722
 
3851
- export default ColorPickerElement;
3723
+ export { ColorPickerElement as default };