@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,13 +1,13 @@
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
  */
6
6
  (function (global, factory) {
7
7
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
8
8
  typeof define === 'function' && define.amd ? define(factory) :
9
- (global = global || self, global.ColorPickerElement = factory());
10
- }(this, (function () { 'use strict';
9
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ColorPickerElement = factory());
10
+ })(this, (function () { 'use strict';
11
11
 
12
12
  /**
13
13
  * Returns the `document` or the `#document` element.
@@ -88,14 +88,9 @@
88
88
  const getAttribute = (element, attribute) => element.getAttribute(attribute);
89
89
 
90
90
  /**
91
- * Returns the `document.head` or the `<head>` element.
92
- *
93
- * @param {(Node | HTMLElement | Element | globalThis)=} node
94
- * @returns {HTMLElement | HTMLHeadElement}
91
+ * A global namespace for `document.head`.
95
92
  */
96
- function getDocumentHead(node) {
97
- return getDocument(node).head;
98
- }
93
+ const { head: documentHead } = document;
99
94
 
100
95
  /**
101
96
  * Shortcut for `window.getComputedStyle(element).propertyName`
@@ -116,20 +111,21 @@
116
111
  return property in computedStyle ? computedStyle[property] : '';
117
112
  }
118
113
 
119
- /**
120
- * Shortcut for `Object.keys()` static method.
121
- * @param {Record<string, any>} obj a target object
122
- * @returns {string[]}
123
- */
124
- const ObjectKeys = (obj) => Object.keys(obj);
125
-
126
114
  /**
127
115
  * Shortcut for multiple uses of `HTMLElement.style.propertyName` method.
128
116
  * @param {HTMLElement | Element} element target element
129
117
  * @param {Partial<CSSStyleDeclaration>} styles attribute value
130
118
  */
131
119
  // @ts-ignore
132
- const setElementStyle = (element, styles) => ObjectAssign(element.style, styles);
120
+ const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
121
+
122
+ /**
123
+ * Shortcut for `String.toLowerCase()`.
124
+ *
125
+ * @param {string} source input string
126
+ * @returns {string} lowercase output string
127
+ */
128
+ const toLowerCase = (source) => source.toLowerCase();
133
129
 
134
130
  /**
135
131
  * A list of explicit default non-color values.
@@ -147,7 +143,7 @@
147
143
  }
148
144
 
149
145
  // Color supported formats
150
- const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
146
+ const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsv', 'hwb'];
151
147
 
152
148
  // Hue angles
153
149
  const ANGLES = 'deg|rad|grad|turn';
@@ -169,10 +165,17 @@
169
165
  // Add angles to the mix
170
166
  const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
171
167
 
168
+ // Start & end
169
+ const START_MATCH = '(?:[\\s|\\(\\s|\\s\\(\\s]+)?';
170
+ const END_MATCH = '(?:[\\s|\\)\\s]+)?';
171
+ // Components separation
172
+ const SEP = '(?:[,|\\s]+)';
173
+ const SEP2 = '(?:[,|\\/\\s]*)?';
174
+
172
175
  // Actual matching.
173
176
  // Parentheses and commas are optional, but not required.
174
177
  // Whitespace can take the place of commas or opening paren
175
- const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
178
+ const PERMISSIVE_MATCH = `${START_MATCH}(${CSS_UNIT2})${SEP}(${CSS_UNIT})${SEP}(${CSS_UNIT})${SEP2}(${CSS_UNIT})?${END_MATCH}`;
176
179
 
177
180
  const matchers = {
178
181
  CSS_UNIT: new RegExp(CSS_UNIT2),
@@ -205,23 +208,24 @@
205
208
  return `${n}`.includes('%');
206
209
  }
207
210
 
208
- /**
209
- * Check to see if string passed in is an angle
210
- * @param {string} n testing string
211
- * @returns {boolean} the query result
212
- */
213
- function isAngle(n) {
214
- return ANGLES.split('|').some((a) => `${n}`.includes(a));
215
- }
216
-
217
211
  /**
218
212
  * Check to see if string passed is a web safe colour.
213
+ * @see https://stackoverflow.com/a/16994164
219
214
  * @param {string} color a colour name, EG: *red*
220
215
  * @returns {boolean} the query result
221
216
  */
222
217
  function isColorName(color) {
223
- return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
224
- && !/[0-9]/.test(color);
218
+ if (nonColors.includes(color)
219
+ || ['#', ...COLOR_FORMAT].some((f) => color.includes(f))) return false;
220
+
221
+ if (['black', 'white'].includes(color)) return true;
222
+
223
+ return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
224
+ setElementStyle(documentHead, { color });
225
+ const computedColor = getElementStyle(documentHead, 'color');
226
+ setElementStyle(documentHead, { color: '' });
227
+ return computedColor !== c;
228
+ });
225
229
  }
226
230
 
227
231
  /**
@@ -242,15 +246,20 @@
242
246
  */
243
247
  function bound01(N, max) {
244
248
  let n = N;
245
- if (isOnePointZero(n)) n = '100%';
246
249
 
247
- n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
250
+ if (typeof N === 'number'
251
+ && Math.min(N, 0) === 0 // round values to 6 decimals Math.round(N * (10 ** 6)) / 10 ** 6
252
+ && Math.max(N, 1) === 1) return N;
253
+
254
+ if (isOnePointZero(N)) n = '100%';
248
255
 
249
- // Handle hue angles
250
- if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
256
+ const processPercent = isPercentage(n);
257
+ n = max === 360
258
+ ? parseFloat(n)
259
+ : Math.min(max, Math.max(0, parseFloat(n)));
251
260
 
252
261
  // Automatically convert percentage into number
253
- if (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
262
+ if (processPercent) n = (n * max) / 100;
254
263
 
255
264
  // Handle floating point rounding errors
256
265
  if (Math.abs(n - max) < 0.000001) {
@@ -261,11 +270,11 @@
261
270
  // If n is a hue given in degrees,
262
271
  // wrap around out-of-range values into [0, 360] range
263
272
  // then convert into [0, 1].
264
- n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max));
273
+ n = (n < 0 ? (n % max) + max : n % max) / max;
265
274
  } else {
266
275
  // If n not a hue given in degrees
267
276
  // Convert into [0, 1] range if it isn't already.
268
- n = (n % max) / parseFloat(String(max));
277
+ n = (n % max) / max;
269
278
  }
270
279
  return n;
271
280
  }
@@ -300,7 +309,6 @@
300
309
  * @returns {string}
301
310
  */
302
311
  function getRGBFromName(name) {
303
- const documentHead = getDocumentHead();
304
312
  setElementStyle(documentHead, { color: name });
305
313
  const colorName = getElementStyle(documentHead, 'color');
306
314
  setElementStyle(documentHead, { color: '' });
@@ -346,15 +354,12 @@
346
354
  /**
347
355
  * Converts an RGB colour value to HSL.
348
356
  *
349
- * @param {number} R Red component [0, 255]
350
- * @param {number} G Green component [0, 255]
351
- * @param {number} B Blue component [0, 255]
357
+ * @param {number} r Red component [0, 1]
358
+ * @param {number} g Green component [0, 1]
359
+ * @param {number} b Blue component [0, 1]
352
360
  * @returns {CP.HSL} {h,s,l} object with [0, 1] ranged values
353
361
  */
354
- function rgbToHsl(R, G, B) {
355
- const r = R / 255;
356
- const g = G / 255;
357
- const b = B / 255;
362
+ function rgbToHsl(r, g, b) {
358
363
  const max = Math.max(r, g, b);
359
364
  const min = Math.min(r, g, b);
360
365
  let h = 0;
@@ -366,17 +371,10 @@
366
371
  } else {
367
372
  const d = max - min;
368
373
  s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
369
- switch (max) {
370
- case r:
371
- h = (g - b) / d + (g < b ? 6 : 0);
372
- break;
373
- case g:
374
- h = (b - r) / d + 2;
375
- break;
376
- case b:
377
- h = (r - g) / d + 4;
378
- break;
379
- }
374
+ if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
375
+ if (max === g) h = (b - r) / d + 2;
376
+ if (max === b) h = (r - g) / d + 4;
377
+
380
378
  h /= 6;
381
379
  }
382
380
  return { h, s, l };
@@ -399,21 +397,46 @@
399
397
  return p;
400
398
  }
401
399
 
400
+ /**
401
+ * Converts an HSL colour value to RGB.
402
+ *
403
+ * @param {number} h Hue Angle [0, 1]
404
+ * @param {number} s Saturation [0, 1]
405
+ * @param {number} l Lightness Angle [0, 1]
406
+ * @returns {CP.RGB} {r,g,b} object with [0, 1] ranged values
407
+ */
408
+ function hslToRgb(h, s, l) {
409
+ let r = 0;
410
+ let g = 0;
411
+ let b = 0;
412
+
413
+ if (s === 0) {
414
+ // achromatic
415
+ g = l;
416
+ b = l;
417
+ r = l;
418
+ } else {
419
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
420
+ const p = 2 * l - q;
421
+ r = hueToRgb(p, q, h + 1 / 3);
422
+ g = hueToRgb(p, q, h);
423
+ b = hueToRgb(p, q, h - 1 / 3);
424
+ }
425
+
426
+ return { r, g, b };
427
+ }
428
+
402
429
  /**
403
430
  * Returns an HWB colour object from an RGB colour object.
404
431
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
405
432
  * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
406
433
  *
407
- * @param {number} R Red component [0, 255]
408
- * @param {number} G Green [0, 255]
409
- * @param {number} B Blue [0, 255]
434
+ * @param {number} r Red component [0, 1]
435
+ * @param {number} g Green [0, 1]
436
+ * @param {number} b Blue [0, 1]
410
437
  * @return {CP.HWB} {h,w,b} object with [0, 1] ranged values
411
438
  */
412
- function rgbToHwb(R, G, B) {
413
- const r = R / 255;
414
- const g = G / 255;
415
- const b = B / 255;
416
-
439
+ function rgbToHwb(r, g, b) {
417
440
  let f = 0;
418
441
  let i = 0;
419
442
  const whiteness = Math.min(r, g, b);
@@ -443,50 +466,18 @@
443
466
  * @param {number} H Hue Angle [0, 1]
444
467
  * @param {number} W Whiteness [0, 1]
445
468
  * @param {number} B Blackness [0, 1]
446
- * @return {CP.RGB} {r,g,b} object with [0, 255] ranged values
469
+ * @return {CP.RGB} {r,g,b} object with [0, 1] ranged values
447
470
  *
448
471
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
449
472
  * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
450
473
  */
451
474
  function hwbToRgb(H, W, B) {
452
475
  if (W + B >= 1) {
453
- const gray = (W / (W + B)) * 255;
476
+ const gray = W / (W + B);
454
477
  return { r: gray, g: gray, b: gray };
455
478
  }
456
479
  let { r, g, b } = hslToRgb(H, 1, 0.5);
457
- [r, g, b] = [r, g, b]
458
- .map((v) => (v / 255) * (1 - W - B) + W)
459
- .map((v) => v * 255);
460
-
461
- return { r, g, b };
462
- }
463
-
464
- /**
465
- * Converts an HSL colour value to RGB.
466
- *
467
- * @param {number} h Hue Angle [0, 1]
468
- * @param {number} s Saturation [0, 1]
469
- * @param {number} l Lightness Angle [0, 1]
470
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
471
- */
472
- function hslToRgb(h, s, l) {
473
- let r = 0;
474
- let g = 0;
475
- let b = 0;
476
-
477
- if (s === 0) {
478
- // achromatic
479
- g = l;
480
- b = l;
481
- r = l;
482
- } else {
483
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
484
- const p = 2 * l - q;
485
- r = hueToRgb(p, q, h + 1 / 3);
486
- g = hueToRgb(p, q, h);
487
- b = hueToRgb(p, q, h - 1 / 3);
488
- }
489
- [r, g, b] = [r, g, b].map((x) => x * 255);
480
+ [r, g, b] = [r, g, b].map((v) => v * (1 - W - B) + W);
490
481
 
491
482
  return { r, g, b };
492
483
  }
@@ -494,15 +485,12 @@
494
485
  /**
495
486
  * Converts an RGB colour value to HSV.
496
487
  *
497
- * @param {number} R Red component [0, 255]
498
- * @param {number} G Green [0, 255]
499
- * @param {number} B Blue [0, 255]
488
+ * @param {number} r Red component [0, 1]
489
+ * @param {number} g Green [0, 1]
490
+ * @param {number} b Blue [0, 1]
500
491
  * @returns {CP.HSV} {h,s,v} object with [0, 1] ranged values
501
492
  */
502
- function rgbToHsv(R, G, B) {
503
- const r = R / 255;
504
- const g = G / 255;
505
- const b = B / 255;
493
+ function rgbToHsv(r, g, b) {
506
494
  const max = Math.max(r, g, b);
507
495
  const min = Math.min(r, g, b);
508
496
  let h = 0;
@@ -512,17 +500,10 @@
512
500
  if (max === min) {
513
501
  h = 0; // achromatic
514
502
  } else {
515
- switch (max) {
516
- case r:
517
- h = (g - b) / d + (g < b ? 6 : 0);
518
- break;
519
- case g:
520
- h = (b - r) / d + 2;
521
- break;
522
- case b:
523
- h = (r - g) / d + 4;
524
- break;
525
- }
503
+ if (r === max) h = (g - b) / d + (g < b ? 6 : 0);
504
+ if (g === max) h = (b - r) / d + 2;
505
+ if (b === max) h = (r - g) / d + 4;
506
+
526
507
  h /= 6;
527
508
  }
528
509
  return { h, s, v };
@@ -549,7 +530,7 @@
549
530
  const r = [v, q, p, p, t, v][mod];
550
531
  const g = [t, v, v, q, p, p][mod];
551
532
  const b = [p, p, t, v, v, q][mod];
552
- return { r: r * 255, g: g * 255, b: b * 255 };
533
+ return { r, g, b };
553
534
  }
554
535
 
555
536
  /**
@@ -573,7 +554,7 @@
573
554
  // Return a 3 character hex if possible
574
555
  if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
575
556
  && hex[1].charAt(0) === hex[1].charAt(1)
576
- && hex[2].charAt(0) === hex[2].charAt(1)) {
557
+ && hex[2].charAt(0) === hex[2].charAt(1)) {
577
558
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
578
559
  }
579
560
 
@@ -601,51 +582,34 @@
601
582
  // Return a 4 character hex if possible
602
583
  if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
603
584
  && hex[1].charAt(0) === hex[1].charAt(1)
604
- && hex[2].charAt(0) === hex[2].charAt(1)
605
- && hex[3].charAt(0) === hex[3].charAt(1)) {
585
+ && hex[2].charAt(0) === hex[2].charAt(1)
586
+ && hex[3].charAt(0) === hex[3].charAt(1)) {
606
587
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
607
588
  }
608
589
  return hex.join('');
609
590
  }
610
591
 
611
- /**
612
- * Returns a colour object corresponding to a given number.
613
- * @param {number} color input number
614
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
615
- */
616
- function numberInputToObject(color) {
617
- /* eslint-disable no-bitwise */
618
- return {
619
- r: color >> 16,
620
- g: (color & 0xff00) >> 8,
621
- b: color & 0xff,
622
- };
623
- /* eslint-enable no-bitwise */
624
- }
625
-
626
592
  /**
627
593
  * Permissive string parsing. Take in a number of formats, and output an object
628
594
  * based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
629
595
  * @param {string} input colour value in any format
630
- * @returns {Record<string, (number | string)> | false} an object matching the RegExp
596
+ * @returns {Record<string, (number | string | boolean)> | false} an object matching the RegExp
631
597
  */
632
598
  function stringInputToObject(input) {
633
- let color = input.trim().toLowerCase();
599
+ let color = toLowerCase(input.trim());
600
+
634
601
  if (color.length === 0) {
635
602
  return {
636
- r: 0, g: 0, b: 0, a: 0,
603
+ r: 0, g: 0, b: 0, a: 1,
637
604
  };
638
605
  }
639
- let named = false;
606
+
640
607
  if (isColorName(color)) {
641
608
  color = getRGBFromName(color);
642
- named = true;
643
609
  } else if (nonColors.includes(color)) {
644
- const isTransparent = color === 'transparent';
645
- const rgb = isTransparent ? 0 : 255;
646
- const a = isTransparent ? 0 : 1;
610
+ const a = color === 'transparent' ? 0 : 1;
647
611
  return {
648
- r: rgb, g: rgb, b: rgb, a, format: 'rgb',
612
+ r: 0, g: 0, b: 0, a, format: 'rgb', ok: true,
649
613
  };
650
614
  }
651
615
 
@@ -660,24 +624,28 @@
660
624
  r: m1, g: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'rgb',
661
625
  };
662
626
  }
627
+
663
628
  [, m1, m2, m3, m4] = matchers.hsl.exec(color) || [];
664
629
  if (m1 && m2 && m3/* && m4 */) {
665
630
  return {
666
631
  h: m1, s: m2, l: m3, a: m4 !== undefined ? m4 : 1, format: 'hsl',
667
632
  };
668
633
  }
634
+
669
635
  [, m1, m2, m3, m4] = matchers.hsv.exec(color) || [];
670
636
  if (m1 && m2 && m3/* && m4 */) {
671
637
  return {
672
638
  h: m1, s: m2, v: m3, a: m4 !== undefined ? m4 : 1, format: 'hsv',
673
639
  };
674
640
  }
641
+
675
642
  [, m1, m2, m3, m4] = matchers.hwb.exec(color) || [];
676
643
  if (m1 && m2 && m3) {
677
644
  return {
678
645
  h: m1, w: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'hwb',
679
646
  };
680
647
  }
648
+
681
649
  [, m1, m2, m3, m4] = matchers.hex8.exec(color) || [];
682
650
  if (m1 && m2 && m3 && m4) {
683
651
  return {
@@ -685,19 +653,20 @@
685
653
  g: parseIntFromHex(m2),
686
654
  b: parseIntFromHex(m3),
687
655
  a: convertHexToDecimal(m4),
688
- // format: named ? 'rgb' : 'hex8',
689
- format: named ? 'rgb' : 'hex',
656
+ format: 'hex',
690
657
  };
691
658
  }
659
+
692
660
  [, m1, m2, m3] = matchers.hex6.exec(color) || [];
693
661
  if (m1 && m2 && m3) {
694
662
  return {
695
663
  r: parseIntFromHex(m1),
696
664
  g: parseIntFromHex(m2),
697
665
  b: parseIntFromHex(m3),
698
- format: named ? 'rgb' : 'hex',
666
+ format: 'hex',
699
667
  };
700
668
  }
669
+
701
670
  [, m1, m2, m3, m4] = matchers.hex4.exec(color) || [];
702
671
  if (m1 && m2 && m3 && m4) {
703
672
  return {
@@ -705,19 +674,20 @@
705
674
  g: parseIntFromHex(m2 + m2),
706
675
  b: parseIntFromHex(m3 + m3),
707
676
  a: convertHexToDecimal(m4 + m4),
708
- // format: named ? 'rgb' : 'hex8',
709
- format: named ? 'rgb' : 'hex',
677
+ format: 'hex',
710
678
  };
711
679
  }
680
+
712
681
  [, m1, m2, m3] = matchers.hex3.exec(color) || [];
713
682
  if (m1 && m2 && m3) {
714
683
  return {
715
684
  r: parseIntFromHex(m1 + m1),
716
685
  g: parseIntFromHex(m2 + m2),
717
686
  b: parseIntFromHex(m3 + m3),
718
- format: named ? 'rgb' : 'hex',
687
+ format: 'hex',
719
688
  };
720
689
  }
690
+
721
691
  return false;
722
692
  }
723
693
 
@@ -748,7 +718,9 @@
748
718
  */
749
719
  function inputToRGB(input) {
750
720
  let rgb = { r: 0, g: 0, b: 0 };
721
+ /** @type {*} */
751
722
  let color = input;
723
+ /** @type {string | number} */
752
724
  let a = 1;
753
725
  let s = null;
754
726
  let v = null;
@@ -759,58 +731,67 @@
759
731
  let r = null;
760
732
  let g = null;
761
733
  let ok = false;
762
- let format = 'hex';
734
+ const inputFormat = typeof color === 'object' && color.format;
735
+ let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
763
736
 
764
737
  if (typeof input === 'string') {
765
- // @ts-ignore -- this now is converted to object
766
738
  color = stringInputToObject(input);
767
739
  if (color) ok = true;
768
740
  }
769
741
  if (typeof color === 'object') {
770
742
  if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
771
743
  ({ r, g, b } = color);
772
- // RGB values now are all in [0, 255] range
773
- [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255);
744
+ // RGB values now are all in [0, 1] range
745
+ [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255));
774
746
  rgb = { r, g, b };
775
747
  ok = true;
776
- format = 'rgb';
777
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
748
+ format = color.format || 'rgb';
749
+ }
750
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
778
751
  ({ h, s, v } = color);
779
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
780
- s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
781
- v = typeof v === 'number' ? v : bound01(v, 100); // brightness can be `5%` or a [0, 1] value
752
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
753
+ s = bound01(s, 100); // saturation can be `5%` or a [0, 1] value
754
+ v = bound01(v, 100); // brightness can be `5%` or a [0, 1] value
782
755
  rgb = hsvToRgb(h, s, v);
783
756
  ok = true;
784
757
  format = 'hsv';
785
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
758
+ }
759
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
786
760
  ({ h, s, l } = color);
787
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
788
- s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
789
- l = typeof l === 'number' ? l : bound01(l, 100); // lightness can be `5%` or a [0, 1] value
761
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
762
+ s = bound01(s, 100); // saturation can be `5%` or a [0, 1] value
763
+ l = bound01(l, 100); // lightness can be `5%` or a [0, 1] value
790
764
  rgb = hslToRgb(h, s, l);
791
765
  ok = true;
792
766
  format = 'hsl';
793
- } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
767
+ }
768
+ if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
794
769
  ({ h, w, b } = color);
795
- h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
796
- w = typeof w === 'number' ? w : bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
797
- b = typeof b === 'number' ? b : bound01(b, 100); // blackness can be `5%` or a [0, 1] value
770
+ h = bound01(h, 360); // hue can be `5deg` or a [0, 1] value
771
+ w = bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
772
+ b = bound01(b, 100); // blackness can be `5%` or a [0, 1] value
798
773
  rgb = hwbToRgb(h, w, b);
799
774
  ok = true;
800
775
  format = 'hwb';
801
776
  }
802
777
  if (isValidCSSUnit(color.a)) {
803
- a = color.a;
804
- a = isPercentage(`${a}`) ? bound01(a, 100) : a;
778
+ a = color.a; // @ts-ignore -- `parseFloat` works with numbers too
779
+ a = isPercentage(`${a}`) || parseFloat(a) > 1 ? bound01(a, 100) : a;
805
780
  }
806
781
  }
782
+ if (typeof color === 'undefined') {
783
+ ok = true;
784
+ }
807
785
 
808
786
  return {
809
- ok, // @ts-ignore
810
- format: color.format || format,
811
- r: Math.min(255, Math.max(rgb.r, 0)),
812
- g: Math.min(255, Math.max(rgb.g, 0)),
813
- b: Math.min(255, Math.max(rgb.b, 0)),
787
+ ok,
788
+ format,
789
+ // r: Math.min(255, Math.max(rgb.r, 0)),
790
+ // g: Math.min(255, Math.max(rgb.g, 0)),
791
+ // b: Math.min(255, Math.max(rgb.b, 0)),
792
+ r: rgb.r,
793
+ g: rgb.g,
794
+ b: rgb.b,
814
795
  a: boundAlpha(a),
815
796
  };
816
797
  }
@@ -829,15 +810,13 @@
829
810
  constructor(input, config) {
830
811
  let color = input;
831
812
  const configFormat = config && COLOR_FORMAT.includes(config)
832
- ? config : 'rgb';
813
+ ? config : '';
833
814
 
834
- // If input is already a `Color`, return itself
815
+ // If input is already a `Color`, clone its values
835
816
  if (color instanceof Color) {
836
817
  color = inputToRGB(color);
837
818
  }
838
- if (typeof color === 'number') {
839
- color = numberInputToObject(color);
840
- }
819
+
841
820
  const {
842
821
  r, g, b, a, ok, format,
843
822
  } = inputToRGB(color);
@@ -846,7 +825,7 @@
846
825
  const self = this;
847
826
 
848
827
  /** @type {CP.ColorInput} */
849
- self.originalInput = color;
828
+ self.originalInput = input;
850
829
  /** @type {number} */
851
830
  self.r = r;
852
831
  /** @type {number} */
@@ -887,24 +866,21 @@
887
866
  let R = 0;
888
867
  let G = 0;
889
868
  let B = 0;
890
- const rp = r / 255;
891
- const rg = g / 255;
892
- const rb = b / 255;
893
869
 
894
- if (rp <= 0.03928) {
895
- R = rp / 12.92;
870
+ if (r <= 0.03928) {
871
+ R = r / 12.92;
896
872
  } else {
897
- R = ((rp + 0.055) / 1.055) ** 2.4;
873
+ R = ((r + 0.055) / 1.055) ** 2.4;
898
874
  }
899
- if (rg <= 0.03928) {
900
- G = rg / 12.92;
875
+ if (g <= 0.03928) {
876
+ G = g / 12.92;
901
877
  } else {
902
- G = ((rg + 0.055) / 1.055) ** 2.4;
878
+ G = ((g + 0.055) / 1.055) ** 2.4;
903
879
  }
904
- if (rb <= 0.03928) {
905
- B = rb / 12.92;
880
+ if (b <= 0.03928) {
881
+ B = b / 12.92;
906
882
  } else {
907
- B = ((rb + 0.055) / 1.055) ** 2.4;
883
+ B = ((b + 0.055) / 1.055) ** 2.4;
908
884
  }
909
885
  return 0.2126 * R + 0.7152 * G + 0.0722 * B;
910
886
  }
@@ -914,7 +890,7 @@
914
890
  * @returns {number} a number in the [0, 255] range
915
891
  */
916
892
  get brightness() {
917
- const { r, g, b } = this;
893
+ const { r, g, b } = this.toRgb();
918
894
  return (r * 299 + g * 587 + b * 114) / 1000;
919
895
  }
920
896
 
@@ -923,12 +899,14 @@
923
899
  * @returns {CP.RGBA} an {r,g,b,a} object with [0, 255] ranged values
924
900
  */
925
901
  toRgb() {
926
- const {
902
+ let {
927
903
  r, g, b, a,
928
904
  } = this;
929
905
 
906
+ [r, g, b] = [r, g, b].map((n) => roundPart(n * 255 * 100) / 100);
907
+ a = roundPart(a * 100) / 100;
930
908
  return {
931
- r, g, b, a: roundPart(a * 100) / 100,
909
+ r, g, b, a,
932
910
  };
933
911
  }
934
912
 
@@ -1022,7 +1000,7 @@
1022
1000
  toHsv() {
1023
1001
  const {
1024
1002
  r, g, b, a,
1025
- } = this.toRgb();
1003
+ } = this;
1026
1004
  const { h, s, v } = rgbToHsv(r, g, b);
1027
1005
 
1028
1006
  return {
@@ -1037,7 +1015,7 @@
1037
1015
  toHsl() {
1038
1016
  const {
1039
1017
  r, g, b, a,
1040
- } = this.toRgb();
1018
+ } = this;
1041
1019
  const { h, s, l } = rgbToHsl(r, g, b);
1042
1020
 
1043
1021
  return {
@@ -1122,6 +1100,7 @@
1122
1100
  */
1123
1101
  setAlpha(alpha) {
1124
1102
  const self = this;
1103
+ if (typeof alpha !== 'number') return self;
1125
1104
  self.a = boundAlpha(alpha);
1126
1105
  return self;
1127
1106
  }
@@ -1236,6 +1215,7 @@
1236
1215
  isOnePointZero,
1237
1216
  isPercentage,
1238
1217
  isValidCSSUnit,
1218
+ isColorName,
1239
1219
  pad2,
1240
1220
  clamp01,
1241
1221
  bound01,
@@ -1253,10 +1233,11 @@
1253
1233
  hueToRgb,
1254
1234
  hwbToRgb,
1255
1235
  parseIntFromHex,
1256
- numberInputToObject,
1257
1236
  stringInputToObject,
1258
1237
  inputToRGB,
1259
1238
  roundPart,
1239
+ getElementStyle,
1240
+ setElementStyle,
1260
1241
  ObjectAssign,
1261
1242
  });
1262
1243
 
@@ -1386,24 +1367,6 @@
1386
1367
  */
1387
1368
  const ariaValueNow = 'aria-valuenow';
1388
1369
 
1389
- /**
1390
- * A global namespace for aria-haspopup.
1391
- * @type {string}
1392
- */
1393
- const ariaHasPopup = 'aria-haspopup';
1394
-
1395
- /**
1396
- * A global namespace for aria-hidden.
1397
- * @type {string}
1398
- */
1399
- const ariaHidden = 'aria-hidden';
1400
-
1401
- /**
1402
- * A global namespace for aria-labelledby.
1403
- * @type {string}
1404
- */
1405
- const ariaLabelledBy = 'aria-labelledby';
1406
-
1407
1370
  /**
1408
1371
  * A global namespace for `ArrowDown` key.
1409
1372
  * @type {string} e.which = 40 equivalent
@@ -1530,37 +1493,6 @@
1530
1493
  */
1531
1494
  const focusoutEvent = 'focusout';
1532
1495
 
1533
- // @ts-ignore
1534
- const { userAgentData: uaDATA } = navigator;
1535
-
1536
- /**
1537
- * A global namespace for `userAgentData` object.
1538
- */
1539
- const userAgentData = uaDATA;
1540
-
1541
- const { userAgent: userAgentString } = navigator;
1542
-
1543
- /**
1544
- * A global namespace for `navigator.userAgent` string.
1545
- */
1546
- const userAgent = userAgentString;
1547
-
1548
- const mobileBrands = /iPhone|iPad|iPod|Android/i;
1549
- let isMobileCheck = false;
1550
-
1551
- if (userAgentData) {
1552
- isMobileCheck = userAgentData.brands
1553
- .some((/** @type {Record<String, any>} */x) => mobileBrands.test(x.brand));
1554
- } else {
1555
- isMobileCheck = mobileBrands.test(userAgent);
1556
- }
1557
-
1558
- /**
1559
- * A global `boolean` for mobile detection.
1560
- * @type {boolean}
1561
- */
1562
- const isMobile = isMobileCheck;
1563
-
1564
1496
  /**
1565
1497
  * Returns the `document.documentElement` or the `<html>` element.
1566
1498
  *
@@ -1745,30 +1677,6 @@
1745
1677
  return lookUp.getElementsByClassName(selector);
1746
1678
  }
1747
1679
 
1748
- /**
1749
- * This is a shortie for `document.createElementNS` method
1750
- * which allows you to create a new `HTMLElement` for a given `tagName`
1751
- * or based on an object with specific non-readonly attributes:
1752
- * `id`, `className`, `textContent`, `style`, etc.
1753
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
1754
- *
1755
- * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
1756
- * @param {Record<string, string> | string} param `tagName` or object
1757
- * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
1758
- */
1759
- function createElementNS(namespace, param) {
1760
- if (typeof param === 'string') {
1761
- return getDocument().createElementNS(namespace, param);
1762
- }
1763
-
1764
- const { tagName } = param;
1765
- const attr = { ...param };
1766
- const newElement = createElementNS(namespace, tagName);
1767
- delete attr.tagName;
1768
- ObjectAssign(newElement, attr);
1769
- return newElement;
1770
- }
1771
-
1772
1680
  /**
1773
1681
  * Shortcut for the `Element.dispatchEvent(Event)` method.
1774
1682
  *
@@ -1886,12 +1794,11 @@
1886
1794
  }
1887
1795
 
1888
1796
  /**
1889
- * Shortcut for `String.toLowerCase()`.
1890
- *
1891
- * @param {string} source input string
1892
- * @returns {string} lowercase output string
1797
+ * Shortcut for `Object.keys()` static method.
1798
+ * @param {Record<string, any>} obj a target object
1799
+ * @returns {string[]}
1893
1800
  */
1894
- const toLowerCase = (source) => source.toLowerCase();
1801
+ const ObjectKeys = (obj) => Object.keys(obj);
1895
1802
 
1896
1803
  /**
1897
1804
  * Utility to normalize component options.
@@ -1996,6 +1903,77 @@
1996
1903
  */
1997
1904
  const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
1998
1905
 
1906
+ /**
1907
+ * @class
1908
+ * Returns a color palette with a given set of parameters.
1909
+ * @example
1910
+ * new ColorPalette(0, 12, 10);
1911
+ * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: Array<Color> }
1912
+ */
1913
+ class ColorPalette {
1914
+ /**
1915
+ * The `hue` parameter is optional, which would be set to 0.
1916
+ * @param {number[]} args represeinting hue, hueSteps, lightSteps
1917
+ * * `args.hue` the starting Hue [0, 360]
1918
+ * * `args.hueSteps` Hue Steps Count [5, 24]
1919
+ * * `args.lightSteps` Lightness Steps Count [5, 12]
1920
+ */
1921
+ constructor(...args) {
1922
+ let hue = 0;
1923
+ let hueSteps = 12;
1924
+ let lightSteps = 10;
1925
+ let lightnessArray = [0.5];
1926
+
1927
+ if (args.length === 3) {
1928
+ [hue, hueSteps, lightSteps] = args;
1929
+ } else if (args.length === 2) {
1930
+ [hueSteps, lightSteps] = args;
1931
+ if ([hueSteps, lightSteps].some((n) => n < 1)) {
1932
+ throw TypeError('ColorPalette: both arguments must be higher than 0.');
1933
+ }
1934
+ }
1935
+
1936
+ /** @type {*} */
1937
+ const colors = [];
1938
+ const hueStep = 360 / hueSteps;
1939
+ const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
1940
+ const steps1To13 = [0.25, 0.2, 0.15, 0.11, 0.09, 0.075];
1941
+ const lightSets = [[1, 2, 3], [4, 5], [6, 7], [8, 9], [10, 11], [12, 13]];
1942
+ const closestSet = lightSets.find((set) => set.includes(lightSteps));
1943
+
1944
+ // find a lightStep that won't go beyond black and white
1945
+ // something within the [10-90] range of lightness
1946
+ const lightStep = closestSet
1947
+ ? steps1To13[lightSets.indexOf(closestSet)]
1948
+ : (100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100);
1949
+
1950
+ // light tints
1951
+ for (let i = 1; i < half + 1; i += 1) {
1952
+ lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
1953
+ }
1954
+
1955
+ // dark tints
1956
+ for (let i = 1; i < lightSteps - half; i += 1) {
1957
+ lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
1958
+ }
1959
+
1960
+ // feed `colors` Array
1961
+ for (let i = 0; i < hueSteps; i += 1) {
1962
+ const currentHue = ((hue + i * hueStep) % 360) / 360;
1963
+ lightnessArray.forEach((l) => {
1964
+ colors.push(new Color({ h: currentHue, s: 1, l }));
1965
+ });
1966
+ }
1967
+
1968
+ this.hue = hue;
1969
+ this.hueSteps = hueSteps;
1970
+ this.lightSteps = lightSteps;
1971
+ this.colors = colors;
1972
+ }
1973
+ }
1974
+
1975
+ ObjectAssign(ColorPalette, { Color });
1976
+
1999
1977
  /** @type {Record<string, string>} */
2000
1978
  const colorPickerLabels = {
2001
1979
  pickerLabel: 'Colour Picker',
@@ -2023,14 +2001,72 @@
2023
2001
  */
2024
2002
  const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
2025
2003
 
2004
+ const tabIndex = 'tabindex';
2005
+
2026
2006
  /**
2027
- * Shortcut for `String.toUpperCase()`.
2028
- *
2029
- * @param {string} source input string
2030
- * @returns {string} uppercase output string
2007
+ * Check if a string is valid JSON string.
2008
+ * @param {string} str the string input
2009
+ * @returns {boolean} the query result
2010
+ */
2011
+ function isValidJSON(str) {
2012
+ try {
2013
+ JSON.parse(str);
2014
+ } catch (e) {
2015
+ return false;
2016
+ }
2017
+ return true;
2018
+ }
2019
+
2020
+ /**
2021
+ * Shortcut for `String.toUpperCase()`.
2022
+ *
2023
+ * @param {string} source input string
2024
+ * @returns {string} uppercase output string
2031
2025
  */
2032
2026
  const toUpperCase = (source) => source.toUpperCase();
2033
2027
 
2028
+ /**
2029
+ * A global namespace for aria-haspopup.
2030
+ * @type {string}
2031
+ */
2032
+ const ariaHasPopup = 'aria-haspopup';
2033
+
2034
+ /**
2035
+ * A global namespace for aria-hidden.
2036
+ * @type {string}
2037
+ */
2038
+ const ariaHidden = 'aria-hidden';
2039
+
2040
+ /**
2041
+ * A global namespace for aria-labelledby.
2042
+ * @type {string}
2043
+ */
2044
+ const ariaLabelledBy = 'aria-labelledby';
2045
+
2046
+ /**
2047
+ * This is a shortie for `document.createElementNS` method
2048
+ * which allows you to create a new `HTMLElement` for a given `tagName`
2049
+ * or based on an object with specific non-readonly attributes:
2050
+ * `id`, `className`, `textContent`, `style`, etc.
2051
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
2052
+ *
2053
+ * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
2054
+ * @param {Record<string, string> | string} param `tagName` or object
2055
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
2056
+ */
2057
+ function createElementNS(namespace, param) {
2058
+ if (typeof param === 'string') {
2059
+ return getDocument().createElementNS(namespace, param);
2060
+ }
2061
+
2062
+ const { tagName } = param;
2063
+ const attr = { ...param };
2064
+ const newElement = createElementNS(namespace, tagName);
2065
+ delete attr.tagName;
2066
+ ObjectAssign(newElement, attr);
2067
+ return newElement;
2068
+ }
2069
+
2034
2070
  const vHidden = 'v-hidden';
2035
2071
 
2036
2072
  /**
@@ -2110,8 +2146,6 @@
2110
2146
  */
2111
2147
  const ariaValueMax = 'aria-valuemax';
2112
2148
 
2113
- const tabIndex = 'tabindex';
2114
-
2115
2149
  /**
2116
2150
  * Returns all color controls for `ColorPicker`.
2117
2151
  *
@@ -2219,75 +2253,6 @@
2219
2253
  });
2220
2254
  }
2221
2255
 
2222
- /**
2223
- * @class
2224
- * Returns a color palette with a given set of parameters.
2225
- * @example
2226
- * new ColorPalette(0, 12, 10);
2227
- * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
2228
- */
2229
- class ColorPalette {
2230
- /**
2231
- * The `hue` parameter is optional, which would be set to 0.
2232
- * @param {number[]} args represeinting hue, hueSteps, lightSteps
2233
- * * `args.hue` the starting Hue [0, 360]
2234
- * * `args.hueSteps` Hue Steps Count [5, 24]
2235
- * * `args.lightSteps` Lightness Steps Count [5, 12]
2236
- */
2237
- constructor(...args) {
2238
- let hue = 0;
2239
- let hueSteps = 12;
2240
- let lightSteps = 10;
2241
- let lightnessArray = [0.5];
2242
-
2243
- if (args.length === 3) {
2244
- [hue, hueSteps, lightSteps] = args;
2245
- } else if (args.length === 2) {
2246
- [hueSteps, lightSteps] = args;
2247
- } else {
2248
- throw TypeError('ColorPalette requires minimum 2 arguments');
2249
- }
2250
-
2251
- /** @type {string[]} */
2252
- const colors = [];
2253
-
2254
- const hueStep = 360 / hueSteps;
2255
- const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
2256
- const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
2257
-
2258
- let lightStep = 0.25;
2259
- lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
2260
- lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
2261
- lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
2262
- lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
2263
- lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
2264
- lightStep = lightSteps > 13 ? estimatedStep : lightStep;
2265
-
2266
- // light tints
2267
- for (let i = 1; i < half + 1; i += 1) {
2268
- lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
2269
- }
2270
-
2271
- // dark tints
2272
- for (let i = 1; i < lightSteps - half; i += 1) {
2273
- lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
2274
- }
2275
-
2276
- // feed `colors` Array
2277
- for (let i = 0; i < hueSteps; i += 1) {
2278
- const currentHue = ((hue + i * hueStep) % 360) / 360;
2279
- lightnessArray.forEach((l) => {
2280
- colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
2281
- });
2282
- }
2283
-
2284
- this.hue = hue;
2285
- this.hueSteps = hueSteps;
2286
- this.lightSteps = lightSteps;
2287
- this.colors = colors;
2288
- }
2289
- }
2290
-
2291
2256
  /**
2292
2257
  * Returns a color-defaults with given values and class.
2293
2258
  * @param {CP.ColorPicker} self
@@ -2321,7 +2286,8 @@
2321
2286
  optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2322
2287
  const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2323
2288
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2324
-
2289
+ /** @type {HTMLUListElement} */
2290
+ // @ts-ignore -- <UL> is an `HTMLElement`
2325
2291
  const menu = createElement({
2326
2292
  tagName: 'ul',
2327
2293
  className: finalClass,
@@ -2329,7 +2295,7 @@
2329
2295
  setAttribute(menu, 'role', 'listbox');
2330
2296
  setAttribute(menu, ariaLabel, menuLabel);
2331
2297
 
2332
- if (isScrollable) { // @ts-ignore
2298
+ if (isScrollable) {
2333
2299
  setCSSProperties(menu, {
2334
2300
  '--grid-item-size': `${optionSize}rem`,
2335
2301
  '--grid-fit': fit,
@@ -2340,15 +2306,19 @@
2340
2306
  }
2341
2307
 
2342
2308
  colorsArray.forEach((x) => {
2343
- const [value, label] = x.trim().split(':');
2344
- const xRealColor = new Color(value, format).toString();
2345
- const isActive = xRealColor === getAttribute(input, 'value');
2309
+ let [value, label] = typeof x === 'string' ? x.trim().split(':') : [];
2310
+ if (x instanceof Color) {
2311
+ value = x.toHexString();
2312
+ label = value;
2313
+ }
2314
+ const color = new Color(x instanceof Color ? x : value, format);
2315
+ const isActive = color.toString() === getAttribute(input, 'value');
2346
2316
  const active = isActive ? ' active' : '';
2347
2317
 
2348
2318
  const option = createElement({
2349
2319
  tagName: 'li',
2350
2320
  className: `color-option${active}`,
2351
- innerText: `${label || x}`,
2321
+ innerText: `${label || value}`,
2352
2322
  });
2353
2323
 
2354
2324
  setAttribute(option, tabIndex, '0');
@@ -2357,7 +2327,7 @@
2357
2327
  setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2358
2328
 
2359
2329
  if (isOptionsMenu) {
2360
- setElementStyle(option, { backgroundColor: x });
2330
+ setElementStyle(option, { backgroundColor: value });
2361
2331
  }
2362
2332
 
2363
2333
  menu.append(option);
@@ -2366,55 +2336,10 @@
2366
2336
  }
2367
2337
 
2368
2338
  /**
2369
- * Check if a string is valid JSON string.
2370
- * @param {string} str the string input
2371
- * @returns {boolean} the query result
2372
- */
2373
- function isValidJSON(str) {
2374
- try {
2375
- JSON.parse(str);
2376
- } catch (e) {
2377
- return false;
2378
- }
2379
- return true;
2380
- }
2381
-
2382
- var version = "0.0.1";
2383
-
2384
- // @ts-ignore
2385
-
2386
- const Version = version;
2387
-
2388
- // ColorPicker GC
2389
- // ==============
2390
- const colorPickerString = 'color-picker';
2391
- const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2392
- const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2393
- const colorPickerDefaults = {
2394
- componentLabels: colorPickerLabels,
2395
- colorLabels: colorNames,
2396
- format: 'rgb',
2397
- colorPresets: false,
2398
- colorKeywords: false,
2399
- };
2400
-
2401
- // ColorPicker Static Methods
2402
- // ==========================
2403
-
2404
- /** @type {CP.GetInstance<ColorPicker>} */
2405
- const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2406
-
2407
- /** @type {CP.InitCallback<ColorPicker>} */
2408
- const initColorPicker = (element) => new ColorPicker(element);
2409
-
2410
- // ColorPicker Private Methods
2411
- // ===========================
2412
-
2413
- /**
2414
- * Generate HTML markup and update instance properties.
2415
- * @param {ColorPicker} self
2416
- */
2417
- function initCallback(self) {
2339
+ * Generate HTML markup and update instance properties.
2340
+ * @param {CP.ColorPicker} self
2341
+ */
2342
+ function setMarkup(self) {
2418
2343
  const {
2419
2344
  input, parent, format, id, componentLabels, colorKeywords, colorPresets,
2420
2345
  } = self;
@@ -2429,9 +2354,7 @@
2429
2354
  self.color = new Color(color, format);
2430
2355
 
2431
2356
  // set initial controls dimensions
2432
- // make the controls smaller on mobile
2433
- const dropClass = isMobile ? ' mobile' : '';
2434
- const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
2357
+ const formatString = format === 'hex' ? hexLabel : toUpperCase(format);
2435
2358
 
2436
2359
  const pickerBtn = createElement({
2437
2360
  id: `picker-btn-${id}`,
@@ -2448,7 +2371,7 @@
2448
2371
 
2449
2372
  const pickerDropdown = createElement({
2450
2373
  tagName: 'div',
2451
- className: `color-dropdown picker${dropClass}`,
2374
+ className: 'color-dropdown picker',
2452
2375
  });
2453
2376
  setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
2454
2377
  setAttribute(pickerDropdown, 'role', 'group');
@@ -2464,7 +2387,7 @@
2464
2387
  if (colorKeywords || colorPresets) {
2465
2388
  const presetsDropdown = createElement({
2466
2389
  tagName: 'div',
2467
- className: `color-dropdown scrollable menu${dropClass}`,
2390
+ className: 'color-dropdown scrollable menu',
2468
2391
  });
2469
2392
 
2470
2393
  // color presets
@@ -2514,6 +2437,37 @@
2514
2437
  setAttribute(input, tabIndex, '-1');
2515
2438
  }
2516
2439
 
2440
+ var version = "0.0.2alpha3";
2441
+
2442
+ // @ts-ignore
2443
+
2444
+ const Version = version;
2445
+
2446
+ // ColorPicker GC
2447
+ // ==============
2448
+ const colorPickerString = 'color-picker';
2449
+ const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2450
+ const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2451
+ const colorPickerDefaults = {
2452
+ componentLabels: colorPickerLabels,
2453
+ colorLabels: colorNames,
2454
+ format: 'rgb',
2455
+ colorPresets: false,
2456
+ colorKeywords: false,
2457
+ };
2458
+
2459
+ // ColorPicker Static Methods
2460
+ // ==========================
2461
+
2462
+ /** @type {CP.GetInstance<ColorPicker>} */
2463
+ const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2464
+
2465
+ /** @type {CP.InitCallback<ColorPicker>} */
2466
+ const initColorPicker = (element) => new ColorPicker(element);
2467
+
2468
+ // ColorPicker Private Methods
2469
+ // ===========================
2470
+
2517
2471
  /**
2518
2472
  * Add / remove `ColorPicker` main event listeners.
2519
2473
  * @param {ColorPicker} self
@@ -2526,8 +2480,6 @@
2526
2480
  fn(input, focusinEvent, self.showPicker);
2527
2481
  fn(pickerToggle, mouseclickEvent, self.togglePicker);
2528
2482
 
2529
- fn(input, keydownEvent, self.keyToggle);
2530
-
2531
2483
  if (menuToggle) {
2532
2484
  fn(menuToggle, mouseclickEvent, self.toggleMenu);
2533
2485
  }
@@ -2565,8 +2517,7 @@
2565
2517
  fn(doc, pointerEvents.move, self.pointerMove);
2566
2518
  fn(doc, pointerEvents.up, self.pointerUp);
2567
2519
  fn(parent, focusoutEvent, self.handleFocusOut);
2568
- // @ts-ignore -- this is `Window`
2569
- fn(win, keyupEvent, self.handleDismiss);
2520
+ fn(doc, keyupEvent, self.handleDismiss);
2570
2521
  }
2571
2522
 
2572
2523
  /**
@@ -2650,7 +2601,7 @@
2650
2601
  const input = querySelector(target);
2651
2602
 
2652
2603
  // invalidate
2653
- if (!input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
2604
+ if (!input) throw new TypeError(`ColorPicker target "${target}" cannot be found.`);
2654
2605
  self.input = input;
2655
2606
 
2656
2607
  const parent = closest(input, colorPickerParentSelector);
@@ -2697,15 +2648,14 @@
2697
2648
  });
2698
2649
 
2699
2650
  // update and expose component labels
2700
- const tempLabels = ObjectAssign({}, colorPickerLabels);
2701
- const jsonLabels = componentLabels && isValidJSON(componentLabels)
2702
- ? JSON.parse(componentLabels) : componentLabels || {};
2651
+ const tempComponentLabels = componentLabels && isValidJSON(componentLabels)
2652
+ ? JSON.parse(componentLabels) : componentLabels;
2703
2653
 
2704
2654
  /** @type {Record<string, string>} */
2705
- self.componentLabels = ObjectAssign(tempLabels, jsonLabels);
2655
+ self.componentLabels = ObjectAssign(colorPickerLabels, tempComponentLabels);
2706
2656
 
2707
2657
  /** @type {Color} */
2708
- self.color = new Color('white', format);
2658
+ self.color = new Color(input.value || '#fff', format);
2709
2659
 
2710
2660
  /** @type {CP.ColorFormats} */
2711
2661
  self.format = format;
@@ -2714,7 +2664,7 @@
2714
2664
  if (colorKeywords instanceof Array) {
2715
2665
  self.colorKeywords = colorKeywords;
2716
2666
  } else if (typeof colorKeywords === 'string' && colorKeywords.length) {
2717
- self.colorKeywords = colorKeywords.split(',');
2667
+ self.colorKeywords = colorKeywords.split(',').map((x) => x.trim());
2718
2668
  }
2719
2669
 
2720
2670
  // set colour presets
@@ -2743,11 +2693,10 @@
2743
2693
  self.handleFocusOut = self.handleFocusOut.bind(self);
2744
2694
  self.changeHandler = self.changeHandler.bind(self);
2745
2695
  self.handleDismiss = self.handleDismiss.bind(self);
2746
- self.keyToggle = self.keyToggle.bind(self);
2747
2696
  self.handleKnobs = self.handleKnobs.bind(self);
2748
2697
 
2749
2698
  // generate markup
2750
- initCallback(self);
2699
+ setMarkup(self);
2751
2700
 
2752
2701
  const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
2753
2702
  // set main elements
@@ -2835,76 +2784,83 @@
2835
2784
  return inputValue !== '' && new Color(inputValue).isValid;
2836
2785
  }
2837
2786
 
2787
+ /** Returns the colour appearance, usually the closest colour name for the current value. */
2788
+ get appearance() {
2789
+ const {
2790
+ colorLabels, hsl, hsv, format,
2791
+ } = this;
2792
+
2793
+ const hue = roundPart(hsl.h * 360);
2794
+ const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
2795
+ const saturation = roundPart(saturationSource * 100);
2796
+ const lightness = roundPart(hsl.l * 100);
2797
+ const hsvl = hsv.v * 100;
2798
+
2799
+ let colorName;
2800
+
2801
+ // determine color appearance
2802
+ if (lightness === 100 && saturation === 0) {
2803
+ colorName = colorLabels.white;
2804
+ } else if (lightness === 0) {
2805
+ colorName = colorLabels.black;
2806
+ } else if (saturation === 0) {
2807
+ colorName = colorLabels.grey;
2808
+ } else if (hue < 15 || hue >= 345) {
2809
+ colorName = colorLabels.red;
2810
+ } else if (hue >= 15 && hue < 45) {
2811
+ colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
2812
+ } else if (hue >= 45 && hue < 75) {
2813
+ const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
2814
+ const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
2815
+ colorName = isGold ? colorLabels.gold : colorLabels.yellow;
2816
+ colorName = isOlive ? colorLabels.olive : colorName;
2817
+ } else if (hue >= 75 && hue < 155) {
2818
+ colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
2819
+ } else if (hue >= 155 && hue < 175) {
2820
+ colorName = colorLabels.teal;
2821
+ } else if (hue >= 175 && hue < 195) {
2822
+ colorName = colorLabels.cyan;
2823
+ } else if (hue >= 195 && hue < 255) {
2824
+ colorName = colorLabels.blue;
2825
+ } else if (hue >= 255 && hue < 270) {
2826
+ colorName = colorLabels.violet;
2827
+ } else if (hue >= 270 && hue < 295) {
2828
+ colorName = colorLabels.magenta;
2829
+ } else if (hue >= 295 && hue < 345) {
2830
+ colorName = colorLabels.pink;
2831
+ }
2832
+ return colorName;
2833
+ }
2834
+
2838
2835
  /** Updates `ColorPicker` visuals. */
2839
2836
  updateVisuals() {
2840
2837
  const self = this;
2841
2838
  const {
2842
- format, controlPositions, visuals,
2839
+ controlPositions, visuals,
2843
2840
  } = self;
2844
2841
  const [v1, v2, v3] = visuals;
2845
- const { offsetWidth, offsetHeight } = v1;
2846
- const hue = format === 'hsl'
2847
- ? controlPositions.c1x / offsetWidth
2848
- : controlPositions.c2y / offsetHeight;
2849
- // @ts-ignore - `hslToRgb` is assigned to `Color` as static method
2850
- const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
2842
+ const { offsetHeight } = v1;
2843
+ const hue = controlPositions.c2y / offsetHeight;
2844
+ const { r, g, b } = new Color({ h: hue, s: 1, l: 0.5 }).toRgb();
2851
2845
  const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
2852
2846
  const alpha = 1 - controlPositions.c3y / offsetHeight;
2853
2847
  const roundA = roundPart((alpha * 100)) / 100;
2854
2848
 
2855
- if (format !== 'hsl') {
2856
- const fill = new Color({
2857
- h: hue, s: 1, l: 0.5, a: alpha,
2858
- }).toRgbString();
2859
- const hueGradient = `linear-gradient(
2860
- rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2861
- rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2862
- rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2863
- rgb(255,0,0) 100%)`;
2864
- setElementStyle(v1, {
2865
- background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2866
- linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2867
- ${whiteGrad}`,
2868
- });
2869
- setElementStyle(v2, { background: hueGradient });
2870
- } else {
2871
- const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
2872
- const fill0 = new Color({
2873
- r: 255, g: 0, b: 0, a: alpha,
2874
- }).saturate(-saturation).toRgbString();
2875
- const fill1 = new Color({
2876
- r: 255, g: 255, b: 0, a: alpha,
2877
- }).saturate(-saturation).toRgbString();
2878
- const fill2 = new Color({
2879
- r: 0, g: 255, b: 0, a: alpha,
2880
- }).saturate(-saturation).toRgbString();
2881
- const fill3 = new Color({
2882
- r: 0, g: 255, b: 255, a: alpha,
2883
- }).saturate(-saturation).toRgbString();
2884
- const fill4 = new Color({
2885
- r: 0, g: 0, b: 255, a: alpha,
2886
- }).saturate(-saturation).toRgbString();
2887
- const fill5 = new Color({
2888
- r: 255, g: 0, b: 255, a: alpha,
2889
- }).saturate(-saturation).toRgbString();
2890
- const fill6 = new Color({
2891
- r: 255, g: 0, b: 0, a: alpha,
2892
- }).saturate(-saturation).toRgbString();
2893
- const fillGradient = `linear-gradient(to right,
2894
- ${fill0} 0%, ${fill1} 16.67%, ${fill2} 33.33%, ${fill3} 50%,
2895
- ${fill4} 66.67%, ${fill5} 83.33%, ${fill6} 100%)`;
2896
- const lightGrad = `linear-gradient(rgba(255,255,255,${roundA}) 0%, rgba(255,255,255,0) 50%),
2897
- linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,${roundA}) 100%)`;
2898
-
2899
- setElementStyle(v1, { background: `${lightGrad},${fillGradient},${whiteGrad}` });
2900
- const {
2901
- r: gr, g: gg, b: gb,
2902
- } = new Color({ r, g, b }).greyscale().toRgb();
2849
+ const fill = new Color({
2850
+ h: hue, s: 1, l: 0.5, a: alpha,
2851
+ }).toRgbString();
2852
+ const hueGradient = `linear-gradient(
2853
+ rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2854
+ rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2855
+ rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2856
+ rgb(255,0,0) 100%)`;
2857
+ setElementStyle(v1, {
2858
+ background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2859
+ linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2860
+ ${whiteGrad}`,
2861
+ });
2862
+ setElementStyle(v2, { background: hueGradient });
2903
2863
 
2904
- setElementStyle(v2, {
2905
- background: `linear-gradient(rgb(${r},${g},${b}) 0%, rgb(${gr},${gg},${gb}) 100%)`,
2906
- });
2907
- }
2908
2864
  setElementStyle(v3, {
2909
2865
  background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
2910
2866
  });
@@ -2943,7 +2899,7 @@
2943
2899
  const self = this;
2944
2900
  const { activeElement } = getDocument(self.input);
2945
2901
 
2946
- if ((isMobile && self.dragElement)
2902
+ if ((e.type === touchmoveEvent && self.dragElement)
2947
2903
  || (activeElement && self.controlKnobs.includes(activeElement))) {
2948
2904
  e.stopPropagation();
2949
2905
  e.preventDefault();
@@ -3054,13 +3010,13 @@
3054
3010
  const [v1, v2, v3] = visuals;
3055
3011
  const [c1, c2, c3] = controlKnobs;
3056
3012
  /** @type {HTMLElement} */
3057
- const visual = hasClass(target, 'visual-control')
3058
- ? target : querySelector('.visual-control', target.parentElement);
3013
+ const visual = controlKnobs.includes(target) ? target.previousElementSibling : target;
3059
3014
  const visualRect = getBoundingClientRect(visual);
3015
+ const html = getDocumentElement(v1);
3060
3016
  const X = type === 'touchstart' ? touches[0].pageX : pageX;
3061
3017
  const Y = type === 'touchstart' ? touches[0].pageY : pageY;
3062
- const offsetX = X - window.pageXOffset - visualRect.left;
3063
- const offsetY = Y - window.pageYOffset - visualRect.top;
3018
+ const offsetX = X - html.scrollLeft - visualRect.left;
3019
+ const offsetY = Y - html.scrollTop - visualRect.top;
3064
3020
 
3065
3021
  if (target === v1 || target === c1) {
3066
3022
  self.dragElement = visual;
@@ -3120,10 +3076,11 @@
3120
3076
  if (!dragElement) return;
3121
3077
 
3122
3078
  const controlRect = getBoundingClientRect(dragElement);
3123
- const X = type === 'touchmove' ? touches[0].pageX : pageX;
3124
- const Y = type === 'touchmove' ? touches[0].pageY : pageY;
3125
- const offsetX = X - window.pageXOffset - controlRect.left;
3126
- const offsetY = Y - window.pageYOffset - controlRect.top;
3079
+ const win = getDocumentElement(v1);
3080
+ const X = type === touchmoveEvent ? touches[0].pageX : pageX;
3081
+ const Y = type === touchmoveEvent ? touches[0].pageY : pageY;
3082
+ const offsetX = X - win.scrollLeft - controlRect.left;
3083
+ const offsetY = Y - win.scrollTop - controlRect.top;
3127
3084
 
3128
3085
  if (dragElement === v1) {
3129
3086
  self.changeControl1(offsetX, offsetY);
@@ -3150,19 +3107,19 @@
3150
3107
  if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
3151
3108
  e.preventDefault();
3152
3109
 
3153
- const { format, controlKnobs, visuals } = self;
3110
+ const { controlKnobs, visuals } = self;
3154
3111
  const { offsetWidth, offsetHeight } = visuals[0];
3155
3112
  const [c1, c2, c3] = controlKnobs;
3156
3113
  const { activeElement } = getDocument(c1);
3157
3114
  const currentKnob = controlKnobs.find((x) => x === activeElement);
3158
- const yRatio = offsetHeight / (format === 'hsl' ? 100 : 360);
3115
+ const yRatio = offsetHeight / 360;
3159
3116
 
3160
3117
  if (currentKnob) {
3161
3118
  let offsetX = 0;
3162
3119
  let offsetY = 0;
3163
3120
 
3164
3121
  if (target === c1) {
3165
- const xRatio = offsetWidth / (format === 'hsl' ? 360 : 100);
3122
+ const xRatio = offsetWidth / 100;
3166
3123
 
3167
3124
  if ([keyArrowLeft, keyArrowRight].includes(code)) {
3168
3125
  self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
@@ -3212,7 +3169,7 @@
3212
3169
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
3213
3170
  if (activeElement === input) {
3214
3171
  if (isNonColorValue) {
3215
- colorSource = 'white';
3172
+ colorSource = currentValue === 'transparent' ? 'rgba(0,0,0,0)' : 'rgb(0,0,0)';
3216
3173
  } else {
3217
3174
  colorSource = currentValue;
3218
3175
  }
@@ -3263,9 +3220,7 @@
3263
3220
  changeControl1(X, Y) {
3264
3221
  const self = this;
3265
3222
  let [offsetX, offsetY] = [0, 0];
3266
- const {
3267
- format, controlPositions, visuals,
3268
- } = self;
3223
+ const { controlPositions, visuals } = self;
3269
3224
  const { offsetHeight, offsetWidth } = visuals[0];
3270
3225
 
3271
3226
  if (X > offsetWidth) offsetX = offsetWidth;
@@ -3274,29 +3229,19 @@
3274
3229
  if (Y > offsetHeight) offsetY = offsetHeight;
3275
3230
  else if (Y >= 0) offsetY = Y;
3276
3231
 
3277
- const hue = format === 'hsl'
3278
- ? offsetX / offsetWidth
3279
- : controlPositions.c2y / offsetHeight;
3232
+ const hue = controlPositions.c2y / offsetHeight;
3280
3233
 
3281
- const saturation = format === 'hsl'
3282
- ? 1 - controlPositions.c2y / offsetHeight
3283
- : offsetX / offsetWidth;
3234
+ const saturation = offsetX / offsetWidth;
3284
3235
 
3285
3236
  const lightness = 1 - offsetY / offsetHeight;
3286
3237
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3287
3238
 
3288
- const colorObject = format === 'hsl'
3289
- ? {
3290
- h: hue, s: saturation, l: lightness, a: alpha,
3291
- }
3292
- : {
3293
- h: hue, s: saturation, v: lightness, a: alpha,
3294
- };
3295
-
3296
3239
  // new color
3297
3240
  const {
3298
3241
  r, g, b, a,
3299
- } = new Color(colorObject);
3242
+ } = new Color({
3243
+ h: hue, s: saturation, v: lightness, a: alpha,
3244
+ });
3300
3245
 
3301
3246
  ObjectAssign(self.color, {
3302
3247
  r, g, b, a,
@@ -3323,7 +3268,7 @@
3323
3268
  changeControl2(Y) {
3324
3269
  const self = this;
3325
3270
  const {
3326
- format, controlPositions, visuals,
3271
+ controlPositions, visuals,
3327
3272
  } = self;
3328
3273
  const { offsetHeight, offsetWidth } = visuals[0];
3329
3274
 
@@ -3332,26 +3277,17 @@
3332
3277
  if (Y > offsetHeight) offsetY = offsetHeight;
3333
3278
  else if (Y >= 0) offsetY = Y;
3334
3279
 
3335
- const hue = format === 'hsl'
3336
- ? controlPositions.c1x / offsetWidth
3337
- : offsetY / offsetHeight;
3338
- const saturation = format === 'hsl'
3339
- ? 1 - offsetY / offsetHeight
3340
- : controlPositions.c1x / offsetWidth;
3280
+ const hue = offsetY / offsetHeight;
3281
+ const saturation = controlPositions.c1x / offsetWidth;
3341
3282
  const lightness = 1 - controlPositions.c1y / offsetHeight;
3342
3283
  const alpha = 1 - controlPositions.c3y / offsetHeight;
3343
- const colorObject = format === 'hsl'
3344
- ? {
3345
- h: hue, s: saturation, l: lightness, a: alpha,
3346
- }
3347
- : {
3348
- h: hue, s: saturation, v: lightness, a: alpha,
3349
- };
3350
3284
 
3351
3285
  // new color
3352
3286
  const {
3353
3287
  r, g, b, a,
3354
- } = new Color(colorObject);
3288
+ } = new Color({
3289
+ h: hue, s: saturation, v: lightness, a: alpha,
3290
+ });
3355
3291
 
3356
3292
  ObjectAssign(self.color, {
3357
3293
  r, g, b, a,
@@ -3438,18 +3374,18 @@
3438
3374
  setControlPositions() {
3439
3375
  const self = this;
3440
3376
  const {
3441
- format, visuals, color, hsl, hsv,
3377
+ visuals, color, hsv,
3442
3378
  } = self;
3443
3379
  const { offsetHeight, offsetWidth } = visuals[0];
3444
3380
  const alpha = color.a;
3445
- const hue = hsl.h;
3381
+ const hue = hsv.h;
3446
3382
 
3447
- const saturation = format !== 'hsl' ? hsv.s : hsl.s;
3448
- const lightness = format !== 'hsl' ? hsv.v : hsl.l;
3383
+ const saturation = hsv.s;
3384
+ const lightness = hsv.v;
3449
3385
 
3450
- self.controlPositions.c1x = format !== 'hsl' ? saturation * offsetWidth : hue * offsetWidth;
3386
+ self.controlPositions.c1x = saturation * offsetWidth;
3451
3387
  self.controlPositions.c1y = (1 - lightness) * offsetHeight;
3452
- self.controlPositions.c2y = format !== 'hsl' ? hue * offsetHeight : (1 - saturation) * offsetHeight;
3388
+ self.controlPositions.c2y = hue * offsetHeight;
3453
3389
  self.controlPositions.c3y = (1 - alpha) * offsetHeight;
3454
3390
  }
3455
3391
 
@@ -3457,78 +3393,40 @@
3457
3393
  updateAppearance() {
3458
3394
  const self = this;
3459
3395
  const {
3460
- componentLabels, colorLabels, color, parent,
3461
- hsl, hsv, hex, format, controlKnobs,
3396
+ componentLabels, color, parent,
3397
+ hsv, hex, format, controlKnobs,
3462
3398
  } = self;
3463
3399
  const {
3464
3400
  appearanceLabel, hexLabel, valueLabel,
3465
3401
  } = componentLabels;
3466
- const { r, g, b } = color.toRgb();
3402
+ let { r, g, b } = color.toRgb();
3467
3403
  const [knob1, knob2, knob3] = controlKnobs;
3468
- const hue = roundPart(hsl.h * 360);
3404
+ const hue = roundPart(hsv.h * 360);
3469
3405
  const alpha = color.a;
3470
- const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
3471
- const saturation = roundPart(saturationSource * 100);
3472
- const lightness = roundPart(hsl.l * 100);
3473
- const hsvl = hsv.v * 100;
3474
- let colorName;
3475
-
3476
- // determine color appearance
3477
- if (lightness === 100 && saturation === 0) {
3478
- colorName = colorLabels.white;
3479
- } else if (lightness === 0) {
3480
- colorName = colorLabels.black;
3481
- } else if (saturation === 0) {
3482
- colorName = colorLabels.grey;
3483
- } else if (hue < 15 || hue >= 345) {
3484
- colorName = colorLabels.red;
3485
- } else if (hue >= 15 && hue < 45) {
3486
- colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
3487
- } else if (hue >= 45 && hue < 75) {
3488
- const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
3489
- const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
3490
- colorName = isGold ? colorLabels.gold : colorLabels.yellow;
3491
- colorName = isOlive ? colorLabels.olive : colorName;
3492
- } else if (hue >= 75 && hue < 155) {
3493
- colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
3494
- } else if (hue >= 155 && hue < 175) {
3495
- colorName = colorLabels.teal;
3496
- } else if (hue >= 175 && hue < 195) {
3497
- colorName = colorLabels.cyan;
3498
- } else if (hue >= 195 && hue < 255) {
3499
- colorName = colorLabels.blue;
3500
- } else if (hue >= 255 && hue < 270) {
3501
- colorName = colorLabels.violet;
3502
- } else if (hue >= 270 && hue < 295) {
3503
- colorName = colorLabels.magenta;
3504
- } else if (hue >= 295 && hue < 345) {
3505
- colorName = colorLabels.pink;
3506
- }
3406
+ const saturation = roundPart(hsv.s * 100);
3407
+ const lightness = roundPart(hsv.v * 100);
3408
+ const colorName = self.appearance;
3507
3409
 
3508
3410
  let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
3509
3411
 
3510
- if (format === 'hsl') {
3511
- colorLabel = `HSL: ${hue}°, ${saturation}%, ${lightness}%`;
3512
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3513
- setAttribute(knob1, ariaValueText, `${hue}° & ${lightness}%`);
3514
- setAttribute(knob1, ariaValueNow, `${hue}`);
3515
- setAttribute(knob2, ariaValueText, `${saturation}%`);
3516
- setAttribute(knob2, ariaValueNow, `${saturation}`);
3517
- } else if (format === 'hwb') {
3412
+ if (format === 'hwb') {
3518
3413
  const { hwb } = self;
3519
3414
  const whiteness = roundPart(hwb.w * 100);
3520
3415
  const blackness = roundPart(hwb.b * 100);
3521
3416
  colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
3522
- setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3523
3417
  setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
3524
3418
  setAttribute(knob1, ariaValueNow, `${whiteness}`);
3419
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3525
3420
  setAttribute(knob2, ariaValueText, `${hue}%`);
3526
3421
  setAttribute(knob2, ariaValueNow, `${hue}`);
3527
3422
  } else {
3423
+ [r, g, b] = [r, g, b].map(roundPart);
3424
+ colorLabel = format === 'hsl' ? `HSL: ${hue}°, ${saturation}%, ${lightness}%` : colorLabel;
3528
3425
  colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
3529
- setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3426
+
3530
3427
  setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
3531
3428
  setAttribute(knob1, ariaValueNow, `${lightness}`);
3429
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3532
3430
  setAttribute(knob2, ariaValueText, `${hue}°`);
3533
3431
  setAttribute(knob2, ariaValueNow, `${hue}`);
3534
3432
  }
@@ -3623,37 +3521,13 @@
3623
3521
  }
3624
3522
  }
3625
3523
 
3626
- /**
3627
- * The `Space` & `Enter` keys specific event listener.
3628
- * Toggle visibility of the `ColorPicker` / the presets menu, showing one will hide the other.
3629
- * @param {KeyboardEvent} e
3630
- * @this {ColorPicker}
3631
- */
3632
- keyToggle(e) {
3633
- const self = this;
3634
- const { menuToggle } = self;
3635
- const { activeElement } = getDocument(menuToggle);
3636
- const { code } = e;
3637
-
3638
- if ([keyEnter, keySpace].includes(code)) {
3639
- if ((menuToggle && activeElement === menuToggle) || !activeElement) {
3640
- e.preventDefault();
3641
- if (!activeElement) {
3642
- self.togglePicker(e);
3643
- } else {
3644
- self.toggleMenu();
3645
- }
3646
- }
3647
- }
3648
- }
3649
-
3650
3524
  /**
3651
3525
  * Toggle the `ColorPicker` dropdown visibility.
3652
- * @param {Event} e
3526
+ * @param {Event=} e
3653
3527
  * @this {ColorPicker}
3654
3528
  */
3655
3529
  togglePicker(e) {
3656
- e.preventDefault();
3530
+ if (e) e.preventDefault();
3657
3531
  const self = this;
3658
3532
  const { colorPicker } = self;
3659
3533
 
@@ -3674,8 +3548,13 @@
3674
3548
  }
3675
3549
  }
3676
3550
 
3677
- /** Toggles the visibility of the `ColorPicker` presets menu. */
3678
- toggleMenu() {
3551
+ /**
3552
+ * Toggles the visibility of the `ColorPicker` presets menu.
3553
+ * @param {Event=} e
3554
+ * @this {ColorPicker}
3555
+ */
3556
+ toggleMenu(e) {
3557
+ if (e) e.preventDefault();
3679
3558
  const self = this;
3680
3559
  const { colorMenu } = self;
3681
3560
 
@@ -3701,6 +3580,10 @@
3701
3580
  const relatedBtn = openPicker ? pickerToggle : menuToggle;
3702
3581
  const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
3703
3582
 
3583
+ // if (!self.isValid) {
3584
+ self.value = self.color.toString(true);
3585
+ // }
3586
+
3704
3587
  if (openDropdown) {
3705
3588
  removeClass(openDropdown, 'show');
3706
3589
  setAttribute(relatedBtn, ariaExpanded, 'false');
@@ -3714,9 +3597,6 @@
3714
3597
  }, animationDuration);
3715
3598
  }
3716
3599
 
3717
- if (!self.isValid) {
3718
- self.value = self.color.toString();
3719
- }
3720
3600
  if (!focusPrevented) {
3721
3601
  focus(pickerToggle);
3722
3602
  }
@@ -3765,90 +3645,82 @@
3765
3645
  * `ColorPickerElement` Web Component.
3766
3646
  * @example
3767
3647
  * <label for="UNIQUE_ID">Label</label>
3768
- * <color-picker data-format="hex" data-value="#075">
3769
- * <input id="UNIQUE_ID" type="text" class="color-preview btn-appearance">
3648
+ * <color-picker>
3649
+ * <input id="UNIQUE_ID" value="red" format="hex" class="color-preview btn-appearance">
3770
3650
  * </color-picker>
3651
+ * // or
3652
+ * <label for="UNIQUE_ID">Label</label>
3653
+ * <color-picker data-id="UNIQUE_ID" data-value="red" data-format="hex"></color-picker>
3771
3654
  */
3772
3655
  class ColorPickerElement extends HTMLElement {
3773
3656
  constructor() {
3774
3657
  super();
3775
- /** @type {boolean} */
3776
- this.isDisconnected = true;
3777
3658
  this.attachShadow({ mode: 'open' });
3778
3659
  }
3779
3660
 
3780
3661
  /**
3781
3662
  * Returns the current color value.
3782
- * @returns {string?}
3663
+ * @returns {string | undefined}
3783
3664
  */
3784
- get value() { return this.input ? this.input.value : null; }
3665
+ get value() { return this.input && this.input.value; }
3785
3666
 
3786
3667
  connectedCallback() {
3787
- if (this.colorPicker) {
3788
- if (this.isDisconnected) {
3789
- this.isDisconnected = false;
3790
- }
3791
- return;
3792
- }
3668
+ if (this.input) return;
3793
3669
 
3794
- const inputs = getElementsByTagName('input', this);
3670
+ let [input] = getElementsByTagName('input', this);
3671
+ const value = (input && getAttribute(input, 'value')) || getAttribute(this, 'data-value') || '#fff';
3672
+ const format = (input && getAttribute(input, 'format')) || getAttribute(this, 'data-format') || 'rgb';
3673
+ let id = (input && getAttribute(input, 'id')) || getAttribute(this, 'data-id');
3674
+
3675
+ if (!id) {
3676
+ id = `color-picker-${format}-${CPID}`;
3677
+ CPID += 1;
3678
+ }
3795
3679
 
3796
- if (!inputs.length) {
3797
- const label = getAttribute(this, 'data-label');
3798
- const value = getAttribute(this, 'data-value') || '#069';
3799
- const format = getAttribute(this, 'data-format') || 'rgb';
3800
- const newInput = createElement({
3680
+ if (!input) {
3681
+ input = createElement({
3801
3682
  tagName: 'input',
3802
3683
  type: 'text',
3803
3684
  className: 'color-preview btn-appearance',
3804
3685
  });
3805
- let id = getAttribute(this, 'data-id');
3806
- if (!id) {
3807
- id = `color-picker-${format}-${CPID}`;
3808
- CPID += 1;
3809
- }
3810
3686
 
3811
- const labelElement = createElement({ tagName: 'label', innerText: label || 'Color Picker' });
3812
- this.before(labelElement);
3813
- setAttribute(labelElement, 'for', id);
3814
- setAttribute(newInput, 'id', id);
3815
- setAttribute(newInput, 'name', id);
3816
- setAttribute(newInput, 'autocomplete', 'off');
3817
- setAttribute(newInput, 'spellcheck', 'false');
3818
- setAttribute(newInput, 'value', value);
3819
- this.append(newInput);
3687
+ setAttribute(input, 'id', id);
3688
+ setAttribute(input, 'name', id);
3689
+ setAttribute(input, 'autocomplete', 'off');
3690
+ setAttribute(input, 'spellcheck', 'false');
3691
+ setAttribute(input, 'value', value);
3692
+ this.append(input);
3820
3693
  }
3694
+ /** @type {HTMLInputElement} */
3695
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3696
+ this.input = input;
3821
3697
 
3822
- const [input] = inputs;
3823
-
3824
- if (input) {
3825
- /** @type {HTMLInputElement} */
3826
- // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3827
- this.input = input;
3828
-
3829
- // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3830
- this.colorPicker = new ColorPicker(input);
3831
- this.color = this.colorPicker.color;
3832
-
3833
- if (this.shadowRoot) {
3834
- this.shadowRoot.append(createElement('slot'));
3835
- }
3698
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3699
+ this.colorPicker = new ColorPicker(input);
3836
3700
 
3837
- this.isDisconnected = false;
3838
- }
3701
+ // @ts-ignore - `shadowRoot` is defined in the constructor
3702
+ this.shadowRoot.append(createElement('slot'));
3839
3703
  }
3840
3704
 
3705
+ /** @this {ColorPickerElement} */
3841
3706
  disconnectedCallback() {
3842
- if (this.colorPicker) this.colorPicker.dispose();
3843
- this.isDisconnected = true;
3707
+ const { input, colorPicker, shadowRoot } = this;
3708
+ if (colorPicker) colorPicker.dispose();
3709
+ if (input) input.remove();
3710
+ if (shadowRoot) shadowRoot.innerHTML = '';
3711
+
3712
+ ObjectAssign(this, {
3713
+ colorPicker: undefined,
3714
+ input: undefined,
3715
+ });
3844
3716
  }
3845
3717
  }
3846
3718
 
3847
3719
  ObjectAssign(ColorPickerElement, {
3848
3720
  Color,
3849
3721
  ColorPicker,
3850
- ColorPalette,
3851
- getInstance: getColorPickerInstance,
3722
+ ColorPalette, // @ts-ignore
3723
+ getInstance: ColorPicker.getInstance,
3852
3724
  Version,
3853
3725
  });
3854
3726
 
@@ -3856,4 +3728,4 @@
3856
3728
 
3857
3729
  return ColorPickerElement;
3858
3730
 
3859
- })));
3731
+ }));