@thednp/color-picker 0.0.2 → 1.0.0

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.2 (http://thednp.github.io/color-picker)
2
+ * ColorPickerElement v1.0.0 (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
  */
@@ -15,25 +15,6 @@ function getDocument(node) {
15
15
  return window.document;
16
16
  }
17
17
 
18
- /**
19
- * A global array of possible `ParentNode`.
20
- */
21
- const parentNodes = [Document, Element, HTMLElement];
22
-
23
- /**
24
- * Shortcut for `HTMLElement.getElementsByTagName` method. Some `Node` elements
25
- * like `ShadowRoot` do not support `getElementsByTagName`.
26
- *
27
- * @param {string} selector the tag name
28
- * @param {(HTMLElement | Element | Document)=} parent optional Element to look into
29
- * @return {HTMLCollectionOf<HTMLElement | Element>} the 'HTMLCollection'
30
- */
31
- function getElementsByTagName(selector, parent) {
32
- const lookUp = parent && parentNodes
33
- .some((x) => parent instanceof x) ? parent : getDocument();
34
- return lookUp.getElementsByTagName(selector);
35
- }
36
-
37
18
  /**
38
19
  * Shortcut for `Object.assign()` static method.
39
20
  * @param {Record<string, any>} obj a target object
@@ -1241,27 +1222,26 @@ const EventRegistry = {};
1241
1222
  /**
1242
1223
  * The global event listener.
1243
1224
  *
1244
- * @this {Element | HTMLElement | Window | Document}
1245
- * @param {Event} e
1246
- * @returns {void}
1225
+ * @type {EventListener}
1226
+ * @this {EventTarget}
1247
1227
  */
1248
1228
  function globalListener(e) {
1249
1229
  const that = this;
1250
- const { type } = e;
1251
- const oneEvMap = EventRegistry[type] ? [...EventRegistry[type]] : [];
1230
+ const { type, target } = e;
1252
1231
 
1253
- oneEvMap.forEach((elementsMap) => {
1232
+ [...EventRegistry[type]].forEach((elementsMap) => {
1254
1233
  const [element, listenersMap] = elementsMap;
1255
- [...listenersMap].forEach((listenerMap) => {
1256
- if (element === that) {
1234
+ /* istanbul ignore else */
1235
+ if ([target, that].some((el) => element === el)) {
1236
+ [...listenersMap].forEach((listenerMap) => {
1257
1237
  const [listener, options] = listenerMap;
1258
1238
  listener.apply(element, [e]);
1259
1239
 
1260
1240
  if (options && options.once) {
1261
1241
  removeListener(element, type, listener, options);
1262
1242
  }
1263
- }
1264
- });
1243
+ });
1244
+ }
1265
1245
  });
1266
1246
  }
1267
1247
 
@@ -1269,10 +1249,7 @@ function globalListener(e) {
1269
1249
  * Register a new listener with its options and attach the `globalListener`
1270
1250
  * to the target if this is the first listener.
1271
1251
  *
1272
- * @param {Element | HTMLElement | Window | Document} element
1273
- * @param {string} eventType
1274
- * @param {EventListenerObject['handleEvent']} listener
1275
- * @param {AddEventListenerOptions=} options
1252
+ * @type {Listener.ListenerAction<EventTarget>}
1276
1253
  */
1277
1254
  const addListener = (element, eventType, listener, options) => {
1278
1255
  // get element listeners first
@@ -1290,9 +1267,7 @@ const addListener = (element, eventType, listener, options) => {
1290
1267
  const { size } = oneElementMap;
1291
1268
 
1292
1269
  // register listener with its options
1293
- if (oneElementMap) {
1294
- oneElementMap.set(listener, options);
1295
- }
1270
+ oneElementMap.set(listener, options);
1296
1271
 
1297
1272
  // add listener last
1298
1273
  if (!size) {
@@ -1304,10 +1279,7 @@ const addListener = (element, eventType, listener, options) => {
1304
1279
  * Remove a listener from registry and detach the `globalListener`
1305
1280
  * if no listeners are found in the registry.
1306
1281
  *
1307
- * @param {Element | HTMLElement | Window | Document} element
1308
- * @param {string} eventType
1309
- * @param {EventListenerObject['handleEvent']} listener
1310
- * @param {AddEventListenerOptions=} options
1282
+ * @type {Listener.ListenerAction<EventTarget>}
1311
1283
  */
1312
1284
  const removeListener = (element, eventType, listener, options) => {
1313
1285
  // get listener first
@@ -1326,6 +1298,7 @@ const removeListener = (element, eventType, listener, options) => {
1326
1298
  if (!oneEventMap || !oneEventMap.size) delete EventRegistry[eventType];
1327
1299
 
1328
1300
  // remove listener last
1301
+ /* istanbul ignore else */
1329
1302
  if (!oneElementMap || !oneElementMap.size) {
1330
1303
  element.removeEventListener(eventType, globalListener, eventOptions);
1331
1304
  }
@@ -1427,12 +1400,6 @@ const keydownEvent = 'keydown';
1427
1400
  */
1428
1401
  const changeEvent = 'change';
1429
1402
 
1430
- /**
1431
- * A global namespace for `touchstart` event.
1432
- * @type {string}
1433
- */
1434
- const touchstartEvent = 'touchstart';
1435
-
1436
1403
  /**
1437
1404
  * A global namespace for `touchmove` event.
1438
1405
  * @type {string}
@@ -1440,28 +1407,22 @@ const touchstartEvent = 'touchstart';
1440
1407
  const touchmoveEvent = 'touchmove';
1441
1408
 
1442
1409
  /**
1443
- * A global namespace for `touchend` event.
1444
- * @type {string}
1445
- */
1446
- const touchendEvent = 'touchend';
1447
-
1448
- /**
1449
- * A global namespace for `mousedown` event.
1410
+ * A global namespace for `pointerdown` event.
1450
1411
  * @type {string}
1451
1412
  */
1452
- const mousedownEvent = 'mousedown';
1413
+ const pointerdownEvent = 'pointerdown';
1453
1414
 
1454
1415
  /**
1455
- * A global namespace for `mousemove` event.
1416
+ * A global namespace for `pointermove` event.
1456
1417
  * @type {string}
1457
1418
  */
1458
- const mousemoveEvent = 'mousemove';
1419
+ const pointermoveEvent = 'pointermove';
1459
1420
 
1460
1421
  /**
1461
- * A global namespace for `mouseup` event.
1422
+ * A global namespace for `pointerup` event.
1462
1423
  * @type {string}
1463
1424
  */
1464
- const mouseupEvent = 'mouseup';
1425
+ const pointerupEvent = 'pointerup';
1465
1426
 
1466
1427
  /**
1467
1428
  * A global namespace for `scroll` event.
@@ -1497,27 +1458,6 @@ function getDocumentElement(node) {
1497
1458
  return getDocument(node).documentElement;
1498
1459
  }
1499
1460
 
1500
- /**
1501
- * Returns the `Window` object of a target node.
1502
- * @see https://github.com/floating-ui/floating-ui
1503
- *
1504
- * @param {(Node | HTMLElement | Element | Window)=} node target node
1505
- * @returns {globalThis}
1506
- */
1507
- function getWindow(node) {
1508
- if (node == null) {
1509
- return window;
1510
- }
1511
-
1512
- if (!(node instanceof Window)) {
1513
- const { ownerDocument } = node;
1514
- return ownerDocument ? ownerDocument.defaultView || window : window;
1515
- }
1516
-
1517
- // @ts-ignore
1518
- return node;
1519
- }
1520
-
1521
1461
  let elementUID = 0;
1522
1462
  let elementMapUID = 0;
1523
1463
  const elementIDMap = new Map();
@@ -1617,6 +1557,11 @@ function getElementTransitionDuration(element) {
1617
1557
  return !Number.isNaN(duration) ? duration : 0;
1618
1558
  }
1619
1559
 
1560
+ /**
1561
+ * A global array of possible `ParentNode`.
1562
+ */
1563
+ const parentNodes = [Document, Element, HTMLElement];
1564
+
1620
1565
  /**
1621
1566
  * A global array with `Element` | `HTMLElement`.
1622
1567
  */
@@ -2260,17 +2205,16 @@ function getColorMenu(self, colorsSource, menuClass) {
2260
2205
  const isOptionsMenu = menuClass === 'color-options';
2261
2206
  const isPalette = colorsSource instanceof ColorPalette;
2262
2207
  const menuLabel = isOptionsMenu ? presetsLabel : defaultsLabel;
2263
- let colorsArray = isPalette ? colorsSource.colors : colorsSource;
2264
- colorsArray = colorsArray instanceof Array ? colorsArray : [];
2208
+ const colorsArray = isPalette ? colorsSource.colors : colorsSource;
2265
2209
  const colorsCount = colorsArray.length;
2266
2210
  const { lightSteps } = isPalette ? colorsSource : { lightSteps: null };
2267
- const fit = lightSteps || [9, 10].find((x) => colorsCount > x * 2 && !(colorsCount % x)) || 5;
2211
+ const fit = lightSteps || [9, 10].find((x) => colorsCount >= x * 2 && !(colorsCount % x)) || 5;
2268
2212
  const isMultiLine = isOptionsMenu && colorsCount > fit;
2269
2213
  let rowCountHover = 2;
2270
- rowCountHover = isMultiLine && colorsCount >= fit * 2 ? 3 : rowCountHover;
2271
- rowCountHover = colorsCount >= fit * 3 ? 4 : rowCountHover;
2272
- rowCountHover = colorsCount >= fit * 4 ? 5 : rowCountHover;
2273
- const rowCount = rowCountHover - (colorsCount < fit * 3 ? 1 : 2);
2214
+ rowCountHover = isMultiLine && colorsCount > fit * 2 ? 3 : rowCountHover;
2215
+ rowCountHover = isMultiLine && colorsCount > fit * 3 ? 4 : rowCountHover;
2216
+ rowCountHover = isMultiLine && colorsCount > fit * 4 ? 5 : rowCountHover;
2217
+ const rowCount = rowCountHover - (colorsCount <= fit * 3 ? 1 : 2);
2274
2218
  const isScrollable = isMultiLine && colorsCount > rowCount * fit;
2275
2219
  let finalClass = menuClass;
2276
2220
  finalClass += isScrollable ? ' scrollable' : '';
@@ -2278,7 +2222,7 @@ function getColorMenu(self, colorsSource, menuClass) {
2278
2222
  const gap = isMultiLine ? '1px' : '0.25rem';
2279
2223
  let optionSize = isMultiLine ? 1.75 : 2;
2280
2224
  optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2281
- const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2225
+ const menuHeight = `${rowCount * optionSize}rem`;
2282
2226
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2283
2227
  /** @type {HTMLUListElement} */
2284
2228
  // @ts-ignore -- <UL> is an `HTMLElement`
@@ -2385,16 +2329,14 @@ function setMarkup(self) {
2385
2329
  });
2386
2330
 
2387
2331
  // color presets
2388
- if ((colorPresets instanceof Array && colorPresets.length)
2389
- || (colorPresets instanceof ColorPalette && colorPresets.colors)) {
2390
- const presetsMenu = getColorMenu(self, colorPresets, 'color-options');
2391
- presetsDropdown.append(presetsMenu);
2332
+ if (colorPresets) {
2333
+ presetsDropdown.append(getColorMenu(self, colorPresets, 'color-options'));
2392
2334
  }
2393
2335
 
2394
2336
  // explicit defaults [reset, initial, inherit, transparent, currentColor]
2337
+ // also custom defaults [default: #069, complementary: #930]
2395
2338
  if (colorKeywords && colorKeywords.length) {
2396
- const keywordsMenu = getColorMenu(self, colorKeywords, 'color-defaults');
2397
- presetsDropdown.append(keywordsMenu);
2339
+ presetsDropdown.append(getColorMenu(self, colorKeywords, 'color-defaults'));
2398
2340
  }
2399
2341
 
2400
2342
  const presetsBtn = createElement({
@@ -2431,7 +2373,7 @@ function setMarkup(self) {
2431
2373
  setAttribute(input, tabIndex, '-1');
2432
2374
  }
2433
2375
 
2434
- var version = "0.0.2";
2376
+ var version = "1.0.0";
2435
2377
 
2436
2378
  // @ts-ignore
2437
2379
 
@@ -2453,7 +2395,7 @@ const colorPickerDefaults = {
2453
2395
  // ColorPicker Static Methods
2454
2396
  // ==========================
2455
2397
 
2456
- /** @type {CP.GetInstance<ColorPicker>} */
2398
+ /** @type {CP.GetInstance<ColorPicker, HTMLInputElement>} */
2457
2399
  const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2458
2400
 
2459
2401
  /** @type {CP.InitCallback<ColorPicker>} */
@@ -2488,12 +2430,10 @@ function toggleEventsOnShown(self, action) {
2488
2430
  const fn = action ? addListener : removeListener;
2489
2431
  const { input, colorMenu, parent } = self;
2490
2432
  const doc = getDocument(input);
2491
- const win = getWindow(input);
2492
- const pointerEvents = `on${touchstartEvent}` in doc
2493
- ? { down: touchstartEvent, move: touchmoveEvent, up: touchendEvent }
2494
- : { down: mousedownEvent, move: mousemoveEvent, up: mouseupEvent };
2433
+ // const win = getWindow(input);
2434
+ const win = doc.defaultView;
2495
2435
 
2496
- fn(self.controls, pointerEvents.down, self.pointerDown);
2436
+ fn(self.controls, pointerdownEvent, self.pointerDown);
2497
2437
  self.controlKnobs.forEach((x) => fn(x, keydownEvent, self.handleKnobs));
2498
2438
 
2499
2439
  // @ts-ignore -- this is `Window`
@@ -2508,8 +2448,8 @@ function toggleEventsOnShown(self, action) {
2508
2448
  fn(colorMenu, keydownEvent, self.menuKeyHandler);
2509
2449
  }
2510
2450
 
2511
- fn(doc, pointerEvents.move, self.pointerMove);
2512
- fn(doc, pointerEvents.up, self.pointerUp);
2451
+ fn(doc, pointermoveEvent, self.pointerMove);
2452
+ fn(doc, pointerupEvent, self.pointerUp);
2513
2453
  fn(parent, focusoutEvent, self.handleFocusOut);
2514
2454
  fn(doc, keyupEvent, self.handleDismiss);
2515
2455
  }
@@ -2528,6 +2468,7 @@ function firePickerChange(self) {
2528
2468
  * @returns {void}
2529
2469
  */
2530
2470
  function removePosition(element) {
2471
+ /* istanbul ignore else */
2531
2472
  if (element) {
2532
2473
  ['bottom', 'top'].forEach((x) => removeClass(element, x));
2533
2474
  }
@@ -2630,6 +2571,7 @@ class ColorPicker {
2630
2571
  } = normalizeOptions(this.isCE ? parent : input, colorPickerDefaults, config || {});
2631
2572
 
2632
2573
  let translatedColorLabels = colorNames;
2574
+ /* istanbul ignore else */
2633
2575
  if (colorLabels instanceof Array && colorLabels.length === 17) {
2634
2576
  translatedColorLabels = colorLabels;
2635
2577
  } else if (colorLabels && colorLabels.split(',').length === 17) {
@@ -2646,7 +2588,7 @@ class ColorPicker {
2646
2588
  ? JSON.parse(componentLabels) : componentLabels;
2647
2589
 
2648
2590
  /** @type {Record<string, string>} */
2649
- self.componentLabels = ObjectAssign(colorPickerLabels, tempComponentLabels);
2591
+ self.componentLabels = ObjectAssign({ ...colorPickerLabels }, tempComponentLabels);
2650
2592
 
2651
2593
  /** @type {Color} */
2652
2594
  self.color = new Color(input.value || '#fff', format);
@@ -2655,14 +2597,14 @@ class ColorPicker {
2655
2597
  self.format = format;
2656
2598
 
2657
2599
  // set colour defaults
2658
- if (colorKeywords instanceof Array) {
2600
+ if (colorKeywords instanceof Array && colorKeywords.length) {
2659
2601
  self.colorKeywords = colorKeywords;
2660
2602
  } else if (typeof colorKeywords === 'string' && colorKeywords.length) {
2661
2603
  self.colorKeywords = colorKeywords.split(',').map((x) => x.trim());
2662
2604
  }
2663
2605
 
2664
2606
  // set colour presets
2665
- if (colorPresets instanceof Array) {
2607
+ if (colorPresets instanceof Array && colorPresets.length) {
2666
2608
  self.colorPresets = colorPresets;
2667
2609
  } else if (typeof colorPresets === 'string' && colorPresets.length) {
2668
2610
  if (isValidJSON(colorPresets)) {
@@ -2793,6 +2735,7 @@ class ColorPicker {
2793
2735
  let colorName;
2794
2736
 
2795
2737
  // determine color appearance
2738
+ /* istanbul ignore else */
2796
2739
  if (lightness === 100 && saturation === 0) {
2797
2740
  colorName = colorLabels.white;
2798
2741
  } else if (lightness === 0) {
@@ -2893,13 +2836,14 @@ class ColorPicker {
2893
2836
  const self = this;
2894
2837
  const { activeElement } = getDocument(self.input);
2895
2838
 
2896
- if ((e.type === touchmoveEvent && self.dragElement)
2839
+ self.updateDropdownPosition();
2840
+
2841
+ /* istanbul ignore next */
2842
+ if (([pointermoveEvent, touchmoveEvent].includes(e.type) && self.dragElement)
2897
2843
  || (activeElement && self.controlKnobs.includes(activeElement))) {
2898
2844
  e.stopPropagation();
2899
2845
  e.preventDefault();
2900
2846
  }
2901
-
2902
- self.updateDropdownPosition();
2903
2847
  }
2904
2848
 
2905
2849
  /**
@@ -2973,7 +2917,9 @@ class ColorPicker {
2973
2917
 
2974
2918
  self.update();
2975
2919
 
2920
+ /* istanbul ignore else */
2976
2921
  if (currentActive !== target) {
2922
+ /* istanbul ignore else */
2977
2923
  if (currentActive) {
2978
2924
  removeClass(currentActive, 'active');
2979
2925
  removeAttribute(currentActive, ariaSelected);
@@ -2991,15 +2937,13 @@ class ColorPicker {
2991
2937
 
2992
2938
  /**
2993
2939
  * The `ColorPicker` *touchstart* / *mousedown* events listener for control knobs.
2994
- * @param {TouchEvent} e
2940
+ * @param {PointerEvent} e
2995
2941
  * @this {ColorPicker}
2996
2942
  */
2997
2943
  pointerDown(e) {
2998
2944
  const self = this;
2999
2945
  /** @type {*} */
3000
- const {
3001
- type, target, touches, pageX, pageY,
3002
- } = e;
2946
+ const { target, pageX, pageY } = e;
3003
2947
  const { colorMenu, visuals, controlKnobs } = self;
3004
2948
  const [v1, v2, v3] = visuals;
3005
2949
  const [c1, c2, c3] = controlKnobs;
@@ -3007,11 +2951,10 @@ class ColorPicker {
3007
2951
  const visual = controlKnobs.includes(target) ? target.previousElementSibling : target;
3008
2952
  const visualRect = getBoundingClientRect(visual);
3009
2953
  const html = getDocumentElement(v1);
3010
- const X = type === 'touchstart' ? touches[0].pageX : pageX;
3011
- const Y = type === 'touchstart' ? touches[0].pageY : pageY;
3012
- const offsetX = X - html.scrollLeft - visualRect.left;
3013
- const offsetY = Y - html.scrollTop - visualRect.top;
2954
+ const offsetX = pageX - html.scrollLeft - visualRect.left;
2955
+ const offsetY = pageY - html.scrollTop - visualRect.top;
3014
2956
 
2957
+ /* istanbul ignore else */
3015
2958
  if (target === v1 || target === c1) {
3016
2959
  self.dragElement = visual;
3017
2960
  self.changeControl1(offsetX, offsetY);
@@ -3035,7 +2978,7 @@ class ColorPicker {
3035
2978
 
3036
2979
  /**
3037
2980
  * The `ColorPicker` *touchend* / *mouseup* events listener for control knobs.
3038
- * @param {TouchEvent} e
2981
+ * @param {PointerEvent} e
3039
2982
  * @this {ColorPicker}
3040
2983
  */
3041
2984
  pointerUp({ target }) {
@@ -3044,9 +2987,8 @@ class ColorPicker {
3044
2987
  const doc = getDocument(parent);
3045
2988
  const currentOpen = querySelector(`${colorPickerParentSelector}.open`, doc) !== null;
3046
2989
  const selection = doc.getSelection();
3047
- // @ts-ignore
2990
+
3048
2991
  if (!self.dragElement && !selection.toString().length
3049
- // @ts-ignore
3050
2992
  && !parent.contains(target)) {
3051
2993
  self.hide(currentOpen);
3052
2994
  }
@@ -3056,25 +2998,20 @@ class ColorPicker {
3056
2998
 
3057
2999
  /**
3058
3000
  * The `ColorPicker` *touchmove* / *mousemove* events listener for control knobs.
3059
- * @param {TouchEvent} e
3001
+ * @param {PointerEvent} e
3060
3002
  */
3061
3003
  pointerMove(e) {
3062
3004
  const self = this;
3063
3005
  const { dragElement, visuals } = self;
3064
3006
  const [v1, v2, v3] = visuals;
3065
- const {
3066
- // @ts-ignore
3067
- type, touches, pageX, pageY,
3068
- } = e;
3007
+ const { pageX, pageY } = e;
3069
3008
 
3070
3009
  if (!dragElement) return;
3071
3010
 
3072
3011
  const controlRect = getBoundingClientRect(dragElement);
3073
3012
  const win = getDocumentElement(v1);
3074
- const X = type === touchmoveEvent ? touches[0].pageX : pageX;
3075
- const Y = type === touchmoveEvent ? touches[0].pageY : pageY;
3076
- const offsetX = X - win.scrollLeft - controlRect.left;
3077
- const offsetY = Y - win.scrollTop - controlRect.top;
3013
+ const offsetX = pageX - win.scrollLeft - controlRect.left;
3014
+ const offsetY = pageY - win.scrollTop - controlRect.top;
3078
3015
 
3079
3016
  if (dragElement === v1) {
3080
3017
  self.changeControl1(offsetX, offsetY);
@@ -3108,13 +3045,16 @@ class ColorPicker {
3108
3045
  const currentKnob = controlKnobs.find((x) => x === activeElement);
3109
3046
  const yRatio = offsetHeight / 360;
3110
3047
 
3048
+ /* istanbul ignore else */
3111
3049
  if (currentKnob) {
3112
3050
  let offsetX = 0;
3113
3051
  let offsetY = 0;
3114
3052
 
3053
+ /* istanbul ignore else */
3115
3054
  if (target === c1) {
3116
3055
  const xRatio = offsetWidth / 100;
3117
3056
 
3057
+ /* istanbul ignore else */
3118
3058
  if ([keyArrowLeft, keyArrowRight].includes(code)) {
3119
3059
  self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
3120
3060
  } else if ([keyArrowUp, keyArrowDown].includes(code)) {
@@ -3160,6 +3100,7 @@ class ColorPicker {
3160
3100
  const isNonColorValue = self.hasNonColor && nonColors.includes(currentValue);
3161
3101
  const alpha = i4 ? v4 : (1 - controlPositions.c3y / offsetHeight);
3162
3102
 
3103
+ /* istanbul ignore else */
3163
3104
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
3164
3105
  if (activeElement === input) {
3165
3106
  if (isNonColorValue) {
@@ -3474,6 +3415,7 @@ class ColorPicker {
3474
3415
  const hue = roundPart(hsl.h * 360);
3475
3416
  let newColor;
3476
3417
 
3418
+ /* istanbul ignore else */
3477
3419
  if (format === 'hex') {
3478
3420
  newColor = self.color.toHexString(true);
3479
3421
  i1.value = self.hex;
@@ -3574,15 +3516,15 @@ class ColorPicker {
3574
3516
  const relatedBtn = openPicker ? pickerToggle : menuToggle;
3575
3517
  const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
3576
3518
 
3577
- // if (!self.isValid) {
3578
3519
  self.value = self.color.toString(true);
3579
- // }
3580
3520
 
3521
+ /* istanbul ignore else */
3581
3522
  if (openDropdown) {
3582
3523
  removeClass(openDropdown, 'show');
3583
3524
  setAttribute(relatedBtn, ariaExpanded, 'false');
3584
3525
  setTimeout(() => {
3585
3526
  removePosition(openDropdown);
3527
+ /* istanbul ignore else */
3586
3528
  if (!querySelector('.show', parent)) {
3587
3529
  removeClass(parent, 'open');
3588
3530
  toggleEventsOnShown(self);
@@ -3595,7 +3537,7 @@ class ColorPicker {
3595
3537
  focus(pickerToggle);
3596
3538
  }
3597
3539
  setAttribute(input, tabIndex, '-1');
3598
- if (menuToggle) {
3540
+ if (relatedBtn === menuToggle) {
3599
3541
  setAttribute(menuToggle, tabIndex, '-1');
3600
3542
  }
3601
3543
  }
@@ -3633,14 +3575,76 @@ ObjectAssign(ColorPicker, {
3633
3575
  getBoundingClientRect,
3634
3576
  });
3635
3577
 
3578
+ /**
3579
+ * A small utility to toggle `ColorPickerElement` attributes
3580
+ * when `connectedCallback` or `disconnectedCallback` methods
3581
+ * are called and helps the instance keep its value and settings instact.
3582
+ *
3583
+ * @param {CP.ColorPickerElement} self ColorPickerElement instance
3584
+ * @param {Function=} callback when `true`, attributes are added
3585
+ *
3586
+ * @example
3587
+ * const attributes = [
3588
+ * // essentials
3589
+ * 'value', 'format',
3590
+ * // presets menus
3591
+ * 'color-presets', 'color-keywords',
3592
+ * // labels
3593
+ * 'color-labels', 'component-labels',
3594
+ * ];
3595
+ */
3596
+ function toggleCEAttr(self, callback) {
3597
+ if (callback) {
3598
+ const { input, colorPicker } = self;
3599
+
3600
+ const {
3601
+ value, format, colorPresets, colorKeywords, componentLabels, colorLabels,
3602
+ } = colorPicker;
3603
+
3604
+ const { id, placeholder } = input;
3605
+
3606
+ setAttribute(self, 'data-id', id);
3607
+ setAttribute(self, 'data-value', value);
3608
+ setAttribute(self, 'data-format', format);
3609
+ setAttribute(self, 'data-placeholder', placeholder);
3610
+
3611
+ if (ObjectKeys(colorPickerLabels).some((l) => colorPickerLabels[l] !== componentLabels[l])) {
3612
+ setAttribute(self, 'data-component-labels', JSON.stringify(componentLabels));
3613
+ }
3614
+ if (!colorNames.every((c) => c === colorLabels[c])) {
3615
+ setAttribute(self, 'data-color-labels', colorNames.map((n) => colorLabels[n]).join(','));
3616
+ }
3617
+ if (colorPresets instanceof ColorPalette) {
3618
+ const { hue, hueSteps, lightSteps } = colorPresets;
3619
+ setAttribute(self, 'data-color-presets', JSON.stringify({ hue, hueSteps, lightSteps }));
3620
+ }
3621
+ if (Array.isArray(colorPresets) && colorPresets.length) {
3622
+ setAttribute(self, 'data-color-presets', colorPresets.join(','));
3623
+ }
3624
+ if (colorKeywords) {
3625
+ setAttribute(self, 'data-color-keywords', colorKeywords.join(','));
3626
+ }
3627
+ setTimeout(callback, 0);
3628
+ } else {
3629
+ // keep id
3630
+ // removeAttribute(self, 'data-id');
3631
+ removeAttribute(self, 'data-value');
3632
+ removeAttribute(self, 'data-format');
3633
+ removeAttribute(self, 'data-placeholder');
3634
+ removeAttribute(self, 'data-component-labels');
3635
+ removeAttribute(self, 'data-color-labels');
3636
+ removeAttribute(self, 'data-color-presets');
3637
+ removeAttribute(self, 'data-color-keywords');
3638
+ }
3639
+ }
3640
+
3636
3641
  let CPID = 0;
3637
3642
 
3638
3643
  /**
3639
3644
  * `ColorPickerElement` Web Component.
3640
3645
  * @example
3641
3646
  * <label for="UNIQUE_ID">Label</label>
3642
- * <color-picker>
3643
- * <input id="UNIQUE_ID" value="red" format="hex" class="color-preview btn-appearance">
3647
+ * <color-picker data-id="UNIQUE_ID" data-value="red" data-format="hex">
3644
3648
  * </color-picker>
3645
3649
  * // or
3646
3650
  * <label for="UNIQUE_ID">Label</label>
@@ -3659,54 +3663,66 @@ class ColorPickerElement extends HTMLElement {
3659
3663
  get value() { return this.input && this.input.value; }
3660
3664
 
3661
3665
  connectedCallback() {
3662
- if (this.input) return;
3666
+ const self = this;
3667
+ if (self.input) return;
3663
3668
 
3664
- let [input] = getElementsByTagName('input', this);
3665
- const value = (input && getAttribute(input, 'value')) || getAttribute(this, 'data-value') || '#fff';
3666
- const format = (input && getAttribute(input, 'format')) || getAttribute(this, 'data-format') || 'rgb';
3667
- let id = (input && getAttribute(input, 'id')) || getAttribute(this, 'data-id');
3669
+ let id = getAttribute(self, 'data-id');
3670
+ const value = getAttribute(self, 'data-value') || '#fff';
3671
+ const format = getAttribute(self, 'data-format') || 'rgb';
3672
+ const placeholder = getAttribute(self, 'data-placeholder') || '';
3668
3673
 
3669
3674
  if (!id) {
3670
3675
  id = `color-picker-${format}-${CPID}`;
3671
3676
  CPID += 1;
3672
3677
  }
3673
3678
 
3674
- if (!input) {
3675
- input = createElement({
3676
- tagName: 'input',
3677
- type: 'text',
3678
- className: 'color-preview btn-appearance',
3679
- });
3679
+ const input = createElement({
3680
+ tagName: 'input',
3681
+ type: 'text',
3682
+ className: 'color-preview btn-appearance',
3683
+ });
3684
+
3685
+ setAttribute(input, 'id', id);
3686
+ setAttribute(input, 'name', id);
3687
+ setAttribute(input, 'autocomplete', 'off');
3688
+ setAttribute(input, 'spellcheck', 'false');
3689
+ setAttribute(input, 'value', value);
3690
+ setAttribute(input, 'placeholder', placeholder);
3691
+ self.append(input);
3680
3692
 
3681
- setAttribute(input, 'id', id);
3682
- setAttribute(input, 'name', id);
3683
- setAttribute(input, 'autocomplete', 'off');
3684
- setAttribute(input, 'spellcheck', 'false');
3685
- setAttribute(input, 'value', value);
3686
- this.append(input);
3687
- }
3688
3693
  /** @type {HTMLInputElement} */
3689
3694
  // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3690
- this.input = input;
3695
+ self.input = input;
3691
3696
 
3692
3697
  // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3693
- this.colorPicker = new ColorPicker(input);
3698
+ self.colorPicker = new ColorPicker(input);
3694
3699
 
3695
3700
  // @ts-ignore - `shadowRoot` is defined in the constructor
3696
- this.shadowRoot.append(createElement('slot'));
3701
+ self.shadowRoot.append(createElement('slot'));
3702
+
3703
+ // remove Attributes
3704
+ toggleCEAttr(self);
3697
3705
  }
3698
3706
 
3699
3707
  /** @this {ColorPickerElement} */
3700
3708
  disconnectedCallback() {
3701
- const { input, colorPicker, shadowRoot } = this;
3702
- if (colorPicker) colorPicker.dispose();
3703
- if (input) input.remove();
3704
- if (shadowRoot) shadowRoot.innerHTML = '';
3705
-
3706
- ObjectAssign(this, {
3707
- colorPicker: undefined,
3708
- input: undefined,
3709
- });
3709
+ const self = this;
3710
+ const { input, colorPicker, shadowRoot } = self;
3711
+
3712
+ const callback = () => {
3713
+ // remove markup
3714
+ input.remove();
3715
+ colorPicker.dispose();
3716
+ shadowRoot.innerHTML = '';
3717
+
3718
+ ObjectAssign(self, {
3719
+ colorPicker: undefined,
3720
+ input: undefined,
3721
+ });
3722
+ };
3723
+
3724
+ // re-add Attributes
3725
+ toggleCEAttr(self, callback);
3710
3726
  }
3711
3727
  }
3712
3728