@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
  */
@@ -21,25 +21,6 @@
21
21
  return window.document;
22
22
  }
23
23
 
24
- /**
25
- * A global array of possible `ParentNode`.
26
- */
27
- const parentNodes = [Document, Element, HTMLElement];
28
-
29
- /**
30
- * Shortcut for `HTMLElement.getElementsByTagName` method. Some `Node` elements
31
- * like `ShadowRoot` do not support `getElementsByTagName`.
32
- *
33
- * @param {string} selector the tag name
34
- * @param {(HTMLElement | Element | Document)=} parent optional Element to look into
35
- * @return {HTMLCollectionOf<HTMLElement | Element>} the 'HTMLCollection'
36
- */
37
- function getElementsByTagName(selector, parent) {
38
- const lookUp = parent && parentNodes
39
- .some((x) => parent instanceof x) ? parent : getDocument();
40
- return lookUp.getElementsByTagName(selector);
41
- }
42
-
43
24
  /**
44
25
  * Shortcut for `Object.assign()` static method.
45
26
  * @param {Record<string, any>} obj a target object
@@ -1247,27 +1228,26 @@
1247
1228
  /**
1248
1229
  * The global event listener.
1249
1230
  *
1250
- * @this {Element | HTMLElement | Window | Document}
1251
- * @param {Event} e
1252
- * @returns {void}
1231
+ * @type {EventListener}
1232
+ * @this {EventTarget}
1253
1233
  */
1254
1234
  function globalListener(e) {
1255
1235
  const that = this;
1256
- const { type } = e;
1257
- const oneEvMap = EventRegistry[type] ? [...EventRegistry[type]] : [];
1236
+ const { type, target } = e;
1258
1237
 
1259
- oneEvMap.forEach((elementsMap) => {
1238
+ [...EventRegistry[type]].forEach((elementsMap) => {
1260
1239
  const [element, listenersMap] = elementsMap;
1261
- [...listenersMap].forEach((listenerMap) => {
1262
- if (element === that) {
1240
+ /* istanbul ignore else */
1241
+ if ([target, that].some((el) => element === el)) {
1242
+ [...listenersMap].forEach((listenerMap) => {
1263
1243
  const [listener, options] = listenerMap;
1264
1244
  listener.apply(element, [e]);
1265
1245
 
1266
1246
  if (options && options.once) {
1267
1247
  removeListener(element, type, listener, options);
1268
1248
  }
1269
- }
1270
- });
1249
+ });
1250
+ }
1271
1251
  });
1272
1252
  }
1273
1253
 
@@ -1275,10 +1255,7 @@
1275
1255
  * Register a new listener with its options and attach the `globalListener`
1276
1256
  * to the target if this is the first listener.
1277
1257
  *
1278
- * @param {Element | HTMLElement | Window | Document} element
1279
- * @param {string} eventType
1280
- * @param {EventListenerObject['handleEvent']} listener
1281
- * @param {AddEventListenerOptions=} options
1258
+ * @type {Listener.ListenerAction<EventTarget>}
1282
1259
  */
1283
1260
  const addListener = (element, eventType, listener, options) => {
1284
1261
  // get element listeners first
@@ -1296,9 +1273,7 @@
1296
1273
  const { size } = oneElementMap;
1297
1274
 
1298
1275
  // register listener with its options
1299
- if (oneElementMap) {
1300
- oneElementMap.set(listener, options);
1301
- }
1276
+ oneElementMap.set(listener, options);
1302
1277
 
1303
1278
  // add listener last
1304
1279
  if (!size) {
@@ -1310,10 +1285,7 @@
1310
1285
  * Remove a listener from registry and detach the `globalListener`
1311
1286
  * if no listeners are found in the registry.
1312
1287
  *
1313
- * @param {Element | HTMLElement | Window | Document} element
1314
- * @param {string} eventType
1315
- * @param {EventListenerObject['handleEvent']} listener
1316
- * @param {AddEventListenerOptions=} options
1288
+ * @type {Listener.ListenerAction<EventTarget>}
1317
1289
  */
1318
1290
  const removeListener = (element, eventType, listener, options) => {
1319
1291
  // get listener first
@@ -1332,6 +1304,7 @@
1332
1304
  if (!oneEventMap || !oneEventMap.size) delete EventRegistry[eventType];
1333
1305
 
1334
1306
  // remove listener last
1307
+ /* istanbul ignore else */
1335
1308
  if (!oneElementMap || !oneElementMap.size) {
1336
1309
  element.removeEventListener(eventType, globalListener, eventOptions);
1337
1310
  }
@@ -1433,12 +1406,6 @@
1433
1406
  */
1434
1407
  const changeEvent = 'change';
1435
1408
 
1436
- /**
1437
- * A global namespace for `touchstart` event.
1438
- * @type {string}
1439
- */
1440
- const touchstartEvent = 'touchstart';
1441
-
1442
1409
  /**
1443
1410
  * A global namespace for `touchmove` event.
1444
1411
  * @type {string}
@@ -1446,28 +1413,22 @@
1446
1413
  const touchmoveEvent = 'touchmove';
1447
1414
 
1448
1415
  /**
1449
- * A global namespace for `touchend` event.
1450
- * @type {string}
1451
- */
1452
- const touchendEvent = 'touchend';
1453
-
1454
- /**
1455
- * A global namespace for `mousedown` event.
1416
+ * A global namespace for `pointerdown` event.
1456
1417
  * @type {string}
1457
1418
  */
1458
- const mousedownEvent = 'mousedown';
1419
+ const pointerdownEvent = 'pointerdown';
1459
1420
 
1460
1421
  /**
1461
- * A global namespace for `mousemove` event.
1422
+ * A global namespace for `pointermove` event.
1462
1423
  * @type {string}
1463
1424
  */
1464
- const mousemoveEvent = 'mousemove';
1425
+ const pointermoveEvent = 'pointermove';
1465
1426
 
1466
1427
  /**
1467
- * A global namespace for `mouseup` event.
1428
+ * A global namespace for `pointerup` event.
1468
1429
  * @type {string}
1469
1430
  */
1470
- const mouseupEvent = 'mouseup';
1431
+ const pointerupEvent = 'pointerup';
1471
1432
 
1472
1433
  /**
1473
1434
  * A global namespace for `scroll` event.
@@ -1503,27 +1464,6 @@
1503
1464
  return getDocument(node).documentElement;
1504
1465
  }
1505
1466
 
1506
- /**
1507
- * Returns the `Window` object of a target node.
1508
- * @see https://github.com/floating-ui/floating-ui
1509
- *
1510
- * @param {(Node | HTMLElement | Element | Window)=} node target node
1511
- * @returns {globalThis}
1512
- */
1513
- function getWindow(node) {
1514
- if (node == null) {
1515
- return window;
1516
- }
1517
-
1518
- if (!(node instanceof Window)) {
1519
- const { ownerDocument } = node;
1520
- return ownerDocument ? ownerDocument.defaultView || window : window;
1521
- }
1522
-
1523
- // @ts-ignore
1524
- return node;
1525
- }
1526
-
1527
1467
  let elementUID = 0;
1528
1468
  let elementMapUID = 0;
1529
1469
  const elementIDMap = new Map();
@@ -1623,6 +1563,11 @@
1623
1563
  return !Number.isNaN(duration) ? duration : 0;
1624
1564
  }
1625
1565
 
1566
+ /**
1567
+ * A global array of possible `ParentNode`.
1568
+ */
1569
+ const parentNodes = [Document, Element, HTMLElement];
1570
+
1626
1571
  /**
1627
1572
  * A global array with `Element` | `HTMLElement`.
1628
1573
  */
@@ -2266,17 +2211,16 @@
2266
2211
  const isOptionsMenu = menuClass === 'color-options';
2267
2212
  const isPalette = colorsSource instanceof ColorPalette;
2268
2213
  const menuLabel = isOptionsMenu ? presetsLabel : defaultsLabel;
2269
- let colorsArray = isPalette ? colorsSource.colors : colorsSource;
2270
- colorsArray = colorsArray instanceof Array ? colorsArray : [];
2214
+ const colorsArray = isPalette ? colorsSource.colors : colorsSource;
2271
2215
  const colorsCount = colorsArray.length;
2272
2216
  const { lightSteps } = isPalette ? colorsSource : { lightSteps: null };
2273
- const fit = lightSteps || [9, 10].find((x) => colorsCount > x * 2 && !(colorsCount % x)) || 5;
2217
+ const fit = lightSteps || [9, 10].find((x) => colorsCount >= x * 2 && !(colorsCount % x)) || 5;
2274
2218
  const isMultiLine = isOptionsMenu && colorsCount > fit;
2275
2219
  let rowCountHover = 2;
2276
- rowCountHover = isMultiLine && colorsCount >= fit * 2 ? 3 : rowCountHover;
2277
- rowCountHover = colorsCount >= fit * 3 ? 4 : rowCountHover;
2278
- rowCountHover = colorsCount >= fit * 4 ? 5 : rowCountHover;
2279
- const rowCount = rowCountHover - (colorsCount < fit * 3 ? 1 : 2);
2220
+ rowCountHover = isMultiLine && colorsCount > fit * 2 ? 3 : rowCountHover;
2221
+ rowCountHover = isMultiLine && colorsCount > fit * 3 ? 4 : rowCountHover;
2222
+ rowCountHover = isMultiLine && colorsCount > fit * 4 ? 5 : rowCountHover;
2223
+ const rowCount = rowCountHover - (colorsCount <= fit * 3 ? 1 : 2);
2280
2224
  const isScrollable = isMultiLine && colorsCount > rowCount * fit;
2281
2225
  let finalClass = menuClass;
2282
2226
  finalClass += isScrollable ? ' scrollable' : '';
@@ -2284,7 +2228,7 @@
2284
2228
  const gap = isMultiLine ? '1px' : '0.25rem';
2285
2229
  let optionSize = isMultiLine ? 1.75 : 2;
2286
2230
  optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2287
- const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2231
+ const menuHeight = `${rowCount * optionSize}rem`;
2288
2232
  const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2289
2233
  /** @type {HTMLUListElement} */
2290
2234
  // @ts-ignore -- <UL> is an `HTMLElement`
@@ -2391,16 +2335,14 @@
2391
2335
  });
2392
2336
 
2393
2337
  // color presets
2394
- if ((colorPresets instanceof Array && colorPresets.length)
2395
- || (colorPresets instanceof ColorPalette && colorPresets.colors)) {
2396
- const presetsMenu = getColorMenu(self, colorPresets, 'color-options');
2397
- presetsDropdown.append(presetsMenu);
2338
+ if (colorPresets) {
2339
+ presetsDropdown.append(getColorMenu(self, colorPresets, 'color-options'));
2398
2340
  }
2399
2341
 
2400
2342
  // explicit defaults [reset, initial, inherit, transparent, currentColor]
2343
+ // also custom defaults [default: #069, complementary: #930]
2401
2344
  if (colorKeywords && colorKeywords.length) {
2402
- const keywordsMenu = getColorMenu(self, colorKeywords, 'color-defaults');
2403
- presetsDropdown.append(keywordsMenu);
2345
+ presetsDropdown.append(getColorMenu(self, colorKeywords, 'color-defaults'));
2404
2346
  }
2405
2347
 
2406
2348
  const presetsBtn = createElement({
@@ -2437,7 +2379,7 @@
2437
2379
  setAttribute(input, tabIndex, '-1');
2438
2380
  }
2439
2381
 
2440
- var version = "0.0.2";
2382
+ var version = "1.0.0";
2441
2383
 
2442
2384
  // @ts-ignore
2443
2385
 
@@ -2459,7 +2401,7 @@
2459
2401
  // ColorPicker Static Methods
2460
2402
  // ==========================
2461
2403
 
2462
- /** @type {CP.GetInstance<ColorPicker>} */
2404
+ /** @type {CP.GetInstance<ColorPicker, HTMLInputElement>} */
2463
2405
  const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2464
2406
 
2465
2407
  /** @type {CP.InitCallback<ColorPicker>} */
@@ -2494,12 +2436,10 @@
2494
2436
  const fn = action ? addListener : removeListener;
2495
2437
  const { input, colorMenu, parent } = self;
2496
2438
  const doc = getDocument(input);
2497
- const win = getWindow(input);
2498
- const pointerEvents = `on${touchstartEvent}` in doc
2499
- ? { down: touchstartEvent, move: touchmoveEvent, up: touchendEvent }
2500
- : { down: mousedownEvent, move: mousemoveEvent, up: mouseupEvent };
2439
+ // const win = getWindow(input);
2440
+ const win = doc.defaultView;
2501
2441
 
2502
- fn(self.controls, pointerEvents.down, self.pointerDown);
2442
+ fn(self.controls, pointerdownEvent, self.pointerDown);
2503
2443
  self.controlKnobs.forEach((x) => fn(x, keydownEvent, self.handleKnobs));
2504
2444
 
2505
2445
  // @ts-ignore -- this is `Window`
@@ -2514,8 +2454,8 @@
2514
2454
  fn(colorMenu, keydownEvent, self.menuKeyHandler);
2515
2455
  }
2516
2456
 
2517
- fn(doc, pointerEvents.move, self.pointerMove);
2518
- fn(doc, pointerEvents.up, self.pointerUp);
2457
+ fn(doc, pointermoveEvent, self.pointerMove);
2458
+ fn(doc, pointerupEvent, self.pointerUp);
2519
2459
  fn(parent, focusoutEvent, self.handleFocusOut);
2520
2460
  fn(doc, keyupEvent, self.handleDismiss);
2521
2461
  }
@@ -2534,6 +2474,7 @@
2534
2474
  * @returns {void}
2535
2475
  */
2536
2476
  function removePosition(element) {
2477
+ /* istanbul ignore else */
2537
2478
  if (element) {
2538
2479
  ['bottom', 'top'].forEach((x) => removeClass(element, x));
2539
2480
  }
@@ -2636,6 +2577,7 @@
2636
2577
  } = normalizeOptions(this.isCE ? parent : input, colorPickerDefaults, config || {});
2637
2578
 
2638
2579
  let translatedColorLabels = colorNames;
2580
+ /* istanbul ignore else */
2639
2581
  if (colorLabels instanceof Array && colorLabels.length === 17) {
2640
2582
  translatedColorLabels = colorLabels;
2641
2583
  } else if (colorLabels && colorLabels.split(',').length === 17) {
@@ -2652,7 +2594,7 @@
2652
2594
  ? JSON.parse(componentLabels) : componentLabels;
2653
2595
 
2654
2596
  /** @type {Record<string, string>} */
2655
- self.componentLabels = ObjectAssign(colorPickerLabels, tempComponentLabels);
2597
+ self.componentLabels = ObjectAssign({ ...colorPickerLabels }, tempComponentLabels);
2656
2598
 
2657
2599
  /** @type {Color} */
2658
2600
  self.color = new Color(input.value || '#fff', format);
@@ -2661,14 +2603,14 @@
2661
2603
  self.format = format;
2662
2604
 
2663
2605
  // set colour defaults
2664
- if (colorKeywords instanceof Array) {
2606
+ if (colorKeywords instanceof Array && colorKeywords.length) {
2665
2607
  self.colorKeywords = colorKeywords;
2666
2608
  } else if (typeof colorKeywords === 'string' && colorKeywords.length) {
2667
2609
  self.colorKeywords = colorKeywords.split(',').map((x) => x.trim());
2668
2610
  }
2669
2611
 
2670
2612
  // set colour presets
2671
- if (colorPresets instanceof Array) {
2613
+ if (colorPresets instanceof Array && colorPresets.length) {
2672
2614
  self.colorPresets = colorPresets;
2673
2615
  } else if (typeof colorPresets === 'string' && colorPresets.length) {
2674
2616
  if (isValidJSON(colorPresets)) {
@@ -2799,6 +2741,7 @@
2799
2741
  let colorName;
2800
2742
 
2801
2743
  // determine color appearance
2744
+ /* istanbul ignore else */
2802
2745
  if (lightness === 100 && saturation === 0) {
2803
2746
  colorName = colorLabels.white;
2804
2747
  } else if (lightness === 0) {
@@ -2899,13 +2842,14 @@
2899
2842
  const self = this;
2900
2843
  const { activeElement } = getDocument(self.input);
2901
2844
 
2902
- if ((e.type === touchmoveEvent && self.dragElement)
2845
+ self.updateDropdownPosition();
2846
+
2847
+ /* istanbul ignore next */
2848
+ if (([pointermoveEvent, touchmoveEvent].includes(e.type) && self.dragElement)
2903
2849
  || (activeElement && self.controlKnobs.includes(activeElement))) {
2904
2850
  e.stopPropagation();
2905
2851
  e.preventDefault();
2906
2852
  }
2907
-
2908
- self.updateDropdownPosition();
2909
2853
  }
2910
2854
 
2911
2855
  /**
@@ -2979,7 +2923,9 @@
2979
2923
 
2980
2924
  self.update();
2981
2925
 
2926
+ /* istanbul ignore else */
2982
2927
  if (currentActive !== target) {
2928
+ /* istanbul ignore else */
2983
2929
  if (currentActive) {
2984
2930
  removeClass(currentActive, 'active');
2985
2931
  removeAttribute(currentActive, ariaSelected);
@@ -2997,15 +2943,13 @@
2997
2943
 
2998
2944
  /**
2999
2945
  * The `ColorPicker` *touchstart* / *mousedown* events listener for control knobs.
3000
- * @param {TouchEvent} e
2946
+ * @param {PointerEvent} e
3001
2947
  * @this {ColorPicker}
3002
2948
  */
3003
2949
  pointerDown(e) {
3004
2950
  const self = this;
3005
2951
  /** @type {*} */
3006
- const {
3007
- type, target, touches, pageX, pageY,
3008
- } = e;
2952
+ const { target, pageX, pageY } = e;
3009
2953
  const { colorMenu, visuals, controlKnobs } = self;
3010
2954
  const [v1, v2, v3] = visuals;
3011
2955
  const [c1, c2, c3] = controlKnobs;
@@ -3013,11 +2957,10 @@
3013
2957
  const visual = controlKnobs.includes(target) ? target.previousElementSibling : target;
3014
2958
  const visualRect = getBoundingClientRect(visual);
3015
2959
  const html = getDocumentElement(v1);
3016
- const X = type === 'touchstart' ? touches[0].pageX : pageX;
3017
- const Y = type === 'touchstart' ? touches[0].pageY : pageY;
3018
- const offsetX = X - html.scrollLeft - visualRect.left;
3019
- const offsetY = Y - html.scrollTop - visualRect.top;
2960
+ const offsetX = pageX - html.scrollLeft - visualRect.left;
2961
+ const offsetY = pageY - html.scrollTop - visualRect.top;
3020
2962
 
2963
+ /* istanbul ignore else */
3021
2964
  if (target === v1 || target === c1) {
3022
2965
  self.dragElement = visual;
3023
2966
  self.changeControl1(offsetX, offsetY);
@@ -3041,7 +2984,7 @@
3041
2984
 
3042
2985
  /**
3043
2986
  * The `ColorPicker` *touchend* / *mouseup* events listener for control knobs.
3044
- * @param {TouchEvent} e
2987
+ * @param {PointerEvent} e
3045
2988
  * @this {ColorPicker}
3046
2989
  */
3047
2990
  pointerUp({ target }) {
@@ -3050,9 +2993,8 @@
3050
2993
  const doc = getDocument(parent);
3051
2994
  const currentOpen = querySelector(`${colorPickerParentSelector}.open`, doc) !== null;
3052
2995
  const selection = doc.getSelection();
3053
- // @ts-ignore
2996
+
3054
2997
  if (!self.dragElement && !selection.toString().length
3055
- // @ts-ignore
3056
2998
  && !parent.contains(target)) {
3057
2999
  self.hide(currentOpen);
3058
3000
  }
@@ -3062,25 +3004,20 @@
3062
3004
 
3063
3005
  /**
3064
3006
  * The `ColorPicker` *touchmove* / *mousemove* events listener for control knobs.
3065
- * @param {TouchEvent} e
3007
+ * @param {PointerEvent} e
3066
3008
  */
3067
3009
  pointerMove(e) {
3068
3010
  const self = this;
3069
3011
  const { dragElement, visuals } = self;
3070
3012
  const [v1, v2, v3] = visuals;
3071
- const {
3072
- // @ts-ignore
3073
- type, touches, pageX, pageY,
3074
- } = e;
3013
+ const { pageX, pageY } = e;
3075
3014
 
3076
3015
  if (!dragElement) return;
3077
3016
 
3078
3017
  const controlRect = getBoundingClientRect(dragElement);
3079
3018
  const win = getDocumentElement(v1);
3080
- const X = type === touchmoveEvent ? touches[0].pageX : pageX;
3081
- const Y = type === touchmoveEvent ? touches[0].pageY : pageY;
3082
- const offsetX = X - win.scrollLeft - controlRect.left;
3083
- const offsetY = Y - win.scrollTop - controlRect.top;
3019
+ const offsetX = pageX - win.scrollLeft - controlRect.left;
3020
+ const offsetY = pageY - win.scrollTop - controlRect.top;
3084
3021
 
3085
3022
  if (dragElement === v1) {
3086
3023
  self.changeControl1(offsetX, offsetY);
@@ -3114,13 +3051,16 @@
3114
3051
  const currentKnob = controlKnobs.find((x) => x === activeElement);
3115
3052
  const yRatio = offsetHeight / 360;
3116
3053
 
3054
+ /* istanbul ignore else */
3117
3055
  if (currentKnob) {
3118
3056
  let offsetX = 0;
3119
3057
  let offsetY = 0;
3120
3058
 
3059
+ /* istanbul ignore else */
3121
3060
  if (target === c1) {
3122
3061
  const xRatio = offsetWidth / 100;
3123
3062
 
3063
+ /* istanbul ignore else */
3124
3064
  if ([keyArrowLeft, keyArrowRight].includes(code)) {
3125
3065
  self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
3126
3066
  } else if ([keyArrowUp, keyArrowDown].includes(code)) {
@@ -3166,6 +3106,7 @@
3166
3106
  const isNonColorValue = self.hasNonColor && nonColors.includes(currentValue);
3167
3107
  const alpha = i4 ? v4 : (1 - controlPositions.c3y / offsetHeight);
3168
3108
 
3109
+ /* istanbul ignore else */
3169
3110
  if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
3170
3111
  if (activeElement === input) {
3171
3112
  if (isNonColorValue) {
@@ -3480,6 +3421,7 @@
3480
3421
  const hue = roundPart(hsl.h * 360);
3481
3422
  let newColor;
3482
3423
 
3424
+ /* istanbul ignore else */
3483
3425
  if (format === 'hex') {
3484
3426
  newColor = self.color.toHexString(true);
3485
3427
  i1.value = self.hex;
@@ -3580,15 +3522,15 @@
3580
3522
  const relatedBtn = openPicker ? pickerToggle : menuToggle;
3581
3523
  const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
3582
3524
 
3583
- // if (!self.isValid) {
3584
3525
  self.value = self.color.toString(true);
3585
- // }
3586
3526
 
3527
+ /* istanbul ignore else */
3587
3528
  if (openDropdown) {
3588
3529
  removeClass(openDropdown, 'show');
3589
3530
  setAttribute(relatedBtn, ariaExpanded, 'false');
3590
3531
  setTimeout(() => {
3591
3532
  removePosition(openDropdown);
3533
+ /* istanbul ignore else */
3592
3534
  if (!querySelector('.show', parent)) {
3593
3535
  removeClass(parent, 'open');
3594
3536
  toggleEventsOnShown(self);
@@ -3601,7 +3543,7 @@
3601
3543
  focus(pickerToggle);
3602
3544
  }
3603
3545
  setAttribute(input, tabIndex, '-1');
3604
- if (menuToggle) {
3546
+ if (relatedBtn === menuToggle) {
3605
3547
  setAttribute(menuToggle, tabIndex, '-1');
3606
3548
  }
3607
3549
  }
@@ -3639,14 +3581,76 @@
3639
3581
  getBoundingClientRect,
3640
3582
  });
3641
3583
 
3584
+ /**
3585
+ * A small utility to toggle `ColorPickerElement` attributes
3586
+ * when `connectedCallback` or `disconnectedCallback` methods
3587
+ * are called and helps the instance keep its value and settings instact.
3588
+ *
3589
+ * @param {CP.ColorPickerElement} self ColorPickerElement instance
3590
+ * @param {Function=} callback when `true`, attributes are added
3591
+ *
3592
+ * @example
3593
+ * const attributes = [
3594
+ * // essentials
3595
+ * 'value', 'format',
3596
+ * // presets menus
3597
+ * 'color-presets', 'color-keywords',
3598
+ * // labels
3599
+ * 'color-labels', 'component-labels',
3600
+ * ];
3601
+ */
3602
+ function toggleCEAttr(self, callback) {
3603
+ if (callback) {
3604
+ const { input, colorPicker } = self;
3605
+
3606
+ const {
3607
+ value, format, colorPresets, colorKeywords, componentLabels, colorLabels,
3608
+ } = colorPicker;
3609
+
3610
+ const { id, placeholder } = input;
3611
+
3612
+ setAttribute(self, 'data-id', id);
3613
+ setAttribute(self, 'data-value', value);
3614
+ setAttribute(self, 'data-format', format);
3615
+ setAttribute(self, 'data-placeholder', placeholder);
3616
+
3617
+ if (ObjectKeys(colorPickerLabels).some((l) => colorPickerLabels[l] !== componentLabels[l])) {
3618
+ setAttribute(self, 'data-component-labels', JSON.stringify(componentLabels));
3619
+ }
3620
+ if (!colorNames.every((c) => c === colorLabels[c])) {
3621
+ setAttribute(self, 'data-color-labels', colorNames.map((n) => colorLabels[n]).join(','));
3622
+ }
3623
+ if (colorPresets instanceof ColorPalette) {
3624
+ const { hue, hueSteps, lightSteps } = colorPresets;
3625
+ setAttribute(self, 'data-color-presets', JSON.stringify({ hue, hueSteps, lightSteps }));
3626
+ }
3627
+ if (Array.isArray(colorPresets) && colorPresets.length) {
3628
+ setAttribute(self, 'data-color-presets', colorPresets.join(','));
3629
+ }
3630
+ if (colorKeywords) {
3631
+ setAttribute(self, 'data-color-keywords', colorKeywords.join(','));
3632
+ }
3633
+ setTimeout(callback, 0);
3634
+ } else {
3635
+ // keep id
3636
+ // removeAttribute(self, 'data-id');
3637
+ removeAttribute(self, 'data-value');
3638
+ removeAttribute(self, 'data-format');
3639
+ removeAttribute(self, 'data-placeholder');
3640
+ removeAttribute(self, 'data-component-labels');
3641
+ removeAttribute(self, 'data-color-labels');
3642
+ removeAttribute(self, 'data-color-presets');
3643
+ removeAttribute(self, 'data-color-keywords');
3644
+ }
3645
+ }
3646
+
3642
3647
  let CPID = 0;
3643
3648
 
3644
3649
  /**
3645
3650
  * `ColorPickerElement` Web Component.
3646
3651
  * @example
3647
3652
  * <label for="UNIQUE_ID">Label</label>
3648
- * <color-picker>
3649
- * <input id="UNIQUE_ID" value="red" format="hex" class="color-preview btn-appearance">
3653
+ * <color-picker data-id="UNIQUE_ID" data-value="red" data-format="hex">
3650
3654
  * </color-picker>
3651
3655
  * // or
3652
3656
  * <label for="UNIQUE_ID">Label</label>
@@ -3665,54 +3669,66 @@
3665
3669
  get value() { return this.input && this.input.value; }
3666
3670
 
3667
3671
  connectedCallback() {
3668
- if (this.input) return;
3672
+ const self = this;
3673
+ if (self.input) return;
3669
3674
 
3670
- let [input] = getElementsByTagName('input', this);
3671
- const value = (input && getAttribute(input, 'value')) || getAttribute(this, 'data-value') || '#fff';
3672
- const format = (input && getAttribute(input, 'format')) || getAttribute(this, 'data-format') || 'rgb';
3673
- let id = (input && getAttribute(input, 'id')) || getAttribute(this, 'data-id');
3675
+ let id = getAttribute(self, 'data-id');
3676
+ const value = getAttribute(self, 'data-value') || '#fff';
3677
+ const format = getAttribute(self, 'data-format') || 'rgb';
3678
+ const placeholder = getAttribute(self, 'data-placeholder') || '';
3674
3679
 
3675
3680
  if (!id) {
3676
3681
  id = `color-picker-${format}-${CPID}`;
3677
3682
  CPID += 1;
3678
3683
  }
3679
3684
 
3680
- if (!input) {
3681
- input = createElement({
3682
- tagName: 'input',
3683
- type: 'text',
3684
- className: 'color-preview btn-appearance',
3685
- });
3685
+ const input = createElement({
3686
+ tagName: 'input',
3687
+ type: 'text',
3688
+ className: 'color-preview btn-appearance',
3689
+ });
3690
+
3691
+ setAttribute(input, 'id', id);
3692
+ setAttribute(input, 'name', id);
3693
+ setAttribute(input, 'autocomplete', 'off');
3694
+ setAttribute(input, 'spellcheck', 'false');
3695
+ setAttribute(input, 'value', value);
3696
+ setAttribute(input, 'placeholder', placeholder);
3697
+ self.append(input);
3686
3698
 
3687
- setAttribute(input, 'id', id);
3688
- setAttribute(input, 'name', id);
3689
- setAttribute(input, 'autocomplete', 'off');
3690
- setAttribute(input, 'spellcheck', 'false');
3691
- setAttribute(input, 'value', value);
3692
- this.append(input);
3693
- }
3694
3699
  /** @type {HTMLInputElement} */
3695
3700
  // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3696
- this.input = input;
3701
+ self.input = input;
3697
3702
 
3698
3703
  // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3699
- this.colorPicker = new ColorPicker(input);
3704
+ self.colorPicker = new ColorPicker(input);
3700
3705
 
3701
3706
  // @ts-ignore - `shadowRoot` is defined in the constructor
3702
- this.shadowRoot.append(createElement('slot'));
3707
+ self.shadowRoot.append(createElement('slot'));
3708
+
3709
+ // remove Attributes
3710
+ toggleCEAttr(self);
3703
3711
  }
3704
3712
 
3705
3713
  /** @this {ColorPickerElement} */
3706
3714
  disconnectedCallback() {
3707
- const { input, colorPicker, shadowRoot } = this;
3708
- if (colorPicker) colorPicker.dispose();
3709
- if (input) input.remove();
3710
- if (shadowRoot) shadowRoot.innerHTML = '';
3711
-
3712
- ObjectAssign(this, {
3713
- colorPicker: undefined,
3714
- input: undefined,
3715
- });
3715
+ const self = this;
3716
+ const { input, colorPicker, shadowRoot } = self;
3717
+
3718
+ const callback = () => {
3719
+ // remove markup
3720
+ input.remove();
3721
+ colorPicker.dispose();
3722
+ shadowRoot.innerHTML = '';
3723
+
3724
+ ObjectAssign(self, {
3725
+ colorPicker: undefined,
3726
+ input: undefined,
3727
+ });
3728
+ };
3729
+
3730
+ // re-add Attributes
3731
+ toggleCEAttr(self, callback);
3716
3732
  }
3717
3733
  }
3718
3734