@thednp/color-picker 0.0.1 → 0.0.2-alpha3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }));