@thednp/color-picker 0.0.2-alpha4 → 1.0.0

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.2alpha4 (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.2alpha4";
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