@thednp/color-picker 0.0.1 → 0.0.2-alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * ColorPickerElement v0.0.1 (http://thednp.github.io/color-picker)
2
+ * ColorPickerElement v0.0.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 };