@thednp/color-picker 0.0.1 → 0.0.2-alpha1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * ColorPickerElement v0.0.1 (http://thednp.github.io/color-picker)
2
+ * ColorPickerElement v0.0.2alpha1 (http://thednp.github.io/color-picker)
3
3
  * Copyright 2022 © thednp
4
4
  * Licensed under MIT (https://github.com/thednp/color-picker/blob/master/LICENSE)
5
5
  */
@@ -82,14 +82,9 @@ const setAttribute = (element, attribute, value) => element.setAttribute(attribu
82
82
  const getAttribute = (element, attribute) => element.getAttribute(attribute);
83
83
 
84
84
  /**
85
- * Returns the `document.head` or the `<head>` element.
86
- *
87
- * @param {(Node | HTMLElement | Element | globalThis)=} node
88
- * @returns {HTMLElement | HTMLHeadElement}
85
+ * A global namespace for `document.head`.
89
86
  */
90
- function getDocumentHead(node) {
91
- return getDocument(node).head;
92
- }
87
+ const { head: documentHead } = document;
93
88
 
94
89
  /**
95
90
  * Shortcut for `window.getComputedStyle(element).propertyName`
@@ -110,20 +105,21 @@ function getElementStyle(element, property) {
110
105
  return property in computedStyle ? computedStyle[property] : '';
111
106
  }
112
107
 
113
- /**
114
- * Shortcut for `Object.keys()` static method.
115
- * @param {Record<string, any>} obj a target object
116
- * @returns {string[]}
117
- */
118
- const ObjectKeys = (obj) => Object.keys(obj);
119
-
120
108
  /**
121
109
  * Shortcut for multiple uses of `HTMLElement.style.propertyName` method.
122
110
  * @param {HTMLElement | Element} element target element
123
111
  * @param {Partial<CSSStyleDeclaration>} styles attribute value
124
112
  */
125
113
  // @ts-ignore
126
- const setElementStyle = (element, styles) => ObjectAssign(element.style, styles);
114
+ const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
115
+
116
+ /**
117
+ * Shortcut for `String.toLowerCase()`.
118
+ *
119
+ * @param {string} source input string
120
+ * @returns {string} lowercase output string
121
+ */
122
+ const toLowerCase = (source) => source.toLowerCase();
127
123
 
128
124
  /**
129
125
  * A list of explicit default non-color values.
@@ -141,7 +137,7 @@ function roundPart(v) {
141
137
  }
142
138
 
143
139
  // Color supported formats
144
- const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
140
+ const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsv', 'hwb'];
145
141
 
146
142
  // Hue angles
147
143
  const ANGLES = 'deg|rad|grad|turn';
@@ -163,10 +159,17 @@ const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
163
159
  // Add angles to the mix
164
160
  const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
165
161
 
162
+ // Start & end
163
+ const START_MATCH = '(?:[\\s|\\(\\s|\\s\\(\\s]+)?';
164
+ const END_MATCH = '(?:[\\s|\\)\\s]+)?';
165
+ // Components separation
166
+ const SEP = '(?:[,|\\s]+)';
167
+ const SEP2 = '(?:[,|\\/\\s]*)?';
168
+
166
169
  // Actual matching.
167
170
  // Parentheses and commas are optional, but not required.
168
171
  // Whitespace can take the place of commas or opening paren
169
- const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
172
+ const PERMISSIVE_MATCH = `${START_MATCH}(${CSS_UNIT2})${SEP}(${CSS_UNIT})${SEP}(${CSS_UNIT})${SEP2}(${CSS_UNIT})?${END_MATCH}`;
170
173
 
171
174
  const matchers = {
172
175
  CSS_UNIT: new RegExp(CSS_UNIT2),
@@ -199,23 +202,22 @@ function isPercentage(n) {
199
202
  return `${n}`.includes('%');
200
203
  }
201
204
 
202
- /**
203
- * Check to see if string passed in is an angle
204
- * @param {string} n testing string
205
- * @returns {boolean} the query result
206
- */
207
- function isAngle(n) {
208
- return ANGLES.split('|').some((a) => `${n}`.includes(a));
209
- }
210
-
211
205
  /**
212
206
  * Check to see if string passed is a web safe colour.
207
+ * @see https://stackoverflow.com/a/16994164
213
208
  * @param {string} color a colour name, EG: *red*
214
209
  * @returns {boolean} the query result
215
210
  */
216
211
  function isColorName(color) {
217
- return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
218
- && !/[0-9]/.test(color);
212
+ if (nonColors.includes(color)
213
+ || ['#', ...COLOR_FORMAT].some((f) => color.includes(f))) return false;
214
+
215
+ return ['rgb(255, 255, 255)', 'rgb(0, 0, 0)'].every((c) => {
216
+ setElementStyle(documentHead, { color });
217
+ const computedColor = getElementStyle(documentHead, 'color');
218
+ setElementStyle(documentHead, { color: '' });
219
+ return computedColor !== c;
220
+ });
219
221
  }
220
222
 
221
223
  /**
@@ -236,15 +238,15 @@ function isValidCSSUnit(color) {
236
238
  */
237
239
  function bound01(N, max) {
238
240
  let n = N;
239
- if (isOnePointZero(n)) n = '100%';
241
+ if (isOnePointZero(N)) n = '100%';
240
242
 
241
- n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
242
-
243
- // Handle hue angles
244
- if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
243
+ const processPercent = isPercentage(n);
244
+ n = max === 360
245
+ ? parseFloat(n)
246
+ : Math.min(max, Math.max(0, parseFloat(n)));
245
247
 
246
248
  // Automatically convert percentage into number
247
- if (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
249
+ if (processPercent) n = (n * max) / 100;
248
250
 
249
251
  // Handle floating point rounding errors
250
252
  if (Math.abs(n - max) < 0.000001) {
@@ -255,11 +257,11 @@ function bound01(N, max) {
255
257
  // If n is a hue given in degrees,
256
258
  // wrap around out-of-range values into [0, 360] range
257
259
  // then convert into [0, 1].
258
- n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max));
260
+ n = (n < 0 ? (n % max) + max : n % max) / max;
259
261
  } else {
260
262
  // If n not a hue given in degrees
261
263
  // Convert into [0, 1] range if it isn't already.
262
- n = (n % max) / parseFloat(String(max));
264
+ n = (n % max) / max;
263
265
  }
264
266
  return n;
265
267
  }
@@ -294,7 +296,6 @@ function clamp01(v) {
294
296
  * @returns {string}
295
297
  */
296
298
  function getRGBFromName(name) {
297
- const documentHead = getDocumentHead();
298
299
  setElementStyle(documentHead, { color: name });
299
300
  const colorName = getElementStyle(documentHead, 'color');
300
301
  setElementStyle(documentHead, { color: '' });
@@ -393,6 +394,36 @@ function hueToRgb(p, q, t) {
393
394
  return p;
394
395
  }
395
396
 
397
+ /**
398
+ * Converts an HSL colour value to RGB.
399
+ *
400
+ * @param {number} h Hue Angle [0, 1]
401
+ * @param {number} s Saturation [0, 1]
402
+ * @param {number} l Lightness Angle [0, 1]
403
+ * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
404
+ */
405
+ function hslToRgb(h, s, l) {
406
+ let r = 0;
407
+ let g = 0;
408
+ let b = 0;
409
+
410
+ if (s === 0) {
411
+ // achromatic
412
+ g = l;
413
+ b = l;
414
+ r = l;
415
+ } else {
416
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
417
+ const p = 2 * l - q;
418
+ r = hueToRgb(p, q, h + 1 / 3);
419
+ g = hueToRgb(p, q, h);
420
+ b = hueToRgb(p, q, h - 1 / 3);
421
+ }
422
+ [r, g, b] = [r, g, b].map((x) => x * 255);
423
+
424
+ return { r, g, b };
425
+ }
426
+
396
427
  /**
397
428
  * Returns an HWB colour object from an RGB colour object.
398
429
  * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
@@ -455,36 +486,6 @@ function hwbToRgb(H, W, B) {
455
486
  return { r, g, b };
456
487
  }
457
488
 
458
- /**
459
- * Converts an HSL colour value to RGB.
460
- *
461
- * @param {number} h Hue Angle [0, 1]
462
- * @param {number} s Saturation [0, 1]
463
- * @param {number} l Lightness Angle [0, 1]
464
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
465
- */
466
- function hslToRgb(h, s, l) {
467
- let r = 0;
468
- let g = 0;
469
- let b = 0;
470
-
471
- if (s === 0) {
472
- // achromatic
473
- g = l;
474
- b = l;
475
- r = l;
476
- } else {
477
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
478
- const p = 2 * l - q;
479
- r = hueToRgb(p, q, h + 1 / 3);
480
- g = hueToRgb(p, q, h);
481
- b = hueToRgb(p, q, h - 1 / 3);
482
- }
483
- [r, g, b] = [r, g, b].map((x) => x * 255);
484
-
485
- return { r, g, b };
486
- }
487
-
488
489
  /**
489
490
  * Converts an RGB colour value to HSV.
490
491
  *
@@ -540,10 +541,11 @@ function hsvToRgb(H, S, V) {
540
541
  const q = v * (1 - f * s);
541
542
  const t = v * (1 - (1 - f) * s);
542
543
  const mod = i % 6;
543
- const r = [v, q, p, p, t, v][mod];
544
- const g = [t, v, v, q, p, p][mod];
545
- const b = [p, p, t, v, v, q][mod];
546
- return { r: r * 255, g: g * 255, b: b * 255 };
544
+ let r = [v, q, p, p, t, v][mod];
545
+ let g = [t, v, v, q, p, p][mod];
546
+ let b = [p, p, t, v, v, q][mod];
547
+ [r, g, b] = [r, g, b].map((n) => n * 255);
548
+ return { r, g, b };
547
549
  }
548
550
 
549
551
  /**
@@ -567,7 +569,7 @@ function rgbToHex(r, g, b, allow3Char) {
567
569
  // Return a 3 character hex if possible
568
570
  if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
569
571
  && hex[1].charAt(0) === hex[1].charAt(1)
570
- && hex[2].charAt(0) === hex[2].charAt(1)) {
572
+ && hex[2].charAt(0) === hex[2].charAt(1)) {
571
573
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
572
574
  }
573
575
 
@@ -595,39 +597,24 @@ function rgbaToHex(r, g, b, a, allow4Char) {
595
597
  // Return a 4 character hex if possible
596
598
  if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
597
599
  && hex[1].charAt(0) === hex[1].charAt(1)
598
- && hex[2].charAt(0) === hex[2].charAt(1)
599
- && hex[3].charAt(0) === hex[3].charAt(1)) {
600
+ && hex[2].charAt(0) === hex[2].charAt(1)
601
+ && hex[3].charAt(0) === hex[3].charAt(1)) {
600
602
  return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
601
603
  }
602
604
  return hex.join('');
603
605
  }
604
606
 
605
- /**
606
- * Returns a colour object corresponding to a given number.
607
- * @param {number} color input number
608
- * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
609
- */
610
- function numberInputToObject(color) {
611
- /* eslint-disable no-bitwise */
612
- return {
613
- r: color >> 16,
614
- g: (color & 0xff00) >> 8,
615
- b: color & 0xff,
616
- };
617
- /* eslint-enable no-bitwise */
618
- }
619
-
620
607
  /**
621
608
  * Permissive string parsing. Take in a number of formats, and output an object
622
609
  * based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
623
610
  * @param {string} input colour value in any format
624
- * @returns {Record<string, (number | string)> | false} an object matching the RegExp
611
+ * @returns {Record<string, (number | string | boolean)> | false} an object matching the RegExp
625
612
  */
626
613
  function stringInputToObject(input) {
627
- let color = input.trim().toLowerCase();
614
+ let color = toLowerCase(input.trim());
628
615
  if (color.length === 0) {
629
616
  return {
630
- r: 0, g: 0, b: 0, a: 0,
617
+ r: 0, g: 0, b: 0, a: 1,
631
618
  };
632
619
  }
633
620
  let named = false;
@@ -635,11 +622,9 @@ function stringInputToObject(input) {
635
622
  color = getRGBFromName(color);
636
623
  named = true;
637
624
  } else if (nonColors.includes(color)) {
638
- const isTransparent = color === 'transparent';
639
- const rgb = isTransparent ? 0 : 255;
640
- const a = isTransparent ? 0 : 1;
625
+ const a = color === 'transparent' ? 0 : 1;
641
626
  return {
642
- r: rgb, g: rgb, b: rgb, a, format: 'rgb',
627
+ r: 0, g: 0, b: 0, a, format: 'rgb', ok: true,
643
628
  };
644
629
  }
645
630
 
@@ -679,7 +664,6 @@ function stringInputToObject(input) {
679
664
  g: parseIntFromHex(m2),
680
665
  b: parseIntFromHex(m3),
681
666
  a: convertHexToDecimal(m4),
682
- // format: named ? 'rgb' : 'hex8',
683
667
  format: named ? 'rgb' : 'hex',
684
668
  };
685
669
  }
@@ -743,6 +727,7 @@ function stringInputToObject(input) {
743
727
  function inputToRGB(input) {
744
728
  let rgb = { r: 0, g: 0, b: 0 };
745
729
  let color = input;
730
+ /** @type {string | number} */
746
731
  let a = 1;
747
732
  let s = null;
748
733
  let v = null;
@@ -753,7 +738,8 @@ function inputToRGB(input) {
753
738
  let r = null;
754
739
  let g = null;
755
740
  let ok = false;
756
- let format = 'hex';
741
+ const inputFormat = typeof color === 'object' && color.format;
742
+ let format = inputFormat && COLOR_FORMAT.includes(inputFormat) ? inputFormat : 'rgb';
757
743
 
758
744
  if (typeof input === 'string') {
759
745
  // @ts-ignore -- this now is converted to object
@@ -794,14 +780,17 @@ function inputToRGB(input) {
794
780
  format = 'hwb';
795
781
  }
796
782
  if (isValidCSSUnit(color.a)) {
797
- a = color.a;
798
- a = isPercentage(`${a}`) ? bound01(a, 100) : a;
783
+ a = color.a; // @ts-ignore -- `parseFloat` works with numbers too
784
+ a = isPercentage(`${a}`) || parseFloat(a) > 1 ? bound01(a, 100) : a;
799
785
  }
800
786
  }
787
+ if (typeof color === 'undefined') {
788
+ ok = true;
789
+ }
801
790
 
802
791
  return {
803
- ok, // @ts-ignore
804
- format: color.format || format,
792
+ ok,
793
+ format,
805
794
  r: Math.min(255, Math.max(rgb.r, 0)),
806
795
  g: Math.min(255, Math.max(rgb.g, 0)),
807
796
  b: Math.min(255, Math.max(rgb.b, 0)),
@@ -830,7 +819,8 @@ class Color {
830
819
  color = inputToRGB(color);
831
820
  }
832
821
  if (typeof color === 'number') {
833
- color = numberInputToObject(color);
822
+ const len = `${color}`.length;
823
+ color = `#${(len === 2 ? '0' : '00')}${color}`;
834
824
  }
835
825
  const {
836
826
  r, g, b, a, ok, format,
@@ -840,7 +830,7 @@ class Color {
840
830
  const self = this;
841
831
 
842
832
  /** @type {CP.ColorInput} */
843
- self.originalInput = color;
833
+ self.originalInput = input;
844
834
  /** @type {number} */
845
835
  self.r = r;
846
836
  /** @type {number} */
@@ -1230,6 +1220,7 @@ ObjectAssign(Color, {
1230
1220
  isOnePointZero,
1231
1221
  isPercentage,
1232
1222
  isValidCSSUnit,
1223
+ isColorName,
1233
1224
  pad2,
1234
1225
  clamp01,
1235
1226
  bound01,
@@ -1247,10 +1238,11 @@ ObjectAssign(Color, {
1247
1238
  hueToRgb,
1248
1239
  hwbToRgb,
1249
1240
  parseIntFromHex,
1250
- numberInputToObject,
1251
1241
  stringInputToObject,
1252
1242
  inputToRGB,
1253
1243
  roundPart,
1244
+ getElementStyle,
1245
+ setElementStyle,
1254
1246
  ObjectAssign,
1255
1247
  });
1256
1248
 
@@ -1380,24 +1372,6 @@ const ariaValueText = 'aria-valuetext';
1380
1372
  */
1381
1373
  const ariaValueNow = 'aria-valuenow';
1382
1374
 
1383
- /**
1384
- * A global namespace for aria-haspopup.
1385
- * @type {string}
1386
- */
1387
- const ariaHasPopup = 'aria-haspopup';
1388
-
1389
- /**
1390
- * A global namespace for aria-hidden.
1391
- * @type {string}
1392
- */
1393
- const ariaHidden = 'aria-hidden';
1394
-
1395
- /**
1396
- * A global namespace for aria-labelledby.
1397
- * @type {string}
1398
- */
1399
- const ariaLabelledBy = 'aria-labelledby';
1400
-
1401
1375
  /**
1402
1376
  * A global namespace for `ArrowDown` key.
1403
1377
  * @type {string} e.which = 40 equivalent
@@ -1524,37 +1498,6 @@ const resizeEvent = 'resize';
1524
1498
  */
1525
1499
  const focusoutEvent = 'focusout';
1526
1500
 
1527
- // @ts-ignore
1528
- const { userAgentData: uaDATA } = navigator;
1529
-
1530
- /**
1531
- * A global namespace for `userAgentData` object.
1532
- */
1533
- const userAgentData = uaDATA;
1534
-
1535
- const { userAgent: userAgentString } = navigator;
1536
-
1537
- /**
1538
- * A global namespace for `navigator.userAgent` string.
1539
- */
1540
- const userAgent = userAgentString;
1541
-
1542
- const mobileBrands = /iPhone|iPad|iPod|Android/i;
1543
- let isMobileCheck = false;
1544
-
1545
- if (userAgentData) {
1546
- isMobileCheck = userAgentData.brands
1547
- .some((/** @type {Record<String, any>} */x) => mobileBrands.test(x.brand));
1548
- } else {
1549
- isMobileCheck = mobileBrands.test(userAgent);
1550
- }
1551
-
1552
- /**
1553
- * A global `boolean` for mobile detection.
1554
- * @type {boolean}
1555
- */
1556
- const isMobile = isMobileCheck;
1557
-
1558
1501
  /**
1559
1502
  * Returns the `document.documentElement` or the `<html>` element.
1560
1503
  *
@@ -1739,30 +1682,6 @@ function getElementsByClassName(selector, parent) {
1739
1682
  return lookUp.getElementsByClassName(selector);
1740
1683
  }
1741
1684
 
1742
- /**
1743
- * This is a shortie for `document.createElementNS` method
1744
- * which allows you to create a new `HTMLElement` for a given `tagName`
1745
- * or based on an object with specific non-readonly attributes:
1746
- * `id`, `className`, `textContent`, `style`, etc.
1747
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
1748
- *
1749
- * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
1750
- * @param {Record<string, string> | string} param `tagName` or object
1751
- * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
1752
- */
1753
- function createElementNS(namespace, param) {
1754
- if (typeof param === 'string') {
1755
- return getDocument().createElementNS(namespace, param);
1756
- }
1757
-
1758
- const { tagName } = param;
1759
- const attr = { ...param };
1760
- const newElement = createElementNS(namespace, tagName);
1761
- delete attr.tagName;
1762
- ObjectAssign(newElement, attr);
1763
- return newElement;
1764
- }
1765
-
1766
1685
  /**
1767
1686
  * Shortcut for the `Element.dispatchEvent(Event)` method.
1768
1687
  *
@@ -1880,12 +1799,11 @@ function normalizeValue(value) {
1880
1799
  }
1881
1800
 
1882
1801
  /**
1883
- * Shortcut for `String.toLowerCase()`.
1884
- *
1885
- * @param {string} source input string
1886
- * @returns {string} lowercase output string
1802
+ * Shortcut for `Object.keys()` static method.
1803
+ * @param {Record<string, any>} obj a target object
1804
+ * @returns {string[]}
1887
1805
  */
1888
- const toLowerCase = (source) => source.toLowerCase();
1806
+ const ObjectKeys = (obj) => Object.keys(obj);
1889
1807
 
1890
1808
  /**
1891
1809
  * Utility to normalize component options.
@@ -1990,6 +1908,80 @@ function removeClass(element, classNAME) {
1990
1908
  */
1991
1909
  const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
1992
1910
 
1911
+ /**
1912
+ * @class
1913
+ * Returns a color palette with a given set of parameters.
1914
+ * @example
1915
+ * new ColorPalette(0, 12, 10);
1916
+ * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: Array<Color> }
1917
+ */
1918
+ class ColorPalette {
1919
+ /**
1920
+ * The `hue` parameter is optional, which would be set to 0.
1921
+ * @param {number[]} args represeinting hue, hueSteps, lightSteps
1922
+ * * `args.hue` the starting Hue [0, 360]
1923
+ * * `args.hueSteps` Hue Steps Count [5, 24]
1924
+ * * `args.lightSteps` Lightness Steps Count [5, 12]
1925
+ */
1926
+ constructor(...args) {
1927
+ let hue = 0;
1928
+ let hueSteps = 12;
1929
+ let lightSteps = 10;
1930
+ let lightnessArray = [0.5];
1931
+
1932
+ if (args.length === 3) {
1933
+ [hue, hueSteps, lightSteps] = args;
1934
+ } else if (args.length === 2) {
1935
+ [hueSteps, lightSteps] = args;
1936
+ if ([hueSteps, lightSteps].some((n) => n < 1)) {
1937
+ throw TypeError('ColorPalette: when 2 arguments used, both must be larger than 0.');
1938
+ }
1939
+ } else {
1940
+ throw TypeError('ColorPalette requires minimum 2 arguments');
1941
+ }
1942
+
1943
+ /** @type {Color[]} */
1944
+ const colors = [];
1945
+
1946
+ const hueStep = 360 / hueSteps;
1947
+ const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
1948
+ const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
1949
+
1950
+ let lightStep = 0.25;
1951
+ lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
1952
+ lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
1953
+ lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
1954
+ lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
1955
+ lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
1956
+ lightStep = lightSteps > 13 ? estimatedStep : lightStep;
1957
+
1958
+ // light tints
1959
+ for (let i = 1; i < half + 1; i += 1) {
1960
+ lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
1961
+ }
1962
+
1963
+ // dark tints
1964
+ for (let i = 1; i < lightSteps - half; i += 1) {
1965
+ lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
1966
+ }
1967
+
1968
+ // feed `colors` Array
1969
+ for (let i = 0; i < hueSteps; i += 1) {
1970
+ const currentHue = ((hue + i * hueStep) % 360) / 360;
1971
+ lightnessArray.forEach((l) => {
1972
+ colors.push(new Color({ h: currentHue, s: 1, l }));
1973
+ });
1974
+ }
1975
+
1976
+ this.hue = hue;
1977
+ this.hueSteps = hueSteps;
1978
+ this.lightSteps = lightSteps;
1979
+ this.colors = colors;
1980
+ }
1981
+ }
1982
+
1983
+ ObjectAssign(ColorPalette, { Color });
1984
+
1993
1985
  /** @type {Record<string, string>} */
1994
1986
  const colorPickerLabels = {
1995
1987
  pickerLabel: 'Colour Picker',
@@ -2017,6 +2009,22 @@ const colorPickerLabels = {
2017
2009
  */
2018
2010
  const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
2019
2011
 
2012
+ const tabIndex = 'tabindex';
2013
+
2014
+ /**
2015
+ * Check if a string is valid JSON string.
2016
+ * @param {string} str the string input
2017
+ * @returns {boolean} the query result
2018
+ */
2019
+ function isValidJSON(str) {
2020
+ try {
2021
+ JSON.parse(str);
2022
+ } catch (e) {
2023
+ return false;
2024
+ }
2025
+ return true;
2026
+ }
2027
+
2020
2028
  /**
2021
2029
  * Shortcut for `String.toUpperCase()`.
2022
2030
  *
@@ -2025,6 +2033,48 @@ const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold',
2025
2033
  */
2026
2034
  const toUpperCase = (source) => source.toUpperCase();
2027
2035
 
2036
+ /**
2037
+ * A global namespace for aria-haspopup.
2038
+ * @type {string}
2039
+ */
2040
+ const ariaHasPopup = 'aria-haspopup';
2041
+
2042
+ /**
2043
+ * A global namespace for aria-hidden.
2044
+ * @type {string}
2045
+ */
2046
+ const ariaHidden = 'aria-hidden';
2047
+
2048
+ /**
2049
+ * A global namespace for aria-labelledby.
2050
+ * @type {string}
2051
+ */
2052
+ const ariaLabelledBy = 'aria-labelledby';
2053
+
2054
+ /**
2055
+ * This is a shortie for `document.createElementNS` method
2056
+ * which allows you to create a new `HTMLElement` for a given `tagName`
2057
+ * or based on an object with specific non-readonly attributes:
2058
+ * `id`, `className`, `textContent`, `style`, etc.
2059
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
2060
+ *
2061
+ * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
2062
+ * @param {Record<string, string> | string} param `tagName` or object
2063
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
2064
+ */
2065
+ function createElementNS(namespace, param) {
2066
+ if (typeof param === 'string') {
2067
+ return getDocument().createElementNS(namespace, param);
2068
+ }
2069
+
2070
+ const { tagName } = param;
2071
+ const attr = { ...param };
2072
+ const newElement = createElementNS(namespace, tagName);
2073
+ delete attr.tagName;
2074
+ ObjectAssign(newElement, attr);
2075
+ return newElement;
2076
+ }
2077
+
2028
2078
  const vHidden = 'v-hidden';
2029
2079
 
2030
2080
  /**
@@ -2104,8 +2154,6 @@ const ariaValueMin = 'aria-valuemin';
2104
2154
  */
2105
2155
  const ariaValueMax = 'aria-valuemax';
2106
2156
 
2107
- const tabIndex = 'tabindex';
2108
-
2109
2157
  /**
2110
2158
  * Returns all color controls for `ColorPicker`.
2111
2159
  *
@@ -2213,75 +2261,6 @@ function setCSSProperties(element, props) {
2213
2261
  });
2214
2262
  }
2215
2263
 
2216
- /**
2217
- * @class
2218
- * Returns a color palette with a given set of parameters.
2219
- * @example
2220
- * new ColorPalette(0, 12, 10);
2221
- * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
2222
- */
2223
- class ColorPalette {
2224
- /**
2225
- * The `hue` parameter is optional, which would be set to 0.
2226
- * @param {number[]} args represeinting hue, hueSteps, lightSteps
2227
- * * `args.hue` the starting Hue [0, 360]
2228
- * * `args.hueSteps` Hue Steps Count [5, 24]
2229
- * * `args.lightSteps` Lightness Steps Count [5, 12]
2230
- */
2231
- constructor(...args) {
2232
- let hue = 0;
2233
- let hueSteps = 12;
2234
- let lightSteps = 10;
2235
- let lightnessArray = [0.5];
2236
-
2237
- if (args.length === 3) {
2238
- [hue, hueSteps, lightSteps] = args;
2239
- } else if (args.length === 2) {
2240
- [hueSteps, lightSteps] = args;
2241
- } else {
2242
- throw TypeError('ColorPalette requires minimum 2 arguments');
2243
- }
2244
-
2245
- /** @type {string[]} */
2246
- const colors = [];
2247
-
2248
- const hueStep = 360 / hueSteps;
2249
- const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
2250
- const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
2251
-
2252
- let lightStep = 0.25;
2253
- lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
2254
- lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
2255
- lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
2256
- lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
2257
- lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
2258
- lightStep = lightSteps > 13 ? estimatedStep : lightStep;
2259
-
2260
- // light tints
2261
- for (let i = 1; i < half + 1; i += 1) {
2262
- lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
2263
- }
2264
-
2265
- // dark tints
2266
- for (let i = 1; i < lightSteps - half; i += 1) {
2267
- lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
2268
- }
2269
-
2270
- // feed `colors` Array
2271
- for (let i = 0; i < hueSteps; i += 1) {
2272
- const currentHue = ((hue + i * hueStep) % 360) / 360;
2273
- lightnessArray.forEach((l) => {
2274
- colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
2275
- });
2276
- }
2277
-
2278
- this.hue = hue;
2279
- this.hueSteps = hueSteps;
2280
- this.lightSteps = lightSteps;
2281
- this.colors = colors;
2282
- }
2283
- }
2284
-
2285
2264
  /**
2286
2265
  * Returns a color-defaults with given values and class.
2287
2266
  * @param {CP.ColorPicker} self
@@ -2315,7 +2294,8 @@ function getColorMenu(self, colorsSource, menuClass) {
2315
2294
  optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2316
2295
  const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2317
2296
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2318
-
2297
+ /** @type {HTMLUListElement} */
2298
+ // @ts-ignore -- <UL> is an `HTMLElement`
2319
2299
  const menu = createElement({
2320
2300
  tagName: 'ul',
2321
2301
  className: finalClass,
@@ -2323,7 +2303,7 @@ function getColorMenu(self, colorsSource, menuClass) {
2323
2303
  setAttribute(menu, 'role', 'listbox');
2324
2304
  setAttribute(menu, ariaLabel, menuLabel);
2325
2305
 
2326
- if (isScrollable) { // @ts-ignore
2306
+ if (isScrollable) {
2327
2307
  setCSSProperties(menu, {
2328
2308
  '--grid-item-size': `${optionSize}rem`,
2329
2309
  '--grid-fit': fit,
@@ -2334,15 +2314,19 @@ function getColorMenu(self, colorsSource, menuClass) {
2334
2314
  }
2335
2315
 
2336
2316
  colorsArray.forEach((x) => {
2337
- const [value, label] = x.trim().split(':');
2338
- const xRealColor = new Color(value, format).toString();
2339
- const isActive = xRealColor === getAttribute(input, 'value');
2317
+ let [value, label] = typeof x === 'string' ? x.trim().split(':') : [];
2318
+ if (x instanceof Color) {
2319
+ value = x.toHexString();
2320
+ label = value;
2321
+ }
2322
+ const color = new Color(x instanceof Color ? x : value, format);
2323
+ const isActive = color.toString() === getAttribute(input, 'value');
2340
2324
  const active = isActive ? ' active' : '';
2341
2325
 
2342
2326
  const option = createElement({
2343
2327
  tagName: 'li',
2344
2328
  className: `color-option${active}`,
2345
- innerText: `${label || x}`,
2329
+ innerText: `${label || value}`,
2346
2330
  });
2347
2331
 
2348
2332
  setAttribute(option, tabIndex, '0');
@@ -2351,7 +2335,7 @@ function getColorMenu(self, colorsSource, menuClass) {
2351
2335
  setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2352
2336
 
2353
2337
  if (isOptionsMenu) {
2354
- setElementStyle(option, { backgroundColor: x });
2338
+ setElementStyle(option, { backgroundColor: value });
2355
2339
  }
2356
2340
 
2357
2341
  menu.append(option);
@@ -2360,55 +2344,10 @@ function getColorMenu(self, colorsSource, menuClass) {
2360
2344
  }
2361
2345
 
2362
2346
  /**
2363
- * Check if a string is valid JSON string.
2364
- * @param {string} str the string input
2365
- * @returns {boolean} the query result
2366
- */
2367
- function isValidJSON(str) {
2368
- try {
2369
- JSON.parse(str);
2370
- } catch (e) {
2371
- return false;
2372
- }
2373
- return true;
2374
- }
2375
-
2376
- var version = "0.0.1";
2377
-
2378
- // @ts-ignore
2379
-
2380
- const Version = version;
2381
-
2382
- // ColorPicker GC
2383
- // ==============
2384
- const colorPickerString = 'color-picker';
2385
- const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2386
- const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2387
- const colorPickerDefaults = {
2388
- componentLabels: colorPickerLabels,
2389
- colorLabels: colorNames,
2390
- format: 'rgb',
2391
- colorPresets: false,
2392
- colorKeywords: false,
2393
- };
2394
-
2395
- // ColorPicker Static Methods
2396
- // ==========================
2397
-
2398
- /** @type {CP.GetInstance<ColorPicker>} */
2399
- const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2400
-
2401
- /** @type {CP.InitCallback<ColorPicker>} */
2402
- const initColorPicker = (element) => new ColorPicker(element);
2403
-
2404
- // ColorPicker Private Methods
2405
- // ===========================
2406
-
2407
- /**
2408
- * Generate HTML markup and update instance properties.
2409
- * @param {ColorPicker} self
2410
- */
2411
- function initCallback(self) {
2347
+ * Generate HTML markup and update instance properties.
2348
+ * @param {CP.ColorPicker} self
2349
+ */
2350
+ function setMarkup(self) {
2412
2351
  const {
2413
2352
  input, parent, format, id, componentLabels, colorKeywords, colorPresets,
2414
2353
  } = self;
@@ -2423,9 +2362,7 @@ function initCallback(self) {
2423
2362
  self.color = new Color(color, format);
2424
2363
 
2425
2364
  // set initial controls dimensions
2426
- // make the controls smaller on mobile
2427
- const dropClass = isMobile ? ' mobile' : '';
2428
- const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
2365
+ const formatString = format === 'hex' ? hexLabel : toUpperCase(format);
2429
2366
 
2430
2367
  const pickerBtn = createElement({
2431
2368
  id: `picker-btn-${id}`,
@@ -2442,7 +2379,7 @@ function initCallback(self) {
2442
2379
 
2443
2380
  const pickerDropdown = createElement({
2444
2381
  tagName: 'div',
2445
- className: `color-dropdown picker${dropClass}`,
2382
+ className: 'color-dropdown picker',
2446
2383
  });
2447
2384
  setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
2448
2385
  setAttribute(pickerDropdown, 'role', 'group');
@@ -2458,7 +2395,7 @@ function initCallback(self) {
2458
2395
  if (colorKeywords || colorPresets) {
2459
2396
  const presetsDropdown = createElement({
2460
2397
  tagName: 'div',
2461
- className: `color-dropdown scrollable menu${dropClass}`,
2398
+ className: 'color-dropdown scrollable menu',
2462
2399
  });
2463
2400
 
2464
2401
  // color presets
@@ -2508,6 +2445,37 @@ function initCallback(self) {
2508
2445
  setAttribute(input, tabIndex, '-1');
2509
2446
  }
2510
2447
 
2448
+ var version = "0.0.2alpha1";
2449
+
2450
+ // @ts-ignore
2451
+
2452
+ const Version = version;
2453
+
2454
+ // ColorPicker GC
2455
+ // ==============
2456
+ const colorPickerString = 'color-picker';
2457
+ const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2458
+ const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2459
+ const colorPickerDefaults = {
2460
+ componentLabels: colorPickerLabels,
2461
+ colorLabels: colorNames,
2462
+ format: 'rgb',
2463
+ colorPresets: false,
2464
+ colorKeywords: false,
2465
+ };
2466
+
2467
+ // ColorPicker Static Methods
2468
+ // ==========================
2469
+
2470
+ /** @type {CP.GetInstance<ColorPicker>} */
2471
+ const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2472
+
2473
+ /** @type {CP.InitCallback<ColorPicker>} */
2474
+ const initColorPicker = (element) => new ColorPicker(element);
2475
+
2476
+ // ColorPicker Private Methods
2477
+ // ===========================
2478
+
2511
2479
  /**
2512
2480
  * Add / remove `ColorPicker` main event listeners.
2513
2481
  * @param {ColorPicker} self
@@ -2741,7 +2709,7 @@ class ColorPicker {
2741
2709
  self.handleKnobs = self.handleKnobs.bind(self);
2742
2710
 
2743
2711
  // generate markup
2744
- initCallback(self);
2712
+ setMarkup(self);
2745
2713
 
2746
2714
  const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
2747
2715
  // set main elements
@@ -2937,7 +2905,7 @@ class ColorPicker {
2937
2905
  const self = this;
2938
2906
  const { activeElement } = getDocument(self.input);
2939
2907
 
2940
- if ((isMobile && self.dragElement)
2908
+ if ((e.type === touchmoveEvent && self.dragElement)
2941
2909
  || (activeElement && self.controlKnobs.includes(activeElement))) {
2942
2910
  e.stopPropagation();
2943
2911
  e.preventDefault();
@@ -3848,4 +3816,4 @@ ObjectAssign(ColorPickerElement, {
3848
3816
 
3849
3817
  customElements.define('color-picker', ColorPickerElement);
3850
3818
 
3851
- export default ColorPickerElement;
3819
+ export { ColorPickerElement as default };