@thednp/color-picker 0.0.1-alpha2 → 0.0.1-alpha3

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.1alpha2 (http://thednp.github.io/color-picker)
2
+ * ColorPickerElement v0.0.1alpha3 (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
  */
@@ -116,19 +116,36 @@
116
116
  return property in computedStyle ? computedStyle[property] : '';
117
117
  }
118
118
 
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
+
119
126
  /**
120
127
  * Shortcut for multiple uses of `HTMLElement.style.propertyName` method.
121
128
  * @param {HTMLElement | Element} element target element
122
129
  * @param {Partial<CSSStyleDeclaration>} styles attribute value
123
130
  */
124
131
  // @ts-ignore
125
- const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
132
+ const setElementStyle = (element, styles) => ObjectAssign(element.style, styles);
126
133
 
127
134
  /**
128
135
  * A list of explicit default non-color values.
129
136
  */
130
137
  const nonColors = ['transparent', 'currentColor', 'inherit', 'revert', 'initial'];
131
138
 
139
+ /**
140
+ * Round colour components, for all formats except HEX.
141
+ * @param {number} v one of the colour components
142
+ * @returns {number} the rounded number
143
+ */
144
+ function roundPart(v) {
145
+ const floor = Math.floor(v);
146
+ return v - floor < 0.5 ? floor : Math.round(v);
147
+ }
148
+
132
149
  // Color supported formats
133
150
  const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
134
151
 
@@ -296,7 +313,7 @@
296
313
  * @returns {string} - the hexadecimal value
297
314
  */
298
315
  function convertDecimalToHex(d) {
299
- return Math.round(d * 255).toString(16);
316
+ return roundPart(d * 255).toString(16);
300
317
  }
301
318
 
302
319
  /**
@@ -548,9 +565,9 @@
548
565
  */
549
566
  function rgbToHex(r, g, b, allow3Char) {
550
567
  const hex = [
551
- pad2(Math.round(r).toString(16)),
552
- pad2(Math.round(g).toString(16)),
553
- pad2(Math.round(b).toString(16)),
568
+ pad2(roundPart(r).toString(16)),
569
+ pad2(roundPart(g).toString(16)),
570
+ pad2(roundPart(b).toString(16)),
554
571
  ];
555
572
 
556
573
  // Return a 3 character hex if possible
@@ -575,9 +592,9 @@
575
592
  */
576
593
  function rgbaToHex(r, g, b, a, allow4Char) {
577
594
  const hex = [
578
- pad2(Math.round(r).toString(16)),
579
- pad2(Math.round(g).toString(16)),
580
- pad2(Math.round(b).toString(16)),
595
+ pad2(roundPart(r).toString(16)),
596
+ pad2(roundPart(g).toString(16)),
597
+ pad2(roundPart(b).toString(16)),
581
598
  pad2(convertDecimalToHex(a)),
582
599
  ];
583
600
 
@@ -739,6 +756,8 @@
739
756
  let w = null;
740
757
  let b = null;
741
758
  let h = null;
759
+ let r = null;
760
+ let g = null;
742
761
  let ok = false;
743
762
  let format = 'hex';
744
763
 
@@ -749,7 +768,10 @@
749
768
  }
750
769
  if (typeof color === 'object') {
751
770
  if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
752
- rgb = { r: color.r, g: color.g, b: color.b }; // RGB values in [0, 255] range
771
+ ({ r, g, b } = color);
772
+ [r, g, b] = [...[r, g, b]]
773
+ .map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255).map(roundPart);
774
+ rgb = { r, g, b }; // RGB values now are all in [0, 255] range
753
775
  ok = true;
754
776
  format = 'rgb';
755
777
  } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
@@ -837,14 +859,6 @@
837
859
  self.ok = ok;
838
860
  /** @type {CP.ColorFormats} */
839
861
  self.format = configFormat || format;
840
-
841
- // Don't let the range of [0,255] come back in [0,1].
842
- // Potentially lose a little bit of precision here, but will fix issues where
843
- // .5 gets interpreted as half of the total, instead of half of 1
844
- // If it was supposed to be 128, this was already taken care of by `inputToRgb`
845
- if (r < 1) self.r = Math.round(r);
846
- if (g < 1) self.g = Math.round(g);
847
- if (b < 1) self.b = Math.round(b);
848
862
  }
849
863
 
850
864
  /**
@@ -860,7 +874,7 @@
860
874
  * @returns {boolean} the query result
861
875
  */
862
876
  get isDark() {
863
- return this.brightness < 128;
877
+ return this.brightness < 120;
864
878
  }
865
879
 
866
880
  /**
@@ -912,13 +926,13 @@
912
926
  const {
913
927
  r, g, b, a,
914
928
  } = this;
915
- const [R, G, B] = [r, g, b].map((x) => Math.round(x));
929
+ const [R, G, B] = [r, g, b].map((x) => roundPart(x));
916
930
 
917
931
  return {
918
932
  r: R,
919
933
  g: G,
920
934
  b: B,
921
- a: Math.round(a * 100) / 100,
935
+ a: roundPart(a * 100) / 100,
922
936
  };
923
937
  }
924
938
 
@@ -948,7 +962,7 @@
948
962
  const {
949
963
  r, g, b, a,
950
964
  } = this.toRgb();
951
- const A = a === 1 ? '' : ` / ${Math.round(a * 100)}%`;
965
+ const A = a === 1 ? '' : ` / ${roundPart(a * 100)}%`;
952
966
 
953
967
  return `rgb(${r} ${g} ${b}${A})`;
954
968
  }
@@ -1043,10 +1057,10 @@
1043
1057
  let {
1044
1058
  h, s, l, a,
1045
1059
  } = this.toHsl();
1046
- h = Math.round(h * 360);
1047
- s = Math.round(s * 100);
1048
- l = Math.round(l * 100);
1049
- a = Math.round(a * 100) / 100;
1060
+ h = roundPart(h * 360);
1061
+ s = roundPart(s * 100);
1062
+ l = roundPart(l * 100);
1063
+ a = roundPart(a * 100) / 100;
1050
1064
 
1051
1065
  return a === 1
1052
1066
  ? `hsl(${h}, ${s}%, ${l}%)`
@@ -1063,11 +1077,11 @@
1063
1077
  let {
1064
1078
  h, s, l, a,
1065
1079
  } = this.toHsl();
1066
- h = Math.round(h * 360);
1067
- s = Math.round(s * 100);
1068
- l = Math.round(l * 100);
1069
- a = Math.round(a * 100);
1070
- const A = a < 100 ? ` / ${Math.round(a)}%` : '';
1080
+ h = roundPart(h * 360);
1081
+ s = roundPart(s * 100);
1082
+ l = roundPart(l * 100);
1083
+ a = roundPart(a * 100);
1084
+ const A = a < 100 ? ` / ${roundPart(a)}%` : '';
1071
1085
 
1072
1086
  return `hsl(${h}deg ${s}% ${l}%${A})`;
1073
1087
  }
@@ -1094,11 +1108,11 @@
1094
1108
  let {
1095
1109
  h, w, b, a,
1096
1110
  } = this.toHwb();
1097
- h = Math.round(h * 360);
1098
- w = Math.round(w * 100);
1099
- b = Math.round(b * 100);
1100
- a = Math.round(a * 100);
1101
- const A = a < 100 ? ` / ${Math.round(a)}%` : '';
1111
+ h = roundPart(h * 360);
1112
+ w = roundPart(w * 100);
1113
+ b = roundPart(b * 100);
1114
+ a = roundPart(a * 100);
1115
+ const A = a < 100 ? ` / ${roundPart(a)}%` : '';
1102
1116
 
1103
1117
  return `hwb(${h}deg ${w}% ${b}%${A})`;
1104
1118
  }
@@ -1244,6 +1258,7 @@
1244
1258
  numberInputToObject,
1245
1259
  stringInputToObject,
1246
1260
  inputToRGB,
1261
+ roundPart,
1247
1262
  ObjectAssign,
1248
1263
  });
1249
1264
 
@@ -1872,13 +1887,6 @@
1872
1887
  return value;
1873
1888
  }
1874
1889
 
1875
- /**
1876
- * Shortcut for `Object.keys()` static method.
1877
- * @param {Record<string, any>} obj a target object
1878
- * @returns {string[]}
1879
- */
1880
- const ObjectKeys = (obj) => Object.keys(obj);
1881
-
1882
1890
  /**
1883
1891
  * Shortcut for `String.toLowerCase()`.
1884
1892
  *
@@ -2081,7 +2089,6 @@
2081
2089
  max,
2082
2090
  step,
2083
2091
  });
2084
- // }
2085
2092
  colorForm.append(cInputLabel, cInput);
2086
2093
  });
2087
2094
  return colorForm;
@@ -2105,6 +2112,8 @@
2105
2112
  */
2106
2113
  const ariaValueMax = 'aria-valuemax';
2107
2114
 
2115
+ const tabIndex = 'tabindex';
2116
+
2108
2117
  /**
2109
2118
  * Returns all color controls for `ColorPicker`.
2110
2119
  *
@@ -2170,10 +2179,8 @@
2170
2179
  const {
2171
2180
  i, c, l, min, max,
2172
2181
  } = template;
2173
- // const hidden = i === 2 && format === 'hwb' ? ' v-hidden' : '';
2174
2182
  const control = createElement({
2175
2183
  tagName: 'div',
2176
- // className: `color-control${hidden}`,
2177
2184
  className: 'color-control',
2178
2185
  });
2179
2186
  setAttribute(control, 'role', 'presentation');
@@ -2193,7 +2200,7 @@
2193
2200
 
2194
2201
  setAttribute(knob, ariaLabel, l);
2195
2202
  setAttribute(knob, 'role', 'slider');
2196
- setAttribute(knob, 'tabindex', '0');
2203
+ setAttribute(knob, tabIndex, '0');
2197
2204
  setAttribute(knob, ariaValueMin, `${min}`);
2198
2205
  setAttribute(knob, ariaValueMax, `${max}`);
2199
2206
  control.append(knob);
@@ -2203,6 +2210,17 @@
2203
2210
  return colorControls;
2204
2211
  }
2205
2212
 
2213
+ /**
2214
+ * Helps setting CSS variables to the color-menu.
2215
+ * @param {HTMLElement} element
2216
+ * @param {Record<string,any>} props
2217
+ */
2218
+ function setCSSProperties(element, props) {
2219
+ ObjectKeys(props).forEach((prop) => {
2220
+ element.style.setProperty(prop, props[prop]);
2221
+ });
2222
+ }
2223
+
2206
2224
  /**
2207
2225
  * @class
2208
2226
  * Returns a color palette with a given set of parameters.
@@ -2215,8 +2233,8 @@
2215
2233
  * The `hue` parameter is optional, which would be set to 0.
2216
2234
  * @param {number[]} args represeinting hue, hueSteps, lightSteps
2217
2235
  * * `args.hue` the starting Hue [0, 360]
2218
- * * `args.hueSteps` Hue Steps Count [5, 13]
2219
- * * `args.lightSteps` Lightness Steps Count [8, 10]
2236
+ * * `args.hueSteps` Hue Steps Count [5, 24]
2237
+ * * `args.lightSteps` Lightness Steps Count [5, 12]
2220
2238
  */
2221
2239
  constructor(...args) {
2222
2240
  let hue = 0;
@@ -2229,24 +2247,32 @@
2229
2247
  } else if (args.length === 2) {
2230
2248
  [hueSteps, lightSteps] = args;
2231
2249
  } else {
2232
- throw TypeError('The ColorPalette requires minimum 2 arguments');
2250
+ throw TypeError('ColorPalette requires minimum 2 arguments');
2233
2251
  }
2234
2252
 
2235
2253
  /** @type {string[]} */
2236
2254
  const colors = [];
2237
2255
 
2238
2256
  const hueStep = 360 / hueSteps;
2239
- const lightStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
2240
- const half = Math.round((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
2257
+ const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
2258
+ const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
2259
+
2260
+ let lightStep = 0.25;
2261
+ lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
2262
+ lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
2263
+ lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
2264
+ lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
2265
+ lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
2266
+ lightStep = lightSteps > 13 ? estimatedStep : lightStep;
2241
2267
 
2242
2268
  // light tints
2243
- for (let i = 0; i < half; i += 1) {
2244
- lightnessArray = [...lightnessArray, (0.5 + lightStep * (i + 1))];
2269
+ for (let i = 1; i < half + 1; i += 1) {
2270
+ lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
2245
2271
  }
2246
2272
 
2247
2273
  // dark tints
2248
- for (let i = 0; i < lightSteps - half - 1; i += 1) {
2249
- lightnessArray = [(0.5 - lightStep * (i + 1)), ...lightnessArray];
2274
+ for (let i = 1; i < lightSteps - half; i += 1) {
2275
+ lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
2250
2276
  }
2251
2277
 
2252
2278
  // feed `colors` Array
@@ -2281,45 +2307,38 @@
2281
2307
  colorsArray = colorsArray instanceof Array ? colorsArray : [];
2282
2308
  const colorsCount = colorsArray.length;
2283
2309
  const { lightSteps } = isPalette ? colorsSource : { lightSteps: null };
2284
- let fit = lightSteps
2285
- || Math.max(...[5, 6, 7, 8, 9, 10].filter((x) => colorsCount > (x * 2) && !(colorsCount % x)));
2286
- fit = Number.isFinite(fit) ? fit : 5;
2310
+ const fit = lightSteps || [9, 10].find((x) => colorsCount > x * 2 && !(colorsCount % x)) || 5;
2287
2311
  const isMultiLine = isOptionsMenu && colorsCount > fit;
2288
- let rowCountHover = 1;
2289
- rowCountHover = isMultiLine && colorsCount < 27 ? 2 : rowCountHover;
2290
- rowCountHover = colorsCount >= 27 ? 3 : rowCountHover;
2291
- rowCountHover = colorsCount >= 36 ? 4 : rowCountHover;
2292
- rowCountHover = colorsCount >= 45 ? 5 : rowCountHover;
2293
- const rowCount = rowCountHover - (colorsCount < 27 ? 1 : 2);
2294
- const isScrollable = isMultiLine && colorsCount > rowCountHover * fit;
2312
+ let rowCountHover = 2;
2313
+ rowCountHover = isMultiLine && colorsCount >= fit * 2 ? 3 : rowCountHover;
2314
+ rowCountHover = colorsCount >= fit * 3 ? 4 : rowCountHover;
2315
+ rowCountHover = colorsCount >= fit * 4 ? 5 : rowCountHover;
2316
+ const rowCount = rowCountHover - (colorsCount < fit * 3 ? 1 : 2);
2317
+ const isScrollable = isMultiLine && colorsCount > rowCount * fit;
2295
2318
  let finalClass = menuClass;
2296
2319
  finalClass += isScrollable ? ' scrollable' : '';
2297
2320
  finalClass += isMultiLine ? ' multiline' : '';
2298
2321
  const gap = isMultiLine ? '1px' : '0.25rem';
2299
2322
  let optionSize = isMultiLine ? 1.75 : 2;
2300
- optionSize = !(colorsCount % 10) && isMultiLine ? 1.5 : optionSize;
2323
+ optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2301
2324
  const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2302
2325
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2303
- const gridTemplateColumns = `repeat(${fit}, ${optionSize}rem)`;
2304
- const gridTemplateRows = `repeat(auto-fill, ${optionSize}rem)`;
2305
2326
 
2306
2327
  const menu = createElement({
2307
2328
  tagName: 'ul',
2308
2329
  className: finalClass,
2309
2330
  });
2310
2331
  setAttribute(menu, 'role', 'listbox');
2311
- setAttribute(menu, ariaLabel, `${menuLabel}`);
2312
-
2313
- if (isOptionsMenu) {
2314
- if (isScrollable) {
2315
- const styleText = 'this.style.height=';
2316
- setAttribute(menu, 'onmouseout', `${styleText}'${menuHeight}'`);
2317
- setAttribute(menu, 'onmouseover', `${styleText}'${menuHeightHover}'`);
2318
- }
2319
- const menuStyle = {
2320
- height: isScrollable ? menuHeight : '', gridTemplateColumns, gridTemplateRows, gap,
2321
- };
2322
- setElementStyle(menu, menuStyle);
2332
+ setAttribute(menu, ariaLabel, menuLabel);
2333
+
2334
+ if (isScrollable) { // @ts-ignore
2335
+ setCSSProperties(menu, {
2336
+ '--grid-item-size': `${optionSize}rem`,
2337
+ '--grid-fit': fit,
2338
+ '--grid-gap': gap,
2339
+ '--grid-height': menuHeight,
2340
+ '--grid-hover-height': menuHeightHover,
2341
+ });
2323
2342
  }
2324
2343
 
2325
2344
  colorsArray.forEach((x) => {
@@ -2334,15 +2353,13 @@
2334
2353
  innerText: `${label || x}`,
2335
2354
  });
2336
2355
 
2337
- setAttribute(option, 'tabindex', '0');
2356
+ setAttribute(option, tabIndex, '0');
2338
2357
  setAttribute(option, 'data-value', `${value}`);
2339
2358
  setAttribute(option, 'role', 'option');
2340
2359
  setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2341
2360
 
2342
2361
  if (isOptionsMenu) {
2343
- setElementStyle(option, {
2344
- width: `${optionSize}rem`, height: `${optionSize}rem`, backgroundColor: x,
2345
- });
2362
+ setElementStyle(option, { backgroundColor: x });
2346
2363
  }
2347
2364
 
2348
2365
  menu.append(option);
@@ -2364,7 +2381,7 @@
2364
2381
  return true;
2365
2382
  }
2366
2383
 
2367
- var version = "0.0.1alpha2";
2384
+ var version = "0.0.1alpha3";
2368
2385
 
2369
2386
  // @ts-ignore
2370
2387
 
@@ -2379,8 +2396,8 @@
2379
2396
  componentLabels: colorPickerLabels,
2380
2397
  colorLabels: colorNames,
2381
2398
  format: 'rgb',
2382
- colorPresets: undefined,
2383
- colorKeywords: nonColors,
2399
+ colorPresets: false,
2400
+ colorKeywords: false,
2384
2401
  };
2385
2402
 
2386
2403
  // ColorPicker Static Methods
@@ -2469,7 +2486,7 @@
2469
2486
  tagName: 'button',
2470
2487
  className: 'menu-toggle btn-appearance',
2471
2488
  });
2472
- setAttribute(presetsBtn, 'tabindex', '-1');
2489
+ setAttribute(presetsBtn, tabIndex, '-1');
2473
2490
  setAttribute(presetsBtn, ariaExpanded, 'false');
2474
2491
  setAttribute(presetsBtn, ariaHasPopup, 'true');
2475
2492
 
@@ -2496,7 +2513,7 @@
2496
2513
  if (colorKeywords && nonColors.includes(colorValue)) {
2497
2514
  self.value = colorValue;
2498
2515
  }
2499
- setAttribute(input, 'tabindex', '-1');
2516
+ setAttribute(input, tabIndex, '-1');
2500
2517
  }
2501
2518
 
2502
2519
  /**
@@ -2597,8 +2614,19 @@
2597
2614
  addClass(dropdown, 'bottom');
2598
2615
  reflow(dropdown);
2599
2616
  addClass(dropdown, 'show');
2617
+
2600
2618
  if (isPicker) self.update();
2601
- self.show();
2619
+
2620
+ if (!self.isOpen) {
2621
+ toggleEventsOnShown(self, true);
2622
+ self.updateDropdownPosition();
2623
+ self.isOpen = true;
2624
+ setAttribute(self.input, tabIndex, '0');
2625
+ if (menuToggle) {
2626
+ setAttribute(menuToggle, tabIndex, '0');
2627
+ }
2628
+ }
2629
+
2602
2630
  setAttribute(nextBtn, ariaExpanded, 'true');
2603
2631
  if (activeBtn) {
2604
2632
  setAttribute(activeBtn, ariaExpanded, 'false');
@@ -2768,7 +2796,7 @@
2768
2796
  set value(v) { this.input.value = v; }
2769
2797
 
2770
2798
  /** Check if the colour presets include any non-colour. */
2771
- get includeNonColor() {
2799
+ get hasNonColor() {
2772
2800
  return this.colorKeywords instanceof Array
2773
2801
  && this.colorKeywords.some((x) => nonColors.includes(x));
2774
2802
  }
@@ -2824,7 +2852,7 @@
2824
2852
  const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
2825
2853
  const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
2826
2854
  const alpha = 1 - controlPositions.c3y / offsetHeight;
2827
- const roundA = Math.round((alpha * 100)) / 100;
2855
+ const roundA = roundPart((alpha * 100)) / 100;
2828
2856
 
2829
2857
  if (format !== 'hsl') {
2830
2858
  const fill = new Color({
@@ -2842,7 +2870,7 @@
2842
2870
  });
2843
2871
  setElementStyle(v2, { background: hueGradient });
2844
2872
  } else {
2845
- const saturation = Math.round((controlPositions.c2y / offsetHeight) * 100);
2873
+ const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
2846
2874
  const fill0 = new Color({
2847
2875
  r: 255, g: 0, b: 0, a: alpha,
2848
2876
  }).saturate(-saturation).toRgbString();
@@ -2997,12 +3025,12 @@
2997
3025
 
2998
3026
  self.update();
2999
3027
 
3000
- if (currentActive) {
3001
- removeClass(currentActive, 'active');
3002
- removeAttribute(currentActive, ariaSelected);
3003
- }
3004
-
3005
3028
  if (currentActive !== target) {
3029
+ if (currentActive) {
3030
+ removeClass(currentActive, 'active');
3031
+ removeAttribute(currentActive, ariaSelected);
3032
+ }
3033
+
3006
3034
  addClass(target, 'active');
3007
3035
  setAttribute(target, ariaSelected, 'true');
3008
3036
 
@@ -3169,7 +3197,7 @@
3169
3197
  const [v1, v2, v3, v4] = format === 'rgb'
3170
3198
  ? inputs.map((i) => parseFloat(i.value) / (i === i4 ? 100 : 1))
3171
3199
  : inputs.map((i) => parseFloat(i.value) / (i !== i1 ? 100 : 360));
3172
- const isNonColorValue = self.includeNonColor && nonColors.includes(currentValue);
3200
+ const isNonColorValue = self.hasNonColor && nonColors.includes(currentValue);
3173
3201
  const alpha = i4 ? v4 : (1 - controlPositions.c3y / offsetHeight);
3174
3202
 
3175
3203
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
@@ -3428,11 +3456,11 @@
3428
3456
  } = componentLabels;
3429
3457
  const { r, g, b } = color.toRgb();
3430
3458
  const [knob1, knob2, knob3] = controlKnobs;
3431
- const hue = Math.round(hsl.h * 360);
3459
+ const hue = roundPart(hsl.h * 360);
3432
3460
  const alpha = color.a;
3433
3461
  const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
3434
- const saturation = Math.round(saturationSource * 100);
3435
- const lightness = Math.round(hsl.l * 100);
3462
+ const saturation = roundPart(saturationSource * 100);
3463
+ const lightness = roundPart(hsl.l * 100);
3436
3464
  const hsvl = hsv.v * 100;
3437
3465
  let colorName;
3438
3466
 
@@ -3479,8 +3507,8 @@
3479
3507
  setAttribute(knob2, ariaValueNow, `${saturation}`);
3480
3508
  } else if (format === 'hwb') {
3481
3509
  const { hwb } = self;
3482
- const whiteness = Math.round(hwb.w * 100);
3483
- const blackness = Math.round(hwb.b * 100);
3510
+ const whiteness = roundPart(hwb.w * 100);
3511
+ const blackness = roundPart(hwb.b * 100);
3484
3512
  colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
3485
3513
  setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3486
3514
  setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
@@ -3496,7 +3524,7 @@
3496
3524
  setAttribute(knob2, ariaValueNow, `${hue}`);
3497
3525
  }
3498
3526
 
3499
- const alphaValue = Math.round(alpha * 100);
3527
+ const alphaValue = roundPart(alpha * 100);
3500
3528
  setAttribute(knob3, ariaValueText, `${alphaValue}%`);
3501
3529
  setAttribute(knob3, ariaValueNow, `${alphaValue}`);
3502
3530
 
@@ -3519,10 +3547,14 @@
3519
3547
  /** Updates the control knobs actual positions. */
3520
3548
  updateControls() {
3521
3549
  const { controlKnobs, controlPositions } = this;
3550
+ const {
3551
+ c1x, c1y, c2y, c3y,
3552
+ } = controlPositions;
3522
3553
  const [control1, control2, control3] = controlKnobs;
3523
- setElementStyle(control1, { transform: `translate3d(${controlPositions.c1x - 4}px,${controlPositions.c1y - 4}px,0)` });
3524
- setElementStyle(control2, { transform: `translate3d(0,${controlPositions.c2y - 4}px,0)` });
3525
- setElementStyle(control3, { transform: `translate3d(0,${controlPositions.c3y - 4}px,0)` });
3554
+
3555
+ setElementStyle(control1, { transform: `translate3d(${c1x - 4}px,${c1y - 4}px,0)` });
3556
+ setElementStyle(control2, { transform: `translate3d(0,${c2y - 4}px,0)` });
3557
+ setElementStyle(control3, { transform: `translate3d(0,${c3y - 4}px,0)` });
3526
3558
  }
3527
3559
 
3528
3560
  /**
@@ -3535,16 +3567,16 @@
3535
3567
  value: oldColor, format, inputs, color, hsl,
3536
3568
  } = self;
3537
3569
  const [i1, i2, i3, i4] = inputs;
3538
- const alpha = Math.round(color.a * 100);
3539
- const hue = Math.round(hsl.h * 360);
3570
+ const alpha = roundPart(color.a * 100);
3571
+ const hue = roundPart(hsl.h * 360);
3540
3572
  let newColor;
3541
3573
 
3542
3574
  if (format === 'hex') {
3543
3575
  newColor = self.color.toHexString(true);
3544
3576
  i1.value = self.hex;
3545
3577
  } else if (format === 'hsl') {
3546
- const lightness = Math.round(hsl.l * 100);
3547
- const saturation = Math.round(hsl.s * 100);
3578
+ const lightness = roundPart(hsl.l * 100);
3579
+ const saturation = roundPart(hsl.s * 100);
3548
3580
  newColor = self.color.toHslString();
3549
3581
  i1.value = `${hue}`;
3550
3582
  i2.value = `${saturation}`;
@@ -3552,8 +3584,8 @@
3552
3584
  i4.value = `${alpha}`;
3553
3585
  } else if (format === 'hwb') {
3554
3586
  const { w, b } = self.hwb;
3555
- const whiteness = Math.round(w * 100);
3556
- const blackness = Math.round(b * 100);
3587
+ const whiteness = roundPart(w * 100);
3588
+ const blackness = roundPart(b * 100);
3557
3589
 
3558
3590
  newColor = self.color.toHwbString();
3559
3591
  i1.value = `${hue}`;
@@ -3625,7 +3657,7 @@
3625
3657
  const self = this;
3626
3658
  const { colorPicker } = self;
3627
3659
 
3628
- if (!hasClass(colorPicker, 'show')) {
3660
+ if (!['top', 'bottom'].some((c) => hasClass(colorPicker, c))) {
3629
3661
  showDropdown(self, colorPicker);
3630
3662
  }
3631
3663
  }
@@ -3642,21 +3674,6 @@
3642
3674
  }
3643
3675
  }
3644
3676
 
3645
- /** Shows the `ColorPicker` dropdown or the presets menu. */
3646
- show() {
3647
- const self = this;
3648
- const { menuToggle } = self;
3649
- if (!self.isOpen) {
3650
- toggleEventsOnShown(self, true);
3651
- self.updateDropdownPosition();
3652
- self.isOpen = true;
3653
- setAttribute(self.input, 'tabindex', '0');
3654
- if (menuToggle) {
3655
- setAttribute(menuToggle, 'tabindex', '0');
3656
- }
3657
- }
3658
- }
3659
-
3660
3677
  /**
3661
3678
  * Hides the currently open `ColorPicker` dropdown.
3662
3679
  * @param {boolean=} focusPrevented
@@ -3691,9 +3708,9 @@
3691
3708
  if (!focusPrevented) {
3692
3709
  focus(pickerToggle);
3693
3710
  }
3694
- setAttribute(input, 'tabindex', '-1');
3711
+ setAttribute(input, tabIndex, '-1');
3695
3712
  if (menuToggle) {
3696
- setAttribute(menuToggle, 'tabindex', '-1');
3713
+ setAttribute(menuToggle, tabIndex, '-1');
3697
3714
  }
3698
3715
  }
3699
3716
  }
@@ -3707,7 +3724,10 @@
3707
3724
  [...parent.children].forEach((el) => {
3708
3725
  if (el !== input) el.remove();
3709
3726
  });
3727
+
3728
+ removeAttribute(input, tabIndex);
3710
3729
  setElementStyle(input, { backgroundColor: '' });
3730
+
3711
3731
  ['txt-light', 'txt-dark'].forEach((c) => removeClass(parent, c));
3712
3732
  Data.remove(input, colorPickerString);
3713
3733
  }
@@ -3715,10 +3735,16 @@
3715
3735
 
3716
3736
  ObjectAssign(ColorPicker, {
3717
3737
  Color,
3738
+ ColorPalette,
3718
3739
  Version,
3719
3740
  getInstance: getColorPickerInstance,
3720
3741
  init: initColorPicker,
3721
3742
  selector: colorPickerSelector,
3743
+ // utils important for render
3744
+ roundPart,
3745
+ setElementStyle,
3746
+ setAttribute,
3747
+ getBoundingClientRect,
3722
3748
  });
3723
3749
 
3724
3750
  let CPID = 0;
@@ -3726,8 +3752,9 @@
3726
3752
  /**
3727
3753
  * `ColorPickerElement` Web Component.
3728
3754
  * @example
3729
- * <color-picker>
3730
- * <input type="text">
3755
+ * <label for="UNIQUE_ID">Label</label>
3756
+ * <color-picker data-format="hex" data-value="#075">
3757
+ * <input id="UNIQUE_ID" type="text" class="color-preview btn-appearance">
3731
3758
  * </color-picker>
3732
3759
  */
3733
3760
  class ColorPickerElement extends HTMLElement {
@@ -3808,6 +3835,8 @@
3808
3835
  ObjectAssign(ColorPickerElement, {
3809
3836
  Color,
3810
3837
  ColorPicker,
3838
+ ColorPalette,
3839
+ getInstance: getColorPickerInstance,
3811
3840
  Version,
3812
3841
  });
3813
3842